Ransomware Locky Analysis
Locky的變種非常的多,這個樣本來自下面的Url,是最新的一種變種。?
這是程序在剛開始執行時與釋放了Image并替換了之后的對比,很明顯發生了進程替換,因此進行分析之前有必要把它內部釋放出來的image提取出來,分析這個image才能搞清楚它是如何做加密的。
?
?Locky存在一個未知的殼,IDA并不能檢測出這個殼,因為它的導入表等信息并沒有被破壞。它在運行起來后
,執行相當大量的垃圾代碼干擾調試,依據是在調試的過程中它進行相當多的寄存器操作但6個通用寄存器的值始終是0。
這這些垃圾代碼中隱藏著它獲取Kernel32.dll的Addr的邏輯,它會獲取Kernel32的BaseAddress并通過偏移計算出API的地址。
它會調用VirtualAlloc分配一塊可寫可執行的內存塊,大小是61BB,可執行意味著會釋放代碼到這塊內存區中,應該著重分析。(0x404587)
TextSegment中存在一部分未被IDA識別的func,位置在(0x402940)。這部分代碼的作用是將一些數據(在TextSegment中)釋放到剛才通過VirtualAlloc分配的內存中去。
GenerateShellCodes(PVOID?lpShellCodes,?BYTE* pDataInTextSegment,?DWORD?dwLen,?DWORD?dwHEX);
其中bl寄存器用來作為臨時存儲一個字節的寄存器,ebx寄存器用作計數器,它從函數參數中獲得Data的Length,然后遞減,如果等于0就跳出循環,esi是指向VirtualAlloc返回的內存地址。處理完畢后,會在指定的位置寫入一堆數據,這些數據還需要進行異或處理才能變成真正可以被執行的代碼。
以下是沒有被處理的數據,只是單純的填充了緩沖區。
它的解這部分Code的算法如下:
其中dwHEX是這個處理函數的第四個參數。解完了以后的數據如下:
下面已經完成了部分ShellCodes的釋放,接下來將跳轉到ShellCode去執行代碼。
這段ShellCode的目的釋放一個被壓縮的PE文件,并加載這個PE文件,整個過程沒有釋放任何文件出來,無法被監控軟件發現,釋放PE文件使用了非常精巧的方式加載到當前進程中,并完成了進程替換。
被釋放的ShellCode本身還有很多代碼沒有釋放出來,在ShellCode的Offset=247處有一個func用于釋放ShellCode中的代碼(釋放不太嚴謹,應該是通過一些異或操作把原來不是代碼的數據轉換成代碼)。
這個操作會將ShellCode頭部的一些代碼釋放出來。在后面經過調試發現,這個func會被反復的多次調用,用于釋放存在ShellCode中的數據(轉換成可以被執行的CPU代碼)。因此這里應該是釋放ShellCode自身代碼的邏輯。
sub_1D1020用來獲得Kernel32的BaseAddr:
GetModuleHandle
?
?
再被釋放的ShellCode+1020處有一個Func(SC_GetModuleAddr)它用來根據傳入的HardCode得到對應的Module的基地址,例如Kernel32.dll Advapi32.dll等。
LPVOID SC_GetModuleAddr(DWORD dwHEX);
對應的在ShellCode+1122處有一個Func(SC_GetFuncAddr)它用來根據傳入的HardCode得到對應的func的基地址,例如Kernel32的GetProcAddress等。
PFUNC?SC_GetFuncAddr(DWORD?dwHEX,?LPVOID?lpModuleBaseAddr);
如下圖:
ShellCode會調用這段代碼GetProcAddress,并傳入Module的基地址以及要獲取的函數名。這里函數的名字被嵌入到了ShellCode中,被解釋稱代碼了,需要重新解析成字符串。注冊服務,隱藏自己的行為。
后面的邏輯大致符合如下描述:
例如:用同樣的方法獲得了EnumServicesStatusExA的函數地址。
這里調用GlobalAlloc(ShellCode+001D0686)在堆上分配內存分配的內存用0初始化,大小是0x4214。(可能是一個結構)因為被釋放掉了。
這里又使用API分配了一塊內存,大小是1799C。頁屬性是可讀寫不可執行。這個函數的參數很值得注意,居然是當前進程的基地址。
這里從當前的進程中釋放了一堆壓縮數據到剛才分配的內存中,然后又繼續調用VirtualAlloc分配了一塊大小1AE00的內存,頁屬性依然是可讀寫不可以執行,這塊內存用于解壓縮剛才釋放的壓縮數據。
程序接下來調用了RtlDecompressBuffer這個Undocument的API。用于釋放壓縮的數據到指定的Buffer中。釋放出來的文件是一個PE Image。(ShellCode+318E)
把這個PE dump出來之后可以從導入表中發現一堆Crypt相關的API,它就是真正加密用戶數據的PE。
到這里后面的分析都與Ransomware沒有關系了,只要能拿到內部的加密數據的文件進行分析就可以了。下面是這段ShellCode作為加載器還有一些什么具體行為的分析。
這里釋放了壓縮數據所占用的內存。
這里打開了通過獲取當前殼進程的ImagePath,然后調用CreateFile打開文件并計算文件的Size。
計算出來的文件大小是31000,然后調用VirtualAlloc分配相同大小的空間。下面是VirtualAlloc的參數。可讀可寫不可以執行。
?
然后調用ReadFile從當前Image的文件中讀取數據,一次讀取完畢。
然后關閉文件。
現在內存中有2份PE文件了,一份原始的殼的Image,還有一個被脫殼后真正做加密工作的Image。
上面是原始殼的PE數據
上面是被脫殼后的PE數據
判斷它最后會還原回去,不然沒有必要保存一份在內存中。這樣可以不用釋放文件,監控軟件監控不到,非常精明的設計。
這里調用VirtualProtect來對0x400000位置的當前Image設置可寫權限,要開始覆蓋當前的數據了。
開始將Image的內容清空:
然后從debug021:001D17E9開始的一大段代碼用于填充新數據到當前的Image中去。好大的一段代碼,因為內存中的PE的Offset與加載到內存后的Offset不一樣,所以需要小心的分段加載PE數據,這里的代碼類似一個進程加載器。
寫完后的數據已經與上面釋放出來的PE一樣了(對照0x200000地址數據),這里發生了進程替換,它肯定還要找到新的PE的EntryPoint開始新的調用。
調用RtlZeroMemory把釋放出來的在內存中的做加密的PEImage擦除掉。
然后調用VirtualFree釋放掉內存(清理現場)。
?
現在調用VirtualProtect把在0x400000位置的新的PEImage頁屬性設為只讀。
PEHeader,第一個頁面。繼續設置代碼段為可執行可讀。
到這里,新的PE已經加載完畢了,應該可以準備執行了。
這里會把一些API用到的都取出來保存在棧上,后面用。有一些,不過我最關注的是CreateThread,然后它會清理ShellCode,清理犯罪現場。通過使用下面的API調用。清理的過程分幾個階段,頭部的ShellCode還保留,只是把除了頭部以外的都清理成0.
可以看到從F6之后都是0了。
然后調用VirtualFree把這整段Code刪除掉。到這里ShellCode的使命已經完成,簡單來說它的目的就是將內部的一個PE釋放出來,并替換當前進程,并把當前進程的數據緩存到內存中。
然后,程序會執行CreateThread,執行新的PE代碼。
405152就是新的進程的EntryPoint,可以通過對提取的PEImage的分析佐證。
至此,脫殼完成并且成功的分析到了這個殼的運作機制。
=========================運行測試==========================
這3個Sample的行為觀測很相似,但是從反匯編角度分析的話,技術在不斷的演進。雖然都沒有檢測到殼的存在,但是在運行的過程中會從代碼段或數據段釋放code出來執行,也許有進程替換。也懷疑有虛擬機檢查,因為都沒有行為出現。
========================脫殼后分析========================
脫殼后的程序可以正常運行,但是之前也講過即使在物理機器上也沒有跑出行為,所以進一步分析原因,以及分析Locky的整體運作方式。
這段程序的entrypoint據我判斷是手動編寫的,非經過編譯器產生。他在開始調用C runtime的入口之前做了一些自定義的隱蔽工作,這并非編譯器的行為。它的詭異行為如下面所述:.reloc是一個配置塊,它的數據結構用C來表述如下:
程序會根據這個結構的信息做對應的處理,例如如果選擇模仿svchost,它就會偽裝成svchost,如果選擇自動啟動,就會在注冊表中注冊自己,如果選擇了地區保護,就會只針對相應的地區展開攻擊,等待時間被用來隱蔽自身,不立即運行。
?
其中有2個目前仍然可以ping通。這些IP地址在內存中的位置,被它保存到了一個全局指針中。
?
| MEM_IMAGE
0x1000000 | Indicates that the memory pages within the region are mapped into the view of an image section. |
?
這個操作使得原始Image程序的main函數不會被進入,反調試。
Jump2NewImage會跳轉到已經解決了重定位問題的新的在內存中Image中,并開始從pop這條指令開始執行,從下面圖可以看出指令是一致的。
這里已經分析出了它實際會返回到調用指令的下一跳指令中,只不過不再是同一個內存中的Image,解決這個問題直接打掉patch就可以。如下圖:
將PerpareForRunInOtherImage打上Patch,這個func的目的是反調試,會堅持程序斷點,同時它會跳到一個內存中的其它位置執行相同的程序,同時調整重定向表,因此在這個函數內部打patch沒用,因為重定向表被修改后會導致程序崩潰。
最簡單的修改方法。現在,所有的障礙都已經掃除可以調試并分析這個Sample了。
程序在進入主函數之后,立即安裝一個頂級的未處理異常處理器,會在發生無法解決的異常后重新啟動自己。
然后程序會把藏在上述中起迷惑作用的.reloc段中的4個Ip地址以及包含Ip地址的數據結構取出來,Ip地址會被放到一個vector中保存起來。
從上面的一張內存截圖可以看到,IP地址是使用逗號分隔的。
查找逗號在String中。
加入全局的std::vector<std::string>中。
接下來它會檢查藏在.reloc中的數據結構中的相應的值。
它會避開俄語區用戶,LABEL_21處有個function,調用它會先將當前程序的File找到,然后移除所有屬性,隨后生成一個臨時目錄,將Image移動過去,可能會失敗,會采用MOVEFILE_DELAY_UNTIL_REBOOT來調用MoveFileEx標記為重啟刪除。緊接著構造一個“cmd.exe /C del /Q /F”串并把原路徑文件加入到這個串的后面,調用CreateProcess來刪除文件,最后退出程序。
接下來會根據藏在.reloc中的數據然后根據數值去等待,目前等待的時間超過3天,這是我們無論如何都沒有跑出行為的原因。
這里打上patch,跳過這些邏輯,使得程序可以正常運行,避免一些內部邏輯導致的程序不運作不能反映出真實行為。
接下來的代碼會通過”GetVolumeNameForVolumeMountPoint”API,根據Volume獲取一個GUID,然后對這個GUID進行hash得到一個hash值,再通過這個hash值得到一個字符串并把它鏈接到HKLM\Software\{xxxxxxxxxxx}用于創建一個注冊表項。
緊接著它會用藏在.reloc中的數據結構中的值判斷是否需要模仿svchost運行,如果是它會將自己Copy到一個隨機目錄并重命名為svchost.exe,并通過CreateProcess重啟進程并結束自己的當前運行。
接下來會先去獲取本地機器的信息,之前通過Volume的GUID算出的一個hash被用作機器信息的ID,這部分數據會被提交到作者的服務器上面去。
“id=D2BCC112BD05308A&act=getkey&affid=3&lang=en&corp=0&serv=0&os=Windows+7&sp=1&x64=0”
然后用MD5對這部分數據進行hash:
程序接下來將系統信息的hash數據追加到系統信息的頭部,作者可以利用這個hash值校驗數據一致性,然后通過一個循環移位加異或算法對這部分數據加密,如下圖。
接下來它準備要發送數據到作者的WebServer上面去,最開始說了作者在偽裝的.reloc中藏了一些配置信息,同時包含4個主機地址,在程序一開始,這四個主機地址被加入了一個vector中,程序通過獲取一個隨機數字,并對存儲在vector中的ip_addrnum取余數來隨機選擇一個Ip地址用于訪問。
它先構建一個http地址:使用上面的信息創建一個http對象,然后設置一些參數:
發送和接收的延遲設置為30000,重試次數1次。
這里發生了問題,首先我可以連接到服務器,但是在向對方發送數據的時候,失敗了返回的HttpError 403 Forbidden:資源不可用。服務器理解客戶的請求,但拒絕處理它。通常由于服務器上文件或目錄的權限設置導致。所以這個sample因為無法將用戶信息上傳出去,陷入了一個軟件異常,不會繼續執行加密操作。這里得想辦法讓他跳過這個發送的步驟直接去加密用戶數據。以下是嘗試發送的數據,包含了用戶的SysInfo和一個用于校驗的Hash被循環左移右移異或擾亂后的數據。
?
修改2處檢查點,讓它不發送Crypt信息,直接開始加密文件,但我判斷在發送了用戶的SysInfo后,服務器可能會計算一個PublicKey并通過網絡返回到本地,因為Locky如果請求不到網絡的話它不會做任何事情,因此判斷可能不存在Default的Key。
?
上面的判斷是正確的,因為在下面的分析中,當準備開始加密數據的時候,程序調用了一個API函數:CryptImportKey,在這個函數的參數中送入了一個全局變量,通過IDA xref我可以看到這個全局變量既是服務器返回的數據,因此可以判斷出程序使用用戶機器的相關信息計算出一個PublicKey,用這個Key對數據進行加密(部分,或加密對稱加密的Key),因此這個API也許可以去Hook,然后替換一個自己的Key,用于日后解密。
?
我們打上Patch,讓他不發送用戶信息到服務器上面去,直接開始加密文件,進入到枚舉驅動器資源的階段(我看過的所有Ransomware都有這個步驟),首先它會去枚舉網絡驅動器,例如Nas,這里我們不管它,因為在枚舉網絡驅動器的時候虛擬機調試會拋出異常,我也patch了。
?
枚舉到的所有網絡驅動器資源它會先將它們放到一個vector<std::string>向量描述的鏈表中保存起來。
接下來會繼續枚舉本地磁盤驅動器,下面會有一堆的判定條件是否要處理這個磁盤,首先會排除所有帶有remote屬性的,并且如果是可以拔插的如USB也不處理,如果磁盤的總大小低于A00000(1MB)跳過,如果磁盤類型是Fixed、Removable以及Ramdisk都不處理,如果磁盤信息中包含的SystemFlags超過了19也不處理,邏輯如下:
程序將所有符合要求的磁盤依次加入vector中保存起來,最后會返回這個vector(其實這是一個在棧上的vector,程序最后會將這個vector的內容復制到作為入參的外部vector中)。
?
當準備好所有的可以加密的驅動器后,程序會針對每一個驅動器啟動一個線程來執行加密工作,在線程加密的同時,程序會刪除掉系統還原的影子盤,最后程序會block自己知道wait到了所有的線程執行完畢的event。在這個進入waiting之前,它會根據.reloc中的配置選擇是不是要安裝自啟動。這些操作都完畢后,主線程進入wait狀態。
接著程序會進入到線程中執行,線程執行的代碼是加密用戶數據。加密線程首先枚舉當前驅動器中的所有文件,并加入到vector中。這個過程中,篩選的文件會過濾掉一些指定的文件路徑,也會過濾掉不感興趣的后綴名文件。
過濾特定的文件路徑
過濾特定的后綴
?
Locky也是按照訪問時間順序排序的,也就是說最后被訪問的文件最先被加密,不過它存在一個Bug,它會先找最后被訪問的文件夾,比如在一個超大文件夾中有一個文件被修改了,那么這個文件夾的訪問時間也會被修改,但是這個文件夾中其它的文件很長一段時間都沒有被訪問,Locky會最先加密這個文件夾中size最小的文件,直到加密完整個文件夾,才去找下一個文件夾,如果下一個文件夾中所有的文件都是最近訪問過的,它也會優先加密只有一個文件是最后訪問時間的文件夾,Locky沒有處理這種情況。
在我的機器中,它會優先加密IDA文件夾。顯然Ransomware也存在如何精確定位有價值信息這個問題。
接著,程序開始將通過WebServer計算得到的對應用戶SysInfo的PublicKey從內存中取出來,通過API:CryptImportKey來得到一個PublicKey對象。因為這里我們之前分析無法訪問Locky作者的WebServer 403拒絕訪問了,因此這里會丟出軟件異常,無法開始加密數據。
這里的pbData是從全局對象中取出來的,因為這里沒有publickey,我需要構造一個自己Key送進去,讓他繼續工作,因此我生成了一個公私鑰對送了一個自己的PublicKey給程序。
PublicKey
?
這里程序初始化了一個內部使用的CryptObject,它的數據結構如下:
其中hKey是已經導入的PublicKey的句柄,剩下的16個字節是對應于用戶Machine的系統信息產生的Hash被用于用戶標識。
初始化完CryptObject后,程序得到了PublicKey以及User的機器ID,將這個對象傳入EncryptFile開始執行加密工作。
加密函數除了Crypt對象外,還吃一個filename,來決定加密哪一個文件。
在內部,程序會根據傳進來的filename計算一個hash,然后CryptObj中包含一個User機器的ID,新文件名=UserMachineID+filename_hash+.locky;
接著程序會打開需要加密的文件,并把源文件重命名為上面的這種文件名,Locky接著會去檢查是否有硬件加速可以用,如果有的話采用增強指令集加速加密的過程。
Advanced Encryption Standard Instruction Set (or the Intel Advanced Encryption Standard New Instructions; AES-NI) is an extension to the x86 instruction set architecture for microprocessors from Intel and AMD proposed by Intel in March 2008.[1] The purpose of the instruction set is to improve the speed of applications performing encryption and decryption using the Advanced Encryption Standard (AES).
?
Locky通過WindowsAPI CryptGenRandom生成一個16字節的種子值。
如果有硬件加速可以用(目前CPU基本都支持硬件加速),分配一個以16字節對齊的內存塊。接著用16字節種子初始化這個內存塊。
隨后,Locky使用之前傳進來的PublicKey調用API:CryptEncrypt對種子值進行加密。
Size=16,pbBuffer存儲的是通過CryptGenRandom生成的隨機值。
?
如果加密后的返回的數據塊的大小不等于256,那么會丟出異常,這里的作用是判斷生成的Key長度是不是正確。
?
在這個加密函數中,有一個數據結構和派生自一個基類的兩個類值得說一下,其中EncryptBlock這個數據結構大致的作用是用來保存當前加密用的上下文,它保存了兩個固定的用于xor的4字節數據,用戶的SysInfo的ID,一個128位的種子值(通過CryptGenRandom得到),一個MAX_LEN長度的雙字節數組用于保存當前文件的文件名,還有代表當前文件屬性的bitmap,以及文件size,結構如下。
?
另外兩個類對象是Encrypto的主體,它們都用Encrypto派生出來,一個帶有硬件加速功能EncryptoAC,一個沒有帶有硬件加速功能EncryptoNoAC。
?
我只分析了帶有硬件加速的對象,它有5個方法,它的數據成員中包含一個AESkey,它是用EncryptBlock中的128位隨機數生成的(需要知道的是,當用這個隨機數初始化完畢AESKey之后,隨機數即被作者用PublicKey加密了),生成的算法如下:
?
生成完AESKey之后,加密對象會先將當前加密上下文用AESKey加密了,
緊接著,將文件待加密的文件讀到內存中,然后加密文件。
這里可能發現參數不一樣了,實際上因為存在兩個加密對象,根據是否支持硬件加速選擇不同的加密對象,所以IDA這里翻譯的有點問題,不用在意,實際上就是一個function。
緊接著會把加密后的文件寫入磁盤,完成一個文件的加密工作,剩下的工作有寫一些幫助(勒索)文件向用戶提示應該如何轉賬之類的操作,不細說了。下面總結一下Locky的加密邏輯。
?
?
?
解密邏輯,猜測:
?
目前解法就是暴力破解隨機數,看起來是不可行的。
?
從solution的角度,有兩個點,一個是生成隨機數的地方,Hook:CryptGenRandrom,然后替換一個自己的隨機數,這樣我們可以用自己的隨機數推出AESKey,這樣就可以解開文件。
?
另外一個辦法,Hook:CryptImportKey,將從WebServer返回的PublicKey替換成自己的Key,這樣我們可以用自己的PrivateKey解開解密塊中的隨機值,推出AESKey,也可以解開文件。
?
下面有一組測試用的資料:由于安全原因就不上傳了,有興趣可以@ tedzhang2891@gmail.com與我聯系。
原文地址:?http://ec2-52-196-167-189.ap-northeast-1.compute.amazonaws.com/wordpress/index.php/2016/08/07/ransomware-locky-analysis/
總結
以上是生活随笔為你收集整理的Ransomware Locky Analysis的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Algorithm, Secret ke
- 下一篇: Ransomware Cerber An