Lynn's Blog Security Project

2017 Flare-On Challenge 12 missing (APT Attack Analysis)



missing 的情境式是 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.exesub_410DE8 函數是 “Process Hollowing”。Process Hollowing 是一種惡意程式傀儡的技術。其原理是透過 CreateProcess 建立一個正常的 process 當作目標程式,且 CreationFlag 設為 CREATE_SUSPENDED,在 coolprogram.exe 之中的目標程式是”預設瀏覽器”。Process Hollowing 關鍵是用 NtUnmapViewOfSection 來 unmap 預設瀏覽器的 sections 再把惡意程式 mapping 進記憶體中,再透過 SetContextThread 設定 Entry Point,最後利用 ResumeThread 來恢復瀏覽器運作,執行惡意行為。

檔案的位置可以透過 IDA 看出在 sub_405D7cmemcpy 的功能,其第二個參數 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 的格式取出,再進行分析,但這樣的做法會在後續資料處理時,無法呈現封包的來源是 serverclient,因此這次打算直接在 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.exesub_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.AddressOfEntryPoint0xABCDABCD 做 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.AddressOfEntryPoint0xABCDABCD 做 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 中接收 RecvSend 函數,sub_403210 用於 RecvDecodesub_403600 用於 SendEncode。其封包結構如下

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.exesrv2.exepse.exe 就是 Windows Sysinternals 的 PsExec,可以遠端執行指令的工具,透過 pse.exe 在內網橫向移動到 192.168.221.105 執行 srv2.exe,並監聽 Port 16452,其指令為 pse.exe \\larryjohnson-pc -i -c -f -d -u larry.johnson -p n3v3rgunnag1veUup -accepteula srv2.exe。而 srv2.exe 就是 C2 Server,跟 Client secondstage.exe,不同之處是 secondstage.exe 會主動連線,而 srv2.exe 是被動連線。
  • 在 larryjohnson-pc 中下載用於加密的程式 c:\staging\cf.exe
  • 利用加密程式 cf.exelab10 進行加密,之後將原始文件刪除,並且通過 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.exelab10 進行加密後會將原始文件刪除,並且通過 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
  • 下載 PsExecc:\staging\pse.exe

  • 下載 Server 版本 的惡意程式 srv2.exec:\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.exec:\staging\cf.exe
  • cf.exe 來加密 lab10.zip 變成 lab10.zip.cry,指令是 c:\staging\cf.exe lab10.zip tCqlc2+fFiLcuq1ee1eAPOMjxcdijh8z0jrakMA/jxg=

  • 刪除 lab10.zip 並關閉 shell