iOS彙編教程(四)基於 LLDB 動態調試快速分析系統函數的實現

系列文章

  1. iOS彙編入門教程(一)ARM64彙編基礎
  2. iOS彙編入門教程(二)在Xcode工程中嵌入彙編代碼
  3. iOS彙編入門教程(三)彙編中的 Section 與數據存取

前言

在前三篇文章中,主要介紹了彙編中的常見指令和尋址方法,本文將結合這些知識介紹一種基於彙編代碼和動態調試快速分析函數邏輯的方法。git

在進行逆向工程,或是涉及到底層的正向開發(如性能優化、安全防禦)時,經常會使用一些系統級的函數,有些時候從細節上了解這些函數的邏輯是十分必要的,例如 objc_getClassobject_getClass 兩個方法,前者經過名稱拿到類對象,後者經過入參爲實例、類對象和元類對象的不一樣分別返回類對象、元類對象和根元類對象,在不閱讀源碼的狀況下,咱們只能經過實驗的方式去黑盒分析函數邏輯,好在 Objective-C Runtime的源碼 是開放的,咱們能夠直接閱讀這兩個函數的源碼來分析邏輯,這是一種很好的方法,但是若是咱們要分析的函數沒有源碼呢?例如 NSClassFromString 這個函數。github

靜態分析

NSClassFromString 來自 Foundation,一個直接的思路是使用 IDA 或 Hopper 對 Foundation 進行靜態分析,而後將 NSClassFromString 反編譯成 C 的僞代碼來分析,這種方式的優勢在於代碼的可讀性較強,可以快速分析出邏輯,缺點也很明顯,對於較大的 Framework 靜態分析速度很慢,遇到費解的邏輯仍是須要結合動態調試來分析。安全

動態分析

動態分析通常是基於 IDA 或 Hopper 的結果對特定的符號或地址下斷點分析,固然也能夠直接基於內存中的 __TEXT,__text 段,藉助於 LLDB 的反彙編器來直接分析,本文將採用這種方法,以 NSClassFromString 爲例展開分析。性能優化

動態分析 NSClassFromString

添加符號斷點

首先隨便找一個 iOS 工程,添加一個 NSClassFromString 的符號斷點: bash

隨後鏈接一臺 64 位的 iOS 設備,運行,隨後程序斷在了 NSClassFromString 函數內,並展現了這個符號的反彙編結果:函數

Foundation`NSClassFromString:
->  0x18bfd2c98 <+0>:   stp    x28, x27, [sp, #-0x40]!
    0x18bfd2c9c <+4>:   stp    x22, x21, [sp, #0x10]
    0x18bfd2ca0 <+8>:   stp    x20, x19, [sp, #0x20]
    0x18bfd2ca4 <+12>:  stp    x29, x30, [sp, #0x30]
    0x18bfd2ca8 <+16>:  add    x29, sp, #0x30            ; =0x30 
    0x18bfd2cac <+20>:  sub    sp, sp, #0x3f0            ; =0x3f0 
    0x18bfd2cb0 <+24>:  mov    x19, x0
    0x18bfd2cb4 <+28>:  adrp   x8, 196266
    0x18bfd2cb8 <+32>:  ldr    x8, [x8, #0xf0]
    0x18bfd2cbc <+36>:  ldr    x8, [x8]
    0x18bfd2cc0 <+40>:  stur   x8, [x29, #-0x38]
    0x18bfd2cc4 <+44>:  cbz    x19, 0x18bfd2d6c          ; <+212>
    0x18bfd2cc8 <+48>:  adrp   x8, 224951
    0x18bfd2ccc <+52>:  ldr    x1, [x8, #0x4c0]
    0x18bfd2cd0 <+56>:  mov    x0, x19
    0x18bfd2cd4 <+60>:  bl     0x18a72cd60               ; objc_msgSend
    0x18bfd2cd8 <+64>:  mov    x20, x0
    0x18bfd2cdc <+68>:  adrp   x8, 183685
    0x18bfd2ce0 <+72>:  add    x1, x8, #0x8b8            ; =0x8b8 
    0x18bfd2ce4 <+76>:  mov    x21, sp
    0x18bfd2ce8 <+80>:  mov    x2, sp
    0x18bfd2cec <+84>:  mov    w3, #0x3e8
    0x18bfd2cf0 <+88>:  orr    w4, wzr, #0x4
    0x18bfd2cf4 <+92>:  mov    x0, x19
    0x18bfd2cf8 <+96>:  bl     0x18a72cd60               ; objc_msgSend
    0x18bfd2cfc <+100>: cmp    w0, #0x0                  ; =0x0 
    0x18bfd2d00 <+104>: csel   x21, x21, xzr, ne
    0x18bfd2d04 <+108>: cbz    w0, 0x18bfd2d18           ; <+128>
    0x18bfd2d08 <+112>: mov    x0, x21
    0x18bfd2d0c <+116>: bl     0x18c1391a8               ; symbol stub for: +[NSUnitElectricPotentialDifference megavolts]
    0x18bfd2d10 <+120>: cmp    x0, x20
    0x18bfd2d14 <+124>: b.eq   0x18bfd2d5c               ; <+196>
    0x18bfd2d18 <+128>: cbz    x20, 0x18bfd2d48          ; <+176>
    0x18bfd2d1c <+132>: mov    x21, #0x0
    0x18bfd2d20 <+136>: adrp   x8, 183600
    0x18bfd2d24 <+140>: add    x22, x8, #0x9ce           ; =0x9ce 
    0x18bfd2d28 <+144>: mov    x0, x19
    0x18bfd2d2c <+148>: mov    x1, x22
    0x18bfd2d30 <+152>: mov    x2, x21
    0x18bfd2d34 <+156>: bl     0x18a72cd60               ; objc_msgSend
    0x18bfd2d38 <+160>: cbz    w0, 0x18bfd2d68           ; <+208>
    0x18bfd2d3c <+164>: add    x21, x21, #0x1            ; =0x1 
    0x18bfd2d40 <+168>: cmp    x21, x20
    0x18bfd2d44 <+172>: b.lo   0x18bfd2d28               ; <+144>
    0x18bfd2d48 <+176>: adrp   x8, 183538
    0x18bfd2d4c <+180>: add    x1, x8, #0x800            ; =0x800 
    0x18bfd2d50 <+184>: mov    x0, x19
    0x18bfd2d54 <+188>: bl     0x18a72cd60               ; objc_msgSend
    0x18bfd2d58 <+192>: mov    x21, x0
    0x18bfd2d5c <+196>: mov    x0, x21
    0x18bfd2d60 <+200>: bl     0x18a7273e0               ; objc_lookUpClass
    0x18bfd2d64 <+204>: b      0x18bfd2d6c               ; <+212>
    0x18bfd2d68 <+208>: mov    x0, #0x0
    0x18bfd2d6c <+212>: ldur   x8, [x29, #-0x38]
    0x18bfd2d70 <+216>: adrp   x9, 196266
    0x18bfd2d74 <+220>: ldr    x9, [x9, #0xf0]
    0x18bfd2d78 <+224>: ldr    x9, [x9]
    0x18bfd2d7c <+228>: cmp    x9, x8
    0x18bfd2d80 <+232>: b.ne   0x18bfd2d9c               ; <+260>
    0x18bfd2d84 <+236>: add    sp, sp, #0x3f0            ; =0x3f0 
    0x18bfd2d88 <+240>: ldp    x29, x30, [sp, #0x30]
    0x18bfd2d8c <+244>: ldp    x20, x19, [sp, #0x20]
    0x18bfd2d90 <+248>: ldp    x22, x21, [sp, #0x10]
    0x18bfd2d94 <+252>: ldp    x28, x27, [sp], #0x40
    0x18bfd2d98 <+256>: ret    
    0x18bfd2d9c <+260>: bl     0x18b03358c               ; __stack_chk_fail
複製代碼

根據調試器給出的註釋,咱們能夠大體看到 NSClassFromString 函數的調用中包含了 4 個 OC 方法調用,看起來邏輯並非很是直截了當,下面咱們從頭開始分析。工具

首先略過前 6 句對狀態的暫存,咱們看第一個 objc_msgSend 的邏輯:佈局

0x18bfd2cb0 <+24>:  mov    x19, x0
0x18bfd2cb4 <+28>:  adrp   x8, 196266
0x18bfd2cb8 <+32>:  ldr    x8, [x8, #0xf0]
0x18bfd2cbc <+36>:  ldr    x8, [x8]
0x18bfd2cc0 <+40>:  stur   x8, [x29, #-0x38]
0x18bfd2cc4 <+44>:  cbz    x19, 0x18bfd2d6c          ; <+212>
0x18bfd2cc8 <+48>:  adrp   x8, 224951
0x18bfd2ccc <+52>:  ldr    x1, [x8, #0x4c0]
0x18bfd2cd0 <+56>:  mov    x0, x19
0x18bfd2cd4 <+60>:  bl     0x18a72cd60               ; objc_msgSend
0x18bfd2cd8 <+64>:  mov    x20, x0
複製代碼

棧溢出保護

從 +28 到 +40 的這段代碼用於保存棧的哨兵 stack_chk_guard,它經過屢次間接尋址拿到哨兵的值,將其寫入 x29 - 0x38 的區域,這是爲了防止棧溢出而添加的程序保護,運行到 +40 後,棧上的內存佈局以下: post

stack_guard 有效的保護了棧底的寄存器不被覆蓋。當棧溢出時,首先會覆蓋 stack_guard 的值,在函數返回前,經過校驗 stack_guard 是否合法便可判斷是否出現了棧溢出,程序的 +212 到 +232 這段代碼便是退出前檢查:性能

0x18bfd2d6c <+212>: ldur   x8, [x29, #-0x38]
0x18bfd2d70 <+216>: adrp   x9, 196266
0x18bfd2d74 <+220>: ldr    x9, [x9, #0xf0]
0x18bfd2d78 <+224>: ldr    x9, [x9]
0x18bfd2d7c <+228>: cmp    x9, x8
0x18bfd2d80 <+232>: b.ne   0x18bfd2d9c               ; <+260>

# __stack_chk_fail
0x18bfd2d9c <+260>: bl     0x18b03358c               ; __stack_chk_fail
複製代碼

這段代碼從棧上相同的位置取出了 stack_guard,隨後同哨兵的原始值做比較,不相等則直接跳轉到棧溢出的處理邏輯,這種保護手段主要是爲了保護開發者,防止開發者的代碼發生了棧溢出而不自知,從而引發奇怪而未知的 BUG,但不能有效的防止棧溢出攻擊,由於攻擊者能夠刻意構造棧溢出的內容,經過僞造哨兵來繞過檢查,所以在寫偏底層的代碼時要注意提升代碼質量,經過 CodeReview 和靜態、動態掃描等方案規避棧溢出漏洞。

第一個 objc_msgSend 分析

基於上面的分析,咱們略去棧溢出保護的代碼,獲得以下代碼:

0x18bfd2cb0 <+24>:  mov    x19, x0
0x18bfd2cc8 <+48>:  adrp   x8, 224951
0x18bfd2ccc <+52>:  ldr    x1, [x8, #0x4c0]
0x18bfd2cd0 <+56>:  mov    x0, x19
0x18bfd2cd4 <+60>:  bl     0x18a72cd60               ; objc_msgSend
0x18bfd2cd8 <+64>:  mov    x20, x0
複製代碼

咱們知道,objc_msgSend 的第一個參數爲類的實例,第二個參數爲 SEL,這裏的關鍵是根據間接尋址指令計算出 SEL 從而獲得調用的方法,有兩種方式:動態分析和手動計算,因爲這段代碼沒有分支邏輯,所以咱們能夠直接對 +60 行下斷點,經過 lldb 的 register read 指令獲取 SEL 的值:

(lldb) register read x1
x1 = 0x00000001b8c67778  "length"
複製代碼

顯然這是一個 -[NSString length] 的調用,即獲取 OC 字符串的長度,隨後存入 x20:

x20 = [aClassName length]
複製代碼

第二個 objc_msgSend 分析

隨後咱們分析 +68 到 +108 的代碼片斷:

0x18bfd2cdc <+68>:  adrp   x8, 183685
0x18bfd2ce0 <+72>:  add    x1, x8, #0x8b8            ; =0x8b8 
0x18bfd2ce4 <+76>:  mov    x21, sp
0x18bfd2ce8 <+80>:  mov    x2, sp
0x18bfd2cec <+84>:  mov    w3, #0x3e8
0x18bfd2cf0 <+88>:  orr    w4, wzr, #0x4
0x18bfd2cf4 <+92>:  mov    x0, x19
0x18bfd2cf8 <+96>:  bl     0x18a72cd60               ; objc_msgSend
0x18bfd2cfc <+100>: cmp    w0, #0x0                  ; =0x0 
0x18bfd2d00 <+104>: csel   x21, x21, xzr, ne
0x18bfd2d04 <+108>: cbz    w0, 0x18bfd2d18           ; <+128>
複製代碼

顯然 +68 到 +92 行是在爲 objc_msgSend 準備參數,除去前兩個固定入參外,還有 3 個參數:

  1. +80 行準備了方法的第 1 個入參 sp,sp 的值在 +0 處減去了 0x40,隨後在 +20 處減去了 0x3f0,即存儲在距離棧底 0x430 位置,這是一個局部指針變量,根據 +76 可知 x21 保存了這個指針的地址。
  2. +84 行準備了方法的第 2 個入參 0x3e8
  3. +88 行準備了方法的第 3 個入參 0x4,其中 orr 是邏輯或,wzr 是 word zero register,相似於 /dev/zero,寫入無效,讀出爲 0。

採用一樣的策略,在 +96 處下斷點,獲得 SEL 的內容:

(lldb) register read x1
x1 = 0x00000001b8d578b8  "getCString:maxLength:encoding:"
複製代碼

綜合上面的分析,這一方法調用能夠表示成以下形式:

char *aClassNameC; // x21
x0 = [aClassName getCString:aClassNameC maxLength:0x3e8 encoding:0x4];
複製代碼

隨後的 +100 到 +108 行是對函數返回值和 CString 的處理,這裏 +104 行出現了一個 csel 指令,該指令是 ARM 中的三目運算:

csel  Wd, Wn, Wm, cond 
# 等價於
Wd = cond ? Wn : Wm
複製代碼

所以總體能夠翻譯爲:

char *aClassNameC; // x21
bool success = [aClassName getCString:aClassNameC maxLength:0x3e8 encoding:0x4];
aClassNameC = success ? aClassNameC : NULL;
if (!success) {
    goto 0x18bfd2d18;
}
複製代碼

這一處跳轉是將 NSString 轉換成長度不超過 1000 的 CString 的一個異常處理,咱們接下來分析這個異常處理。

NSString2CString 的異常處理

從 0x18bfd2d18 開始,又涉及到一個分支邏輯:

0x18bfd2d18 <+128>: cbz    x20, 0x18bfd2d48          ; <+176>
0x18bfd2d1c <+132>: mov    x21, #0x0
0x18bfd2d20 <+136>: adrp   x8, 183600
0x18bfd2d24 <+140>: add    x22, x8, #0x9ce           ; =0x9ce 
0x18bfd2d28 <+144>: mov    x0, x19
0x18bfd2d2c <+148>: mov    x1, x22
0x18bfd2d30 <+152>: mov    x2, x21
0x18bfd2d34 <+156>: bl     0x18a72cd60               ; objc_msgSend
複製代碼

NSString 長度爲 0 的狀況

從上面的分析可知,x20 是入參 ClassName 的長度,若是轉換失敗,且入參長度爲 0,則跳轉到 0x18bfd2d48:

0x18bfd2d48 <+176>: adrp   x8, 183538
0x18bfd2d4c <+180>: add    x1, x8, #0x800            ; =0x800 
0x18bfd2d50 <+184>: mov    x0, x19
0x18bfd2d54 <+188>: bl     0x18a72cd60               ; objc_msgSend
0x18bfd2d58 <+192>: mov    x21, x0
0x18bfd2d5c <+196>: mov    x0, x21
0x18bfd2d60 <+200>: bl     0x18a7273e0               ; objc_lookUpClass
0x18bfd2d64 <+204>: b      0x18bfd2d6c               ; <+212>
複製代碼

這段代碼先是調用了一個 OC 方法,隨後經過 objc_lookUpClass 直接查找類對象,因爲 objc_lookUpClass 的入參是 const char * 類型,所以這個 OC 方法必定是將 NSString 轉爲 CString 的方法,爲了肯定方法,咱們須要解出 SEL,與上面不一樣的是,在這以前涉及到了兩個分支跳轉,代碼不必定能走到這裏,所以咱們不能再偷懶經過 register read 直接拿到 SEL,而是須要手動計算,下面介紹手動計算 adrp 尋址的方法。

手動計算 ADRP 尋址

iOS彙編入門教程(三)彙編中的 Section 與數據存取 中咱們介紹了 ARM64 的指令長度爲 32 位,所以單條指令很難存下一個較大的偏移量,在基於 PC 尋址時,採用了指令拆分的形式,爲了基於 PC 進行 +-4GB 的尋址,咱們先用 adrp 取出基於二進制基址的頁偏移的高 21 位(@PAGE)到寄存器,隨後累加低 12 位(@PAGEOFF),便可獲得目標地址,以上面的 SEL 取值過程爲例:

0x18bfd2d48 <+176>: adrp   x8, 183538
0x18bfd2d4c <+180>: add    x1, x8, #0x800            ; =0x800 
複製代碼

咱們首先將 adrp 所在地址的低 12 位清空獲得 0x18bfd2000,183538 是 33 位中高 21 位的值,所以須要左移 33 - 21 = 12 位,隨後加上 0x800 便可獲得目標地址,經過 memory read 讀取便可獲得 SEL 的值:

(lldb) p/x 0x18bfd2000 + (183538 << 12) + 0x800
(long) $1 = 0x00000001b8cc4800
(lldb) memory read 0x00000001b8cc4800
0x1b8cc4800: 55 54 46 38 53 74 72 69 6e 67 00 73 74 72 69 6e  UTF8String.strin
0x1b8cc4810: 67 57 69 74 68 43 68 61 72 61 63 74 65 72 73 3a  gWithCharacters:
複製代碼

因而可知,這是一個 [aClassName UTF8String] 的調用,所以 +176 到 +204 能夠翻譯爲:

aClassNameC = [aClassName UTF8String];
Class clazz = objc_lookUpClass(aClassNameC);
goto 0x18bfd2d6c;
複製代碼

隨後咱們看 0x18bfd2d6c 的代碼:

0x18bfd2d6c <+212>: ldur   x8, [x29, #-0x38]
0x18bfd2d70 <+216>: adrp   x9, 196266
0x18bfd2d74 <+220>: ldr    x9, [x9, #0xf0]
0x18bfd2d78 <+224>: ldr    x9, [x9]
0x18bfd2d7c <+228>: cmp    x9, x8
0x18bfd2d80 <+232>: b.ne   0x18bfd2d9c               ; <+260>
0x18bfd2d84 <+236>: add    sp, sp, #0x3f0            ; =0x3f0 
0x18bfd2d88 <+240>: ldp    x29, x30, [sp, #0x30]
0x18bfd2d8c <+244>: ldp    x20, x19, [sp, #0x20]
0x18bfd2d90 <+248>: ldp    x22, x21, [sp, #0x10]
0x18bfd2d94 <+252>: ldp    x28, x27, [sp], #0x40
0x18bfd2d98 <+256>: ret    
0x18bfd2d9c <+260>: bl     0x18b03358c               ; __stack_chk_fail
複製代碼

其中 +212 到 +232 以及 +260 上面已經講過,是棧溢出的檢測代碼,+236 到 +252 是函數返回前的恢復工做,去掉這些代碼後,0x18bfd2d6c 就只剩下了一條 ret,即函數返回,所以上述代碼能夠完整地翻譯爲:

aClassNameC = [aClassName UTF8String];
return objc_lookUpClass(aClassNameC);
複製代碼

NSString 長度不爲 0 的狀況

即從 +132 到 +156:

0x18bfd2d1c <+132>: mov    x21, #0x0
0x18bfd2d20 <+136>: adrp   x8, 183600
0x18bfd2d24 <+140>: add    x22, x8, #0x9ce           ; =0x9ce 
0x18bfd2d28 <+144>: mov    x0, x19
0x18bfd2d2c <+148>: mov    x1, x22
0x18bfd2d30 <+152>: mov    x2, x21
0x18bfd2d34 <+156>: bl     0x18a72cd60               ; objc_msgSend
0x18bfd2d38 <+160>: cbz    w0, 0x18bfd2d68           ; <+208>
0x18bfd2d3c <+164>: add    x21, x21, #0x1            ; =0x1 
0x18bfd2d40 <+168>: cmp    x21, x20
0x18bfd2d44 <+172>: b.lo   0x18bfd2d28               ; <+144>
複製代碼

採用手工計算的方法,咱們能夠獲得這個方法的 SEL 爲 characterAtIndex:

(lldb) p/x 0x18bfd2000 + (183600 << 12) + 0x9ce
(long) $2 = 0x00000001b8d029ce
(lldb) memory read 0x00000001b8d029ce
0x1b8d029ce: 63 68 61 72 61 63 74 65 72 41 74 49 6e 64 65 78  characterAtIndex
0x1b8d029de: 3a 00 67 65 74 41 72 67 75 6d 65 6e 74 3a 61 74  :.getArgument:at
複製代碼

這段代碼是一個循環結構,其中 x21 爲迭代器從 0 開始 (+132),不斷的從 aClassName 中取出第 x21 個字符 (+152 到 +156),判斷是否爲 0 (+160),爲 0 則跳轉到 0x18bfd2d68,不爲 0 則將 x21 + 1 循環直到字符串結尾(+168 到 + 172),這裏的 b.lo 是無符號小於比較,綜上所述這段代碼可翻譯爲:

// x20 爲 aClassName 的 length
// x20 = [aClassName length];
for (int i = 0; i < aClassName.length; i++) {
    if ([aClassName characterAtIndex:i] == '\0') {
        goto 0x18bfd2d68;
    }
}
複製代碼

這段代碼實際上是爲了檢查 NSString 中間是否包含了 \0,若是包含則走 0x18bfd2d68 的異常邏輯,這是由於 \0 在 CString 中是結束符,會在轉換時將字符串截斷,帶來未知的後果,接下來咱們看 0x18bfd2d68 的處理:

0x18bfd2d68 <+208>: mov    x0, #0x0
0x18bfd2d6c <+212>: ldur   x8, [x29, #-0x38]
0x18bfd2d70 <+216>: adrp   x9, 196266
0x18bfd2d74 <+220>: ldr    x9, [x9, #0xf0]
0x18bfd2d78 <+224>: ldr    x9, [x9]
0x18bfd2d7c <+228>: cmp    x9, x8
0x18bfd2d80 <+232>: b.ne   0x18bfd2d9c               ; <+260>
複製代碼

因而可知跳轉到了咱們分析過的 +212 的上一行,+212 開始的代碼是函數的返回工做,所以 +208 的做用是將返回值設爲 0,即返回 nil 做爲返回值,到這裏上面的循環能夠完整的翻譯以下:

for (int i = 0; i < aClassName.length; i++) {
    if ([aClassName characterAtIndex:i] == '\0') {
        return nil;
    }
}
複製代碼

循環後面的代碼從 +176 開始,這段代碼咱們在 NSString 長度爲 0 的狀況 中已經分析過了,是將 NSString 轉成 UTF-8 編碼的 CString 後調用 objc_lookUpClass 並返回。綜合上面的分析,咱們能夠獲得以下的代碼片斷:

NSUInteger lengthOC = [aClassName length];
char *aClassNameC; // x21
bool success = [aClassName getCString:aClassNameC maxLength:0x3e8 encoding:0x4];
aClassNameC = success ? aClassNameC : NULL;
if (!success) {
    if (lengthOC == 0) {
        aClassNameC = [aClassName UTF8String];
        return objc_lookUpClass(aClassNameC);
    } else {
        for (int i = 0; i < aClassName.length; i++) {
            if ([aClassName characterAtIndex:i] == '\0') {
                return nil;
            }
        }
        aClassNameC = [aClassName UTF8String];
        return objc_lookUpClass(aClassNameC);
    }
}
複製代碼

不要被 Xcode 和 LLDB 欺騙

接下來的分析針對 getCString 成功的分支邏輯,即從 +112 開始的代碼,與上面的分析過程大同小異,這裏留給讀者自行完成,這裏只講解一下 +116 行的調用:

0x18bfd2d0c <+116>: bl     0x18c1391a8 ; symbol stub for: +[NSUnitElectricPotentialDifference megavolts]
複製代碼

看 Xcode 中提供的註釋,這彷佛是拿了一下電量中兆伏特 (MV) 的物理單位,真是費解成一匹馬,其實這是 LLDB 動態分析的 BUG,咱們能夠反彙編 0x18c1391a8 的內容一探究竟:

(lldb) dis -a 0x18c1391a8
Foundation`+[NSUnitElectricPotentialDifference megavolts]:
0x18c1391a8 <+0>: adrp   x16, 195909
0x18c1391ac <+4>: ldr    x16, [x16, #0x8b8]
0x18c1391b0 <+8>: br     x16
複製代碼

這裏取出了一個符號地址存入 x16 來執行,咱們來看看 x16 裏究竟是什麼:

(lldb) p/x 0x18c139000 + (195909 << 12) + 0x8b8
(long) $3 = 0x00000001bbe7e8b8
(lldb) memory read 0x00000001bbe7e8b8
0x1bbe7e8b8: 80 23 15 8b 01 00 00 00 38 06 03 8b 01 00 00 00  .#......8.......
0x1bbe7e8c8: f0 23 15 8b 01 00 00 00 1c 1c 15 8b 01 00 00 00  .#..............
(lldb) dis -a 0x018b152380
libsystem_platform.dylib`_platform_strlen:
    0x18b152380 <+0>:  and    x1, x0, #0xfffffffffffffff0
    0x18b152384 <+4>:  ldr    q0, [x1]
複製代碼

一頓操做後發現原來是 strlen,若是你一味地輕信 Xcode 顯示的註釋,分析就沒法進行下去了,這告訴咱們作事情必定要抱着懷疑的態度。

完整分析結果

注意,這裏是徹底按照控制流翻譯出的代碼,其中有大量的重複邏輯能夠簡化

NSClassFromString(NSString *aClassName) {
    if (aClassName == nil) {
    	return nil;
    }
    NSUInteger lengthOC = [aClassName length];
    char *aClassNameC;
    bool success = [aClassName getCString:aClassNameC maxLength:0x3e8 encoding:0x4];
    aClassNameC = success ? aClassNameC : NULL;
    if (!success) {
    	if (lengthOC == 0) { 
    	    aStringNameC = [aClassName UTF8String];
    	    return objc_lookUpClass(aStringNameC);
    	} else {
    	    for (int i = 0; i < lengthOC; i++) {
    	    	if ([aClassName characterAtIndex:0] == '\0') {
                    return nil;
                }
            }
            aStringNameC = [aClassName UTF8String];
            return objc_lookUpClass(aStringNameC);
    	}
    } else {
    	if (strlen(aClassNameC) == lengthOC) {
            return objc_lookUpClass(aClassNameC);
    	} else if (lengthOC == 0) {
            aStringNamcC = [aClassName UTF8String];
            return objc_lookUpClass(aStringNameC);
    	} else {
    	    for (int i = 0; i < lengthOC; i++) {
    	    	if ([aClassName characterAtIndex:0] == '\0') {
                    return nil;
                }
            }
            aStringNameC = [aClassName UTF8String];
            return objc_lookUpClass(aStringNameC);
    	}
    }
}
複製代碼

總結

直接基於 LLDB 動態分析函數實現仍是比較浪費時間的,對此可在 lldb 中封裝一些工具來輔助分析,例如識別棧溢出保護代碼並移除、自動解析循環、自動解析間接尋址等,這會在接下來的文章中進行介紹。

相關文章
相關標籤/搜索