crash工具解析_IDA反汇编静态调试Android平台C++的so文件Crash入门
最近剛接觸到Android的C++編譯出的.so文件崩潰,從后臺上報系統能得到ARM64平臺下發生crash時pc寄儲器地址和函數名稱,但沒有行號,以及當時棧幀對應的31個寄儲器的值,,第一次用IDA調試,邊用邊百度,不到2小時就上手,很就查出野指針問題了。本文引入兩個實戰,一個野指針Crash,一個空指針Crash。
由于以前做windows多,有C++反匯編基礎,也有STL略底層點知識,只是沒有用過IDA。win32指令簡單一些,先介紹一下win32反匯編的一些知識,只要簡單學習一下,就可以很快擴展到arm64平臺中,兩者很相通。
win32平臺下10個寄儲器,加粗的比較重要
- EAX, EBX,ECX,EDX 通用寄儲器,
- ESI,EDI ,索引寄儲器
- EIP ,指令指針寄儲器
- ESP,EBP ,棧寄儲器,ESP表標棧頂指針
- EFL,標志寄存器
Win32常用指令:只取常用的,也不強記,不過用的多就能記住了,平時可以隨時查
- mov 傳送指令
- push 壓棧指令
- pop 出棧指令
- lea 取地址
- jmp 無條件跳轉
- je 相等跳轉
- jle 小于跳轉
- call 函數調用
- ret 返回
- cmp比較指令
- add 相加
- sub 相減
- inc 加1
- mul 相乘
- div 相除
- and 與運算
- or 或運算
- xor 異或運算
- not 取反
- test 測試
- 其它他有很多指令,就單純跳轉就有很多,不再一一列舉了。
上面的隨意可以百度到,下面來說些對剛接觸的來說相對比較有價值點或者比較難搜索到的:
1.要深入理解點棧,棧對匯編來說非常非常重要,也是函數調用關系的基礎。一些高級用法,比如協程,比如自定義子程序調度,都離不開這個和跳轉指令。??梢岳斫鉃橐粋€固定大小的地址空間,win32沒有記錯是1M,當然也可以在VS編譯器設置。esp表示棧頂指針,最開始esp,ebp相同的,棧的內存增長和一般的不同,是向下生長,先進后出,就是push進去,地址越來越小,pop出來,地址變大。
2.EAX常用作函數返回值,非常重要
3.ECX常用作C++普通成員函數函數第一個參數,也就是this指針,C++代碼編寫時默認隱了this指針,編譯器生成代碼時,第一個參數是this*,這個參數是不能省掉的,這種也有人稱為this call。
4.如果寄儲器不夠傳參時,會用棧來傳參
//關于2,3,4驗證,VS2019 debug版本,Release會優化,不利于初始學習5.EIP指向下一條指令代碼,不能直接修改,可以用call,jmp,ret等修改
6.理解Push ,Pop 指令的等效過程
push eax; // 等同兩條偽指令 1.mov esp,eax; // 將eax 放到esp中,偽指令,僅供理解 2.sub esp,4; // 將esp減4,因為棧向下生長的,偽指令,僅供理解pop eax; // 等同兩條偽指令 1.mov eax,esp; // 將esp 放到eax中,偽指令,僅供理解 2.add esp,4; // 將esp加4,因為棧向下生長的,偽指令,僅供理解7.深入理解call,jmp,ret指令的等效過程
call 內存/立即數/寄儲器; //1.原下個EIP對應指令地址入棧;(esp-4) //2.修改EIP為新的; //3.跳轉EIP執行;jmp 內存/立即數/寄儲器; //1.修改EIP為新的; //2.跳轉EIP執行; 注:無條件執行,不改變棧ret; //1.修改EIP為新的棧頂數據; //2.esp +4;(恢復棧) //3.跳轉EIP執行;8.為什么有時看到函數反匯編開頭會push ebp,push ebx,push esi等?
答:因為為了保護這些寄儲器的值在函數調用結束不被錯誤修改。如果研究的比較深,比如函數加上__declspec(naked)修飾 ,或者完全自己寫匯編,的確是可以不進行這樣操作,只要能維護好就行。下面給個簡單偽代碼,舉例為什么push edi
int a = fun1()+fun2(1,2);// 偽代碼,edi用來保存fun1()的結果,然后與fun2()返回值相加,所以edi最好要在fun2內部過程保護好 call fun1_address; // 調用fun1() mov edi eax; // fun1()返回值放到edi中 push 2; push 1;// 放入參數(1,2) call fun2_address; // 調用fun2(1,2) add edi eax; // 執行fun1()+fun2(1,2),并放到edi中 mov ptr [a] edi; // 放到a對應棧內存中9.C++虛函數的與反匯編
我們知道如果一個class沒有虛函數時,開始的第一個字段為第一個成員,但有了虛函數,第一個字段就變成虛表指針的地址,如果有多種繼承與虛繼承,這個復雜些,加入各個vbptr以及各個的vfptr,平同編譯器策略還是不同的,限于篇幅與主題,這個不細討論了,有興趣可以自己寫個demo調試一下。這里先以VS2019平臺編譯的win32分析
mainclass我們可以借助VS自帶工具查看類的內存分布,tools->command line->Developer Command Prompt打開,輸入cl -d1reportSingleClassLayoutXXXX XXXX.cpp便可得到類的layout信息,可以觀測到C是16字節,第一個4字節內存是虛函數表vftab,然后是繼成A的成員m_iA,再次之B的成員m_iB,最后是C的對象m_iC。其中虛表函數分布也是簡單易看。
class C size(16):+---0 | +--- (base class B)0 | | +--- (base class A)0 | | | {vfptr}4 | | | m_iA| | +---8 | | m_iB| +--- 12 | m_iC+---C::$vftable@:| &C_meta| 00 | &C::fun1 | &C::fun22 | &A::funA3 | &B::funB4 | &C::funCC::fun this adjustor: 0 C::fun2 this adjustor: 0 C::funC this adjustor: 0我們用IDA打開這個exe進行查看,32位就用32位IDA,64位就用64位的
File->open->生成exe文件路徑
定位到main函數,查看反匯編代碼
我們來逐行解析IDA的反匯編
.text:由上面分析可知,在匯編層不一定和書寫完全一致,有時會遇到編譯器很強大的優化。我們再解釋一下內存分布,以便更加強化理解,本例pkC在堆上分配,pkC的虛表放在只讀數據段,里面有5個函數地址,(0 | &C::fun 1 | &C::fun2 2 | &A::funA 3 | &B::funB 4 | &C::funC),代碼段當然就是各種函數的指令實體,這5個函數實體也放在這里。
10. STL的知識,最好看一下STL源碼解析那本書,沒看過也問題不大。以vector.size()反匯編解析。
std我們進行IDA反匯編靜態分析
.text:從上面反匯編可看出,std::vector<long> vNum對象分配在棧區,本例在ebp+Memory中,大小是12個字節,是3個指針占用_Myfirst,_Mylast,_Myend,這要看源碼才清楚;vector容器的元素對象實際在堆區分配,這里放了一個long型100,占用8個字節,由STL的Alloc分配器new出來,Alloc的代碼也是值得研讀。這里size()函數根本沒有調用,由內聯復制過來了,且中間還被取std::cout地址插了一腳。size計算兩個指針地址相減,然后除以4,這些略為背后點知識,可能都要深入了解一下STL。
好了,有了上面知識準備,開始進行Android Arm64調試,這里有幾個知識點
1.Arm64有31個通用整型寄存器,r0-r30。當使用64bits時候,命名x0-x30;使用32bits時,命名w0-w30
2.ESP變成sp,EIP變成pc
3.一般函數可以用r0到r7傳遞時不用棧,不夠時也用棧傳
4.返回值一般用r0,有時也用r8
5.常用指令,指令也是挺多的,記不住的也可以查。
- MOV x1,x0; 傳送指令,x0->x1
- ADD x0,x1,x2; 相加,x1+x2 ->x0
- SUB x0,x1,x2; 相減,x1-x2->x0
- AND x0,x0,#8; 相與,(x0&8)->x0
- ORR x0,x0,#8; 相或,(x0|8)->x0
- EOR x0,x0,#8; 相或,(x0^8)->x0
- LDR x0,[x1,#8]; 取內存(x1+8)地址放到x0
- STR x0, [sp, #8]; 取x0數據到SP+8內存地址
- STP x3, x4, [sp, #8]; 入棧
- LDP x3, x4, [sp, #8]; 出棧
- CMP x3,x4;比較x3,x4值
- CBZ x3, xxxxxx; x3為0,就跳到xxxxxx代碼段
- CBNZ x3, xxxxxx; x3不為0就跳到xxxxxx代碼段
- B/BL ; 絕對跳轉,返回地址保存到LR(x30)
實戰:1例野指針,一例沒有判空
1.打開IDA選擇New,打開要調試的so文件
2.打開主界面后,看崩潰的pc代碼段值,比如0x0000000014A3287C,按G鍵跳轉過到0x0000000014A3287C,然后手動找到函數開頭,也可以按空格鍵看調用關系圖。
3.對著代碼反推反匯編,這個需要一些經驗,對指令熟悉的越多,通常會越快,也和復雜度正相關,對于比較重要的地方,可以按;鍵加一些注釋幫助理解。
4.由于實際項目代碼非常復雜且不便公開,要推較長時間,我這里寫些假的代碼,只取最關鍵部分,模擬一下崩潰環境。
A:野指針,直接看這代小代碼,也問題不大,該判斷的也都判斷了
struct我們崩潰在0000000000002C0C,用IDA進行Arm64反匯編,一行一行的分析
.text:B:空指針
這種問題是比較簡單,但實際工程量大了,代碼量大了,比如1000萬行以上,要判斷的地方太多,或者有的地方理論上就不應該為空,都進行判斷有時也挺討厭,這種問題對工程大的相對有點惡心,但這種問題修復起來比較難,因為問題根源不在這,只是這里先發生而已。簡單示意一下,略去前面要推的代碼,只取最核心的。
struct我們崩潰在0000000000002C20,用IDA進行Arm64反匯編,一行一行的分析
.text:通過匯編分析我們可知pkA為空指針,要進行進一步修補。
文章就介紹到這里,希望和大家一起進步。
總結
以上是生活随笔為你收集整理的crash工具解析_IDA反汇编静态调试Android平台C++的so文件Crash入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 航空母舰符号怎么画
- 下一篇: 拉萨泰玺华庭是哪个开发商?