Run Mario!寫一個越獄插件
摘要
本文記敘了製作一個越獄插件的過程,它可以解除 SuperMarioRun 在越獄手機上的限制。(SuperMarioRun 是任天堂在 iOS 平台上推出的一款遊戲)。插件可以通過 Cydia 安裝(搜索 RunMario)。
故事
春節假期玩了很久的 SuperMarioRun,當得到了前三關的全部九個金幣後,終於下決心買了後面的世界,玩得津津有味。
前幾日發現 iOS 10.2 可以越獄了,作為一個開發者,迫不及待地越獄了自己的手機。越獄後打開 Mario,竟然發現了啟動閃退(啟動畫面之後就立刻退出了)。不禁職業性地感嘆,作為一個大廠,產品就這個質量??
重試了幾次,穩定復現,懷疑和剛才的越獄有關。google 一下,發現是遊戲本身做的限制。可以斷定為為了防止在越獄手機上作弊,而主動做出的限制,Ingress、PokemanGo 都有同樣的機制(但起碼人家是提示有問題,而不是像 crash 一樣的表現)。
搜索得知使用 tsProtector 可以使 App 的越獄檢查失效,可以繼續玩。但因為 iOS 10 的越獄剛出,很多軟體還沒有適配,而且 tsProtector 是收費軟體(免費試用一個),就決定自己寫一個,反正也不複雜 (??ω?)??。
原理
在 Jailbreak dev 的概念里,這樣的一個插件稱為 tweak,其單詞原意為「微調」。tweak 的實現依賴於 Cydia Substrate,它提供了運行時注入的功能。它在 app 啟動的時候,把插件作為動態庫載入到內存里,插件使用 Substrate 提供的 API,使用類似於 OC 運行時動態替換方法的形式,修改某些函數實現,以實現修改功能。更多內容可以參考「iOS 應用逆向工程」 這本書,以及 iPhoneDevWiki。
App 檢測越獄環境的方式有很多,其基本原理是做一些在越獄之前不能做的事情,如果能做成功,則是越獄狀態。最常見的方式是檢測一些在越獄之前不可以訪問的路徑,以及一些越獄後常見的文件(比如 cydia.app)。或者檢測 /etc/fstab 的大小、system(0) 等。這裡 提供了一些檢測方式。
為了使 App 的檢測失效,我們可以找到 App 中檢測越獄的函數,直接返回 NO,或者 hook 上面幾種方法中涉及到的函數,使其失效即可。
過程
嘗試一
Xgress 是為 ingress 是提供逃脫越獄檢查的 tweak,開源項目。那我就參照它開始寫吧。
這個項目非常簡單, 文件如下:
其中 plist 文件負責指定插件生效的 app 的 bundle ID,control 是存儲名稱作者等信息,makefile 配置編譯參數,真正的代碼只有一個文件 Tweak.mm 。它的核心內容也非常簡單,最主要的內容就是一句話:
MSHookFunction((FILE **)MSFindSymbol(NULL, "_fopen"), (FILE **)optimized_fopen, (FILE ***)&original_fopen);n
它把 fopen 函數的實現替換為自己的一個實現 optimized_fopen,後者檢測了文件路徑是否是某幾個路徑,如果是則是在進行越獄檢測,替換成一個其他的路徑,否則執行原函數內容。
static FILE *optimized_fopen(const char *filename, const char *mode) {n if (!allowAccess([NSString stringWithUTF8String:filename])) {n filename = "";n }n return original_fopen(filename, mode);n}n
很好很直接 (′?ω?`) 。我把生效的 bundle ID 改成 Mario 的 com.nintendo.zara ,通過 THEOS 編譯 (這裡的作用相當於 xcode)
make package installn
(通過 make package 就可以生成 .deb 包,如果配置好 ssh 就可以直接通過 install 安裝到手機上,也可以手動通過 傳到手機里,通過 iFile 等工具來安裝。)
打開 Mario,然而又閃退了(它退出的方式真的很像閃退,後文會用閃退代指檢測越獄環境後主動退出)。
打開 Ingress,插件生效,看來安裝沒有問題。Xgress 里只對 fopen 等幾個函數做了 hook,但對於 NSFileManager 里的方法並沒有處理。而後添加了對幾乎所有NSFileManager (NSString NSData) 里有關路徑的函數的 hook。
make package install !!!!然而,還是閃退。事情果然沒有這麼簡單。
嘗試二
仔細讀了一下上文中提到的檢測越獄環境里的函數,檢測了十幾項,依次 hook 看起來比較麻煩 ,換個思路 : 去找 App 里調用的地方,比如 -(void)isJailbreak;這樣的函數。
大概的思路是這樣的,找到 App 的 binary,導到電腦上 dump 出頭文件,然後找相應的函數名。
越獄手機上安裝 Clutch(它是一個命令行工具),它是用來「砸殼」的,因為 appstore app 都是經過加密的,否則不能 dump。app binary 存放在 /var/containers/Bundle/Application 下 (Mario 的 app 叫 rb_02_04 … app, 不知道為什麼,因為裡面的 binary 叫 Super Mario Run 我才認出來的),在手機上使用 Clutch 對 binary 文件進行處理 (這裡需要 root 許可權,開始試了幾次都提示 segment fault)。把解密後的 Binary 考到電腦里,使用 classdump 工具生成頭文件。
通過很簡單的方式,OC 的類名、繼承關係、函數名全都生成出來了。 從這個角度看,OC 真是什麼隱私都沒有呀 ╮(╯▽╰)╭ 。
在這裡文件里,嘗試搜索 jailbreak、sandbox 等欄位,找到了 FIRInstanceID 下的 - (_Bool)isSandboxApp; 感覺很像,編輯 hook 代碼:
%hook FIRInstanceIDn+ (void)load {n NSLog(@"GAO: Im in"); // 為了確認 hook 生效n %orig;n}n- (_Bool)isSandboxApp {n NSLog(@"GAO: hooked isSandbox");n return NO;n}n%endn
make package install !!!打開 Mario,繼續閃退。通過 Xcode - Device 可以看到系統 log,hook 生效,但是 isSandboxApp 並沒有被調用。失敗。同時找了其他幾個可疑的函數,都沒有被調用。
既然光看頭文件找不到,那不要怪我放大招了 : 反 匯 編!
把 binary 拖進 Hopper Disassembler 里,
一臉懵逼。。。
嘗試搜索 isSandboxApp 那個函數,找到了實現,但並不能向上找到調用的地方。既然不擅長看彙編,有沒有什麼方式可以找到一些提示呢?
想起了 crash log 中的函數調用棧,可是展示出函數的調用關係。雖然對於一個陌生 App,我們得到的 crash log 中是未解析的 file offset,但這個值正好可以在反彙編中定位內容。
看了一下 device crash log,雖然表現得樣子像閃退,但實際上並沒有 crash log。在 console 里列印出的實時 log 中,發現了這樣一條: (iOS 10 下)
iPhone SpringBoard[1672] <Notice>: Process exited: <FBApplicationProcess: 0x10be76ac0; Super Mario Run; pid: -1> -> <FBApplicationProcessExitContext: 0x174852c00; exitReason: voluntary; terminationReason: (none)>n
exitReason: voluntary ,難道程序自己調用了 exit(0) ? 那我們就 hook 一下 exit() ,作為切入點。
void exit(int);n%hookf(void, exit, int code) {n NSLog(@"GAO: were hooking exit");n NSLog(@"%@",[NSThread callStackSymbols]); // 這句可以得到和 crash log 一樣的調用棧展示n return;n}n
安裝插件,打開 Mario,列印出這樣的 log,
02:37:15 iPhone Super Mario Run(runmario.dylib)[17507] <Notice>: GAO: were hooking exitn02:37:15 iPhone Super Mario Run(runmario.dylib)[17507] <Notice>: (nt0 runmario.dylib 0x0000000102ad6c70 _ZL31_logos_function$_ungrouped$exiti + 48nt1 Super Mario Run 0x0000000100114474 Super Mario Run + 99444nt2 Super Mario Run 0x0000000100106d94 Super Mario Run + 44436nt3 Super Mario Run 0x0000000100106bd4 Super Mario Run + 43988nt4 QuartzCore 0x000000018dfeef24 <redacted> + 44n ...n
(注: 這是 mario 1.1.1 版本時的輸出,我在寫插件的時候還是 1.1,列印的 file offset 是不一樣的)
所以這三個 99444 44436 43988, 這三個 file offset 就是 mario app 在檢測到越獄環境,而直接調用 exit 退出的調用過程。這三個所代表的函數中必然有一個做了越獄檢測。
通過 Hopper 的 Go to file offset 功能,我們找到了這三個函數的實現。OC 的函數調用,在彙編下還是很好認的,以為 OC 的方法調用,其實最終就是 msg_send 函數的調用。方法(selector)作為字元串類型的參數, 可以直接在反彙編中給出注釋。而且函數調用中使用 X0 X1 X2 等寄存器作為參數的存放,其每個寄存器存什麼都是有固定套路的 ([X0 X1: X2 foo:X3 bar:stack...])。下圖是個簡單的例子。很明顯它調用了 [NSBundle mainBundle]。
然而在我找出來的這三個過程里,確實一片茫茫彙編海洋,沒有一個 OC 的函數,都是使用類似 bl 的彙編跳轉指令直接跳轉地址。只有最上層的顯示是一個 OC 函數,[UnityAppController repaint]。遊戲應該是用 Unity 做的,repaint 很明顯是一個與顯示有關的函數,hook 它為空函數。打開 App 後的確沒閃退,但是也什麼都沒有顯示。仔細看了這幾個調用過程里,發現了一段代碼感覺很像:
很明顯,1012bdd84 那行是個 if 判斷,一個分支是走到了 1012bdd90 那行,跟進去地區找到了 exit 函數的調用,算是檢測到越獄環境退出,而另一個分支則繼續運行。這個 procedure 這麼短,明顯不是真正檢測函數所在,而這裡面的 1012bdd78 和 1012bdd7c 兩個 bl 一定就是真正判斷的函數。
然而跟下去又是一片茫茫彙編海洋。沒有函數名,就沒有辦法 hook 更改,無能為力。即使我們知道 if 判斷就在這個地址,也是不能直接改 binary 的 (代碼簽名通不過)。無奈,誰叫遊戲都用 c++ 寫的呢 ╮(╯▽╰)╭
看了很久彙編,沒有進展,有些灰心。
柳暗花明
對封堵檢測越獄的函數,又添加的了幾種新的方式,然而沒有效果。
在彙編中搜索 "/bin/bash"這樣的字元串,試圖找找到調用的地方,而這個調用一定是越獄檢查的函數。真的有這樣的字元串(似乎 1.1.1 版本里已經沒了),該字元串調用的地方只有一處,著一定是我們要找的函數了。然而,還是沒有函數名給我們 hook。但這裡明顯出現了 fileExistsAtPath: 這樣敏感的函數。
對這個函數 hook 過呀?難道封堵的路徑還不全?列印出來看看
%hook NSFileManagern- (BOOL)fileExistsAtPath:(NSString *)path {n if(!allowAccess(path)){n return NO;n }n if (![path hasPrefix:@"/var/mobile/Containers/Data/Application/"]&& // app datan ![path hasPrefix:@"/var/containers/Bundle/Application"]) { // app binaryn %log(@"GAO_ignored",path);n }n return %orig;n}n
真的有輸出結果,補全漏掉的路徑,make package install !!
It works!
Finally~
最後的代碼,可以在 github 上找到。
後續
最後還需要提交到 Cydia。
使用 make package FINALPACKAGE=1 build 一個 release 版的 deb 包。Control 文件里的一些信息就是要在 cydia 里展示的,要好好填寫。
BigBoss 是 Cydia 的默認源之一,並且可以免費提供 host。在 這裡 進行簡單的填寫上傳包即可。等待一兩天就可以上架看到了
Initial published on Run Mario!寫一個越獄插件
推薦閱讀:
※iOS上有什麼好用的健身的軟體嗎?
※iOS 上有哪些好玩的單機遊戲?
※真有滴水不漏的手機翻新技術嗎?
※安卓廠商指紋識別類似於蘋果一樣trustzone?但關機進入recovery雙清,指紋被清除,重啟以後是原始解鎖界面。?