前言
疫情期間在家上班 , 打卡是個(gè)麻煩事 , 筆者就想起實(shí)現(xiàn)這個(gè)需求 , 本文由于講述從思路到完整分析過(guò)程演示 , 涉及到的知識(shí)點(diǎn)也基本都有介紹 , 因此篇幅較長(zhǎng) , 對(duì)相關(guān)知識(shí)比較熟悉的同學(xué)可以自行跳過(guò).
本篇文章將演示從 思路 -> 探索 -> 調(diào)試-> 注入 , 來(lái)實(shí)際逆向一個(gè)應(yīng)用的完整過(guò)程 . 實(shí)現(xiàn)的效果是修改 Wi-Fi 打卡以及 GPS 位置打卡規(guī)則 , 達(dá)到不在要求Wi-Fi 環(huán)境或者 GPS 范圍下也可以正常打卡的需求.
( 本文使用目前釘釘最新版本 5.0.6 , 后續(xù)更新有可能會(huì)更改代碼邏輯導(dǎo)致插件不再適用 , 只是為了交流和學(xué)習(xí))
聲明:本文演示插件無(wú)任何商業(yè)目的,也不從事任何商業(yè)性質(zhì)活動(dòng)。
提示:
本文基于越獄環(huán)境下插件來(lái)實(shí)現(xiàn).
非越獄環(huán)境下重簽應(yīng)用修改 BundleID 釘釘有相應(yīng)監(jiān)測(cè)? ? , 風(fēng)險(xiǎn)較大.
有一些修改手機(jī)虛擬定位的 , 是另外一種方式 , 本文不做講述.
前導(dǎo)知識(shí)
[if !supportLists]·????????[endif]1??、RSA加密原理&密碼學(xué)&HASH
[if !supportLists]·????????[endif]2??、應(yīng)用簽名原理及重簽名 (重簽微信應(yīng)用實(shí)戰(zhàn))
[if !supportLists]·????????[endif]3??、shell 腳本自動(dòng)重簽名與代碼注入
[if !supportLists]·????????[endif]4??、重簽應(yīng)用調(diào)試與代碼修改 (Hook)
[if !supportLists]·????????[endif]5??、Mach-O文件
[if !supportLists]·????????[endif]6??、Hook / fishHook 原理與符號(hào)表
[if !supportLists]·????????[endif]7??、LLDB由淺至深
[if !supportLists]·????????[endif]8??、lldb高級(jí)篇 Chisel 與 Cycript
[if !supportLists]·????????[endif]9??、越獄初探
關(guān)于逆向前導(dǎo)知識(shí),筆者所介紹文章中還缺少應(yīng)用砸殼以及匯編指令的知識(shí),后續(xù)更新會(huì)繼續(xù)講述,以形成一套完整的篇章欄目。
砸殼和匯編部分內(nèi)容并不影響我們今天所要做的釘釘插件。
準(zhǔn)備工作
完美越獄手機(jī) ( 本文使用iPhone 5s , iOS 9.0.1 , 完美越獄版本 , Xcode 10.1)
AppStroe 安裝正版釘釘,本文使用當(dāng)前最新版本 ( 5.0.6 )
Reveal ,關(guān)于 Reveal 本文會(huì)詳細(xì)講述
frida-ios-dump 關(guān)于這個(gè)砸殼工具的安裝 , 請(qǐng)參照git - frida-ios-dump ( 關(guān)于砸殼 , 后續(xù)筆者會(huì)更新文章專門講述動(dòng)態(tài)砸殼 ,
? ? 靜態(tài)砸殼原理以及不同砸殼工具的使用和原理)
Hopper - Mach-O 分析工具 提取密碼: x07n, dmg 解壓密碼 xclient.info.
Sublime Text - 文本編輯工具 , 輕量級(jí) , 全局檢索效率較高.
MonkeyDev 與 Theos , 重簽名應(yīng)用以及編寫越獄插件必不可少的工具.
越獄環(huán)境 lldb 使用 , lldb 插件 - loadCommands
一、Reveal 頁(yè)面調(diào)試工具
很多逆向必須從頁(yè)面入手 , 找到對(duì)應(yīng)按鈕的方法進(jìn)行hook , 可以幫助我們理清楚代碼以及業(yè)務(wù)邏輯.
我們都知道 , 對(duì)于已經(jīng)砸殼或者自己通過(guò)代碼運(yùn)行在手機(jī)上的工程,是沒有加密的,這種應(yīng)用我們可以直接通過(guò)Xcode -> Debug -> Attach to Process , 對(duì)正在運(yùn)行的應(yīng)用進(jìn)行附加 ,以此可以達(dá)到 View-Debug 的效果 。
( 對(duì)于如何判斷應(yīng)用 MachO 是否加殼,使用 otool -l +
Macho文件名稱即可 ) 。
那么對(duì)于正版應(yīng)用,也就是沒有砸殼的應(yīng)用,如何進(jìn)行頁(yè)面調(diào)試呢。這里就是Reveal 解決的問題。
安裝
1、Mac 端工具安裝
Reveal-for-Mac , 提取碼:iv3j,dmg安裝包密碼:xclient.info
下載安裝即可。
2、越獄手機(jī)插件
Cydia 中搜索 Reveal
Loader 并安裝。
[if !vml]
[endif]
3、手機(jī)設(shè)置
按下圖設(shè)置打開權(quán)限 。
[if !vml]
[endif]
4、環(huán)境配置
[if !vml]
[endif]打開終端 , 使用SSH , usb 的方式做端口映射并登錄手機(jī) ( 關(guān)于越獄環(huán)境下手機(jī)和電腦的端口隱射以及文件傳輸參考越獄初探 這篇文章中有非常詳細(xì)的演示和講述) .
端口映射
[if !vml]
[endif]
登錄
[if !vml]
[endif]
從Mac 端拷貝文件到手機(jī) 先在手機(jī)根目錄 /Library/ 下建立 RHRevealLoader 文件夾.
[if !vml]
[endif]
將電腦端 RevealServer.framework 中的 RevealServer 拷貝一份 , 改名為libReveal.dylib , 然后拷貝到手機(jī) /Library/RHRevealLoader/ 目錄下.
scp -P 12345 -r
/Users/libin/Desktop/逆向/libReveal.dylib root@localhost:/Library/RHRevealLoader/
復(fù)制代碼
你同樣可以使用 iFunBox 來(lái)可視化的操作文件.
重啟電腦端 Reveal , 重啟手機(jī) , 打開釘釘 , 即可顯示如下.
[if !vml]
[endif]
5、提示
電腦端 Reveal 并沒有檢測(cè)到所打開應(yīng)用 , 請(qǐng)排查以下問題:
1?? : 檢查下 RevealServer 有沒有改名為 libReveal.dylib.
2?? : 檢查 libReveal.dylib 有無(wú)可執(zhí)行權(quán)限? ? , 沒有+x 即可.
3?? : 手機(jī)與電腦端 Reveal 有無(wú)重啟.
4?? : 手機(jī)重啟后要保持與電腦在同一局域網(wǎng)下.
最終實(shí)現(xiàn)效果如下:
[if !vml]
[endif]
二、砸殼
這里使用 frida-ios-dump 進(jìn)行砸殼 , ( 關(guān)于砸殼, 后續(xù)筆者會(huì)更新文章專門講述動(dòng)態(tài)砸殼 , 靜態(tài)砸殼原理以及不同砸殼工具的使用和原理)
對(duì) usb 端口映射 , 登錄手機(jī) ,然后即可使用砸殼.
[if !vml]
[endif]
( 筆者這里配置了自定義 shell 腳本 , 其實(shí)就是為了在任意路徑下使用frida 的 dump.py , 就不用每次必須 cd 到這個(gè)目錄里 , 砸完殼的應(yīng)用ipa 也就在當(dāng)前目錄下 . 感興趣的同學(xué)可以參照越獄初探 中 2.5.3 配置 shell 自動(dòng)連接 usb 映射并登陸部分有詳細(xì)演示) .
[if !vml]
[endif]
砸完殼后得到 ipa , 修改為 zip , 解壓縮 , 顯示包內(nèi)容 , 即可找到砸殼后的Mach-O 源文件.
筆者這里提供一下砸殼后的應(yīng)用包以及 class-dump 后的頭文件:
鏈接 密碼: pjtt
查看應(yīng)用加密情況:
[if !vml]
[endif]
三、class-dump
使用 class-dump 導(dǎo)出砸殼后應(yīng)用的頭文件 . 不熟悉class-dump 的同學(xué)可以參考 重簽應(yīng)用調(diào)試與代碼修改 這篇文章.
輸入命令:
class-dump -H DingTalk -o ./headers/
DingTalk 為當(dāng)前目錄下 Mach-O 文件名
-o ./headers/ 意思是輸出到同路徑的 headers 文件夾中.
結(jié)果如下:
[if !vml]
[endif]
至此 , 準(zhǔn)備工作就已經(jīng)完成了 , 這也是我們逆向一個(gè)應(yīng)用首先需要做的事情.
1?? : 運(yùn)行應(yīng)用 , 頁(yè)面調(diào)試 ( Reveal , 砸殼應(yīng)用重簽名附加到 Xcode , View-Debug , Chisel 與 Cycript 等 , 參照lldb高級(jí)篇 Chisel 與 Cycript) .
2?? : 應(yīng)用砸殼
3?? : class-dump
四、準(zhǔn)備工作總結(jié)
在實(shí)際逆向開發(fā)中 , 直接上代碼去調(diào)試是很不推薦的方式 , 本著時(shí)間就是成本的原則 , 我們的處理應(yīng)該是如下流程.
先理清楚思路和源開發(fā)邏輯 , 能靜態(tài)分析就靜態(tài)分析 ( 靜態(tài)分析一般包括class-dump 查看頭文件 , macho 分析 , 匯編代碼分析等等. 當(dāng)然 , 不是迫不得已的情況下不需要分析匯編 , 對(duì)于匯編非常熟悉的同學(xué)可以忽略 ) , 盡量少動(dòng)態(tài)調(diào)試 , 在我們有了十足的把握之后再開始上代碼.
因此準(zhǔn)備工具都準(zhǔn)備完畢以后 , 我們來(lái)思考一下業(yè)務(wù)需求和邏輯.
思路
需求:
[if !supportLists]·????????[endif]當(dāng)使用地點(diǎn) GPS 打卡規(guī)則時(shí):
[if !supportLists]o???[endif]我們希望可以不在指定位置也能正常打卡.
[if !supportLists]·????????[endif]當(dāng)使用連接上指定 Wi-Fi 打卡時(shí):
[if !supportLists]o???[endif]我們希望可以不連接上指定 Wi-Fi 也能正常打卡.
那么首先我們能想到的就是如下:
思路1
從打卡的按鈕和點(diǎn)擊方法入手 , 對(duì)該方法進(jìn)行hook 或者查看匯編進(jìn)行靜態(tài)分析 , 查看其處理邏輯 , 修改判斷邏輯 , 當(dāng)不滿足條件也能正常執(zhí)行打卡 .
因此 , 來(lái)到打卡頁(yè)面 , 使用Reveal 查看按鈕方法.
[if !vml]
[endif]
來(lái)到這看到一個(gè)不幸的消息 , 整個(gè)頁(yè)面是個(gè)WebView . 即使我們可以猜測(cè)其WebView 與OC 交互通過(guò)哪種方式 , 會(huì)調(diào)用到哪些固定方法繼續(xù)查找 , 單未免時(shí)間成本過(guò)于大了一些.
因此我們變換一下思路.
思路2
首先我們來(lái)考慮下 GPS 打卡的情況 , 由于iOS 系統(tǒng)要求 , 任何地圖的獲取GPS 位置幾乎都是封裝了原生的 CLLocationManager 來(lái)做的.
因此我們想到 , 對(duì)于獲取到GPS 的代理方法中進(jìn)行修改 , 使其永遠(yuǎn)獲取到的是滿足地址返回的經(jīng)緯度.
Wi-Fi 思路也是如上 . 大體上應(yīng)該可以滿足需求 . 接下來(lái)我們就來(lái)嘗試一下.
調(diào)試過(guò)程
一、靜態(tài)分析
使用 Hopper 打開砸完殼后的 Mach-O . 搜索方法:
locationManager:didUpdateLocations:
復(fù)制代碼
搜索結(jié)果如下:
[if !vml]
[endif]
搜索到整個(gè)工程中 , 有這幾個(gè)類都實(shí)現(xiàn)了這個(gè)方法 . 那么我們應(yīng)該怎么辦 , 去看這幾個(gè)方法的匯編進(jìn)行分析嗎 ? 筆者這里不推薦這種方式.
由于使用習(xí)慣告訴我們 , 每次打開打卡頁(yè)面時(shí)會(huì)獲取GPS 位置.
因此我認(rèn)為可以這么處理:
使用動(dòng)態(tài)調(diào)試 , 對(duì)這些類的這個(gè)方法進(jìn)行Hook , Hook 中保持繼續(xù)調(diào)用原方法 , 但是加一個(gè)打印 , 以此找到當(dāng)打開頁(yè)面時(shí) , 是由哪個(gè)類去獲取GPS 位置 . 從而縮小分析范圍.
使用 logos 進(jìn)行方法 hook 是非常高效而且便捷的 . ( 不熟悉的同學(xué)可以閱讀實(shí)際逆向中 hook 方式 -- Logos)
因此 , 直接來(lái)到動(dòng)態(tài)調(diào)試階段.
二、動(dòng)態(tài)調(diào)試
1. 新建工程
在安裝完 MonkeyDev 插件后 , 新建項(xiàng)目選擇如下:
[if !vml]
[endif]
修改文件類型為 Objective-C++ Source , 文件內(nèi)容只保留一個(gè) UIKit 的引入即可.
( 遇到文件類型修改后不刷新情況的同學(xué)可以左側(cè)選擇其他文件再選擇回來(lái)即可)
[if !vml]
[endif]
2. 獲取目標(biāo)工程的Bundle ID
這里其實(shí)有很多種方式 , 可以直接去砸完殼的應(yīng)用的info.plist 中找.
為了順帶提一點(diǎn) cycript 在越獄環(huán)境下的使用 , 以及自定義 cy 指令 ( 同樣參考lldb高級(jí)篇 Chisel 與 Cycript)
我們使用這種方式:
① : 端口映射及 ssh 登錄手機(jī)
② : 手機(jī)運(yùn)行釘釘 并保證前臺(tái)活躍狀態(tài)
③ : 查看釘釘進(jìn)程 ID
④ : cycript 附加當(dāng)前進(jìn)程
⑤ : 導(dǎo)入自定義 cy 指令
⑥ : 獲取 Bundle
? ? ID[if !vml]
[endif]
將 Bundle ID 填入到工程配置文件中.
[if !vml]
[endif]
2. ssh 配置
使用 MonkeyDev 編寫插件需要在工程配置 ssh 登錄信息.
[if !vml]
[endif]
筆者在 .zshrc 配置了相關(guān)環(huán)境變量 , 就不用每次填寫.
[if !vml]
[endif]
關(guān)于 ssh 端口映射以及登錄參考 越獄初探
至于為什么插件安裝成功需要?dú)⒌暨M(jìn)程重新啟動(dòng) , 這里我們稍微提一點(diǎn).
[if !supportLists]·????????[endif]其實(shí) MonkeyDev 的 tweak 使用的 Theos , 其原理是利用的 DYLD_INSERT_LIBRARY 原理.
[if !supportLists]·????????[endif]dyld 源碼中可以看到 , dyld 會(huì)根據(jù) DYLD_INSERT_LIBRARY 這個(gè)環(huán)境變量來(lái)決定是否加載插入的動(dòng)態(tài)庫(kù).
[if !supportLists]·????????[endif]有些防護(hù)手段就是通過(guò)添加 __RESTRICT 段 , _restrict 節(jié)來(lái)實(shí)現(xiàn)越獄插件的防護(hù)的 . ( 關(guān)于這個(gè)我們會(huì)在后續(xù)越獄環(huán)境攻防中詳細(xì)講述和演示) .
[if !supportLists]·????????[endif]越獄插件打包其實(shí)會(huì)生成 dylib 庫(kù) , 然后通過(guò)ssh 拷貝到手機(jī)中 , 在應(yīng)用加載時(shí) , 由dyld 去加載的 hook 代碼 . ( 這也是為什么插件安裝需要?dú)⒌暨M(jìn)程重新加載的原因) .
dyld 源碼:
const char* const *?????????? DYLD_INSERT_LIBRARIES;
// load any insertedlibraries??
if ( sEnv.DYLD_INSERT_LIBRARIES!= NULL ) {
?for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL;++lib)
?????loadInsertedDylib(*lib);
}
復(fù)制代碼
2. 編寫Logos
這里代碼并沒有什么值得說(shuō)的 , 就是分別hook 我們上述在 hopper 中搜出來(lái)的實(shí)現(xiàn)了 locationManager:didUpdateLocations: 的幾個(gè)類中的這個(gè)方法.
#import
%hook AMapLocationCLMDelegate
- (void)locationManager:(id)arg1 didUpdateLocations:(id)arg2{
??? %log;
??? %orig;
}
%end
%hook AMapLocationManager
- (void)locationManager:(id)arg1didUpdateLocations:(id)arg2{
??? %log;
??? %orig;
}
%end
%hook DTCLocationManager
- (void)locationManager:(id)arg1didUpdateLocations:(id)arg2{
??? %log;
??? %orig;
}
%end
%hook LALocationManager
- (void)locationManager:(id)arg1didUpdateLocations:(id)arg2{
??? %log;
??? %orig;
}
%end
%hook LAPLocationInfo
-(void)dt_locationManager:(id)arg1 didUpdateLocations:(id)arg2{
??? %log;
??? %orig;
}
%end
%hook DTLocationJSAPIHandler
-(void)dt_locationManager:(id)arg1 didUpdateLocations:(id)arg2{
??? %log;
??? %orig;
}
%end
%hook VILocationManager
- (void)locationManager:(id)arg1didUpdateLocations:(id)arg2{
??? %log;
??? %orig;
}
%end
%hook MAMapView
- (void)locationManager:(id)arg1didUpdateLocations:(id)arg2{
??? %log;
??? %orig;
}
%end
復(fù)制代碼
寫完在確保做完本地端口 12345 已經(jīng)映射到 usb 另一端手機(jī)的 22 端口 , 就可以編譯一下了. 編譯成功 , 發(fā)現(xiàn)正在運(yùn)行的釘釘已經(jīng)被殺掉.
3. 查看打印
由于我們 hook 方法里加了打印 , 因此我們可以運(yùn)行釘釘 , 來(lái)到打卡頁(yè)面 , 查看調(diào)用的到底是哪個(gè)類的定位方法.
cmd + shift + 2 打開設(shè)備頁(yè) , 打開控制臺(tái) . 輸入搜索DingTalk 選擇進(jìn)程.
[if !vml]
[endif]
[if !vml]
[endif]
[if !supportLists]·????????[endif]通過(guò)搜索結(jié)果我們發(fā)現(xiàn) , AMapLocationCLMDelegate 與 AMapLocationManager 的 didUpdateLocations 調(diào)用是最頻繁的 , 而且一直是成對(duì)出現(xiàn)的.
[if !supportLists]·????????[endif]那么我們可以推測(cè) , Manager 與 delegate 存在代理關(guān)系 , 由Manager 獲取到定位信息后 , 通過(guò)代理回調(diào)到協(xié)議執(zhí)行者中 . 也就是一個(gè)方法嵌套.
[if !supportLists]·????????[endif]把 AMapLocationCLMDelegate 和 AMapLocationManager 的地址記錄保存.
4. 內(nèi)存斷點(diǎn)
由于生產(chǎn)環(huán)境包并無(wú)符號(hào) , 因此通過(guò)類名和方法名下斷點(diǎn)是行不通的 , 所以我們使用內(nèi)存地址下斷點(diǎn).
ssh 映射并登錄后 . 手機(jī)端輸入:
[if !supportLists]·????????[endif]debugserver *:12346 -a DingTalk
復(fù)制代碼
mac 端輸入 lldb 進(jìn)入斷點(diǎn)狀態(tài) ,
? ? 然后輸入
[if !supportLists]·????????[endif]process connect connect://localhost:12346
復(fù)制代碼
( 以上就是越獄環(huán)境使用 usb 的方式進(jìn)行 lldb 調(diào)試) .
使用 methods + 類內(nèi)存地址獲取類的方法列表和地址( 此指令來(lái)自于lldb 插件 - loadCommands ) .
[if !vml]
[endif]
使用 b + 方法內(nèi)存地址 即可對(duì) AMapLocationCLMDelegate 類的 didUpdateLocations 方法下內(nèi)存斷點(diǎn).
[if !vml]
[endif]
注意:
可能有些同學(xué)會(huì)經(jīng)常使用如上方法進(jìn)行越獄環(huán)境添加斷點(diǎn) . 正常情況下是沒有問題的 , 但是還記得我們做了什么嗎 ? 我們將方法進(jìn)行了hook , 也就是說(shuō)方法實(shí)現(xiàn) imp 已經(jīng)被換成我們自己的方法了 , 因此 , 上述方式是錯(cuò)誤的 !! .
c 一下 , 來(lái)到下一次調(diào)用AMapLocationCLMDelegate 的 didUpdateLocations 方法.
[if !vml]
[endif]可以看到來(lái)到的明顯是我們自己編寫的 logos hook 代碼.
那么我們改如何對(duì)未 hook 的原本這個(gè)方法下斷點(diǎn)呢?
獲取方法基于 mach-O 首地址的偏移量.
[if !vml]
[endif]
獲取 mach-O 首地址
[if !vml]
[endif]
相加獲得方法本次運(yùn)行實(shí)際地址 . 該計(jì)算原理涉及ALSR 和 pageZero 知識(shí) , 參考LLDB由淺至深 文章末尾.
通過(guò)計(jì)算得到 AMapLocationCLMDelegate 的方法地址 0x100267EE8 , AMapLocationManager 的方法地址 0x1002786E4 . 那我們分別添加斷點(diǎn).
添加完后過(guò)掉斷點(diǎn) , 來(lái)到下一次調(diào)用:
[if !vml]
[endif]
可以發(fā)現(xiàn)這次進(jìn)入的就不是我們 hook 的方法了 , OK , 兩個(gè)方法內(nèi)存斷點(diǎn)添加完成.
使用 bt 可以看到函數(shù)嵌套調(diào)用關(guān)系 , 驗(yàn)證了我們之前的猜想:
[if !vml]
[endif]
經(jīng)過(guò)如上調(diào)試 , 我們發(fā)現(xiàn)了 , 在打卡頁(yè)面 , 調(diào)用的是AMapLocationManager 的定位 . 而且是固定一段時(shí)間就會(huì)調(diào)用一次 . 因此 , 接下來(lái) , 我們來(lái)看下這個(gè)類的頭文件.
5. 查看頭文件
來(lái)到 sublime 中打開 class-dump 出來(lái)的頭文件 , cmd + shift + f 搜索 @interface AMapLocationManager.
[if !vml]
[endif]找到這個(gè)類的所有方法和屬性 , 那么我們接下來(lái)對(duì)這個(gè)類的所有方法進(jìn)行hook , 添加打印查看并研究其邏輯與調(diào)用流程.
由于這個(gè)類方法較多 , 手寫太過(guò)麻煩 , 其實(shí)我們配置Theos 時(shí) , 里面有一個(gè)腳本.
[if !vml]
[endif]
將我們 dump 出來(lái)的 AMapLocationManager.h 拷貝到工程目錄下 . 輸入命令:
[if !vml]
[endif]
執(zhí)行完畢將生成的 logAMapManager.xm 拖進(jìn)工程 , 選擇type 為 Objective-C++ Source , 編譯工程 , 此時(shí)項(xiàng)目文件夾中多生成一個(gè)logAMapManager.mm , 同樣拖進(jìn)工程.
[if !vml]
[endif]
此時(shí) , 該類的所有方法hook 代碼已經(jīng)自動(dòng)完成了 . 此時(shí)編譯會(huì)有報(bào)錯(cuò) , 將報(bào)錯(cuò)的類添加一個(gè)聲明即可 , 另外.cxx_destruct , 一些 block 和 delegate 的方法筆者這邊直接注釋掉了 , 沒有必要分析.
筆者這里添加了四個(gè)類的聲明 , 需要的同學(xué)直接拷貝使用即可.
@interface CLLocationManager
@end
@interface AMapNetworkManager
@end
@interface CLLocation
@end
@interface AMapLocationReGeocode
@end
復(fù)制代碼
將我們先前 hook 的那幾個(gè)類的定位方法注釋掉 , 防止打印過(guò)多影響判斷.
重新 build , 查看控制臺(tái)打印.
[if !vml]
[endif]
這里我們發(fā)現(xiàn) , manager 調(diào)用 startUpdatingLocation , 然后后面就有了 didUpdateLocations . 那么同樣 , 我們找到startUpdatingLocation 的地址下斷點(diǎn).
斷點(diǎn)下成功后 , 重新進(jìn)入打卡頁(yè)面 . 來(lái)到斷點(diǎn) ,bt 查看函數(shù)調(diào)用棧.
[if !vml]
[endif]
6. 定位函數(shù)調(diào)用流程
分析:
[if !supportLists]·????????[endif]上圖中我們看到函數(shù)名由于符號(hào)的原因并沒有顯示 , 那么我們想知道函數(shù)名稱 , 同樣, 計(jì)算即可 . 圖中展示為本次進(jìn)程函數(shù)實(shí)際地址.
[if !supportLists]·????????[endif]那么減去 Mach-O 首地址隨機(jī)偏移值 : ALSR , 即可得到基于 Mach-O 首地址偏移量 , 這樣我們即可去Hopper 搜索得到類名和函數(shù)名稱.
[if !supportLists]·????????[endif]注意考慮 pageZero 的問題 ( 實(shí)際物理地址和偏移量都包含了pageZero , arm 64 中計(jì)算時(shí)減去一個(gè) 0x10000000 即可) .
以函數(shù)調(diào)用棧中 #2 為例 , 本次實(shí)際函數(shù)地址為0x00000001024eaad8 , 減去 image list 中得到的 Mach-O 首地址也就是 ALSR 去掉最前面的1 ( 即page zero ) 為 0x94000 , 得到 0x102456AD8.
來(lái)到 Hopper , 按G 鍵, 輸入內(nèi)存地址找到如下:
[if !vml]
[endif]
( 筆者這里由于 Hopper 還未解析完畢 , 正常顯示是具體的匯編指令 , Mac 端的 Hopper 實(shí)在太卡) .
通過(guò)以上方法 , 將startUpdatingLocation 的函數(shù)調(diào)用棧無(wú)符號(hào)的情況下成功翻譯如下:
frame #1: 0x00000001085250e8? [AMapLocationManager startUpdatingLocation]
frame #2: 0x102456AD8 DingTalk`?? [DTALocationManagerdt_startUpdatingLocation] ?DingTalk? +248
frame #3: 0x1039148B0 DingTalk`[LAPLocationInfo start:to:]?DingTalk? +1980
frame #4: 0x103959010 DingTalk`[LAPluginInstanceCollector handleJavaScriptRequest:callback:]?DingTalk? + 52
復(fù)制代碼
這里我們就找到了調(diào)用起始點(diǎn) : webView 的 js 與 OC 交互回調(diào) . 也就是說(shuō)這個(gè)webView 頁(yè)面加載 , 與OC 通訊 , 調(diào)起的開始定位.
那么接下來(lái) , hook 這個(gè)方法 , 添加打印.
%hook LAPluginInstanceCollector
-(void)handleJavaScriptRequest:(id)arg1 callback:(id)arg2{
??? %orig;
??? NSLog(@"LBHook\n\n\n\n\n"
????????? "arg1Class = %@\n"
????????? "arg1 = %@\n"
????????? "arg2Class = %@\n"
????????? "arg2 = %@\n"
????????? ,[arg1 class],arg1,[arg2class],arg2);
}
%end
復(fù)制代碼
[if !vml]
[endif]
通過(guò)添加打印 , 我們發(fā)現(xiàn) , 參數(shù) 1 是一個(gè)NSDictionary 類型的參數(shù) . 參數(shù)2 是一個(gè)棧 block . 而且在第一個(gè)參數(shù)中并沒有找到我們所需要的經(jīng)緯度等信息 , 那么就需要來(lái)看下這個(gè)Block.
7. block 獲取簽名
由于我們需要知道 這個(gè) Block 的參數(shù)和返回值是什么 , 因此我們需要通過(guò)Block 的簽名來(lái)查看 , 關(guān)于Block 獲取簽名 , 這個(gè)是老生常談問題了 , 在Aspect 等源碼庫(kù)中都有實(shí)現(xiàn).
其實(shí)就是根據(jù) flags 的標(biāo)記 , 同時(shí)根據(jù)內(nèi)存偏移拿到Block_descriptor_3 的 signature 的過(guò)程 ( 有關(guān) block 底層原理詳細(xì)探索 , 后續(xù)筆者在iOS 底層篇章會(huì)詳細(xì)講述) .
block 數(shù)據(jù)結(jié)構(gòu)源碼:
struct Block_layout {
??? void *isa;
??? volatile int32_t flags; // contains refcount
??? int32_t reserved;
??? BlockInvokeFunction invoke;//實(shí)現(xiàn)地址!
??? struct Block_descriptor_1 *descriptor;
??? // imported variables
};
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
??? uintptr_t reserved;
??? uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
??? // requires BLOCK_HAS_COPY_DISPOSE
??? BlockCopyFunction copy;
??? BlockDisposeFunction dispose;
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
??? // requires BLOCK_HAS_SIGNATURE
??? const char *signature;
??? const char *layout;???? // contents depend onBLOCK_HAS_EXTENDED_LAYOUT
};
復(fù)制代碼
那么按照上文講述同樣方式 , 我們給[LAPluginInstanceCollector handleJavaScriptRequest:callback:] 添加斷點(diǎn) . 添加成功后運(yùn)行來(lái)到該斷點(diǎn).
[if !vml]
[endif]
拿到 block 地址后 , 我們來(lái)獲取其簽名.
[if !vml]
[endif]
通過(guò)
po [NSMethodSignaturesignatureWithObjCTypes:"v16@?0@8"]
復(fù)制代碼
[if !vml]
[endif]可以很明顯的得出 block 是一個(gè)無(wú)返回值 , 一個(gè)id 類型參數(shù)的 block . 那么如何得知這個(gè) id 類型到底是個(gè)什么類型呢.
8. block 參數(shù)類型確定
為了明確這個(gè) block 參數(shù)的具體類型 , 我們嵌套一個(gè)block , 將原方法調(diào)用我們自定義的 block , 我們自定義的 block 中調(diào)用原本的 block , 同時(shí)添加一個(gè)打印即可.
因此 , 我們將hook 代碼改寫如下:
%hook LAPluginInstanceCollector
-(void)handleJavaScriptRequest:(NSDictionary *)arg1 callback:(void(^)(id))arg2{
??? id myCallBack = ^(id block_arg){
??????? NSLog(@"arg1 %@\n block_argClass:%@\nblock_arg:%@\n",arg1,[block_arg class],block_arg);
??????? //保持原有掉用!!
??????? arg2(block_arg);
??? };
??? %orig(arg1,myCallBack);
}
%end
復(fù)制代碼
編譯安裝 , 重新打開釘釘 , 查看控制臺(tái)打印輸出.
[if !vml]
[endif]可以看出 , block 的參數(shù)類型為 NSDictionary , 同時(shí) , 我們還看到了最希望看到的GPS 位置信息.
??分析完畢:
因此 , 我們完全可以推測(cè)出 , 在這個(gè)方法中調(diào)用了AMapLocationManager 的 startUpdatingLocation , 而在獲得位置后回調(diào)一個(gè)字典到這個(gè) block 中 . 因此 , 在這個(gè)方法中 , 將經(jīng)緯度直接寫死為打卡要求位置的經(jīng)緯度 . 完全可以滿足我們的需求.
當(dāng)然 , 打印中我們看到參數(shù)1 和 參數(shù)2 還有很多種不同的情況和狀態(tài) , 通過(guò)查看發(fā)現(xiàn) , 當(dāng)參數(shù)1 中的 action 為 start , 并且 參數(shù) 2 中的 keep 為 1 時(shí) , 代表需要重新獲取經(jīng)緯度.
因此 , 修改代碼.
9. 修改原代碼邏輯
%hook LAPluginInstanceCollector
-(void)handleJavaScriptRequest:(NSDictionary *)arg1 callback:(void(^)(id))arg2{
??? if([arg1[@"action"]isEqualToString:@"start"]){//有可能需要修改定位信息!
????? ??//定義一個(gè)myBlock
??????? id myCallBack = ^(NSDictionary *block_arg){
??????????? if([block_arg[@"keep"]isEqualToString:@"1"]){//需要修改GPS
??????????????? NSMutableDictionary * tempDic =[NSMutableDictionary dictionaryWithDictionary:block_arg];
??????????????? //修改block中的字典的值!
???????????????tempDic[@"result"][@"latitude"] =@"28.1924070001";
??????????????? tempDic[@"result"][@"longitude"]= @"112.9788130003";
??????????????? //使用修改后的!
??????????????? arg2(tempDic);
??????????? }else{
??????????????? //保持原有掉用!!
??????????????? arg2(block_arg);
??????????? }
??????? };
??????? %orig(arg1,myCallBack);
??? }else{
??????? %orig;
??? }
}
%end
復(fù)制代碼
10. 效果查看
未安裝插件 : 打卡顯示異地.[if !vml]
[endif]
編譯安裝插件.[if !vml]
[endif]
三、WiFi 與 GPS 打卡插件完整版
WiFi 打卡探索思路與 GPS 位置打卡一模一樣.
探索后發(fā)現(xiàn)同樣是我們最終 hook 的 js 與 OC 交互的那個(gè)方法中去獲取當(dāng)前連接 WiFi 信息與管理員設(shè)置的 WiFi 的 macIp 是否一致來(lái)判斷的.
hook 代碼完整版:
%hook LAPluginInstanceCollector
- (void)handleJavaScriptRequest:(NSDictionary*)arg1 callback:(void(^)(id))arg2{
??? if([arg1[@"action"]isEqualToString:@"start"]){//有可能需要修改定位信息!
??????? //定義一個(gè)myBlock
??????? id myCallBack = ^(NSDictionary *block_arg){
??????????? if([block_arg[@"keep"] isEqualToString:@"1"]){//需要修改GPS
??????????????? NSMutableDictionary * tempDic =[NSMutableDictionary dictionaryWithDictionary:block_arg];
??????????????? //修改block中的字典的值!這里修改為你公司設(shè)置的允許打卡 GPS 位置
??????????????? tempDic[@"result"][@"latitude"]= @"31.8567980000";
???????????????tempDic[@"result"][@"longitude"] =@"118.8274580000";
??????????????? //使用修改后的!
??????????????? arg2(tempDic);
??????????? }else{
??????????????? //保持原有調(diào)用!!
??????????????? arg2(block_arg);
??????????? }
??????? };
??????? %orig(arg1,myCallBack);
??? }else if([arg1[@"action"]isEqualToString:@"getInterface"]){//修改WIFI!!
??????? //定義一個(gè)myBlock
??????? id myCallBack = ^(NSDictionary *block_arg){
???????????NSMutableDictionary *tempDic = [NSMutableDictionary dictionaryWithDictionary:block_arg];
??????????? //修改block中的字典的值!? ,這里修改為你公司設(shè)置的WiFimacIP
???????????tempDic[@"result"][@"macIp"] =@"f0:b4:29:6b:fe:51";
??????????? //使用修改后的!
???????????arg2(tempDic);
??????? };
??????? %orig(arg1,myCallBack);
??? }else{
??????? %orig;
??? }
}
%end
復(fù)制代碼
WiFi 效果就不演示了 , 與GPS 規(guī)則表現(xiàn)相同.
至此 , 你就可以拿一臺(tái)越獄設(shè)備舒舒服服在家打卡了??.
總結(jié)
[if !supportLists]·????????[endif]1?? : 本文中詳細(xì)展示了如何從無(wú)到有做出一個(gè)越獄插件 , 其中涉及到的各種調(diào)試工具和知識(shí)點(diǎn)大多都有介紹 . 希望感興趣的同學(xué)多加練習(xí).
[if !supportLists]·????????[endif]2?? : 逆向過(guò)程中 , 思路清晰非常重要 , 同時(shí)要熟練掌握cycript , 砸殼 , 越獄環(huán)境lldb 調(diào)試 , Theos , Chisel , 等等靜態(tài)分析和動(dòng)態(tài)調(diào)試工具 , 底層原理同樣是必不可少的.
[if !supportLists]·????????[endif]3?? : 理清思路后 , 盡量使用靜態(tài)分析 ( 匯編也屬于靜態(tài)分析 , 這個(gè)根據(jù)個(gè)人情況定 ) , 當(dāng)大體思路理順之后即可進(jìn)行動(dòng)態(tài)調(diào)試 , 上hook 代碼和斷點(diǎn)調(diào)試 , cycript 等工具幫助理順原碼邏輯.
[if !supportLists]·????????[endif]4?? : 最后我們發(fā)現(xiàn) , 其實(shí)真正hook 注入修改代碼非常簡(jiǎn)單, 逆向之路本就是如此 , 梳理和推導(dǎo)往往會(huì)花費(fèi)大量時(shí)間.
最后再次聲明:
本文演示插件無(wú)任何商業(yè)目的,也不從事任何商業(yè)性質(zhì)活動(dòng)。