Try   HackMD

0x06. Advanced topic - Decode encode/encrypt network traffic

Why?

駭客創造惡意程式並且想盡辦法散播到被害人電腦裡,常見的目的就是要竊取主機的資料

那竊取到資料後,不免俗的要想辦法把這些重要敏感資料(帳號密碼這類)傳回自己的Server

防毒軟體可以根據各種特徵碼來判斷這是否是由惡意軟體發出的請求,駭客要確保在傳送的同時避免被防毒軟體給抓到

這時候把Traffic加密,或者是藏在不顯眼的HTTP Header(像是Cookie)算是一個相當好的做法


在動態分析中,如果想搞清楚網路部分的話,可以在x64dbg中設下幾個breakpoints

InternetOpen()     - Init connection to internet
GetComputerName()  - Using in User-Agent Header
InternetOpenURL()  
InternetReadFile() - Download content from URL
InternetConnect()
HttpOpenRequest()
HttpSendRequest()

XOR

相當常見的方式,嚴格來說XOR算是加密,因為有密碼

下圖是我用 cyberchef 所呈現的XOR加密,Key為0x8d

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

破解方式也不難,如果幸運的話Key長度為1 byte,可以輕鬆暴力破解

或是觀看密文之中有沒有重複性很高的byte,因為傳送的資料常常會有很多 0x00 bytes

一但被XOR就會出現很多重複的bytes也就會很明顯了

Base64

也是很常見的Enocde方法,特徵是:看到的資料都會是落在 [A-Za-z0-9+/=] 的範圍內

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

辨認方式:常常會看到一大串文字最後為 ===


RC4

對於RC4,通常會在靜態分析的時候特別注意

我們先嘗試用python寫一個RC4

可以發現RC4加密演算法需要生成一個長度為256的byte array

這是在逆向工程時也可以看到的特徵

class RC4: def __init__(self, key): self.state = list(range(256)) j = 0 for i in range(256): j = (j + self.state[i] + key[i % len(key)]) % 256 self.state[i], self.state[j] = self.state[j], self.state[i] self.i = self.j = 0 def crypt(self, data): out = bytearray() for byte in data: self.i = (self.i + 1) % 256 self.j = (self.j + self.state[self.i]) % 256 self.state[self.i], self.state[self.j] = self.state[self.j], self.state[self.i] out.append(byte ^ self.state[(self.state[self.i] + self.state[self.j]) % 256]) return out if __name__ == "__main__": key = bytearray('secret', 'utf-8') data = bytearray('Hello!', 'utf-8') rc4 = RC4(key) # Encrypt encrypted_data = rc4.crypt(data) print(encrypted_data.hex()) rc4 = RC4(key) # reset the state for decryption # Decrypt decrypted_data = rc4.crypt(encrypted_data) print(decrypted_data.decode('utf-8'))

如果不是透過Windows API來加密,由於RC4特有的加密方式,往往可以從相對應的組合語言中看出端倪

inc ecx
...
cmp ecx, 100h 

是一個重要的指標,往往可以幫助判斷屬於RC4加密的一環

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

圖片參考自 zero2auto


Windows API

由於Windows API提供強大的功能,BCryptCrypt提供各式各樣加解密的方式,所以利用API來加密是相當常見的

要找出加密前的資料難度也相對容易,只需要知道調用的API

更多BCrypt詳細用法可以參考 大神的書,講得非常詳細

BCryptOpenAlgorithmProvider()     BCryptGetProperty()
BCryptGenerateSymmetricKey()      BCryptGenerateKeyPair()
BCryptFinalizeKeyPair()           BCryptEncrypt()
BCryptDecrypt()                   BCryptDestroyKey()
BCryptCloseAlgorithmProvider()    BCryptImportKey()
BCryptExportKey()

其中最感興趣的是

BCryptOpenAlgorithmProvider()  - 可以知道是用哪種加密演算法
BCryptEncrypt()                - 等待被加密的明文

如果確定有利用BCrypt加密某些資訊的話,設定breakpoint直接執行就可以了

當執行到BCryptEncrypt,根據 MSDN 的定義

NTSTATUS BCryptEncrypt(
  BCRYPT_KEY_HANDLE hKey,
  PUCHAR            pbInput,      ; address to plaintext
  ULONG             cbInput,      ; 待加密明文的長度
  VOID              *pPaddingInfo,
  PUCHAR            pbIV,
  ULONG             cbIV,
  PUCHAR            pbOutput,
  ULONG             cbOutput,
  ULONG             *pcbResult,
  ULONG             dwFlags
);

pbInput

The address of a buffer that contains the plaintext to be encrypted. The cbInput parameter contains the size of the plaintext to encrypt. For more information, see Remarks.

cbInput

The number of bytes in the pbInput buffer to encrypt.


下面是一個小程式,使用WinAPI中Bcrypt相關函數

可以看到,程式依序用了不少上述提到的API

在做逆向工程的時候可以特別注意

#include <windows.h> #include <bcrypt.h> #include <iostream> #pragma comment(lib, "bcrypt.lib") int main() { BCRYPT_ALG_HANDLE hAesAlg = nullptr; BCRYPT_KEY_HANDLE hKey = nullptr; DWORD cbCipherText = 0, cbData = 0, cbKeyObject = 0; PBYTE pbCipherText = nullptr, pbKeyObject = nullptr; BYTE rgbIV[16] = { 0 }; BYTE rgbPlaintext[16] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; BYTE rgbKey[16] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&hAesAlg, BCRYPT_AES_ALGORITHM, nullptr, 0))) { std::cout << "BCryptOpenAlgorithmProvider failed" << std::endl; return -1; } if (!BCRYPT_SUCCESS(BCryptGetProperty(hAesAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&cbKeyObject, sizeof(DWORD), &cbData, 0))) { std::cout << "BCryptGetProperty failed" << std::endl; return -1; } pbKeyObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbKeyObject); if (pbKeyObject == nullptr) { std::cout << "Memory allocation failed" << std::endl; return -1; } if (!BCRYPT_SUCCESS(BCryptSetProperty(hAesAlg, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0))) { std::cout << "BCryptSetProperty failed" << std::endl; return -1; } if (!BCRYPT_SUCCESS(BCryptGenerateSymmetricKey(hAesAlg, &hKey, pbKeyObject, cbKeyObject, rgbKey, sizeof(rgbKey), 0))) { std::cout << "BCryptGenerateSymmetricKey failed" << std::endl; return -1; } if (!BCRYPT_SUCCESS(BCryptEncrypt(hKey, rgbPlaintext, sizeof(rgbPlaintext), nullptr, rgbIV, sizeof(rgbIV), nullptr, 0, &cbCipherText, BCRYPT_BLOCK_PADDING))) { std::cout << "BCryptEncrypt failed" << std::endl;

當然還有許多其他的方式,像是透過組合語言的 rol/ror,或是客製化自己的byte encrypter

但這邊就不多討論了

下一篇要來介紹一些基本的反逆向/分析手法以及如何對抗他們


-0xbc

tags: Malware Analysis Reverse Engineering tutorials