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