因為Apple已經棄gdb投lldb,是以随着我動态調試的次數越來越頻繁,gdb上一個接一個的bug經常會讓人很惱火。既然蘋果打算建立自己的調試器王國,也投入了财力精力,那我們幹脆也上手lldb玩玩,看看lldb是不是比gdb要更好用(以下操作在iPhone 5,iOS 8.1上測試,方法同樣适用于arm64。更多内容請參照iphonedevwiki)
一、配置debugserver
1. 在iOS中安裝debugserver
debugserver運作在iOS上,顧名思義,它作為服務端,實際執行LLDB(作為用戶端)傳過來的指令,再把執行結果回報給LLDB,顯示給使用者,即所謂的“遠端調試”。在預設情況下,iOS上并沒有安裝debugserver,隻有在裝置連接配接過一次Xcode,并在Window→Devices菜單中添加此裝置後,debugserver才會被Xcode安裝到iOS的“/Developer/usr/bin/”目錄下。
2. 幫debugserver減肥
對照下表,記下裝置的ARM資訊。
Name | ARM |
iPhone 4s | armv7 |
iPhone 5 | armv7s |
iPhone 5c | armv7s |
iPhone 5s | arm64 |
iPhone 6 Plus | arm64 |
iPhone 6 | arm64 |
iPad 2 | armv7 |
iPad mini | armv7 |
The New iPad | armv7 |
iPad with Retina display | armv7s |
iPad Air | arm64 |
iPad Air 2 | arm64 |
iPad mini with Retina display | arm64 |
iPad mini 3 | arm64 |
iPod touch 5 | armv7 |
我的iPhone 5對應的ARM是armv7s。将未經處理的debugserver從 iOS拷貝到OSX中的“/Users/snakeninny/”目錄下:
- snakeninnysiMac:~ snakeninny$ scp [email protected]:/Developer/usr/bin/debugserver ~/debugserver
複制代碼 然後幫它減肥:
- snakeninnysiMac:~ snakeninny$ lipo -thin armv7s ~/debugserver -output ~/debugserver
複制代碼 注意把這裡的“armv7s”換成你的裝置所對應的ARM。
3. 給debugserver添加task_for_pid權限
下載下傳 http://iosre.com/ent.xml到OSX的“/Users/snakeninny/”目錄,然後運作:
- snakeninnysiMac:~ snakeninny$ /opt/theos/bin/ldid -Sent.xml debugserver
複制代碼 注意,此處的 ldid來 自joedj,且“-S”選項與“ent.xml”之間是沒有空格的。
正常情況下,上面這條指令會在5秒内執行完畢。如果ldid卡住了,執行逾時,就換一種方案:下載下傳http://iosre.com/ent.plist到“/Users/snakeninny/”,然後運作:
- snakeninnysiMac:~ snakeninny$ codesign -s - --entitlements ent.plist -f debugserver
複制代碼
4. 将經過處理的debugserver拷回 iOS
将經過處理的debugserver拷回 iOS,并添加執行權限,指令如下:
- snakeninnysiMac:~ snakeninny$ scp ~/debugserver [email protected]:/usr/bin/debugserver
- snakeninnysiMac:~ snakeninny$ ssh [email protected]
- FunMaker-5:~ root# chmod +x /usr/bin/debugserver
複制代碼 這裡之是以把處理過的debugserver存放在iOS的“/usr/bin/”下,而沒有覆寫“/Developer/usr/bin/”下的原版debugserver,一是因為原版debugserver是不可寫的,無法覆寫;二是因為“/usr/bin/”下的指令無須輸入全路徑就可以執行,即在任意目錄下運作“debugserver”都可啟動處理過的debugserver。
二、在iOS上用debugserver來attach程序
debugserver + lldb調試方法跟gdb最大的不同,在于前者是用OSX中的lldb遠端連接配接debugserver,由debugserver作為lldb和iOS的中轉,在執行指令和傳回結果;而後者是gdb直接運作在iOS上。但對于一般的開發者來說,這個差別跟我們沒關系,了解一下就好~
在iOS上運作下面的指令來attach程序,其中1234是我們指定的端口号:
- debugserver *:1234 -a "SpringBoard"
複制代碼 成功後會顯示:
三、在OSX上用lldb遠端調試
首先在Terminal中運作lldb,然後輸入以下指令:
- process connect connect://iOSIP:1234
複制代碼 注意,這條指令執行耗時比較長,很多讀者可能會以為 iOS/OSX死掉了,其實沒有,耐心等一會,看看 @iOS應用逆向工程有沒有重新整理微網誌,或在論壇裡逛逛吧~
執行成功後會顯示:
四、擷取ASLR的offset
首先在lldb裡輸入"c"并回車,讓程序繼續執行;lldb有一個gdb沒有的優點,就是可以在程序運作的過程中執行一些指令,這樣就可以有效避免SpringBoard這樣的程序在暫停過久後被WatchDog給kill掉。在lldb裡輸入
- image list -o -f
複制代碼 顯示如下圖檔:
其中第一列[X]是image的序号,不用管;第二列是ASLR的offset(也就是對應image的虛拟記憶體slide);第三列是image的全路徑和slide之後的基位址,也不用管~是以第二列就是我們需要的資訊。
五、在記憶體位址上下斷點
假如我們在SpringBoard這個image的0xb446(在_menuButtonDown:中)處下斷點,則此位址在記憶體中的實際位置是0xb446 + 0x9a000 = 0xa5446,在lldb中對應的指令是:
- br s -a 0xA5446
複制代碼 執行成功後顯示:
值得注意的是,lldb指令裡如果涉及到加法操作,必須要加上單引号,即
- br s -a '0x0009a000 + 0xb446'
複制代碼 感謝@0xBBC 的提醒
六、更改寄存器的值 按下home鍵,觸發斷點,顯示如圖:
可以看到,lldb把包括斷點在内的4條指令顯示了出來,友善我們調試。這裡,我們将r0的值設為0,讓其跳轉到0xa5470(0xb470 + 0x9a000)處。更改r0值的lldb指令是:
- register write r0 0
複制代碼 接着”ni“兩次,我們就可以看到程式執行到了0xa5470處,如圖:
七、用lldb啟動一個App
- debugserver -x backboard *:1234 /path/to/app/executable
複制代碼 如
- debugserver -x backboard *:1234 /Applications/MobileNotes.app/MobileNotes
複制代碼 此指令會啟動記事本,并斷在dyld的第一條指令上,如圖所示:
接下來,在lldb中持續輸入“ni”,直到出現“error: invalid thread”的字樣,如圖所示:
稍等片刻,lldb即會停在程式的第一條指令上,如圖所示:
此時我們即已處在程序内部,可以開始一窺究竟啦~ 相較attach的半路出家,這種方式更有助于我們從頭調試一個程式,可以觀察到一些變量的初始化過程。
八、更多lldb指令 經過上面的操作,我們可以看到,lldb還是比較友善的,用慣了gdb而對它不熟悉的朋友可以通過lldb與gdb指令對照表來熟悉lldb的指令。其實有了上面的幾個操作,我們就可以開始簡單動态分析程式了,相信能把上面六步走通的朋友,已經具備了舉一反三的能力,其他需要用到的功能都可以Google到,當然更歡迎你到論壇裡發帖提問或分享。好了,debugserver + lldb的簡單介紹到此結束,接下來趕緊打開Terminal,hack起來吧~!