動態除錯及LLDB技巧集合

TuGeLe發表於2018-09-29

學習背景:在非越獄逆向開發的過程中,我們可以從介面(Reveal)、類與方法(class-dump)、或者網路(Charles)等方面及一個應用的功能點入手去尋找一些需要實現功能的入口線索。但是在未知原始碼的情況下,想要精確定位一個功能點的實現位置及實現原理,就需要結合靜態分析和動態除錯來分析程式執行的動態和行為實現。

靜態分析

靜態分析是指在程式沒有執行的情況下進行程式分析的一種方法,一般分為以下三種:

  • 基於ipa和app包靜態分析
  • 基於檔案格式的靜態分析
  • 基於二進位制反彙編的靜態分析

在二進位制反彙編靜態分析的過程中,我們可以兩種反彙編的工具,一個是Hopper,一個是IDA。兩者的功能都非常強大,可以顯示被分析檔案的反彙編程式碼、流程圖及虛擬碼。其中常用的功能有字串搜尋,跳轉地址,變數重新命名等。

動態除錯

動態除錯和靜態分析是相輔相成的。靜態分析只能分析靜態的函式內部執行。要想動態獲取程式在執行時的引數傳遞、執行流程及暫存器記憶體等資訊,就需要使用動態除錯的方法。

LLDB動態除錯

我們都知道,LLDB是Xcode自帶的除錯工具,既可以在本地除錯Mac應用程式,也可以遠端除錯iPhone真機。我們平時在開發中除錯程式介面和執行狀態時也會經常使用。
其實原理是這樣的:當使用Xcode除錯真機App時,Xcode會將debugserver檔案複製到手機中,以便在真機上啟動一個服務,等待Xcode進行遠端連線除錯。所以只有當裝置連線計算機真機除錯App後,debugserver檔案才會安裝到裝置的/Developer/usr/bin目錄下。但是,debugserver檔案預設只能除錯自己開發的應用,在除錯從App Store下載應用時會出現"unable to start the exception thread"錯誤。

LLDB除錯環境設定

不過既然知道了原理就可以試著去對debugserver去做一些許可權處理就可以了,下面是具體的步驟:

1.準備工作

為了除錯其他應用,需要給debugserver檔案富裕task_for_pid許可權。在除錯前需要做如下準備工作。

(1)複製debugserver檔案

使用scp命令將debugserver檔案從手機複製到Mac計算機上。

scp -P 2222 root@localhost:/Developer/usr/bin/debugserver ./
(2)簽名許可權

新建一個entitlement.plist檔案,在其中寫入如下內容:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.springboard.debugapplications</key> <ture/>
	<key>run-unsigned-code</key> 
	<ture/>
	<key>get-task-allow</key> 
	<ture/>	
	<key>task_for_pid-allow</key>
	<ture/>	
</dict>
</plist>

再使用codesign進行簽名,命令如下。

codesign -s - --entitlements entitlements.plist -f debugserver
(3)將debugserver檔案複製回手機

因為/Developer/是系統的只讀資料夾,所以也可以把簽好許可權的debugserver檔案複製到bin/目錄下:

scp -P 2222 ./debugserver root@localhost:/usr/bin/debugserver

2.進行除錯

準備好有task_for_pid許可權的debugserver檔案後,就可以除錯第三方應用了。debugserver支援通過程式名和程式ID進行除錯。通過執行ps aux找到啟動的程式後,執行命令:

debugserver *:1234 -a SogouInput

命令執行完成之後,被除錯的程式會被卡住。接下來,需要通過Mac遠端連線目標的監聽服務,開啟Terminal,輸入lldb除錯啟動後,同過以下命令連線debugserver啟動的服務:

-> ~ lldb
(lldb) process connect connect://10.129.17.89:1234
//10.129.17.89:手機的IP地址
//1234:對應服務的埠號,可通過iproxy進行埠轉發

到這兒為止就可以開始動態除錯第三方應用的功能了!為了能夠更好的進行lldb除錯,下面便來介紹一下LLDB除錯的指令集:

LLDB除錯指令集

(1)斷點設定
//1、設定方法名斷點
(lldb) breakpoint/b set -n xxx  //xxx:方法名

//2、根據對應的類設定該類的方法名斷點(可多個:一組)
(lldb) breakpoint/b set -n "-[Class1 xxx]" -n "-[Class2 xxx]"  //xxx:方法名 Class:類名

在這裡插入圖片描述


如果我們想給整個工程中的同一個Selector都打斷點呢,這個在做逆向時會很有用,便於跟蹤一個Seletor的功能:

//3、根據selecor打上工程中所有的同selector斷點
(lldb) breakpoint/b set --selector xxx"   //xxx:方法名

在這裡插入圖片描述
可以發現有66個地方存在這個selector(touchesBegan:withEvent:)。


//4、根據selecor打上指定檔案中所有的同selector斷點
(lldb) breakpoint/b set --file xxx.m--selector xxx"   //xxx.m:指定檔案

//5、給遍歷整個專案中滿足xxx這個字元的所有方法打斷點
(lldb) breakpoint/b set -r xxx"   //xxx:滿足字元

//6、列出當前程式中存在的所有斷點(按組分)
(lldb) breakpoint/b list"  

在這裡插入圖片描述


//7、禁用或者啟用一組或某個斷點
(lldb) breakpoint enable/disable 3/3.1" 

//8、刪除一組或某個斷點
(lldb) breakpoint delete 3/3.1" 
(2)新增執行程式碼

說到執行程式碼,其中最常用的就是:(lldb) p(lldb) po用來列印一些執行時的變數資訊了,不過po和p指令不是一般理解的print的縮寫,而是expression的意思,我們可以通過(lldb) help p來檢視解釋,如下:
在這裡插入圖片描述
po也可以通過如下(lldb)help expression來檢視解釋:
在這裡插入圖片描述

//1、檢視變數的資訊
(lldb) expression/p xxx  //xxx:變數名   

//2、新增程式碼行 
(lldb) expression/p xxx;  //xxx:程式碼行   

在這裡插入圖片描述


同時還可以新增多行程式碼,需要通過Option+Enter鍵來進行換行操作。

//3、新增多行程式碼 
(lldb) expression/p xxx;//xxx:程式碼行   
    xxx;
    xxx;

以上如果麻煩的話,可以通過下面的指令直接給breakpoint新增程式碼

//4、提前給斷點新增程式碼 
(lldb) breakpoint/b command add x //x:斷點編號

在這裡插入圖片描述
這樣就在執行的過程中注入一些除錯程式碼了!

(3)檢視堆疊資訊
//1、檢視呼叫棧 
(lldb) backtrack/bt

在這裡插入圖片描述


//2、呼叫棧回退和前進  
(lldb) up/down

在這裡插入圖片描述
同時我們也可以通過以下指令直接定位到任意呼叫棧函式:


//3、直接檢視對應序列號呼叫棧方法
(lldb) frame select x   //x:序列號

在這裡插入圖片描述


//4、可檢視定位到的呼叫棧方法引數值
(lldb) frame variable

在這裡插入圖片描述
注:以上棧的回退和定位只是檢視呼叫的方法,而不能做出修改,因為呼叫棧方法方法已經執行

(4)流程控制
//1、繼續執行
(lldb) continue/c
//2、單步執行,將子函式當做整體一步執行
(lldb) next/n
//3、單步執行,遇到子函式會進入
(lldb) stepi/s  //step-in

其實,這些流程控制的指令就是Xcode上面控制檯的幾個視覺化按鈕,其功能是一樣的,同樣可以結合CtrlOption鍵擴充套件功能。

(5)記憶體斷點
//1、設定檢視成員變數的記憶體斷點
(lldb) watchpoint set variable xxx //xxx:成員變數

之後可以通過p指令去檢視地址對應的值,包括oldValue和newValue。


不過如果我們在做逆向分析,將不能獲取到成員變數,還可以通過記憶體地址來設定記憶體斷點:

//2、通過記憶體地址設定檢視成員變數的記憶體斷點
(lldb) watchpoint set expression xxx //xxx:記憶體地址

其他一些常用的副指令跟breakpoint很相似,可以去參考。

(6)stop-hook
//1、每次stop的時候執行程式碼,只對breakpoint、watchpoint有用
(lldb) target stop-hook add -o "xxx"  //xxx:程式碼行

在這裡插入圖片描述
如圖可以看到在每個斷點處都列印了方法的相關資訊和引數資訊。


(7)image

如果我們在真機除錯的時候遇到崩潰,就不能夠向自己程式除錯的時候看到的具體呼叫函式,這樣的話我們可以藉助image來解析對應call stack中的記憶體地址來定位函式:

//1、First throw call stack:(xxx,xxx,xxx)
(lldb) image lookup -a xxx   //xxx:崩潰backtrace地址

在這裡插入圖片描述
如圖可以由記憶體地址定位到陣列崩潰問題。


// 2、檢視類名的標頭檔案
(lldb) image lookup -t xxx   //xxx:類名

在這裡插入圖片描述


//3、 image是專案中包含的的庫,可以看到每個庫對應的記憶體地址
(lldb) image list

在這裡插入圖片描述

(8)其他
//1、讀取暫存器資訊
(lldb) register read
// 2、讀取記憶體資訊
(lldb) memory read

LLDB匯入Python指令碼

LLDB提供了很多API來幫助我們獲取和修改除錯資訊、程式資訊及記憶體資訊。這些介面可以直接通過Python指令碼呼叫,然後透過LLDB載入使用。我們可以閱讀LLDB官方提供的API文件http://lldb.llvm.org/python_reference/index.html。下面有兩個開源庫很實用。

LLDB初始化檔案路徑/Users/xxx/.lldbinit xxx:Mac使用者名稱 (此檔案為隱藏檔案,如果沒有可以自行建立),在這個檔案中可以匯入一些配置檔案,比如上面說的一些開源庫。

//建立lldbinit檔案
touch ~/.lldbinit

//安裝開源庫
brew install chisel

//在~/.lldbinit檔案中匯入下載的指令碼
檔案 ~/.lldbinit
···
command script import /Users/xxx/Documents/iosreversecode/LLDB/chisel/fblldb.py

//載入指令碼
(lldb) command source ~/.lldbinit

這樣我們就可以進行一些高階除錯了,比如輸入以下指令,就可以實現許多類似於Cycript的功能了,還是對逆向很有幫助。

(lldb) expression @import UIKit

//列印App層級結構
(lldb) pviews

//檢視控制元件的響應鏈
(lldb) presponder xxx //xxx:控制元件記憶體地址

//搜尋存在所有的控制元件類物件
(lldb) search xxx  //xxx:控制元件類

總結

LLDB功能非常強大,這裡只是介紹了一些簡單常用的指令,還可以進一步去探索。比如類似於使用dumpdecrypted砸殼的解密附加功能;同時我們還可以通過對應的動態除錯的記憶體地址結合靜態分析的IDA或者Hopper來靜態分析程式碼,這樣一來對逆向分析來說就有了一些很有力的工具。

相關文章