針對Ryuk勒索軟體的分析調查Part2
上一篇,我介紹了Ryuk的一些一本架構,本文我會接著介紹Ryuk和Hermes的其他技術相似之處(注入方法、加密方案),並對幕後團隊的銷贓行為進行分析。
注入方法
Ryuk使用的注入技術非常簡單,首先使用OpenProcess獲取目標程序的控制代碼,然後使用VirtualAllocEx在其地址空間中分配緩衝區。分配的緩衝區大小有惡意軟體映像那麼大,並且需要和惡意軟體映像位於相同的基址。
然後,惡意軟體會將其當前的虛擬映像內容寫入其中,並建立一個執行某些操作的執行緒。請注意,Ryuk通過將虛擬映像寫入具有預定義分配基礎的請求緩衝區後,由於缺少正確的程式碼重定位過程,就有可能發生所請求的地址不可用於分配的風險,從而導致程式碼注入執行失敗。
可能導致失敗的注入方法
注入程式碼
這些已經注入的程式碼中,包含了勒索軟體用於加密檔案的核心功能。不過加密功能的實現有分兩步,首先它是通過使用預定義的金鑰和字串長度陣列解密API函式名稱字串列表來啟動的,然後使用這些陣列來動態載入相應的函式。
不過在分析Ryuk時,為了對加密過程進行解密,研究人員建立了一個IDA Python指令碼,來簡化解密過程。IDA Python指令碼將自動解密這些字串並重命名相關變數,該指令碼可以在下面的附錄中找到。
接下來,惡意軟體會嘗試將虛擬檔案寫入Windows目錄,該目錄只允許具有Admin許可權的使用者訪問。如果檔案建立失敗,它將會休眠一段時間,然後再以相同的方式嘗試5次。如果還是失敗,則Ryuk執行過程將自動終止。
如果檔案成功建立,則它會將向Windows目錄的子資料夾中再寫入兩個檔案。第一個檔名為“PUBLIC”,其中包含一個RSA公鑰,第二個檔案是“UNIQUE_ID_DO_NOT_REMOVE”,其中包含一個硬編碼金鑰。這兩個檔案都可以用於加密,具體過程我會在下文介紹。
加密方案
Ryuk使了一個相對簡單的三層信任模型( three-tier trust model),之所以要採用信任模型,是因為此模型的第一層含有攻擊者持有的全域性RSA金鑰對(global RSA key pair),這在勒索軟體的實現中是很典型的方法。在感染過程中,受害者在任何時候都看不到這個金鑰對中的私鑰。
第二層是則包含每個受害者的RSA金鑰對,通常情況下,勒索軟體會在執行中動態生成這個金鑰對,然後立即使用更高層(也就是第三層)的全域性金鑰(global key)加密生成的私鑰。不過也有例外,就是勒索軟體有時會附帶有預先嵌入的金鑰對和預加密的私鑰,這個方法之所以屬於例外,是應為如果攻擊者使用相同的樣本感染多個受害者,或者如果在多個樣本中嵌入相同的金鑰對,則可能發生“一次付費,多次解密”,顯然這並不是攻擊者希望發生的。但是,假設為每個新樣本生成了一個新的金鑰對,那麼此攻擊模型就比較受攻擊者的歡迎了。
嵌入的RSA公鑰(注意初始位元組06 02)
已加密的RSA私鑰(使用了全域性金鑰,請注意初始位元組07 02)
第三層是使用Win32API函式CryptGenKey為每個受害者檔案生成的標準AES對稱加密金鑰,然後使用CryptExportKey匯出金鑰,使用第二層金鑰加密,並將加密結果附加到加密檔案。所以,在一個完整的攻擊事件中,攻擊者會在閱讀了CryptExportKey的全部內容後,提供了第二層金鑰作為hExpKey引數。大多數勒索軟體會以明文形式匯出AES金鑰,然後使用CryptEncrypt或其他類似方法對結果進行加密。
生成每檔案AES金鑰的程式集,而這只是勒索軟體 “加密檔案”例程的一部分
一旦所有加密基元(cryptographic primitive)準備就緒,勒索軟體就會對受害者系統上的每個驅動器和網路共享執行標準遞迴掃描,並加密每個檔案和目錄,當然除了包含來自硬編碼白名單的任何檔案或目錄,其中包括“Windows”,“Mozilla”,“Chrome”,“RecycleBin”和“Ahnlab”。很明顯,攻擊者希望獲取完整無缺的受害者訪問過的網頁瀏覽器,這樣他們才可能跟蹤到受害者的讀取的贖金和購買加密貨幣等記錄。不過研究人員至今還沒有搞清楚,在這種攻擊沒有專門針對韓國使用者的情況下,為什麼攻擊者要對受害者所安裝的韓國終端保護產品進行加密,這也是Ryuk勒索軟體是否就是HERMES勒索軟體的變異版本的一個猜測依據。除了上文所講的明顯的相同標記外,它們的信任模型還存在一些細微差別。
根據Malwarebytes的 ofollow,noindex">一份報告 ,最初的HERMES實際上只會生成了每受害者的第二級RSA金鑰對,而不是在惡意軟體樣本中嵌入硬編碼副本。但由於加密功能的出現,包括加密檔案格式及其相關的唯一file命令所使用的magic檔案,在重新命名的版本中被大量複製,還有 “AhnLab” 相關檔案的檢查,這些檔案甚至與Ryuk的目標受害者無關。
由Ryuk勒索軟體加密過的檔案的十六進位制轉儲,其檔案結構與HERMES中使用的檔案結構明顯相同,其中甚至還有與眾不同的“HERMES”標記,惡意軟體使用該標記來識別已加密的檔案。
除了本地驅動器,Ryuk還將嘗試加密網路資源。首先,它將通過呼叫WNetOpenEnum開始列舉,然後分配一個初始化為零的緩衝區。在呼叫WNetEnumResource函式的過程中,這個緩衝區將被此函式填充。如果列舉資源是其他資源的容器,則勒索軟體將遞迴呼叫其網路資源列舉函式。
Ryuk找到的每個網路資源的名稱,都將被整理到一個列表中,並用分號分隔開,在稍後,該列表的網路資源都將被加密。
最後,Ryuk將自動刪除其加密金鑰並執行.BAT檔案,該檔案將從磁碟中刪除shadow volume副本和各種備份檔案。
加密系統後,Ryuk要執行的批處理命令列表
對贖金的追蹤過程
不過幸運的是,Ryuk勒索軟體目前尚未廣泛傳播,這一傳播特點與HERMES非常類似。HERMES一開始時也僅用於有針對性的攻擊,這樣研究人員想跟蹤惡意軟體的活動和收入就變得非常困難。目前研究人員所發現的幾乎所有惡意軟體樣本中,都包含了一個獨立的錢包,如果受害者通過這個錢包支付贖金後,不久這些資金被劃分成許多塊,並通過多個其他帳戶銷贓。
然而,贖金票據中所包含的錢包還是暴露一些贖金交易的痕跡,研究人員根據這些線索找到了最有可能用於銷贓的錢包。另外研究人員還能夠發現這些錢包之間的聯絡,因為贖金會在某一時刻被轉移到幾個關鍵的錢包裡。這意味著Ryuk勒索軟體處理贖金的過程,是一個分工極為嚴密的操作,且根據這幾個關鍵的錢包,可以斷定目前已經有幾家固定的公司被定位為攻擊目標。
在受害者向預先分配的錢包支付贖金後,大約25%的資金會被立即轉移到新的錢包。不過,這些資金仍然可以在新建立的錢包中被研究人員找到,研究人員可以假設這些比特幣稍後會被兌現。而剩餘的75%金額,佔了真個贖金的大頭,它們會被怎麼處理呢?研究人員發現,這些資金也會被轉移到新的錢包。而在新錢包中,這部分中的25%又會被立即轉移到新的錢包,然後兌現。其餘的資金將再次依照上面的過程,依此兌現。
有趣的是,有幾個錢包的使用比其他錢包的使用頻率更高一些,且其中有幾筆交易來自不同的贖金。研究人員分析,這些錢包實際上是贖金支付後用於銷贓的核心中轉站,通過分析這些中轉站,研究人員能夠估計出Ryuk勒索軟體所獲贖金的數額,下圖就是研究人員發現的銷贓模式。
從贖金支付到兌現階段的比特幣交易流程
總結
通過分析Ryuk的開發過程、加密過程再到贖金銷贓過程,研究人員發現Ryuk攻擊的目標是能夠支付大量贖金的企業。根據目前的證據,Ryuk與HERMES勒索軟體的關係緊密,研究人員分析其幕後的開發者很可能就是朝鮮Lazarus組織。在成功感染並獲得價值約64萬美元的比特幣後,研究人員認為後續攻擊互動將更為猛烈。
附錄
字串解密Python程式碼
""" Ryuk strings decrypter This is an IDA Python based script which can be used to decrypt the encrypted API strings in recent Ryuk ransomware samples. After the decryption, the script will rename the encrypted string in order to ease analysis. Ryuk sha-256: 8d3f68b16f0710f858d8c1d2c699260e6f43161a5510abb0e7ba567bd72c965b """ __author__= "Itay Cohen, aka @megabeets_" __company__ = "Check Point Software Technologies Ltd" import idc from idaapi import * def decryptStrings (verbose = True): encrypted_strings_array =0x1400280D0 lengths_array =0x1400208B0 num_of_encrypted_strings =68 key ='bZIiQ' if verbose: print ("[!] Starting to decrypt the strings\n\n") # Iterate over the encrypted strings array for i in range(num_of_encrypted_strings): # Get the length of the encrypted string string_length = idc.Dword(lengths_array + i*4) # Get the offset of the encrypted string string_offset = encrypted_strings_array + i*50 # Readbytes from # For IDA version < 7, use get_many_bytes() encrypted_buffer = get_bytes(string_offset, string_length) decrypted_string = '' # Decrypt the bytes and save it to for idx, val in enumerate(encrypted_buffer): decrypted_string += chr( ord(val) ^ ord(key [idx % len(key)])) # Set name for the string variable in IDA idc.MakeName (string_offset, "dec_" + decrypted_string) # Print to the ouput window if verbose: print("0x%x : %s" % (string_offset, decrypted_string,)) if verbose: print ("\n[!] Done.") # Execute the decryption function decryptStrings()
IOC/">IOCs
Ryuk的雜湊值(MD5):
c0202cf6aeab8437c638533d14563d35
d348f536e214a47655af387408b4fca5
958c594909933d4c82e93c22850194aa
86c314bc2dc37ba84f7364acd5108c2b
29340643ca2e6677c19e1d3bf351d654
cb0c1248d3899358a375888bb4e8f3fe
1354ac0d5be0c8d03f4e3aba78d2223e
DROPPER木馬的雜湊值 (MD5):
5ac0f050f93f86e69026faea1fbb4450