逆向分析點滴
作者:admin 文章來源:轉(zhuǎn)載 點擊數(shù):1194 更新時間:2008-7-4
--------------------------------------------------------------------------------
很基礎的一篇文章,適合初學者!
//////////////////////////////////////////////////////////////////////
逆向分析點滴
轉(zhuǎn)自ph4nt0m googlegroups.com
Index
========================================
1. IDA的使用
2. 編譯器優(yōu)化
3. Viusal C++, Borland Delphi程序的逆向分析
4. 算法識別技巧(常用的加解密算法)
1. IDA使用
=========================================
工欲善其事,必先利其器.在開始前,先熟悉下IDA的使用.
[1] 先說一些常用的功能.
(1)編譯器設置(選項->編譯器)用來指定IDA所分析文件是什么編譯器生成的,如Visual C++,Borland C++,Delphi.這有什么用呢?
比如下面是Delphi的反匯編片段,如果編譯器設定為Viusal C++:
code:00479A8F lea edx, [ebp+var_40]
code:00479A92 mov eax, [ebp+someString]
code:00479A95 call @Sysutils@Trim$qqrx17System@AnsiString
code:00479A95
code:00479A9A mov edx, [ebp+var_40]
code:00479A9D lea eax, [ebp+someString]
code:00479AA0 call @System@@LStrLAsg$qqrpvpxv
函數(shù)參數(shù)類型和個數(shù)很難一眼看出來.把編譯器設定為Delphi后,一目了然:
code:00479A8F lea edx, [ebp+var_40]
code:00479A92 mov eax, [ebp+someString]
code:00479A95 call Sysutils::Trim(System::AnsiString)
code:00479A95
code:00479A9A mov edx, [ebp+var_40]
code:00479A9D lea eax, [ebp+someString]
code:00479AA0 call System::__linkproc__ LStrLAsg(void *,void *)
(2)簽名(查看->打開下級查看->簽名,右鍵->Apply new signature...添加簽名文件)用來指定加載IDA的庫函數(shù)簽名文件.這個是IDA的非常有用的一個功能.逆向分析就像玩填字游戲,從提示線索出發(fā),補完整個詞句.而逆向分析的線索是什么呢?庫函數(shù)就是其中之一.無論是MFC還是Delphi編寫的程序,都要用到大量的庫函數(shù),而這些庫函數(shù)就是我們分析的線索之一.IDA的FLIRT庫函數(shù)簽名能夠識別出大部分的庫函數(shù)并在匯編窗口中標注其名稱.分析用戶函數(shù)時,根據(jù)這些庫函數(shù),我們可以大致確定用戶函數(shù)的作用.
但是IDA的庫函數(shù)簽名也不是萬能的,也會遺漏或者錯誤把用戶函數(shù)識別成庫函數(shù).怎么辦?
如果你分析的文件較大或者CPU較慢時,觀察導航器(查看->工具欄->導航器->導航器)就會發(fā)現(xiàn)IDA掃描分析會給指令,正則函數(shù)和庫函數(shù)"染色",而庫函數(shù)往往是在連續(xù)區(qū)域.我的經(jīng)驗是,如果IDA把這個連續(xù)區(qū)域外的函數(shù)標注成庫函數(shù),很大可能就是誤識別,而把此連續(xù)區(qū)域內(nèi)的函數(shù)"染"成用戶函數(shù),有可能就是遺漏,未識別.
(3)創(chuàng)建MAP文件(文件->創(chuàng)建文件->創(chuàng)建MAP文件),能夠?qū)С鰩旌瘮?shù)名,用戶函數(shù)(自己命名的),字符串名等(但是不能導出添加的注釋信息.哪位知道如何導出IDA的注釋,告訴俺一下:-).導出map文件的目的主要是用于ollydbg,因為od自身不具有識別庫函數(shù)的功能,所以在用od調(diào)試delphi程序的時候,往往誤入歧途跟進庫函數(shù)里面,浪費時間.要在od中導入map,需要一個插件LoadMap,它可以很方便的導入map.導入之后,庫函數(shù)都會標注上名稱,調(diào)試起來就容易些了.
另外還有個OD的插件GODUP(IDA簽名載入程序),也可以載入IDA的簽名文件來識別庫函數(shù),也不錯.
[2] IDA的一些常用的快捷鍵.
C 如果你發(fā)現(xiàn)一段數(shù)據(jù)沒有被IDA識別成代碼,那么可以手動轉(zhuǎn)換.
G 跳轉(zhuǎn)到地址,就是od的Ctrl+G.
Ctrl+Enter/Esc 就是od的+/-.
N 重命名.
U 如果一段數(shù)據(jù)被IDA錯誤識別成代碼,用U可以撤銷轉(zhuǎn)換.
X 顯示交叉參考. 對函數(shù),可查看該函數(shù)的所有調(diào)用者;對局部變量,可查看函數(shù)里涉及到該局部變量的所有指令.
Y 在函數(shù)名上點Y,用來設置函數(shù)類型.
: 添加注釋.
; 可重復注釋,在逆向過程中不斷添加注釋和重命名分析過的函數(shù)很重要,好記性不如爛筆頭嘛.
Shift+/ 在IDA中選中需要計算的數(shù)字,Shift+/即可調(diào)用計算器計算,比較方便.不用老去Win+R->calc了.
2. 編譯器優(yōu)化
====================================
編譯器優(yōu)化體現(xiàn)在很多方面,下面舉例說明:
看看下列指令(這是從rc4的密鑰初始化中截取的片段):
.text:0041E208 and edx, 800000FFh
.text:0041E20E jns short loc_41E218
.text:0041E210 dec edx
.text:0041E211 or edx, 0FFFFFF00h
.text:0041E217 inc edx
.text:0041E218
其實這是edx%256,如果求余運算的求余數(shù)是2^n,如16,256等,就會優(yōu)化成上述形式,因為用除法指令進行求余運算需要的時鐘周期較多,執(zhí)行效率不高,所以編譯器盡量避免使用除法指令,就像上面所示.
再看另一個片段:
.text:0040100D mov ecx, eax ; eax是被除數(shù)
.text:0040100F mov eax, 55555556h
.text:00401014 imul ecx ;
.text:00401016 mov eax, edx ;\注意imul是有符號數(shù)乘法,這相當于edx>>31
.text:00401018 shr eax, 1Fh ;/,這樣eax存放的是符號位
.text:0040101B add edx, eax ;加上符號位,對負數(shù)相當于向下舍入為一個較小數(shù)
這段實際上是用乘法來模擬除法運算,翻譯上面的匯編代碼可以得到(var*0x55555556)>>32,即var*0.333333,所以模擬的除法運算是var/3.
Viusal C++, Borland Delphi程序的分析
======================================
[1] Viusal C++程序的分析
先將編譯器設定為Visual C++,載入簽名如vc32rtf,vc32mfc等.待IDA掃描分析完畢就可以開始工作了.
逆向前,先要說下函數(shù)有4種調(diào)用約定,即stdcall,cdecl,fastcall,pascal.它們之間的區(qū)別體現(xiàn)在參數(shù)入棧順序和清理入棧參數(shù)的方式上.
stdcall的參數(shù)入棧順序是從右到左,且在函數(shù)返回前清理入棧參數(shù),在反匯編代碼上體現(xiàn)為retn xx,比如壓入2個寄存器作為參數(shù),函數(shù)返回時就是retn 8.采用stdcall約定的有WINAPI,以及CALLBACK回調(diào)函數(shù)等.
cdecl的參數(shù)入棧順序也是從右到左,但是在函數(shù)返回后清理入棧參數(shù),在反匯編代碼上體現(xiàn)為add esp, xx,比如壓入3個寄存器作為參數(shù),返回時就是add esp, 0Ch.采用cdecl約定的有c標準函數(shù)庫等.可能有人要問stdcall和cdecl貌似沒什么區(qū)別嘛,這樣做不是多此一舉嗎?呵呵,想想最常用的printf函數(shù)族,發(fā)現(xiàn)什么了?它的入棧參數(shù)個數(shù)是不固定的,也就是說在編譯期才能確定入棧參數(shù),所以用stdcall是無法實現(xiàn)這類不固定參數(shù)的函數(shù),只能用cdecl.
pascal的參數(shù)入棧順序與上面二者相反,是從左到右.在函數(shù)返回前清理入棧參數(shù),這與stdcall一致.
fastcall的特點就是采用寄存器來傳遞部分函數(shù)參數(shù),但具體細節(jié)依賴于編譯器.如Visual C++編譯器的fastcall約定是前兩個參數(shù)依次用ecx,edx,第3個開始push入棧.
歸納如下:
調(diào)用約定 入棧參數(shù)清理 參數(shù)入棧順序
----------- -------------- ----------------
cdecl 調(diào)用者處理 右->左
stdcall 函數(shù)自己處理 右->左
pascal 函數(shù)自己處理 左->右
fastcall 函數(shù)自己處理 依賴于編譯器
直接調(diào)用Windows SDK寫的C代碼編譯后是比較好分析的,只要熟悉Windows API基本就能找到和分析自己感興趣的部分.比較棘手的是MFC一類C++代碼的逆向問題.因為出現(xiàn)call dword ptr [esi+XXh]一類的虛函數(shù)調(diào)用,不能一路很順利的跟下去.當然,你可以從調(diào)用點回溯到類實例創(chuàng)建的地方,從而知道調(diào)用的是什么函數(shù).不過這樣比較麻煩,投機取巧的辦法,是用od到虛函數(shù)調(diào)用的地方前下斷,然后由this指針得到虛函數(shù)表地址(this指針指向的類實例存儲結構的第一項就是虛函數(shù)表地址),偏移XXh得到虛函數(shù)地址.
[2] Borland Delphi程序的分析
先把編譯器設定為Delphi,載入簽名d5vcl等.待IDA掃描分析完畢.
Delphi的函數(shù)調(diào)用是fastcall.Borland Delphi的fastcall約定是前3個參數(shù)依次用eax,edx,ecx傳遞,第4個開始push入棧.如果是虛函數(shù),第一個參數(shù)eax就是this指針. 形象點就是function(eax, edx, ecx, push...).
舉個例子:
code:004531CC lea eax, [ebp+var_30]
code:004531CF & nbsp; push eax & nbsp; ; 第4個參數(shù)
code:004531D0 lea ecx, [ebp+var_38] & nbsp;; 第3個參數(shù)
code:004531D3 mov edx, esi & nbsp; ; 第2個參數(shù)
code< style="color: #000000;">:004531D5 mov eax, ebx & nbsp; ; 第1個參數(shù),this指針
code:004531D7 mov edi, [eax] ; edi <- vmt ptr
code:004531D9 call dword ptr [edi+0Ch] ; 虛函數(shù)調(diào)用
先前說過庫函數(shù)是我們分析一個用戶函數(shù)的重要線索,因為Delphi對Windows API做了封裝,所以在Delphi程序里鮮有直接調(diào)用Windows API,基本是對庫的調(diào)用,所以在分析的時候需要常翻Delphi Help.
另外,Delphi的字符串處理方式和C庫有很大不同,沒有熟知的str*函數(shù)族.推薦閱讀看雪上firstrose整理的"Delphi的內(nèi)部字符串處理函數(shù)/過程不完全列表"一文.
算法識別技巧
===================================
這里指的識別比較窄,就是一些通用加解密算法的識別.
算法識別當然依靠算法的特征.其中最明顯的特征莫過于通用算法使用的一些初始化數(shù)據(jù)了.
比如下面這段代碼截取自Blowfish的初始化函數(shù):
code:004513A0 mov eax, offset S_Box_blowfish
code:004513A5 mov ecx, 1000h
code:004513AA call @Move
code:004513AF lea edx, [esi+103Ch]
code:004513B5 mov eax, offset P_Box_blowfish
我們跳轉(zhuǎn)到S_Box_blowfish處,可以看到如下的初始化數(shù)據(jù)(BTW:其實這是pi的16進制表示)
data:0048D308 P_Box_blowfish dd 243F6A88h, 85A308D3h, 13198A2Eh, 3707344h, 0A4093822h, 299F31D0h, 82EFA98h, 0EC4E6C89h
data:0048D308 dd 452821E6h, 38D01377h, 0BE5466CFh, 34E90C6Ch, 0C0AC29B7h, 0C97C50DDh, 3F84D5B5h, 0B5470917h
data:0048D308 dd 9216D5D9h, 8979FB1Bh
data:0048D350 S_Box_blowfish dd 0D1310BA6h, 98DFB5ACh, 2FFD72DBh, 0D01ADFB7h, 0B8E1AFEDh, 6A267E96h, 0BA7C9045h, 0F12C7F99h
假設此時你還不知道這個算法是什么,我的做法是把其中一段初始化數(shù)據(jù),比如S盒的開始這段0D1310BA6h, 98DFB5ACh, 2FFD72DBh, 0D01ADFB7h提交到
http://www.google.com/codesearch去查詢.呵,看到什么了?google返回了blowfish的代碼.現(xiàn)在你可以初步確定這個算法是Blowfish了.
有時候僅靠算法的初始化數(shù)據(jù)是不夠,因為在google codesearch命中的結果太多了.比如:
code:0047EEB7 mov ecx, 9E3779B9h ; Magic Number
code:0047EEBC mov esi, ecx
code:0047EEBE mov edx, ecx
code:0047EEC0 mov [esp+18h+delta1], ecx
code:0047EEC4 mov [esp+18h+delta2], ecx
code:0047EEC8 mov [esp+18h+delta3], ecx
code:0047EECC mov [esp+18h+delta4], ecx
code:0047EED0 mov [esp+18h+delta5], ecx
這里僅有一個初始化數(shù)據(jù)9E3779B9,提交到google codesearch命中了600個結果.這時就結合算法的特征了,比如這里將9E3779B9賦值給esi,edx,delta[1~5]共7個變量.我們在google返回的gnubg-0.14.3/lib/isaac.c里面找到了這樣的特征:
70: r=ctx->randrsl;
a=b=c=d=e=f=g=h=0x9e3779b9; /* the golden ratio */
alpha.gnu.org/gnu/gnubg/gnubg-0.14.3.tar.gz - GPL - C
呵呵,原來這是Isaac偽隨機數(shù)算法.
這里只是為了說明的方便,所以省略了很多,我當時判斷這個算法的時候,將整個Issac_Rand_Initial函數(shù)和Issac的c代碼粗略的比對了一遍后,在od中斷下Issac_Rand_Initial,將輸入的種子扔到c代碼中編譯測試,與od的結果一致,確定為Issac偽隨機數(shù)算法.