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)重仙蚜,容易被開(kāi)發(fā)者檢測(cè)出來(lái)- 通過(guò)
DYLD_INSERT_LIBRARIES
環(huán)境變量,插入動(dòng)態(tài)庫(kù)
Tweak
插件厂汗,使用的是方式二委粉,因?yàn)槌绦驔](méi)有被污染。在MachO
中娶桦,并沒(méi)有找到LC_LOAD_DYLIB (XXX)
執(zhí)行
make package
命令時(shí)贾节,在packages
目錄中,生成.deb
文件衷畦。每執(zhí)行一次打包命令栗涂,都會(huì)生成一個(gè)新的.deb
文件
.deb
格式類似于.ipa
格式
.ipa
包通過(guò)AppStore
安裝,將.ipa
包中的App
安裝到設(shè)備中.deb
包通過(guò)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ù)將被清空
打開(kāi)
dyld-519.2.2
源碼搜索
DYLD_INSERT_LIBRARIES
進(jìn)入
dyld.cpp
文件帖烘,來(lái)到5907
行
DYLD_INSERT_LIBRARIES
為NULL
的判斷這段代碼的上面,來(lái)到
5692
行
- 判斷進(jìn)程限制
- 符合條件橄杨,調(diào)用
pruneEnvironmentVariables
方法秘症,清空插入的動(dòng)態(tài)庫(kù)
一旦插入的動(dòng)態(tài)庫(kù)被清空,意味著越獄插件將會(huì)全部失效式矫。如果我們找到進(jìn)程限制的開(kāi)啟條件乡摹,并將其使用在項(xiàng)目中,相當(dāng)于對(duì)越獄插件進(jìn)行了防護(hù)
找到
processIsRestricted
設(shè)置為true
的代碼
- 判斷條件有兩個(gè)采转,分別是
issetugid
和hasRestrictedSegment
兩個(gè)函數(shù)issetugid
函數(shù)聪廉,無(wú)法在上架的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é)喘沿,開(kāi)啟進(jìn)程限制,對(duì)越獄插件進(jìn)行防護(hù)
搭建
App
項(xiàng)目竭贩,命名:antiTweak
打開(kāi)
ViewController.m
文件蚜印,寫(xiě)入以下代碼:- (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)用打開(kāi)
Tweak.x
文件肪获,寫(xiě)入以下代碼:#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)擊屏幕閃退。說(shuō)明插入的動(dòng)態(tài)庫(kù)已被清空双戳,越獄插件全部失效
這種防護(hù)手段虹蒋,在早期系統(tǒng)中比較有效。但在
iOS11
及更高系統(tǒng)中飒货,dyld
源碼發(fā)生變化魄衅,這種方式已失去作用
修改MachO破解
在老系統(tǒng)的越獄設(shè)備上,遇到使用此方式防護(hù)的應(yīng)用塘辅,導(dǎo)致我們的越獄插件無(wú)法使用晃虫,可以通過(guò)修改
MachO
文件破解防護(hù)使用
MachOView
打開(kāi)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
草巡,我們開(kāi)啟了進(jìn)程限制,如何禁止攻擊者的肆意修改呢?借鑒
dyld
的代碼山憨,循環(huán)讀取segment
和section
查乒,如果缺少__RESTRICT
段或__restrict
節(jié),說(shuō)明我們的防護(hù)代碼被人篡改延用
antiTweak
項(xiàng)目郁竟,將dyld
中的代碼遷移到項(xiàng)目中打開(kāi)
ViewController.m
文件玛迄,寫(xiě)入以下代碼:導(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é)蓖议,返回false
static 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)該讓攻擊者不易察覺(jué)愕宋,在不知不覺(jué)中被系統(tǒng)屏蔽封殺
白名單檢測(cè)
進(jìn)程限制的防護(hù)手段,僅低版本系統(tǒng)有效结榄。對(duì)于高版本系統(tǒng)的防護(hù)中贝,我們可以自制白名單進(jìn)行檢測(cè)
延用
antiTweak
項(xiàng)目整理出
App
依賴庫(kù)的白名單打開(kāi)
ViewController.m
文件,寫(xiě)入以下代碼:#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ù)
打開(kāi)
ViewController.m
文件黎炉,寫(xiě)入以下代碼:#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ù)丹壕,直接跳過(guò)庆械。否則,將其打印當(dāng)前
MachO
文件菌赖,不需要判斷缭乘,因?yàn)樯澈新窂綗o(wú)法固定在越獄設(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)甭┑模瑢⑵溲a(bǔ)充到白名單中黍氮。如果確認(rèn)是惡意注入唐含,再做處理
- 白名單列表,由服務(wù)端下發(fā)沫浆,或者將邏輯直接做到服務(wù)端
白名單寫(xiě)在客戶端的弊端:
- 白名單的字符串捷枯,位于
MachO
的常量區(qū),容易被攻擊者發(fā)現(xiàn)并HOOK
- 當(dāng)系統(tǒng)更新件缸,可能會(huì)出現(xiàn)白名單以外的依賴庫(kù)铜靶,老版本
App
將無(wú)法使用
ptrace
App
可以被lldb
動(dòng)態(tài)調(diào)試,因?yàn)?code>App被設(shè)備中的debugserver
附加他炊,它會(huì)跟蹤我們的應(yīng)用進(jìn)程(trace process
)争剿,而這一過(guò)程利用的就是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)中涩笤,無(wú)法直接使用,需要導(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)程的ID
addr
:目標(biāo)進(jìn)程的地址值盒件,和request
參數(shù)有關(guān)data
:根據(jù)request
的不同而變化蹬碧。如果需要向目標(biāo)進(jìn)程中寫(xiě)入數(shù)據(jù),data
存放的是需要寫(xiě)入的數(shù)據(jù)炒刁。如果從目標(biāo)進(jìn)程中讀數(shù)據(jù)恩沽,data
將存放返回的數(shù)據(jù)
搭建
App
項(xiàng)目,命名:antiDebug
導(dǎo)入
MyPtraceHeader.h
頭文件打開(kāi)
ViewController.m
文件翔始,寫(xiě)入以下代碼:#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
也就是說(shuō)渤闷,用戶在使用
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
同樣附加失敗补憾,無(wú)論以何種方式漫萄,都會(huì)被
ptrace
函數(shù)阻止
破解ptrace
ptrace
是系統(tǒng)內(nèi)核函數(shù),被開(kāi)發(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
頭文件打開(kāi)
InjectCode.m
文件,寫(xiě)入以下代碼:#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/null
iOS11
及更高系統(tǒng)割粮,此防護(hù)無(wú)效修改
MachO
破解
- 使用
MachOView
打開(kāi)MachO
文件盾碗,修改Data
值- 只在以前的數(shù)值上替換,不要對(duì)其增減舀瓢,位數(shù)不要改變
使用
dyld
源碼防護(hù)
- 借鑒
dyld
源碼廷雅,讀取segment
和section
。如果缺少__RESTRICT
段或__restrict
節(jié),說(shuō)明我們的防護(hù)代碼被人篡改- 檢測(cè)到程序被篡改航缀,不要使用痕跡明顯的代碼進(jìn)行防護(hù)商架,容易暴露
- 盡量讓攻擊者在不知不覺(jué)中被系統(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)中灿巧,無(wú)法直接使用赶袄,需要導(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ù)