本文首發于“合天智彙”公衆号 作者:xiaoyuer
前情提要:
逆向入門分析實戰(一):https://www.toutiao.com/i6805482528477020684/
逆向入門分析實戰(二):https://www.toutiao.com/i6806187940901814792/
通過之前兩篇文章,針對惡意代碼為了確定自身隻有一個執行個體在運作進行了正向開發和逆向分析。這種現象在惡意代碼中非常常見,現在對上次的内容進行一個簡要的回顧和擴充:
使用ida pro對惡意代碼進行反彙編時會發現如下特征:
1、可以找到使用了windows的api函數 CreateMutex,該函數其中一個參數為互斥變量名。
2、在調用CreateMutex函數之後,通常會調用GetLastError函數,傳回值與ERROR_ALREADY_EXISTS相同,即會使用cmp指令對傳回值eax和ERROR_ALREADY_EXISTS對應的常量(16進制的B7,10進制的183)進行對比。
當然,本篇文章不是為了繼續講這個,現在我們應該更加深入的分析其他惡意代碼常見的手法,這次分析惡意代碼常見的擷取計算機基本資訊的函數,同樣是先通過正向開發,然後進行逆向分析。
通常,惡意代碼比較關注的計算機基本資訊包括計算機名,計算機使用者名,計算機的版本。下面我們就以擷取這三個基本資訊為例,進行正向開發和逆向分析。
一 正向開發,擷取計算機基本資訊
首先,我們需要掌握幾個知識點:
1、GetComputerName函數,該函數有兩個參數,第一個參數是一個緩沖區,用來接收計算機名。第二個參數指定該緩沖區的大小。對于經常使用Python進行程式設計的人來說,可能覺得有點奇怪,因為以Python語言的風格可能會是這樣的:
computerName=GetComputerName()
函數無需傳遞參數,傳回值即為計算機名。但是對于windows api很多函數來說,都會是這種風格,用某一個參數用來接收傳回值,習慣就好。
2、GetUserName與GetComputerName函數用法十分類似。
3、GetVersionEx是用來擷取計算機版本的函數,該函數隻有一個參數,我乍一看覺得這個函數還挺簡單,肯定和上面兩個函數一樣直接把傳回值即計算機的版本傳回到這個參數裡了,當我仔細去看MSDN文檔時發現,呵,參數居然是lpVersionInfo,這是什麼破東西?經過仔細調研發現,這是一個指向OSVERSIONINFO結構體的指針,好吧,當初學C語言的時候就覺得指針這玩意賊煩,現在又來了。那就好好再學習一下指針吧!這個指針指向OSVERSIONINFO結構體,而這個結構體就是用來承載傳回值系統版本的。也就是我們先建立一個OSVERSIONINFO結構體,之後把結構體的指針作為參數傳入GetVersionEx函數即可,然後再從OSVERSIONINFO結構體中讀取相應的系統版本。
接下來看代碼:
這段代碼中,在主函數中先聲明子函數,然後調用子函數,之後使用getchar函數,用來擷取一個鍵盤輸入。為什麼加這個getchar函數?主要是因為如果不加這個,有的時候,當你直接輕按兩下這個程式時,可能指令行一閃而過讓你看不清輸出的内容。
子函數中,首先是擷取計算機名,建立了一個szComputerName字元數組用來接收計算機名,數組大小為MAXBYTE,為一個常量,通常為256,當然不同作業系統版本對應的大小可能不太一樣,在後面逆向的時候我們可以檢視。
之後,同理擷取計算機使用者名。最後擷取系統版本,其中if語句裡的條件可能看不太懂,這個主要是OSVERSIONINFO結構體的成員變量需要查閱MSDN即可。dwMajorVersion就對應不同的系統版本。當對應的值為6并且dwMinorVersion為1則是windows7或者windows server 2008 R2。如果想了解更多關于系統版本的内容可以檢視MSDN:
https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa
之後編譯,運作:
使用windows7時的效果:
該主機計算機名确實為PC:
版本為windows 7:
而使用windows 10時的效果:
二、逆向分析:
将程式拖入ida pro:
首先調用puts将字元串輸出,之後調用getSystemInfo函數,這就是我們前面編寫的子函數。輕按兩下進入發現有很多行彙編代碼,如果我們現階段就要把每一行都弄得很明白,那要花很長時間,學習很多知識,是以和上次一樣,我們要厘清主次,把所調用的函數分析清楚即可,等後續自己“功力深厚”一些,再努力把每一行彙編代碼都分析清楚。我們選中call,會發現所有的call指令都變黃了,這樣便于我們分析,為了便于描述,将每一行彙編的位址調出來,點選options->General->Line prefixes,将其勾選:
1、逆向分析擷取計算機名和使用者名
此部分内容對應的彙編代碼如下:
檢視0040175E處的call指令,此處調用GetComputerNameA函數,之前提到過後面多出一個A指的是目前使用的為ASCII環境。
在這個函數前面有兩個備注分别是lpBuffer和nSize這兩個其實就是GetComputerNameA的參數,我們之前學習的時候,一般是使用push将參數入棧,此處怎麼不是push?其實是一樣的,仔細檢視彙編指令push即可知道,其實push指令将參數入棧後,棧頂指針esp便會指向該參數,而此處使用的是mov将參數的值指派給esp所指的位址空間,本質上是一樣的。至于為何将nSize指派給esp+4所指的空間,這是因為棧的增長方向以及參數所占的記憶體大小有關,此處簡要介紹一下,因為标準調用約定中需要從右往左入棧,需要先将nSize參數入棧,再将lpBuffer參數入棧。根據棧的特點,先入棧的參數位址高,後入棧的參數位址低。即棧是一種由高位址向低位址擴充的資料結構。有興趣的可以進一步查閱push指令和函數調用約定相關的資料。
執行完0040175E處的call指令傳回值便會存儲在lpBuffer對應的緩沖區内,此處即ebp+Buffer所指的空間内。之後調用printf函數,進行格式化輸出,其中同樣涉及到esp+4,原因也是因為需要将GetComputerNameA擷取到的計算機名先入棧,再将“computer name is %s \r\n”入棧。如果你熟悉格式化字元串漏洞的話,你會有一種很熟悉的感覺,當然如果你已經掌握了這裡的知識,可以深入去了解格式化字元串漏洞的相關内容。
擷取使用者名的逆向分析過程與擷取計算機名的過程類似,此處不過多介紹了。
2、逆向分析擷取作業系統版本
這段内容對應的彙編代碼如下:
首先檢視00401812處的代碼,94h對應10進制為148,這個數值便是sizeof(OSVERSIONINFO)的傳回值,即OSVERSIONINFO結構體的大小。之後使用lea指令,它是Load Effective Address的縮寫,即加載有效位址,将該結構體所在的位址指派給eax,之後将eax中儲存的位址空間存入esp所指的空間中,整個過程即可完成結構體指針入棧工作。
然後調用GetVersionEXA函數,傳回值将會放置在結構體中。之後将該結構體中的dwMajorVersion成員變量與6對比,dwMinorVersion與1對比,之後使用jnz指令來決定是否跳轉,之前的文章将提及過,該指令是jump not zero的縮寫,當不等于0時跳轉。如果此處不是很熟悉,建議查閱之前的文章,掌握cmp和jnz以及zf标志寄存器的相關知識。再之後便使用puts輸出字元串。
總結:
擷取這些基本的資訊其實很簡單,隻需調用幾個windows API即可,對惡意代碼進行逆向分析也很容易定位到是否在擷取這些基本資訊。而如果我們通過對這些windows API調用的過程進行深入分析,便可以進一步掌握C語言、彙編語言和資料結構等相關的原理,比如指針,lea指令、棧的工作原理,或者進一步學習格式化字元串漏洞。
參考書籍:
《C++黑客程式設計揭秘與防範》冀雲著,第1版,2012.6--北京,人民郵電出版社
《C++反彙編與逆向分析技術揭秘》錢松林,趙海旭著--北京:機械工業出版社,2011年9月。
《惡意代碼分析實戰》 (美)Michael Sikorski / Andrew Honig 著,諸葛建偉,姜輝,張光凱譯 -- 北京:電子工業出版社,2014年4月,原書名: Practical Malware Analysis: The Hands-On Guide to Dissecting Malicious Software。
《彙編語言》王爽 著--2版,北京:清華大學出版社,2008年4月。
《逆向工程核心原理》,李承遠著--北京,人民郵電出版社,2014年第1版。
聲明:筆者初衷用于分享與普及網絡知識,若讀者是以作出任何危害網絡安全行為後果自負,與合天智彙及原作者無關!