Tweak原理
執(zhí)行
make命令時(shí),在.theos的隱藏目錄中,編譯出obj/debug目錄,包含arm64、armv7兩種架構(gòu),同時(shí)生成RedDemo.dylib動(dòng)態(tài)庫(kù)
在
arm64、armv7目錄中,有各自架構(gòu)的RedDemo.dylib,而debug目錄中的RedDemo.dylib,是一個(gè)Fat Binary文件file RedDemo.dylib ------------------------- RedDemo.dylib: Mach-O universal binary with 2 architectures: [arm_v7:Mach-O dynamically linked shared library arm_v7] [arm64:Mach-O 64-bit dynamically linked shared library arm64] RedDemo.dylib (for architecture armv7): Mach-O dynamically linked shared library arm_v7 RedDemo.dylib (for architecture arm64): Mach-O 64-bit dynamically linked shared library arm64
Tweak的編譯產(chǎn)物是動(dòng)態(tài)庫(kù),將其注入的方式有兩種:
- 修改
MachO文件的Load Commands,注入LC_LOAD_DYLIB (XXX),然后根據(jù)路徑找到動(dòng)態(tài)庫(kù)。這種方式對(duì)程序的污染比較嚴(yán)重,容易被開發(fā)者檢測(cè)出來- 通過
DYLD_INSERT_LIBRARIES環(huán)境變量,插入動(dòng)態(tài)庫(kù)
Tweak插件,使用的是方式二,因?yàn)槌绦驔]有被污染。在MachO中,并沒有找到LC_LOAD_DYLIB (XXX)
執(zhí)行
make package命令時(shí),在packages目錄中,生成.deb文件。每執(zhí)行一次打包命令,都會(huì)生成一個(gè)新的.deb文件
.deb格式類似于.ipa格式
.ipa包通過AppStore安裝,將.ipa包中的App安裝到設(shè)備中.deb包通過Cydia安裝,將.deb包中的動(dòng)態(tài)庫(kù)安裝到設(shè)備中
執(zhí)行
make install命令時(shí),在.deb包中的動(dòng)態(tài)庫(kù),會(huì)被安裝到設(shè)備的/Library/MobileSubstrate/DynamicLibraries目錄中以相同的名稱,分別存儲(chǔ)
.dylib和.plist文件
.dylib為動(dòng)態(tài)庫(kù),而.plist,記錄.dylib所依附的App包名
DYLD_INSERT_LIBRARIES
在早期的
dyld源碼中,有進(jìn)程限制的判斷。一旦符合條件,使用DYLD_INSERT_LIBRARIES環(huán)境變量插入的動(dòng)態(tài)庫(kù)將被清空
打開
dyld-519.2.2源碼搜索
DYLD_INSERT_LIBRARIES進(jìn)入
dyld.cpp文件,來到5907行
DYLD_INSERT_LIBRARIES為NULL的判斷這段代碼的上面,來到
5692行
- 判斷進(jìn)程限制
- 符合條件,調(diào)用
pruneEnvironmentVariables方法,清空插入的動(dòng)態(tài)庫(kù)
一旦插入的動(dòng)態(tài)庫(kù)被清空,意味著越獄插件將會(huì)全部失效。如果我們找到進(jìn)程限制的開啟條件,并將其使用在項(xiàng)目中,相當(dāng)于對(duì)越獄插件進(jìn)行了防護(hù)
找到
processIsRestricted設(shè)置為true的代碼
- 判斷條件有兩個(gè),分別是
issetugid和hasRestrictedSegment兩個(gè)函數(shù)issetugid函數(shù),無法在上架的App中設(shè)置,放棄使用hasRestrictedSegment函數(shù),判斷主程序的MachO是否受限,可以使用進(jìn)入
hasRestrictedSegment函數(shù)
- 傳入主程序的
Header- 讀取
segment,如果為__RESTRICT段- 讀取
section,如果為__restrict節(jié)- 如果都存在,返回
trur,表示進(jìn)程限制
__RESTRICT段防護(hù)
在項(xiàng)目中,添加
__RESTRICT段,__restrict節(jié),開啟進(jìn)程限制,對(duì)越獄插件進(jìn)行防護(hù)
搭建
App項(xiàng)目,命名:antiTweak打開
ViewController.m文件,寫入以下代碼:- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ exit(0); }進(jìn)程限制,是早期
dyld源碼中的邏輯,在低系統(tǒng)下才能生效使用
iOS9.1系統(tǒng)運(yùn)行項(xiàng)目,點(diǎn)擊屏幕就會(huì)閃退
搭建
Tweak插件,附加antiTweak應(yīng)用打開
Tweak.x文件,寫入以下代碼:#import <UIKit/UIKit.h> %hook ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"??????????"); } %end安裝插件,啟動(dòng)應(yīng)用,
touchesBegan方法被插件HOOK。點(diǎn)擊屏幕,閃退變?yōu)榇蛴?/p>
為
antiTweak項(xiàng)目,添加__RESTRICT段,__restrict節(jié)在
Build Setting的Other Linker Flags中,加入以下設(shè)置:-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null編譯項(xiàng)目,查看
MachO文件
- 成功插入
__RESTRICT段,__restrict節(jié)運(yùn)行項(xiàng)目,點(diǎn)擊屏幕閃退。說明插入的動(dòng)態(tài)庫(kù)已被清空,越獄插件全部失效
這種防護(hù)手段,在早期系統(tǒng)中比較有效。但在
iOS11及更高系統(tǒng)中,dyld源碼發(fā)生變化,這種方式已失去作用
修改MachO破解
在老系統(tǒng)的越獄設(shè)備上,遇到使用此方式防護(hù)的應(yīng)用,導(dǎo)致我們的越獄插件無法使用,可以通過修改
MachO文件破解防護(hù)使用
MachOView打開MachO文件
修改
Data值,將72改為73,52改為53。只在以前的數(shù)值上替換,位數(shù)不要改變
當(dāng)
MachO文件修改后,使用重簽名安裝應(yīng)用,此時(shí)__RESTRICT段和__restrict節(jié)已經(jīng)不存在了,進(jìn)程限制不會(huì)啟動(dòng),越獄插件可正常使用
使用dyld源碼防護(hù)
如果是自己的
App,我們開啟了進(jìn)程限制,如何禁止攻擊者的肆意修改呢?借鑒
dyld的代碼,循環(huán)讀取segment和section,如果缺少__RESTRICT段或__restrict節(jié),說明我們的防護(hù)代碼被人篡改延用
antiTweak項(xiàng)目,將dyld中的代碼遷移到項(xiàng)目中打開
ViewController.m文件,寫入以下代碼:導(dǎo)入頭文件
#import <mach-o/loader.h> #import <mach-o/dyld.h>添加宏定義
#if __LP64__ #define macho_header mach_header_64 #define LC_SEGMENT_COMMAND LC_SEGMENT_64 #define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT #define LC_ENCRYPT_COMMAND LC_ENCRYPTION_INFO #define macho_segment_command segment_command_64 #define macho_section section_64 #else #define macho_header mach_header #define LC_SEGMENT_COMMAND LC_SEGMENT #define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT_64 #define LC_ENCRYPT_COMMAND LC_ENCRYPTION_INFO_64 #define macho_segment_command segment_command #define macho_section section #endif添加
hasRestrictedSegment函數(shù),循環(huán)讀取segment和section。如果缺少__RESTRICT段或__restrict節(jié),返回falsestatic bool hasRestrictedSegment(const struct macho_header* mh) { const uint32_t cmd_count = mh->ncmds; const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(struct macho_header)); const struct load_command* cmd = cmds; for (uint32_t i = 0; i < cmd_count; ++i) { switch (cmd->cmd) { case LC_SEGMENT_COMMAND: { const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; if (strcmp(seg->segname, "__RESTRICT") == 0) { const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command)); const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { if (strcmp(sect->sectname, "__restrict") == 0) return true; } } } break; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } return false; }加入
load方法,調(diào)用防護(hù)代碼+(void)load{ struct macho_header* mhmh= _dyld_get_image_header(0); if(hasRestrictedSegment(mhmh)){ NSLog(@"防護(hù)代碼有效"); } else{ NSLog(@"被篡改"); } }
修改
Other Linker Flags中的配置,模擬MachO被篡改-Wl,-sectcreate,__SESTRICT,__sestrict,/dev/null運(yùn)行項(xiàng)目,輸出以下結(jié)果:
antiTweak[2535:549785] 被篡改當(dāng)檢測(cè)到
MachO被篡改,不要使用痕跡明顯的代碼進(jìn)行防護(hù),例如:exit(0)。此類代碼相當(dāng)于記號(hào),讓攻擊者很容易找到防護(hù)的位置和邏輯高明的防護(hù)手段,應(yīng)該讓攻擊者不易察覺,在不知不覺中被系統(tǒng)屏蔽封殺
白名單檢測(cè)
進(jìn)程限制的防護(hù)手段,僅低版本系統(tǒng)有效。對(duì)于高版本系統(tǒng)的防護(hù),我們可以自制白名單進(jìn)行檢測(cè)
延用
antiTweak項(xiàng)目整理出
App依賴庫(kù)的白名單打開
ViewController.m文件,寫入以下代碼:#import "ViewController.h" #import <mach-o/loader.h> #import <mach-o/dyld.h> @implementation ViewController +(void)load{ uint32_t intCount = _dyld_image_count(); for (int intIndex=0; intIndex<intCount; intIndex++) { const char* strName = _dyld_get_image_name(intIndex); printf("%s",strName); } } @end在未越獄的設(shè)備上,運(yùn)行項(xiàng)目,遍歷所有
image名稱
打印結(jié)果,相當(dāng)于一份白名單。如果
App運(yùn)行時(shí),加載了白名單以外的動(dòng)態(tài)庫(kù),該庫(kù)很可能是被第三方注入的
檢測(cè)注入的動(dòng)態(tài)庫(kù)
打開
ViewController.m文件,寫入以下代碼:#import "ViewController.h" #import <mach-o/loader.h> #import <mach-o/dyld.h> const char* strList = "/private/var/containers/Bundle/Application/E7D8C05C-D581-463F-96AC-791B816265C6/antiTweak..."; @implementation ViewController +(void)load{ uint32_t intCount = _dyld_image_count(); for (int intIndex=0; intIndex<intCount; intIndex++) { const char* strName = _dyld_get_image_name(intIndex); if(intIndex==0 || strstr(strList, strName)){ continue; } printf("注入動(dòng)態(tài)庫(kù):%s\n",strName); } } @end在
load方法中,循環(huán)遍歷依賴的動(dòng)態(tài)庫(kù)。如果動(dòng)態(tài)庫(kù)不是當(dāng)前MachO文件,或者包含白名單中,屬于合法庫(kù),直接跳過。否則,將其打印當(dāng)前
MachO文件,不需要判斷,因?yàn)樯澈新窂綗o法固定在越獄設(shè)備上運(yùn)行項(xiàng)目,輸出很多白名單以外的動(dòng)態(tài)庫(kù),其中包含自制的
antiTweakDemo插件
使用此方法進(jìn)行防護(hù),需要注意以下幾點(diǎn):
- 在不同系統(tǒng)下運(yùn)行項(xiàng)目,整理出盡可能完善的白名單
- 檢測(cè)到白名單以外的動(dòng)態(tài)庫(kù),不要直接處理。這里建議先收集數(shù)據(jù),如果此動(dòng)態(tài)庫(kù)是我們?nèi)甭┑?,將其補(bǔ)充到白名單中。如果確認(rèn)是惡意注入,再做處理
- 白名單列表,由服務(wù)端下發(fā),或者將邏輯直接做到服務(wù)端
白名單寫在客戶端的弊端:
- 白名單的字符串,位于
MachO的常量區(qū),容易被攻擊者發(fā)現(xiàn)并HOOK- 當(dāng)系統(tǒng)更新,可能會(huì)出現(xiàn)白名單以外的依賴庫(kù),老版本
App將無法使用
ptrace
App可以被lldb動(dòng)態(tài)調(diào)試,因?yàn)?code>App被設(shè)備中的debugserver附加,它會(huì)跟蹤我們的應(yīng)用進(jìn)程(trace process),而這一過程利用的就是ptrace函數(shù)
ptrace是系統(tǒng)內(nèi)核函數(shù),它可以決定應(yīng)用能否被debugserver附加。如果我們?cè)陧?xiàng)目中,調(diào)用ptrace函數(shù),將程序設(shè)置為拒絕附加,即可對(duì)lldb動(dòng)態(tài)調(diào)試進(jìn)行有效的防護(hù)
ptrace在iOS系統(tǒng)中,無法直接使用,需要導(dǎo)入頭文件
ptrace函數(shù)的定義:int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);
request:請(qǐng)求ptrace執(zhí)行的操作pid:目標(biāo)進(jìn)程的IDaddr:目標(biāo)進(jìn)程的地址值,和request參數(shù)有關(guān)data:根據(jù)request的不同而變化。如果需要向目標(biāo)進(jìn)程中寫入數(shù)據(jù),data存放的是需要寫入的數(shù)據(jù)。如果從目標(biāo)進(jìn)程中讀數(shù)據(jù),data將存放返回的數(shù)據(jù)
搭建
App項(xiàng)目,命名:antiDebug導(dǎo)入
MyPtraceHeader.h頭文件打開
ViewController.m文件,寫入以下代碼:#import "ViewController.h" #import "MyPtraceHeader.h" @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; ptrace(PT_DENY_ATTACH, 0, 0, 0); } @end使用
Xcode運(yùn)行項(xiàng)目,啟動(dòng)后立即退出。使用ptrace設(shè)置為拒絕附加,只能手動(dòng)啟動(dòng)App也就是說,用戶在使用
App時(shí),不會(huì)有任何影響。一旦被debugserver附加,就會(huì)閃退
如果在越獄環(huán)境,手動(dòng)對(duì)
App進(jìn)行debugserver附加呢?找到
antiDebug進(jìn)程ps -A | grep antiDebug ------------------------- 12233 ?? 0:00.27 /var/containers/Bundle/Application/5DC00A3B-C095-46D1-9842-A3C35401DD07/antiDebug.app/antiDebug手動(dòng)對(duì)
App進(jìn)行debugserver附加debugserver localhost:12346 -a 12233 ------------------------- debugserver-@(#)PROGRAM:LLDB PROJECT:lldb-900.3.87 for arm64. Attaching to process 12233... Segmentation fault: 11同樣附加失敗,無論以何種方式,都會(huì)被
ptrace函數(shù)阻止
破解ptrace
ptrace是系統(tǒng)內(nèi)核函數(shù),被開發(fā)者所熟知。ptrace的防護(hù)痕跡也很明顯,手動(dòng)運(yùn)行程序正常,Xcode運(yùn)行程序閃退我們?cè)谀嫦蛞豢?code>App時(shí),遇到上述情況,第一時(shí)間就會(huì)想到
ptrace防護(hù)由于
ptrace是系統(tǒng)函數(shù),需要間接符號(hào)表,我們可以試探性的下一個(gè)ptrace的符號(hào)斷點(diǎn)
ptrace的斷點(diǎn)命中,我們確定了對(duì)方的防護(hù)手段,想要破解并非難事
延用
antiDebug項(xiàng)目,模擬應(yīng)用重簽名,注入動(dòng)態(tài)庫(kù)創(chuàng)建
Inject動(dòng)態(tài)庫(kù),創(chuàng)建InjectCode類在
Inject動(dòng)態(tài)庫(kù)中,導(dǎo)入fishhook,導(dǎo)入MyPtraceHeader.h頭文件打開
InjectCode.m文件,寫入以下代碼:#import "InjectCode.h" #import "MyPtraceHeader.h" #import "fishhook.h" @implementation InjectCode +(void)load{ struct rebinding reb; reb.name="ptrace"; reb.replacement=my_ptrace; reb.replaced=(void *)&sys_ptrace; struct rebinding rebs[]={reb}; rebind_symbols(rebs, 1); } int (*sys_ptrace)(int _request, pid_t _pid, caddr_t _addr, int _data); int my_ptrace(int _request, pid_t _pid, caddr_t _addr, int _data){ if(_request==PT_DENY_ATTACH){ return 0; } return sys_ptrace(_request, _pid, _addr, _data); } @end在
ptrace_my函數(shù)中,如果是PT_DENY_ATTACH枚舉值,直接返回。如果是其他類型,系統(tǒng)有特定的作用,需要執(zhí)行ptrace原始函數(shù)運(yùn)行項(xiàng)目,進(jìn)入
lldb動(dòng)態(tài)調(diào)試,ptrace破解成功
總結(jié)
Tweak原理
Tweak編譯產(chǎn)物是動(dòng)態(tài)庫(kù)- 打包時(shí),將動(dòng)態(tài)庫(kù)打包成
.deb格式- 插件安裝到
/Library/MobileSubstrate/DynamicLibraries目錄中
? 安裝.dylib和.plist文件
?.plist記錄.dylib所依附的App包名Tweak插件使用DYLD_INSERT_LIBRARIES方式,插入動(dòng)態(tài)庫(kù)
DYLD_INSERT_LIBRARIES
- 早期
dyld源碼中,有進(jìn)程限制的判斷(processIsRestricted)- 啟用進(jìn)程限制,
segment存在__RESTRICT段,section存在__restrict節(jié)- 符合進(jìn)程限制的條件,清空插入動(dòng)態(tài)庫(kù),越獄插件失效
__RESTRICT段防護(hù)
- 在
Build Setting的Other Linker Flags中配置
?-Wl,-sectcreate,__RESTRICT,__restrict,/dev/nulliOS11及更高系統(tǒng),此防護(hù)無效修改
MachO破解
- 使用
MachOView打開MachO文件,修改Data值- 只在以前的數(shù)值上替換,不要對(duì)其增減,位數(shù)不要改變
使用
dyld源碼防護(hù)
- 借鑒
dyld源碼,讀取segment和section。如果缺少__RESTRICT段或__restrict節(jié),說明我們的防護(hù)代碼被人篡改- 檢測(cè)到程序被篡改,不要使用痕跡明顯的代碼進(jìn)行防護(hù),容易暴露
- 盡量讓攻擊者在不知不覺中被系統(tǒng)屏蔽封殺
白名單檢測(cè)
- 遍歷
image名稱
?_dyld_image_count()
?_dyld_get_image_name(i)- 在不同系統(tǒng)下運(yùn)行項(xiàng)目,整理出盡可能完善的白名單
- 檢測(cè)到白名單以外的動(dòng)態(tài)庫(kù),不要直接處理
- 白名單列表,由服務(wù)端下發(fā),或者將邏輯直接做到服務(wù)端
ptrace
- 可阻止
App被debugserver附加- 在
iOS系統(tǒng)中,無法直接使用,需要導(dǎo)入頭文件ptrace函數(shù)的定義
?int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);破解
ptrace
- 防護(hù)效果:手動(dòng)運(yùn)行程序正常,
Xcode運(yùn)行程序閃退- 使用
ptrace符號(hào)斷點(diǎn)試探- 使用
fishhook對(duì)ptrace函數(shù)HOOK- 是
PT_DENY_ATTACH枚舉值,直接返回。其他類型,執(zhí)行原始函數(shù)












