現代軟件設計中,極簡不是特別重要的特性。
并不是因為程序員編寫的代碼多,而是由于許多庫通常都會靜態鏈接到可執行文件中。如果所有的外部庫都移入了外部DLL文件中,情況將有所不同。(C++使用STL和其他模版庫的另一個原因)
因此,確定函數的來源很重要,是否來源于標準庫或者其他著名的庫(比如Boost,libpng),是否與我們在代碼中尋找的東西相關。
通過重寫所有的C/C++代碼來尋找我們想要的東西是不現實的。
逆向工程師的一個主要的任務是迅速定位到目標代碼。
IDA反匯編工具允許我們搜索文本字符串,字節序列和常量。甚至可以導出為.lst或者.asm文件,然后使用grep,awk等工具進一步分析。
當你嘗試去理解某些代碼的功能時,一些開源庫比如libpng會容易理解一些。當你覺得某些常量或者文本字符串眼熟時,值得用google搜索一下。如果你發現他們在某些地方使用了開源項目時,那么只要對比一下函數就可以了。這些方法能夠解決部分問題。
舉個例子,如果一個程序使用XML文件,那么第一步是確定使用了哪個XML庫。通常情況下使用的是標準庫(或者有名的庫)而非自編寫的庫。
再舉個例子,有一次我嘗試去理解SAP 6.0中網絡包如何壓縮與解壓。整個軟件很大,但手頭有一個包含詳細debug信息的.PDB文件,非常方便。最后我找到一個負責解壓網絡包的函數,叫CsDecomprLZC。我馬上就用google搜索了函數名,發現MaxDB(一個開源SAP項目)也使用了這個函數。http://www.google.com/search?q=CsDecomprLZC
然后驚奇的發現,MaxDB和SAP 6.0 使用同樣的代碼來處理壓縮和解壓網絡包。
可導入的MSVC版本和DLL文件如下圖:
msvcp*.dll包含C++相關函數,因此如果導入了這類dll,便可推測是C++程序。
命名通常以問號?開始。
獲取更多關于MSVC命令管理的信息:51.1.1節
除了*NIX環境,Win32下也有GCC,需要Cygwin和MinGW。
命名通常以_Z符號開頭。
更多關于GCC命名管理的信息:51.1.1節
cygwin1.dll經常被導入。
msvcrt.dll可能會被導入。
libifcoremd.dll,libifportmd.dll和libiomp5md.dll(OpenMP支持)可能會被導入。
libifcoremd.dll中許多函數以前綴名for_開始,表示FORTRAN。
命名通常以W符號開始。
舉個例子,下面是"class"類名為"method"的方法沒有任何參數并且返回void的加密:
W?method$_class$n__v
這里有一個有關Borland Delphi和C++開發者命名管理的例子:
@[email protected]$qv
@[email protected]$qp6tagMSG
@TModule@$bctr$qpcpvt1
@TModule@$bdtr$qv
@[email protected]$qp14TWindowsObject
@TrueColorTo8BitN$qpviiiiiit1iiiiii
@TrueColorTo16BitN$qpviiiiiit1iiiiii
@DIB24BitTo8BitBitmap$qpviiiiiit1iiiii
@TrueBitmap@$bctr$qpcl
@TrueBitmap@$bctr$qpvl
@TrueBitmap@$bctr$qiilll
[email protected],然后是類名、方法名、加密方法的參數類型。
這些名稱會被導入到.exe,.dll和debug信息內等等。
Borland Visual Component Libarary(VCL)存儲在.bpl文件中,而不是.dll。比如vcl50.dll,rtl60.dll。
其他可能導入的DLL:BORLNDMM.DLL。
幾乎所有的Delphi可執行文件的代碼段都以"Boolean"字符串開始,和其他類型名稱一起。 下面是一個典型的Delphi程序的代碼段開頭,這個塊緊接著win32 PE文件頭:
00000400 04 10 40 00 03 07 42 6f 6f 6c 65 61 6e 01 00 00 |..@...Boolean...|
00000410 00 00 01 00 00 00 00 10 40 00 05 46 61 6c 73 65 |........@..False|
00000420 04 54 72 75 65 8d 40 00 2c 10 40 00 09 08 57 69 |.True.@.,.@...Wi|
00000430 64 65 43 68 61 72 03 00 00 00 00 ff ff 00 00 90 |deChar..........|
00000440 44 10 40 00 02 04 43 68 61 72 01 00 00 00 00 ff |D.@...Char......|
00000450 00 00 00 90 58 10 40 00 01 08 53 6d 61 6c 6c 69 |....X.@...Smalli|
00000460 6e 74 02 00 80 ff ff ff 7f 00 00 90 70 10 40 00 |nt..........p.@.|
00000470 01 07 49 6e 74 65 67 65 72 04 00 00 00 80 ff ff |..Integer.......|
00000480 ff 7f 8b c0 88 10 40 00 01 04 42 79 74 65 01 00 |......@...Byte..|
00000490 00 00 00 ff 00 00 00 90 9c 10 40 00 01 04 57 6f |..........@...Wo|
000004a0 72 64 03 00 00 00 00 ff ff 00 00 90 b0 10 40 00 |rd............@.|
000004b0 01 08 43 61 72 64 69 6e 61 6c 05 00 00 00 00 ff |..Cardinal......|
000004c0 ff ff ff 90 c8 10 40 00 10 05 49 6e 74 36 34 00 |......@...Int64.|
000004d0 00 00 00 00 00 00 80 ff ff ff ff ff ff ff 7f 90 |................|
000004e0 e4 10 40 00 04 08 45 78 74 65 6e 64 65 64 02 90 |..@...Extended..|
000004f0 f4 10 40 00 04 06 44 6f 75 62 6c 65 01 8d 40 00 |..@...Double..@.|
00000500 04 11 40 00 04 08 43 75 72 72 65 6e 63 79 04 90 |..@...Currency..|
00000510 14 11 40 00 0a 06 73 74 72 69 6e 67 20 11 40 00 |..@...string .@.|
00000520 0b 0a 57 69 64 65 53 74 72 69 6e 67 30 11 40 00 |..WideString0.@.|
00000530 0c 07 56 61 72 69 61 6e 74 8d 40 00 40 11 40 00 |..Variant.@.@.@.|
00000540 0c 0a 4f 6c 65 56 61 72 69 61 6e 74 98 11 40 00 |..OleVariant..@.|
00000550 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000560 00 00 00 00 00 00 00 00 00 00 00 00 98 11 40 00 |..............@.|
00000570 04 00 00 00 00 00 00 00 18 4d 40 00 24 4d 40 00 |.........M@.$M@.|
00000580 28 4d 40 00 2c 4d 40 00 20 4d 40 00 68 4a 40 00 |(M@.,M@. M@.hJ@.|
00000590 84 4a 40 00 c0 4a 40 00 07 54 4f 62 6a 65 63 74 |.J@..J@..TObject|
000005a0 a4 11 40 00 07 07 54 4f 62 6a 65 63 74 98 11 40 |..@...TObject..@|
000005b0 00 00 00 00 00 00 00 06 53 79 73 74 65 6d 00 00 |........System..|
000005c0 c4 11 40 00 0f 0a 49 49 6e 74 65 72 66 61 63 65 |..@...IInterface|
000005d0 00 00 00 00 01 00 00 00 00 00 00 00 00 c0 00 00 |................|
000005e0 00 00 00 00 46 06 53 79 73 74 65 6d 03 00 ff ff |....F.System....|
000005f0 f4 11 40 00 0f 09 49 44 69 73 70 61 74 63 68 c0 |..@...IDispatch.|
00000600 11 40 00 01 00 04 02 00 00 00 00 00 c0 00 00 00 |.@..............|
00000610 00 00 00 46 06 53 79 73 74 65 6d 04 00 ff ff 90 |...F.System.....|
00000620 cc 83 44 24 04 f8 e9 51 6c 00 00 83 44 24 04 f8 |..D$...Ql...D$..|
00000630 e9 6f 6c 00 00 83 44 24 04 f8 e9 79 6c 00 00 cc |.ol...D$...yl...|
00000640 cc 21 12 40 00 2b 12 40 00 35 12 40 00 01 00 00 |.!.@.+.@.5.@....|
00000650 00 00 00 00 00 00 00 00 00 c0 00 00 00 00 00 00 |................|
00000660 46 41 12 40 00 08 00 00 00 00 00 00 00 8d 40 00 |FA.@..........@.|
00000670 bc 12 40 00 4d 12 40 00 00 00 00 00 00 00 00 00 |..@.M.@.........|
00000680 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000690 bc 12 40 00 0c 00 00 00 4c 11 40 00 18 4d 40 00 |..@.....L.@..M@.|
000006a0 50 7e 40 00 5c 7e 40 00 2c 4d 40 00 20 4d 40 00 |P~@.\~@.,M@. M@.|
000006b0 6c 7e 40 00 84 4a 40 00 c0 4a 40 00 11 54 49 6e |l~@..J@..J@..TIn|
000006c0 74 65 72 66 61 63 65 64 4f 62 6a 65 63 74 8b c0 |terfacedObject..|
000006d0 d4 12 40 00 07 11 54 49 6e 74 65 72 66 61 63 65 |..@...TInterface|
000006e0 64 4f 62 6a 65 63 74 bc 12 40 00 a0 11 40 00 00 |dObject..@...@..|
000006f0 00 06 53 79 73 74 65 6d 00 00 8b c0 00 13 40 00 |..System......@.|
00000700 11 0b 54 42 6f 75 6e 64 41 72 72 61 79 04 00 00 |..TBoundArray...|
00000710 00 00 00 00 00 03 00 00 00 6c 10 40 00 06 53 79 |.........l.@..Sy|
00000720 73 74 65 6d 28 13 40 00 04 09 54 44 61 74 65 54 |stem(.@...TDateT|
00000730 69 6d 65 01 ff 25 48 e0 c4 00 8b c0 ff 25 44 e0 |ime..%H......%D.|
數據段(DATA)最開始的四字節可能是00 00 00 00,32 13 8B C0或者FF FF FF FF。在處理加殼/加密的 Delphi可執行文件時這個信息很有用。
有時理解函數的功能通過觀察函數的輸入與輸出就足夠了。這樣可以節省時間。
文件和注冊訪問:對于最基本的分析,SysInternals的Process Monitor工具很有用。
對于基本網絡訪問分析,Wireshark很有幫助。
但接下來你仍需查看內部。
第一步是查看使用的是OS的API哪個函數,標準庫是什么。
如果程序被分為主要的可執行文件和一系列DLL文件,那么DLL文件中的函數名可能會有幫助。
如果我們對指定文本調用MessageBox()的細節感興趣,我們可以在數據段中查找這個文本,定位文本引用處,以及控制權交給我們感興趣的MessageBox()的地方。
如果我們在談論電子游戲,并且對里面的事件的隨機性感興趣,那么我們可以查找rand()函數或者類似函數(比如馬特賽特旋轉演算法),然后定位調用這些函數的地方,更重要的是,函數執行結果如何被使用。
但如果不是一個游戲,并且仍然使用了rand()函數,找出原因也很有意思。這里有一些關于在數據壓縮算法中意外出現rand()函數調用的例子(模仿加密):<blog.yurichev.com>
下面這些函數可能會被導入。值得注意的是并不是每個函數都在代碼中使用。許多函數可能被庫函數和CRT代碼調用。
這里有一個INT3斷點,只觸發了一次,但可以為指定DLL中的所有函數設置。
--one-time-INT3-bp:somedll.dll!.*
我們給所有前綴是xml的函數設置INT3斷點吧:
--one-time-INT3-bp:somedll.dll!xml.*
另一方面,這樣的斷點只會觸發一次。
Tracer會在函數調用發生時顯示調用情況,但只有一次。但查看函數參數是不可能的。
盡管如此,在你知道這個程序使用了一個DLL,但不知道實際上使用了哪個函數并且有許多的函數的情況下,這個特性還是很有用的。
舉個例子,我們來看看,cygwin的uptime工具使用了什么:
tracer -l:uptime.exe --one-time-INT3-bp:cygwin1.dll!.*
我們可以看見所有的至少調用了一次的cygwin1.dll庫函數,以及位置:
One-time INT3 breakpoint: cygwin1.dll!__main (called from uptime.exe!OEP+0x6d (0x40106d))
One-time INT3 breakpoint: cygwin1.dll!_geteuid32 (called from uptime.exe!OEP+0xba3 (0x401ba3))
One-time INT3 breakpoint: cygwin1.dll!_getuid32 (called from uptime.exe!OEP+0xbaa (0x401baa))
One-time INT3 breakpoint: cygwin1.dll!_getegid32 (called from uptime.exe!OEP+0xcb7 (0x401cb7))
One-time INT3 breakpoint: cygwin1.dll!_getgid32 (called from uptime.exe!OEP+0xcbe (0x401cbe))
One-time INT3 breakpoint: cygwin1.dll!sysconf (called from uptime.exe!OEP+0x735 (0x401735))
One-time INT3 breakpoint: cygwin1.dll!setlocale (called from uptime.exe!OEP+0x7b2 (0x4017b2))
One-time INT3 breakpoint: cygwin1.dll!_open64 (called from uptime.exe!OEP+0x994 (0x401994))
One-time INT3 breakpoint: cygwin1.dll!_lseek64 (called from uptime.exe!OEP+0x7ea (0x4017ea))
One-time INT3 breakpoint: cygwin1.dll!read (called from uptime.exe!OEP+0x809 (0x401809))
One-time INT3 breakpoint: cygwin1.dll!sscanf (called from uptime.exe!OEP+0x839 (0x401839))
One-time INT3 breakpoint: cygwin1.dll!uname (called from uptime.exe!OEP+0x139 (0x401139))
One-time INT3 breakpoint: cygwin1.dll!time (called from uptime.exe!OEP+0x22e (0x40122e))
One-time INT3 breakpoint: cygwin1.dll!localtime (called from uptime.exe!OEP+0x236 (0x401236))
One-time INT3 breakpoint: cygwin1.dll!sprintf (called from uptime.exe!OEP+0x25a (0x40125a))
One-time INT3 breakpoint: cygwin1.dll!setutent (called from uptime.exe!OEP+0x3b1 (0x4013b1))
One-time INT3 breakpoint: cygwin1.dll!getutent (called from uptime.exe!OEP+0x3c5 (0x4013c5))
One-time INT3 breakpoint: cygwin1.dll!endutent (called from uptime.exe!OEP+0x3e6 (0x4013
普通的C字符串是以零結束的(ASCIIZ字符串)。
C字符串格式(以零結束)是這樣的是出于歷史原因。[Rit79中]:
A minor difference was that the unit of I/O was the word, not the byte, because the PDP-7 was a word- addressed machine. In practice this meant merely that all programs dealing with character streams ignored null characters, because null was used to pad a file to an even number of characters.
在Hiew或者FAR Manager中,這些字符串看上去是這樣的:
int main() {
printf ("Hello, world!\n");
};
在Pascal和Borland Delphi中字符串為8-bit或者32-bit長。
舉個例子:
CODE:00518AC8 dd 19h
CODE:00518ACC aLoading___Plea db 'Loading... , please wait.',0
...
CODE:00518AFC dd 10h
CODE:00518B00 aPreparingRun__ db 'Preparing run...',0
通常情況下,稱Unicode是一種編碼字符串的方法,每個字符占用2個字節或者16bit。這是一種常見的術語錯誤。在許多語言系統中,Unicode是一種用于給每個字符分配數字的標準,而不是用于描述編碼的方法。
最常用的編碼方法是:UTF-8(在Internet和*NIX系統中使用較多)和UTF-16LE(在Windows中使用)。
UTF-8
UTF-8是最成功的字符編碼方法之一。所有拉丁符號編碼成ASCII,而超出ASCII表的字符的編碼使用多個字節。0的編碼方式和以前一樣,所有的標準C字符串函數處理UTF-8字符串和處理其他字符串一樣。
我們來看看不同語言中的符號在UTF-8中是如何被編碼的,在FAR中看上去又是什么樣的,使用437內碼表:
就像你看到的一樣,英語字符串看上去和ASCII編碼的一樣。匈牙利語使用一些拉丁符號加上變音標志。這些符號使用多個字節編碼。我用紅色下劃線標記出來了。對于冰島語和波蘭語也是一樣的。我在開始處使用"Euro"通行符號,編碼為3個字節。這里剩下的語言系統與拉丁文沒有聯系。至少在俄語、阿拉伯語、希伯來語和印地語中我們可以看到相同的字節,這并不稀奇:語言系統的所有符號通常位于同一個Unicode表中,所以他們的號碼前幾個數字相同。
之前在"How much?"前面,我們看到了3個字節,這實際上是BOM。BOM定義了使用的編碼系統。
UTF-16LE
在Windows中,許多win32函數帶有后綴 -A和-W。第一種類型的函數用于處理普通字符串,第二種類型的函數用于處理UTF-16LE(wide),每個符號存儲類型通常為16比特的short。 UTF-16中拉丁符號在Hiew和FAR中看上去插入了0字節:
int wmain() {
wprintf (L"Hello, world!\n");
};
在Windows NT系統中經常可以看見這樣的:
在IDA中,占兩個字節通常被稱為Unicode:
.data:0040E000 aHelloWorld:
.data:0040E000 unicode 0, <Hello, world!>
.data:0040E000 dw 0Ah, 0
下面是俄語字符串在UTF-16LE中如何被編碼:
容易發現的是,符號被插入了方形字符(ASCII碼為4).實際上,西里爾符號位于Unicode第四個平面。因此,在UTF-16LE中,西里爾符號的范圍為0x400到0x4FF.
我們回到使用多種語言書寫的字符串的例子中吧。下面是他們在UTF-16LE中的樣子。
這里我們也能看到開始處有一個BOM。所有的拉丁字符都被插入了一個0字節。我也給一些帶有變音符號的字符標注了紅色下劃線(匈牙利語和冰島語)。
Base64編碼方法多用于需要將二進制數據以文本字符串的形式傳輸的情況。實際上,這種算法將3個二進制字節編碼為4個可打印字符:所有拉丁字母(包括大小寫)、數字、加號、除號共64個字符。 Base64字符串一個顯著的特性是他們經常(并不總是)以1個或者2個等號結尾,舉個例子:
AVjbbVSVfcUMu1xvjaMgjNtueRwBbxnyJw8dpGnLW8ZW8aKG3v4Y0icuQT+qEJAp9lAOuWs=
WVjbbVSVfcUMu1xvjaMgjNtueRwBbxnyJw8dpGnLW8ZW8aKG3v4Y0icuQT+qEJAp9lAOuQ==
等號不會在base-64編碼的字符串中間出現。
調試信息非常有幫助。在某種程度上,調試信息報告了程序當前的行為。通常這些printf類函數,寫入信息到log文件中,在release模式下不寫任何東西但會顯示調用信息。如果本地或全局變量dump到了調試信息中,可能會有幫助,至少能獲取變量名。比如在Oracle RDBMS中就有這樣一個函數 ksdewt()。
文本字符串常常很有幫助。IDA反匯編器可以展示指定字符串被哪個函數在哪里使用。經常會出現有趣的狀況。
錯誤信息也很有幫助。在Oracle RDBMS中,錯誤信息會報告使用的一系列函數。
更多相關信息:<blog.yurichev.com>。
快速獲知哪個函數在什么情況下報告了錯誤信息是可以做到的。順便說一句,這也是copy-protection系統為什么要設置模糊而難懂的錯誤信息或錯誤碼。沒有人會為軟件破解者僅僅通過錯誤信息就快速找到了copy-protection被觸發的原因而感到高興。
一個關于錯誤信息編碼的例子:78.2節
一些幻數字符串通常使用在后門中,看上去很神秘。舉個例子,下面有一個TP-Link WR740路由器的后門。使用下面的URL可以激活后門:http://192.168.0.1/userRpmNatDebugRpm26525557/start_art.html。 實際上,"userRpmNatDebugRpm26525557"字符串會在硬件中顯示。在后門信息泄漏前,這個字符串并不能被google到。你在任何RFC中都找不到這個。你也無法在任何計算機科學算法中找到使用了這個奇怪字節序列的地方。此外,這看上去也不像錯誤信息或者調試信息。因此,調查這樣一個奇怪字符串的用途是明智的。
有時像這樣的字符串可能使用了base64編碼。所以解碼后再看一遍是明智的,甚至掃一眼就夠了。 更確切的說,這種隱藏后門的方法稱為“security through obscurity”。
有時,assert()宏的出現也是有用的:通常這個宏會泄漏源文件名,行號和條件。
最有用的信息包含在assert的條件中,我們可以從中推斷出變量名或者結構體名。另一個有用的信息是文件名。我們可以從中推斷出使用了什么類型的代碼。并且也可能通過文件名識別出有名的開源庫。
.text:107D4B29 mov dx, [ecx+42h]
.text:107D4B2D cmp edx, 1
.text:107D4B30 jz short loc_107D4B4A
.text:107D4B32 push 1ECh
.text:107D4B37 push offset aWrite_c ; "write.c"
.text:107D4B3C push offset aTdTd_planarcon ; "td->td_planarconfig == PLANARCONFIG_CON"...
.text:107D4B41 call ds:_assert
...
.text:107D52CA mov edx, [ebp-4]
.text:107D52CD and edx, 3
.text:107D52D0 test edx, edx
.text:107D52D2 jz short loc_107D52E9
.text:107D52D4 push 58h
.text:107D52D6 push offset aDumpmode_c ; "dumpmode.c"
.text:107D52DB push offset aN30 ; "(n & 3) == 0"
.text:107D52E0 call ds:_assert
...
.text:107D6759 mov cx, [eax+6]
.text:107D675D cmp ecx, 0Ch
.text:107D6760 jle short loc_107D677A
.text:107D6762 push 2D8h
.text:107D6767 push offset aLzw_c ; "lzw.c"
.text:107D676C push offset aSpLzw_nbitsBit ; "sp->lzw_nbits <= BITS_MAX"
.text:107D6771 call ds:_assert
同時google一下條件和文件名是明智的,可能會因此找到開源庫。舉個例子,如果我們google查找“sp->lzw_nbits <= BITS_MAX”,將會顯示一些與LZW壓縮有關的開源代碼。