国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 系統(tǒng) > iOS > 正文

iOS通過(guò)逆向理解Block的內(nèi)存模型

2020-07-26 02:57:56
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

前言

正常情況下,通過(guò)分析界面以及 class-dump 出來(lái)頭文件就能對(duì)某個(gè)功能的實(shí)現(xiàn)猜個(gè)八九不離十。但是 Block 這種特殊的類(lèi)型在頭文件中是看不出它的聲明的,一些有 Block 回調(diào)的方法名 dump 出來(lái)是類(lèi)似這樣的:

- (void)FM_GetSubscribeList:(long long)arg1 pageSize:(long long)arg2 callBack:(CDUnknownBlockType)arg3;

因?yàn)檫@種回調(diào)看不到它的方法簽名,我們無(wú)法知道這個(gè) Block 到底有幾個(gè)參數(shù),也不知道它函數(shù)體的具體地址,因此在使用 lldb 進(jìn)行動(dòng)態(tài)調(diào)試的時(shí)候也是困難重重。我也一度被這個(gè)困難所阻擋,以為調(diào)用到有 Block 的方法就是進(jìn)了死胡同,沒(méi)辦法繼續(xù)跟蹤下去了。我還因此放棄過(guò)好幾次對(duì)某個(gè)功能的分析,特別受挫。

好在,我們還有 Google 這個(gè)強(qiáng)大的武器。沒(méi)有什么問(wèn)題是一次 Google 不能解決的。如果有,那就兩次。

這篇文章就來(lái)講講如何通過(guò) Block 的內(nèi)存模型來(lái)分析出它的函數(shù)體地址,以及函數(shù)簽名。

Block 的內(nèi)存結(jié)構(gòu)

在 LLVM 文檔中,可以看到 Block 的實(shí)現(xiàn)規(guī)范,其中最關(guān)鍵的地方是對(duì)于 Block 內(nèi)存結(jié)構(gòu)的定義:

struct Block_literal_1 { void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor_1 { unsigned long int reserved;  // NULL unsigned long int size;  // sizeof(struct Block_literal_1) // optional helper functions void (*copy_helper)(void *dst, void *src); // IFF (1<<25) void (*dispose_helper)(void *src);  // IFF (1<<25) // required ABI.2010.3.16 const char *signature;    // IFF (1<<30) } *descriptor; // imported variables};

可以看到第一個(gè)成員是 isa,說(shuō)明了 Block 在 Objective-C 當(dāng)中也是一個(gè)對(duì)象。我們重點(diǎn)要關(guān)注的就是 void (*invode)(void *, ...); 和 descriptor 中的 const char *signature,前者指向了 Block 具體實(shí)現(xiàn)的地址,后者是表示 Block 函數(shù)簽名的字符串。

實(shí)戰(zhàn)

注:本篇文章都是在 64 位系統(tǒng)下進(jìn)行分析,如果是 32 位系統(tǒng),整型與指針類(lèi)型的大小都是與 64 位不一致的,請(qǐng)自行進(jìn)行修改。

知道了 Block 的內(nèi)存模型后,就可以直接打開(kāi) hopper 和 lldb 進(jìn)行調(diào)試了。

我這里使用了邏輯思維的得到 APP 作為分析的例子。順便說(shuō)一句,得到上面的內(nèi)容都相當(dāng)不錯(cuò),很多付費(fèi)專(zhuān)欄的內(nèi)容都是很贊的,值得一看。

準(zhǔn)備

設(shè)備:iPhone 5s iOS 8.2 越獄

usbmuxd

$ tcprelay -t 22:2222 1234:1234Forwarding local port 2222 to remote port 22Forwarding local port 1234 to remote port 1234......

ssh 到 iOS 設(shè)備并啟動(dòng) debugserver:

$ ssh root@localhost -p 2222iPhone $ debugserver *:1234 -a "LuoJiFM-IOS"ebugserver-@(#)PROGRAM:debugserver PROJECT:debugserver-320.2.89 for arm64.Attaching to process LuoJiFM-IOS...Listening to port 1234 for a connection from *...

本地打開(kāi) lldb 并遠(yuǎn)程附加進(jìn)程,進(jìn)行動(dòng)態(tài)調(diào)試:

$ lldb(lldb) process connect connect://localhost:1234

找到偏移地址:

(lldb) image list -o -f [ 0] 0x0000000000074000 /private/var/mobile/Containers/Bundle/Application/D106C0E3-D874-4534-AED6-A7104131B31D/LuoJiFM-IOS.app/LuoJiFM-IOS(0x0000000100074000)[ 1] 0x000000000002c000 /Users/wordbeyond/Library/Developer/Xcode/iOS DeviceSupport/8.2 (12D508)/Symbols/usr/lib/dyld

在 Hopper 下找到需要斷點(diǎn)的地址:

下斷點(diǎn):

(lldb) br s -a 0x0000000000074000+0x0000000100069700Breakpoint 2: where = LuoJiFM-IOS`_mh_execute_header + 407504, address = 0x00000001000dd700

然后在應(yīng)用中點(diǎn)擊訂閱 Tab ,此時(shí)會(huì)命中斷點(diǎn)(如果沒(méi)有命中,手動(dòng)下拉刷新下)。

眾所周知,Objective-C 方法的調(diào)用都會(huì)轉(zhuǎn)化成 objc_msgSend 調(diào)用,因此單步的時(shí)候看到 objc_msgSend 就可以停下來(lái)了:

-> 0x1000dd71c <+431900>: bl 0x100daa2bc  ; symbol stub for: objc_msgSend 0x1000dd720 <+431904>: mov x0, x20 0x1000dd724 <+431908>: bl 0x100daa2ec  ; symbol stub for: objc_release 0x1000dd728 <+431912>: mov x0, x21(lldb) po $x0<DataServiceV2: 0x17400cea0>(lldb) po (char *)$x1"FM_GetSubscribeList:pageSize:callBack:"(lldb) po $x4<__NSStackBlock__: 0x16fd88f88>

可以看到,第四個(gè)參數(shù)是個(gè) StackBlock 對(duì)象,但是 lldb 只為我們打印出了它的地址。接下來(lái),就靠我們自己來(lái)找出它的函數(shù)體地址和函數(shù)簽名了。

找出 Block 的函數(shù)體地址

要找出 Block 的函數(shù)體地址很簡(jiǎn)單,根據(jù)上面的內(nèi)存模型,我們只到找到 invoke 這個(gè)函數(shù)指針的地址,它指向的就是這個(gè) Block 的實(shí)現(xiàn)。

在 64 位系統(tǒng)上,指針類(lèi)型的大小是 8 個(gè)字節(jié),而 int 是 4 個(gè)字節(jié),如下:

因此,invoke 函數(shù)指針的地址就是在第 16 個(gè)字節(jié)之后。我們可以通過(guò) lldb 的 memory 命令來(lái)打印出指定地址的內(nèi)存,我們上面已經(jīng)得到了 block 的地址,現(xiàn)在就打印出它的內(nèi)存內(nèi)容:

(lldb) memory read --size 8 --format x 0x16fd88f880x16fd88f88: 0x000000019b4d8088 0x00000000c20000000x16fd88f98: 0x00000001000dd770 0x0000000100fc66100x16fd88fa8: 0x000000017444c510 0x00000000000000010x16fd88fb8: 0x000000017444c510 0x0000000000000008

如前所述,函數(shù)指針的地址是在第 16 個(gè)字節(jié)之后,并占用 8 個(gè)字節(jié),所以可以得到函數(shù)的地址是 0x00000001000dd770。

有了函數(shù)地址之后,就可以對(duì)這個(gè)地址進(jìn)行反匯編:

(lldb) disassemble --start-address 0x00000001000dd770LuoJiFM-IOS`_mh_execute_header:-> 0x1000dd770 <+431984>: stp x28, x27, [sp, #-96]! 0x1000dd774 <+431988>: stp x26, x25, [sp, #16] 0x1000dd778 <+431992>: stp x24, x23, [sp, #32] 0x1000dd77c <+431996>: stp x22, x21, [sp, #48] 0x1000dd780 <+432000>: stp x20, x19, [sp, #64] 0x1000dd784 <+432004>: stp x29, x30, [sp, #80] 0x1000dd788 <+432008>: add x29, sp, #80  ; =80 0x1000dd78c <+432012>: mov x22, x3

也可以直接在 lldb 當(dāng)中下斷點(diǎn):

(lldb) br s -a 0x00000001000dd770Breakpoint 3: where = LuoJiFM-IOS`_mh_execute_header + 407616, address = 0x00000001000dd770

再次運(yùn)行函數(shù),就可以進(jìn)到回調(diào)的 Block 函數(shù)體內(nèi)了。

但是,大多數(shù)情況下,我們并不需要進(jìn)到 Block 函數(shù)體內(nèi)。在寫(xiě) tweak 的時(shí)候,我們更需要的是知道這個(gè) Block 回調(diào)給了我們哪些參數(shù)。

接下來(lái),我們繼續(xù)進(jìn)行探索。

找出 Block 的函數(shù)簽名

要找出 Block 的函數(shù)簽名,需要通過(guò) descriptor 結(jié)構(gòu)體中的 signature 成員,然后通過(guò)它得到一個(gè) NSMethodSignature 對(duì)象。

首先,需要找到 descriptor 結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體在 Block 中是通過(guò)指針持有的,它的位置正好在 invoke 成員后面,占用 8 個(gè)字節(jié)。可以從上面的內(nèi)存打印中看到 descriptor 指針的地址是 0x0000000100fc6610。

接下來(lái),就可以通過(guò) descriptor 的地址找到 signature 了。但是,文檔指出并不是每個(gè) Block 都是有方法簽名的,我們需要通過(guò) flags 與 block 中定義的枚舉掩碼進(jìn)行與判斷。還是在剛剛的 llvm 文檔中,我們可以看到掩碼的定義如下:

enum { BLOCK_HAS_COPY_DISPOSE = (1 << 25), BLOCK_HAS_CTOR =  (1 << 26), // helpers have C++ code BLOCK_IS_GLOBAL =  (1 << 28), BLOCK_HAS_STRET =  (1 << 29), // IFF BLOCK_HAS_SIGNATURE BLOCK_HAS_SIGNATURE = (1 << 30),};

再次使用 memory 命令打印出 flags 的值:

(lldb) memory read --size 4 --format x 0x16fd8a9580x16fd8a958: 0x9b4d8088 0x00000001 0xc2000000 0x000000000x16fd8a968: 0x000dd770 0x00000001 0x00fc6610 0x00000001

由于 ((0xc2000000 & (1 << 30)) != 0),因此我們可以確定這個(gè) Block 是有簽名的。

雖然在文檔中指出并不是每個(gè) Block 都有函數(shù)簽名的。但是我們可以在 Clang 源碼 中的 CGBlocks.cpp 查看 CodeGenFunction::EmitBlockLiteral 與 buildGlobalBlock 方法,可以看到每個(gè) Block 的 flags 成員都是被默認(rèn)設(shè)置了 BLOCK_HAS_SIGNATURE。因此,我們可以推斷,所有使用 Clang 編譯的代碼中的 Block 都是有簽名的。
為了找出 signature 的地址,我們還需要確認(rèn)這個(gè) Block 是否擁有 copy_helper 和 disponse_helper 這兩個(gè)可選的函數(shù)指針。由于 ((0xc2000000 & (1 << 25)) != 0) ,因此我們可以確認(rèn)這個(gè) Block 擁有剛剛提到的兩個(gè)函數(shù)指針。

現(xiàn)在可以總結(jié)下:signature 的地址是在 descriptor 下偏移兩個(gè) unsiged long 和兩個(gè)指針后的地址,即 32 個(gè)字節(jié)后。現(xiàn)在讓我們找出它的地址,并打印出它的字符串內(nèi)容:

(lldb) memory read --size 8 --format x 0x0000000100fc66100x100fc6610: 0x0000000000000000 0x00000000000000290x100fc6620: 0x00000001000ddb64 0x00000001000ddb700x100fc6630: 0x0000000100dfec18 0x00000000000000010x100fc6640: 0x0000000000000000 0x0000000000000048(lldb) p (char *)0x0000000100dfec18(char *) $4 = 0x0000000100dfec18 "v28@?0q8@"NSDictionary"16B24"

看到這一串亂碼是不是覺(jué)得有點(diǎn)崩潰,折騰了半天,怎么打印出這么一串鬼東西,雖然里面有一個(gè)熟悉的 NSDictionary,但是其它的東西完全看不懂啊。

不要慌,這確實(shí)就是一個(gè)函數(shù)簽名,只是我們需要通過(guò) NSMethodSignature 找出它的參數(shù)類(lèi)型:

(lldb) po [NSMethodSignature signatureWithObjCTypes:"v28@?0q8@/"NSDictionary/"16B24"]<NSMethodSignature: 0x174672940> number of arguments = 4 frame size = 224 is special struct return? NO return value: -------- -------- -------- -------- type encoding (v) 'v' flags {} modifiers {} frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0} memory {offset = 0, size = 0} argument 0: -------- -------- -------- -------- type encoding (@) '@?' flags {isObject, isBlock} modifiers {} frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0} memory {offset = 0, size = 8} argument 1: -------- -------- -------- -------- type encoding (q) 'q' flags {isSigned} modifiers {} frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0} memory {offset = 0, size = 8} argument 2: -------- -------- -------- -------- type encoding (@) '@"NSDictionary"' flags {isObject} modifiers {} frame {offset = 16, offset adjust = 0, size = 8, size adjust = 0} memory {offset = 0, size = 8}  class 'NSDictionary' argument 3: -------- -------- -------- -------- type encoding (B) 'B' flags {} modifiers {} frame {offset = 24, offset adjust = 0, size = 8, size adjust = -7} memory {offset = 0, size = 1}

注意,字符串中的雙引號(hào)需要對(duì)其進(jìn)行轉(zhuǎn)義。

對(duì)我們最有用的 type encoding 字段,這些符號(hào)對(duì)應(yīng)的解釋可以參考 Type Encoding 官方文檔。

所以,總結(jié)來(lái)講就是:這個(gè)方法沒(méi)有返回值,它接受四個(gè)參數(shù),第一個(gè)是 block (即我們自己的 block 的引用),第二個(gè)是 (long long) 類(lèi)型的,第三個(gè)是一個(gè) NSDictionary 對(duì)象,第四個(gè)是一個(gè) BOOL 值。

最終,我們得到了這個(gè) Block 的函數(shù)參數(shù)。最初提到的那個(gè)方法簽名的完整版就是:

- (void)FM_GetSubscribeList:(long long)arg1 pageSize:(long long)arg2 callBack:(void (^)(long long, NSDictionary *, BOOL)arg3;

小結(jié)

因?yàn)橄胧褂谜鎸?shí)的例子進(jìn)行演示,所以本文直接使用逆向的動(dòng)態(tài)分析進(jìn)行說(shuō)明。其實(shí)上面提到的所有過(guò)程,都可以直接在 Xcode 通過(guò)自己寫(xiě)的代碼進(jìn)行操作。通過(guò)自己動(dòng)手分析一遍,比看十篇文章來(lái)得更有效果。下次如果面試再有人問(wèn)到 Block 的實(shí)現(xiàn)和內(nèi)存模型,你就可以跟它侃侃而談了。

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流。

發(fā)表評(píng)論 共有條評(píng)論
用戶(hù)名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 定州市| 任丘市| 苗栗市| 运城市| 邓州市| 吉林市| 临沧市| 水富县| 长海县| 寿光市| 汝阳县| 阿拉善左旗| 平阴县| 蒙山县| 铜鼓县| 陈巴尔虎旗| 宣城市| 白山市| 浦县| 勃利县| 中阳县| 阳高县| 浦江县| 茶陵县| 宜兰市| 高邑县| 海安县| 威海市| 富锦市| 屏边| 睢宁县| 卓尼县| 郴州市| 壶关县| 自治县| 鄂州市| 商洛市| 天镇县| 温宿县| 汨罗市| 汶川县|