本文作者:
1.木馬分析入門
大家好,我最近從Web安全開始學習二進制安全,分享一下自己學習過程的收獲和心得體會。由於是入門的內容,所以對於二進制大佬來說這很簡單,所以本文主要面向的對象主要是和我一樣一直做Web安全,又想入門二進制安全的人。本次我學習的案例是木馬和病毒常用的一個技術:確保只有一個病毒或者木馬在系統中運行,即運行單一實例。對於病毒和木馬而言,如果多次重復運行,會增加暴露的風險。所以要確保系統中只運行一個病毒或木馬的進程。
1.1.正向編寫C代碼
要實現運行單一實例,一種常見且簡單的方法是通過創建系統命名互斥對象實現的,這種方法主要是利用CreateMutex函數,通過該函數我們也可以查看是否已經有一個進程運行了。那么,如果通過CreateMutex函數得知是否已經有一個在運行的進程了呢?
下面簡要介紹下CreateMutex函數,它的功能是創建或者打開一個已命名或者未命名的互斥對象。
關於它的返回值,如果函數成功,則返回值是新創建的互斥對象的句柄。如果函數失敗,則返回值為NULL。要獲得擴展的錯誤信息,請調用GetLastError。如果互斥鎖是一個已命名的互斥鎖,並且該對象在此函數調用之前就存在,則返回值是現有對象的句柄,GetLastError返回ERROR_ALREADY_EXISTS。
簡單點來說,就是如果CreateMutex函數的返回值不是NULL,並且調用GetLastError函數后,返回值是ERROR_ALREADY_EXISTS,那么可以判定已經存在了一個在運行的進程。用C語言代碼實現如下:
使用VC++6.0完成編譯鏈接后生成可執行文件,雙擊運行,顯示如下:
在不關閉上述進程的前提下,再次雙擊生成的可執行文件,顯示如下:
可以看出當系統中運行第二個進程的時候,輸出了Already Run!!!!說明程序已經成功地判斷出重復運行了。
1.2.逆向分析
下面我們就對這個程序進行逆向分析,我們需要對主函數和子函數分別進行逆向分析,這次先分析主函數。
關於學習的方法,我的收獲是在初學階段,我們先分析自己寫的代碼,分析完之后再進行印證,慢慢地就可以脫離源碼並嘗試分析其他未公開源碼的程序流程。
在學習過程中,十分重要的一點是:要分清主次。什么意思呢?
學習匯編語言和逆向,我們完全沒有必要逐條指令去仔細閱讀所有的代碼,重要的是從整體上理解程序究竟做了哪些操作。匯編語言也是一種編程語言,平常大家也不會去一行一行地仔細閱讀別人寫的大量代碼,除了必須要理解的重要部分花時間仔細讀一讀,剩下的部分基本都是一帶而過,只要大體上理解程序在做什么事就好了。逆向工程也是一樣,“重要的部分花時間仔細理解”“其余部分大概知道怎么回事就好”這兩條原則同樣適用。
那么哪些是重要的呢?在病毒木馬分析中,其中一點比較重要的是分析call函數,只要將這個程序所調用的函數分析清楚了,那么就知道這個病毒木馬在做什么了。與此同時要弄清楚它的邏輯結構,比如什么時候跳轉到哪執行。
接下來我們首先對主函數進行分析。
1.2.1.主函數:
主函數的C語言代碼如下圖所示:
下面對其匯編代碼進行分析:
_main_0 proc near
var_40= byte ptr -40h
push ebp mov ebp, esp sub esp, 40h push ebx push esi push edi lea edi, [ebp+var_40] mov ecx, 10h mov eax, 0CCCCCCCCh rep stosd
以上代碼完成所有的函數入棧操作,每個函數開始時都會有這樣的操作,這里我們無需過分細究,如感興趣,對這段代碼詳細的分析可參考《C++反匯編與逆向分析技術揭秘》p150,我也將其主要的內容貼出來了:
關於這段內容中補充介紹兩個指令,其中,xor eax,eax直接會將eax的值設置為0,這是將寄存器設置為0最常見的方式,cmp指令是條件指令,詳細內容如下圖所示:
這段內容在這里暫時只需了解,無需深究,等需要的時候再去研究也不遲。
之后,到了需要認真理解的地方了,下一條的指令是
call sub_401005
它的意思是調用子函數sub_401005,在這里其實對應的是我們編寫的IsAlreadyRun函數。
需要補充的是在函數調用時,如果有參數需要傳遞,需要在call指令之前,使用push先將參數從后往前入棧。這里因為無任何參數傳遞,所以在調用之前,無需使用push指令將參數入棧。后面還會詳細介紹是如何從后往前入棧的,這里需要先記住這個知識點。
還有一個要記住的是在函數調用完成后,VC中,會使用eax寄存器來保存函數的返回值。
接下來的一條指令是
test eax, eax
關於test指令,只需記住若eax為0,則zf標志位會設置為1,此時eax中的值是上一條指令的返回值,若對test指令感興趣可參考如下解釋:
接下來的指令是
jz short loc_4010E0
jz是跳轉指令,即jump zero,即當零標志位ZF=1的時候跳轉到 loc_4010E0這個位置執行,此時也就是上一個指令test eax,eax得到的操作使得ZF=1,也就是eax=0,由於eax保存的是子函數的返回值,所以我們得知子函數的返回值為0。由我們編寫的C語言代碼可知,此時對應的是IsAlreadyRun函數返回結果為false,和我們的分析相對應。
跳轉到該位置后:
loc_4010E0: ; "NOT Already Run!\n" push offset aNotAlreadyRun call _printf add esp, 4
可以看到的call _printf 指令,這將會調用printf輸出函數。由於printf需要傳遞參數,所以在call _printf之前,需要先執行push的操作。我們將鼠標放在aNotAlreadyRun上可以看到對應的字符串與loc_4010E0:后的備注信息"NOT Already Run!\n"一樣,如下圖所示:
所以得出結論,若子函數sub_401005的返回值為0時,跳轉到loc_4010E0位置,將會輸出"NOT Already Run!\n"。與我們編寫的C語言代碼相符合。
那么,若子函數sub_401005的返回值為1時,會怎么樣呢?
此時,會走到左邊的執行框內,不會跳轉到右邊。同理,這里將會輸出"NOT Already Run!\n"字符串。
執行完成后,左右兩邊的內容都會到loc_4010ED這個位置繼續執行:
這段內容看到有call ds:Sleep指令,此處是調用了Sleep函數,對應的是我們C代碼中的Sleep(10000):
由於要傳遞參數,所以需要先將參數入棧,即push操作,可以看到在call ds:Sleep指令之前,有push 186A0h 指令,將鼠標放置在186A0h上,右鍵可觀察對應的十進制:
剛好也是100000,所以和我們編寫的C代碼也剛好符合。
之后,使用pop和call __chkesp等指令,完成出棧、檢查棧平衡等函數返回工作。
目前,關於主函數的分析已經完成,下篇我們一起進一步對子函數進行詳細的分析。
參考書籍
《Windows黑客編程技術詳解》甘迪文著--北京:人民郵電出版社,2018年12月。
《C++反匯編與逆向分析技術揭秘》錢松林,趙海旭著--北京:機械工業出版社,2011年9月。
《惡意代碼分析實戰》 (美)Michael Sikorski / Andrew Honig 著,諸葛建偉,姜輝,張光凱譯 -- 北京:電子工業出版社,2014年4月,原書名:Practical Malware Analysis: The Hands-On Guide to Dissecting Malicious Software。
《匯編語言》王爽 著--2版,北京:清華大學出版社,2008年4月。