iOS逆向,釘釘在家就可以打卡的秘訣!

前言

疫情期間在家上班 , 打卡是個(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è)其WebViewOC 交互通過(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)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容