Challenge 09

9 - crackinstaller

What kind of crackme doesn't even ask for the password? We need to work on our COMmunication skills.

Ở bài này, chúng ta lại có 1 file .exe. Ta mở file lên trong IDA, nhảy thẳng tới hàm main, hàm này khá đơn giản, chỉ làm nhiệm vụ drop 1 file dll ra “C:\Users\YOUR_NAME\AppData\Local\Microsoft\Credentials\credHelper.dll”, sau đó load dll này và gọi hàm DllRegisterServer.

Vậy là file này khá đơn giản, giờ ta chuyển sang phân tích hàm DllRegisterServer của credHelper.dll

HRESULT __stdcall DllRegisterServer()
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  mb_memset((__int64)Filename, 0, 0x200ui64);
  mb_memset((__int64)sz, 0, 0x102ui64);
  mb_memset((__int64)&v16, 0, 0x1F2ui64);
  v8 = 0x720065;
  *(_OWORD *)credHelperUString = xmmword_180017790;// = UNICODE "CredHelper"
  v9 = 0;
  v11 = 't';
  *(_OWORD *)apartmentUString = xmmword_1800177A8;// = UNICODE "Apartment"
  v18 = 0;
  GetModuleFileNameW(hModule, (LPWSTR)Filename, 0xFFu);
  v0 = -1i64;
  do
    ++v0;
  while ( Filename[v0] );                       // v0 = lstrlenW(FileName)
  v1 = 2 * v0 + 2;
  StringFromGUID2(&rguid, (LPOLESTR)sz, 129);
  v2 = &sz[135];
  v14 = '\\\0D';
  v15 = 0;
  *(_QWORD *)SubKey = 20548029787144259i64;     // UNICODE: 'CLSID\'
  do
    ++v2;
  while ( *v2 );                                // v2 = endof(SubKey)
  v3 = 0i64;
  do
  {
    v4 = sz[v3];
    v2[v3++] = v4;                              // SubKey = SubKey + sz
  }
  while ( v4 );
  v5 = RegCreateKeyExW(HKEY_CLASSES_ROOT, SubKey, 0, 0i64, 0, KEY_ALL_ACCESS, 0i64, &hKey, 0i64);
  if ( v5
    || (v5 = RegSetValueExW(hKey, 0i64, 0, REG_SZ, credHelperUString, 22u)) != 0
    || (v5 = RegCreateKeyExW(hKey, L"InProcServer32", 0, 0i64, 0, KEY_ALL_ACCESS, 0i64, &hSubKeyInProcServer32, 0i64)) != 0
    || (v5 = RegCreateKeyExW(hKey, L"Config", 0, 0i64, 0, KEY_ALL_ACCESS, 0i64, &hSubKeyConfig, 0i64)) != 0
    || (v5 = RegSetValueExW(hSubKeyInProcServer32, 0i64, 0, REG_SZ, (const BYTE *)Filename, v1)) != 0
    || (v5 = RegSetValueExW(hSubKeyInProcServer32, L"ThreadingModel", 0, REG_SZ, apartmentUString, 20u)) != 0 )
  {
    result = (unsigned __int16)v5 | 0x80070000;
    if ( v5 <= 0 )
    {
      result = v5;
    }
  }
  else
  {
    RegSetValueExW(hSubKeyConfig, L"Password", 0, REG_SZ, (const BYTE *)&v18, 2u);
    RegSetValueExW(hSubKeyConfig, L"Flag", 0, REG_SZ, (const BYTE *)&v18, 2u);
    result = 0;
  }
  return result;
}

Đoạn code trên thêm vào registry 2 key sau:

  • HKEY_CLASSES_ROOT\CLSID{CEEACC6E-CCB2-4C4F-BCF6-D2176037A9A7}\InProcServer32
  • HKEY_CLASSES_ROOT\CLSID{CEEACC6E-CCB2-4C4F-BCF6-D2176037A9A7}\Config

Ở đây ta thấy 1 entry có name là “Flag”, có thể đó chính là flag cần tìm.

Ta thử vào string windows trong IDA, dùng xref lên chuỗi “Flag” để xem có hàm nào sử dụng chuỗi này nữa không, thì thấy có hàm ở 0x1800016D8:

__int64 __fastcall sub_1800016D8(__int64 a1, unsigned __int8 *a2)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v2 = a2;
  v3 = -2147467259;
  mb_memset(&SubKey, 0i64, 512i64);
  mb_memset(&sz, 0i64, 258i64);
  v14 = 0i64;
  v15 = 0;
  v16 = 0;
  *(_OWORD *)Source = 0i64;
  v13 = 0i64;
  mb_memset(Data, 0i64, 180i64);
  v4 = *v2;
  v5 = 0i64;
  v6 = v2[1];
  do
  {
    v7 = v2[++v4 + 2];
    v6 += v7;
    v8 = v2[v6 + 2];
    v2[v4 + 2] = v8;
    v2[v6 + 2] = v7;
    Source[v5] = byte_18001A9F0[v5] ^ v2[(unsigned __int8)(v7 + v8) + 2];
    ++v5;
  } // <---------------------------- RC4 here
  while ( v5 < 44 );
  *v2 = v4;
  v2[1] = v6;
  v9 = mbstowcs(Data, Source, 0x2Dui64);
  v10 = v9;
  if ( v9 == -1 || v9 == 45 )
    return v3;
  StringFromGUID2(&rguid, &sz, 129);
  wsprintfW(&SubKey, L"%s\\%s\\%s", L"CLSID", &sz, L"Config");
  if ( RegOpenKeyExW(HKEY_CLASSES_ROOT, &SubKey, 0, 0x20006u, &hKey) )
    return v3;
  RegSetValueExW(hKey, L"Flag", 0, 1u, (const BYTE *)Data, 2 * v10);
  v3 = 0;
  return v3;
}

Ở đây, ta lại thêm 1 lần thấy RC4. Hàm này nhận vào 2 tham số, trong đó a2 là con trỏ tới RC4 state đã được khởi tạo từ trước.

Ở hàm này ta không thấy pattern for (i = 0; i < 256; ++i) a[i] = i , nên khả năng cao trong dll này có 1 hàm khác dùng để khởi tạo state RC4.

Và chính xác là như vậy, nếu ta dùng xref để xem hàm nào reference tới string “Password”, ta sẽ tới được hàm ở 0x18000153C.

__int64 __fastcall sub_18000153C(__int64 a1, _WORD *a2)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v2 = a2;
  mb_memset(&pvData, 0i64, 1040i64);
  mb_memset(&SubKey, 0i64, 512i64);
  mb_memset(&sz, 0i64, 258i64);
  StringFromGUID2(&rguid, &sz, 129);
  wsprintfW(&SubKey, L"%s\\%s\\%s", L"CLSID", &sz, L"Config");
  v3 = 0;
  if ( RegGetValueW(HKEY_CLASSES_ROOT, &SubKey, L"Password", 2u, 0i64, &pvData, &pcbData)
    || pcbData <= 2
    || (v4 = sub_180005A2C(v20, &pvData, 260i64), v4 == 260)
    || v4 == -1 )
  {
    v3 = 0x80004005;
  }
  else
  {
    v5 = (__int64)(v2 + 1);
    *v2 = 0;
    v6 = v2 + 1;
    LOBYTE(v7) = 0;
    v8 = 0;
    v9 = 0;
    v10 = 256i64;
    do
      *v6++ = v9++;
    while ( v9 < 256 );
    v11 = v4;
    v12 = 0i64;
    v13 = (char *)v5;
    do
    {
      v14 = *v13;
      v15 = v12 + 1;
      v16 = v20[v12];
      v12 = 0i64;
      v7 = (unsigned __int8)(v7 + *v13 + v16);
      *v13++ = *(_BYTE *)(v7 + v5);
      *(_BYTE *)(v7 + v5) = v14;
      v17 = v8 + 1;
      v8 = 0;
      if ( v15 < v11 )
        v8 = v17;
      if ( v15 < v11 )
        v12 = v15;
      --v10;
    }
    while ( v10 );
  }
  return v3;
}

Hàm ở trên sẽ lấy data trong registry key “Password”, biến đổi nó trước khi dùng để làm RC4 key. Ở trên ta có thể thấy pattern "do *v6++ = v9++; while (v9 < 256);".

Vậy là ta đã biết cách để lấy được Flag, nhưng … không có Password.

Mình đã ngồi phân tích file credHelper.dll rất lâu nhưng không thấy chỗ nào tạo Password, đến đây mình quay lại xem file crackinstaller.exe để xem lại thì phát hiện ra mình đã bỏ sót các hàm Constructor.

Constructor là các hàm được gọi trước cả hàm main. Ví dụ, cho đoạn code sau:

#include <stdio.h>
class Test {
public:
  Test() {
      printf("Hello world from test\n");
  }  
};
Test test;
int main() {
    printf("Hello world\n");
}

Và output của chương trình trên là:

Hello world from test
Hello world

Đó là một trong những trường hợp mà tồn tại hàm được gọi trước hàm main.

.rdata:000000014000F2B8 dq offset ?pre_cpp_initialization@@YAXXZ ; pre_cpp_initialization(void)
.rdata:000000014000F2C0 dq offset sub_140001000

Ta có thể thấy ở phần section .rdata có chứa địa chỉ các hàm Constructor, ta bắt đầu phân tích hàm sub_140001000.

.text:0000000140001000 sub_140001000   proc near               ; DATA XREF: .rdata:000000014000F2C0↓o
.text:0000000140001000                 jmp     sub_140002530
.text:0000000140001000 sub_140001000   endp

Hàm này lại gọi hàm sub_140002530, ta tiếp tục follow hàm này:

__int64 sub_140002530()
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v0 = -1i64;
  v1 = 0;
  hObjectRecv = (HANDLE)-1i64;
  pedata2 = 0i64;
  if ( (unsigned int)resolveDIAT_140001CD8() ) // <--- resolve IAT dynamically
  {
    pedata1 = decrypt_data(&data, 8069ui64, 0x2950ui64);
    if ( pedata1 )
    {
      pedata2 = (IMAGE_DOS_HEADER *)decrypt_data(&byte_140034080, 8882ui64, 0x5800ui64);
      if ( pedata2 )
      {
        v4 = dec_unicode_string((__int64)&unk_140019988, 0x1Cu, 0);// C:\Windows\System32\cfs.dll
        v1 = writeFileByMapping_140002ED8(v4, (__int64)pedata1);// cfs.dll
        if ( v1 )
        {
          pCfsString = *(_QWORD *)dec_unicode_string((__int64)&unk_140019900, 4u, 0);// cfs
          v5 = dec_unicode_string((__int64)&unk_140019988, 0x1Cu, 0);// C:\Windows\System32\cfs.dll
          *(_OWORD *)pFileName = *(_OWORD *)v5;
          *(_OWORD *)&pFileName[8] = *((_OWORD *)v5 + 1);
          *(_OWORD *)&pFileName[16] = *((_OWORD *)v5 + 2);
          *(_QWORD *)&pFileName[24] = *((_QWORD *)v5 + 6);
          v6 = dec_unicode_string((__int64)&unk_1400199C0, 0xFu, 0);// \\.\Htsysm72FB
          *(_OWORD *)pPathSmth = *(_OWORD *)v6;
          *(_QWORD *)&pPathSmth[8] = *((_QWORD *)v6 + 2);
          *(_DWORD *)&pPathSmth[12] = *((_DWORD *)v6 + 6);
          v14 = v6[14];
          v7 = service_140001FB4((__int64)&pCfsString, (__int64)pFileName, (__int64)pPathSmth, (__int64 *)&hObjectRecv);// setup service
          v0 = (__int64)hObjectRecv;
          v1 = v7;
          if ( v7 )
          {
            LODWORD(pCfsString) = 0;
            *(_QWORD *)&pFileName[8] = 0i64;
            *(_OWORD *)pFileName = 0i64;
            *(_DWORD *)&pFileName[12] = 0;
            *(_QWORD *)pPathSmth = 0i64;
            *(_DWORD *)&pPathSmth[4] = 0;
            pPathSmth[6] = 0;
            LOBYTE(pPathSmth[7]) = 0;
            v1 = sendDeviceIO_140002C44(hObjectRecv, pedata2);
            if ( v1 )
            {
              v8 = dec_unicode_string((__int64)&unk_140019900, 4u, 0);// cfs
              v1 = (unsigned __int64)sub_140001EB4((__int64)v8) != 0;
            }
          }
        }
      }
    }
  }
	// ... truncated
}

Hàm này đầu tiên sẽ lấy địa chỉ các hàm windows API bằng LoadLibrary, GetProcAddress.

Tiếp theo, nó decrypt 1 file PE, rồi drop ra đường dẫn “C:\Windows\System32\cfs.dll” (file này thật ra là file driver).

Sau đó, nó load driver mới drop ra trong hàm ở 0x140001FB4, đồng thời gọi CreateFile để tạo 1 device object.

__int64 __fastcall service_140001FB4(__int64 a1, __int64 a2, __int64 pPathSth, __int64 *phObject)
{
     // truncated ...
    // a1 = &L"\\.\Htsysm72FB";
  v10 = pOpenSCManagerW(0i64, 0i64, 0xF003Fi64);
  v11 = v10;
  if ( v10 )
  {
    v12 = pOpenServiceW(v10, pServiceName, 0xF01FFi64);
    v13 = v12;
    if ( v12 )
    {
      pDeleteService(v12);
      pCloseServiceHandle(v13);
    }
    v14 = pCreateServiceW(v11, pServiceName, pServiceName, 0xF01FFi64, 1, 3, 1, pFileName, 0i64, 0i64, 0i64, 0i64, 0i64);
    if ( v14 )
    {
      pCloseServiceHandle(v14);
    }
    v15 = pOpenServiceW(v11, pServiceName, 0xF01FFi64);
    v16 = v15;
    if ( v15 )
    {
      if ( !(unsigned int)pStartServiceW(v15, 0i64, 0i64) )
      {
        GetLastError();
      }
      pCloseServiceHandle(v16);
    }
    if ( _phObject )
    {
      v17 = pCreateFileW(_pPathSmt, 0xC0000000i64, 0i64, 0i64, 3, 128, 0i64);
      *_phObject = v17;
      LOBYTE(v4) = v17 != -1;
    }
    pCloseServiceHandle(v11);
  }
  return v4;
}

Cuối cùng nó decrypt 1 file PE thứ 2, copy một vài “hằng số” vào buffer tạo bởi VirtualAlloc rồi gửi control code 0xAA013044 đến driver vừa load.

if ( v23 )
{
  unk_14003634B = v23;
  unk_14003633B = pDos; // <---- pointer to second PE file
  unk_140036345 = 0x5800;
  v24 = (char *)VirtualAlloc(0i64, 45ui64, 0x3000u, 0x40u);
  if ( v24 )
  {
    *(_QWORD *)v24 = v24 + 8;
    *(_OWORD *)(v24 + 8) = unk_140036338;
    *(_OWORD *)(v24 + 0x18) = unk_140036348;
    *((_DWORD *)v24 + 0xA) = unk_140036358;
    v24[44] = unk_14003635C;
    InBuffer = v24 + 8;
    if ( DeviceIoControl(v4, 0xAA013044, &InBuffer, 8u, &OutBuffer, 4u, &BytesReturned, 0i64) )
    {
      v5 = 1;
    }
  }
    // ... truncated
}

Giờ ta phân tích file “cfs.dll”, ta dùng PE-Bear để xem tổng quát về file này:

Thử tìm thông tin về hash MD5 của file này:

Ta tìm được thông tin đây là file driver đã bị exploit từ năm 2016.

Theo như trong video này thì các “hằng số” ở trên kia chính là kernel shellcode. Bây giờ ta sẽ setup (kernel) debug để xem đoạn shellcode trên làm gì.

Cách setup window kernel debugging.

Đầu tiên ta dùng lệnh lm để liệt kê các module được load:

start             end                 module name
00000000`77640000 00000000`7773a000   USER32     (deferred)             
00000000`77740000 00000000`7785f000   kernel32   (deferred)             
00000000`77860000 00000000`77a09000   ntdll      (export symbols)       C:\Windows\SYSTEM32\ntdll.dll
00000001`3fd30000 00000001`3fd6d000   image00000001_3fd30000   (deferred)             
000007fe`fd860000 000007fe`fd8cb000   KERNELBASE   (deferred)             
000007fe`fdb80000 000007fe`fdbe7000   GDI32      (deferred)             
...

Như trên thì crackinstaller được load tại 0x00000013fd30000, ta vào IDA lấy offset:

.text:0000000140002E1A    call    cs:DeviceIoControl

Trong lúc viết bài này, mình có tắt windbg và bật lại nhiều lần nên Imagebase có thể thay đổi, tuy nhiên offset thì không !

Imagebase là 0x140000000 nên offset sẽ là 0x2E1A. Ta gõ “bp 000000013fd30000+0x2E1A” để đặt breakpoint ngay tại đây, sau đó dùng lệnh g để tiếp tục chương trình, lúc này @rip đang ở ngay tại 0x00000013fd32e1a. Ta gõ “u poi(@r8)” để disassemble đoạn shellcode này.

0:000> u poi(@r8)
00000000`00070008 fb              sti
00000000`00070009 48ba20920c0000000000 mov rdx,0C9220h
00000000`00070013 41b800580000    mov     r8d,5800h
00000000`00070019 41b970310000    mov     r9d,3170h
00000000`0007001f ff2500000000    jmp     qword ptr [00000000`00070025]
00000000`00070025 102a            adc     byte ptr [rdx],ch
00000000`00070027 d33f            sar     dword ptr [rdi],cl
00000000`00070029 0100            add     dword ptr [rax],eax

Ngoài ra, ở dòng code thứ 2 (0x70009) ta còn thấy được con trỏ tới file PE thứ 2, và size của nó là 0x5800.

0:000> db 0C9220
00000000`000c9220  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
00000000`000c9230  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......
00000000`000c9240  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000000`000c9250  00 00 00 00 00 00 00 00-00 00 00 00 e0 00 00 00  ................
00000000`000c9260  0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68  ........!..L.!Th
00000000`000c9270  69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f  is program canno
00000000`000c9280  74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20  t be run in DOS 
00000000`000c9290  6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00  mode....$.......

Ta dump file này ra bằng lệnh: “.writemem C:/path/to/file.bin 0C9220 L5800”.

Tiếp theo ta sẽ breakpoint ngay tại chỗ này trong cfs.dll:

__int64 __fastcall sub_10524(void (__fastcall *a1)(_QWORD))
{
  __int64 v2; // [rsp+20h] [rbp-28h]
  void (__fastcall *v3)(PVOID (__stdcall *)(PUNICODE_STRING)); // [rsp+28h] [rbp-20h]
  PVOID (__stdcall *v4)(PUNICODE_STRING); // [rsp+30h] [rbp-18h]

  if ( *((void (__fastcall **)(_QWORD))a1 - 1) != a1 )
    return 0i64;
  v3 = (void (__fastcall *)(PVOID (__stdcall *)(PUNICODE_STRING)))a1;
  v4 = MmGetSystemRoutineAddress;
  v2 = 0i64;
  enable((unsigned __int64 *)&v2);
  v3(v4);                                       // <--- breakpoint here, offset 0x573
  disable((unsigned __int64 *)&v2);
  return 1i64;
}

Ta gõ “.breakin” để switch từ user-mode debugging vào kernel-mode debugging, rồi gõ “bp cfs+573”, sau đó ở crackinstaller ta gõ lệnh “g” thì ta sẽ dừng laị tại cfs+573.

kd> g
0:000> g
Breakpoint 2 hit
cfs+0x573:
fffff880`06530573 ff542428        call    qword ptr [rsp+28h]
kd> u poi(@rsp+28)
00000000`00070008 fb              sti
00000000`00070009 48ba2092300000000000 mov rdx,309220h
00000000`00070013 41b800580000    mov     r8d,5800h
00000000`00070019 41b970310000    mov     r9d,3170h
00000000`0007001f ff2500000000    jmp     qword ptr [00000000`00070025]

Ta step in để vào hàm ở [0x70025].

kd> u @rip
00000001`3ffe2a10 44894c2420      mov     dword ptr [rsp+20h],r9d
00000001`3ffe2a15 4489442418      mov     dword ptr [rsp+18h],r8d
00000001`3ffe2a1a 4889542410      mov     qword ptr [rsp+10h],rdx
00000001`3ffe2a1f 53              push    rbx
00000001`3ffe2a20 55              push    rbp
00000001`3ffe2a21 56              push    rsi
00000001`3ffe2a22 57              push    rdi
00000001`3ffe2a23 4154            push    r12

Hàm này chính là hàm sub_140002A10 của crackinstaller.

__int64 __fastcall shellcode_140002A10(__int64 a1, const void *a2, unsigned int a3, unsigned int a4)
{
    //  truncated ...
  v29 = a4;                                     // 0x3170
  v28 = a3;                                     // 0x5800
  v27 = a2;                                     // DOS
  v4 = a3;
  v5 = 0xC0000001;
  g_fnMmGetSystemRoutineAddress = (__int64 (__fastcall *)(_QWORD))a1;
  get_g_imagebase_by_hash_140002768();
  v22 = 0i64;
  v23 = 0i64;
  _mm_storeu_si128((__m128i *)&v25, (__m128i)0i64);
  v21 = 48;
  v24 = 512;
  ExAllocatePoolWithTag = (__int64 (__fastcall *)(_QWORD, __int64, __int64))get_function_by_hash(0x490A231A); 
  if ( ExAllocatePoolWithTag )
  {
    ExFreePoolWithTag = (void (__fastcall *)(char *, _QWORD))get_function_by_hash(0x34262863); 
    if ( ExFreePoolWithTag )
    {
      IoCreateDriver = get_function_by_hash(0x1128974);
      if ( IoCreateDriver )
      {
        RtlImageNtHeader = get_function_by_hash(0xE2A9259B); 
        if ( RtlImageNtHeader )
        {
          RtlImageDirectoryEntryToData = get_function_by_hash(0xCF424038);
          if ( RtlImageDirectoryEntryToData )
          {
            RtlQueryModuleInformation = get_function_by_hash(0xCE968D51);
            if ( RtlQueryModuleInformation )
            {
              PsCreateSystemThread = (__int64 (__fastcall *)(__int64 *, __int64, int *, _QWORD, _QWORD, char *, __int64))get_function_by_hash(0xB40D00D9);// 
              if ( PsCreateSystemThread )
              {
                ZwClose = (void (__fastcall *)(__int64))get_function_by_hash(0xA95BE347);
                if ( ZwClose )
                {
                  v11 = v4;
                  v12 = (char *)ExAllocatePoolWithTag(0i64, v4, 0x52414C46i64);
                  if ( v12 )
                  {
                    v13 = ExAllocatePoolWithTag(0i64, 68i64, 0x52414C46i64);
                    v14 = v13;
                    if ( v13 )
                    {
                      *(_QWORD *)v13 = ExAllocatePoolWithTag;
                      *(_DWORD *)(v13 + 56) = v28;
                      *(_QWORD *)(v13 + 8) = ExFreePoolWithTag;
                      *(_QWORD *)(v13 + 40) = IoCreateDriver;
                      *(_QWORD *)(v13 + 48) = v12;
                      *(_QWORD *)(v13 + 24) = RtlImageDirectoryEntryToData;
                      *(_QWORD *)(v13 + 32) = RtlQueryModuleInformation;
                      v16 = v12;
                      *(_QWORD *)(v14 + 16) = RtlImageNtHeader;
                      qmemcpy(v12, v27, 8 * (v11 >> 3));
                      v17 = (unsigned __int64)&v12[v28 - 8];
                      if ( (unsigned __int64)v12 < v17 )
                      {
                        do
                        {
                          if ( *(_QWORD *)v16 == 0xDC16F3C3B57323i64 )
                          {
                            strcpy(v16, "BBACABA");
                          }
                          ++v16;
                        }
                        while ( (unsigned __int64)v16 < v17 );
                      }
                      v5 = PsCreateSystemThread(&threadHandle, 0x10000000i64, &v21, 0i64, 0i64, &v12[v29], v14);// call driverBoostrap
                      if ( (v5 & 0x80000000) == 0 )
                      {
                        ZwClose(threadHandle);
                        return v5;
                      }
                    }
   // truncated ....
}

Hàm sub_140002964 thực hiện việc tìm địa chỉ của function bằng hash, không cần RE hàm này, chỉ cần quan sát qua debugger, ta có bảng sau:

Hash Function
0x490A231A ExAllocatePoolWithTag
0x34262863 ExFreePoolWithTag
0x1128974 IoCreateDriver
0xE2A9259B RtlImageNtHeader
0xCF424038 RtlImageDirectoryEntryToData
0xCE968D51 RtlQueryModuleInformation
0xB40D00D9 PsCreateSystemThread
0xA95BE347 ZwClose

Tiếp theo, ta đặt breakpoint ngay tại chỗ v5 = PsCreateSystemThread(...) và để chương trình chạy tới đó.

kd> bp 1`3ffe2c26
kd> g
Breakpoint 3 hit
00000001`3ffe2c26 ff542448        call    qword ptr [rsp+48h]

Ta có hàm PsCreateSystemThread được định nghĩa như sau:

NTSTATUS PsCreateSystemThread(
  PHANDLE            ThreadHandle,
  ULONG              DesiredAccess,
  POBJECT_ATTRIBUTES ObjectAttributes,
  HANDLE             ProcessHandle,
  PCLIENT_ID         ClientId,
  PKSTART_ROUTINE    StartRoutine,
  PVOID              StartContext
);

Trong đó StartRoutine là con trỏ tới hàm cần tạo thread. Nó là tham số thứ 6 trong hàm, ta dùng lệnh “u poi(@rsp+28)” để disassemble hàm này:

kd> u poi(@rsp+28)
fffffa80`04472170 48894c2408      mov     qword ptr [rsp+8],rcx
fffffa80`04472175 56              push    rsi
fffffa80`04472176 57              push    rdi
fffffa80`04472177 4881ec88000000  sub     rsp,88h
fffffa80`0447217e 488d442448      lea     rax,[rsp+48h]
fffffa80`04472183 488bf8          mov     rdi,rax
fffffa80`04472186 33c0            xor     eax,eax
fffffa80`04472188 b930000000      mov     ecx,30h

Ta đặt breakpoint ngay tại hàm này và bấm “g” để dừng lại ngay tại hàm này.

kd> bp fffffa80`04472170
kd> g
Breakpoint 4 hit
fffffa80`04472170 48894c2408      mov     qword ptr [rsp+8],rcx

Ta đang dừng lại ngay hàm DriverBootstrap của file PE thứ 2.

__int64 __fastcall DriverBootstrap(PARAM *a1)
{
 	// truncated ...
              if ( v5 )
              {
                v7 = (IMAGE_SECTION_HEADER *)((char *)&v6->OptionalHeader + v6->FileHeader.SizeOfOptionalHeader);
                qmemcpy(v5, v15->pAllocateMemory_0x5800, 8 * (v6->OptionalHeader.SizeOfHeaders / 8ui64));
                for ( i = 0; i < v6->FileHeader.NumberOfSections; ++i )
                {
                  qmemcpy(
                    (char *)v5 + v7[i].VirtualAddress,
                    (char *)v15->pAllocateMemory_0x5800 + v7[i].PointerToRawData,
                    8 * (v7[i].SizeOfRawData / 8ui64));
                }
                if ( (int)mb_fixup_140003B80((__int64)&pExAllocatePoolWithTag, (__int64)v5) >= 0
                  && (int)mb_find_import_140003FE0((tbl *)&pExAllocatePoolWithTag, (__int64)v5, v1) >= 0 )
                {
                  v14 = (IMAGE_DOS_HEADER *)((char *)v5 + v6->OptionalHeader.AddressOfEntryPoint);
                  if ( v14 )
                  {
                    v4 = pIoCreateDriver(0i64, v14);
                  }
                  else
                  {
                    v4 = 0xC0000001;
                  }
                }
 // truncated ...
}

Hàm này thực hiện 1 việc rất giống Reflective DLL load, và đúng là như vậy, đoạn code trên được copy từ đây, reflective driver loader (trong repo này cũng sử dụng lại capcom.sys để làm ví dụ).

Ta thấy sau khi thực hiện load và fix bảng IAT, reloc table, thì nó gọi IOCreateDriver để gọi DriverEntry của file PE thứ 2.

NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
  struct _DRIVER_OBJECT *v2; // rdi

  v2 = DriverObject;
  _security_init_cookie();
  return sub_140009000(v2);
}
__int64 __fastcall sub_140009000(struct _DRIVER_OBJECT *a1)
{
  	// truncated ...
  if ( v2 >= 0 )
  {
	// truncated ...
    v2 = CmRegisterCallbackEx(sub_140004570, &v5, DriverObject, DriverObject, &Cookie, 0i64);
  }
 // truncated ...
}

Ở driver này, nó dùng hàm sub_140004570 để làm hàm callback, ta lại tiếp tục phân tích hàm này.

__int64 __fastcall sub_140004570(__int64 a1, __int64 a2, unsigned __int16 **a3)
{
  // truncated ...
         if ( wcsstr(Str, v3) ) // {CEEACC6E-CCB2-4C4F-BCF6-D2176037A9A7}\Config
      {
        memset(&v26, 0, 0x70ui64);
        memset(&v25, 0, 0x20ui64);
        memset(&v27, 0, 0x88ui64);
        memset(&v19, 0, 8ui64);
        memset(v22, 0, sizeof(v22));
        sub_1400034F0(&v26); // sha256 init
        sub_140003AD0(&v26, &unk_14000608C, 7i64); // sha256 update
        sub_140003120(&v26, &v25); // sha256 final
        sub_140002760(&v27, &v25, 32i64, &v19); // init modified salsa20 
        sub_140002490(&v27, &unk_140006078, v22, (unsigned int)dword_140006088); // salsa decrypt
        Class.Length = 2 * dword_140006088;
        Class.MaximumLength = 2 * (dword_140006088 + 1);
        for ( i = 0; i < dword_140006088; ++i )
          Class.Buffer[i] = (unsigned __int8)v22[i];
        v24.Length = 48;
        v24.RootDirectory = 0i64;
        v24.Attributes = 512;
        v24.ObjectName = 0i64;
        v24.SecurityDescriptor = 0i64;
        v24.SecurityQualityOfService = 0i64;
        ObjectAttributes.Length = 48;
        ObjectAttributes.RootDirectory = 0i64;
        ObjectAttributes.Attributes = 576;
        ObjectAttributes.ObjectName = v12;
        ObjectAttributes.SecurityDescriptor = 0i64;
        ObjectAttributes.SecurityQualityOfService = 0i64;
        ZwCreateKey(&KeyHandle, 0xF003Fu, &ObjectAttributes, 0, &Class, 0, (PULONG)v8[8]);
        ObReferenceObjectByHandle(KeyHandle, *((_DWORD *)v8 + 14), (POBJECT_TYPE)v8[2], 0, &Object, 0i64);
        ZwClose(KeyHandle);
 // truncated ...
}

Driver này sẽ theo dõi những thay đổi trong Registry, nếu trong path của key có chứa “{CEEACC6E-CCB2-4C4F-BCF6-D2176037A9A7}\Config” thì đoạn code trên sẽ được thực hiện.

Ta sẽ tìm cách đặt breakpoint tại hàm này. (Hiện tại ta vẫn đang ở đầu của hàm DriverBootstrap).

Ta dùng lệnh “bp nt!CmRegisterCallbackEx” , sau đó bấm “g”, ta sẽ dừng ngay đầu hàm này.

Tiếp theo ta gõ “bp @rcx”, đây chính là địa chỉ hàm callback.

kd> bp nt!CmRegisterCallbackEx
kd> g
Breakpoint 5 hit
nt!CmRegisterCallbackEx:
fffff800`02ad0d30 4883ec38        sub     rsp,38h
kd> u @rcx
fffffa80`045e5570 4c89442418      mov     qword ptr [rsp+18h],r8
fffffa80`045e5575 4889542410      mov     qword ptr [rsp+10h],rdx
fffffa80`045e557a 48894c2408      mov     qword ptr [rsp+8],rcx
fffffa80`045e557f 56              push    rsi
fffffa80`045e5580 57              push    rdi
fffffa80`045e5581 4881ecf8030000  sub     rsp,3F8h
fffffa80`045e5588 488b842420040000 mov     rax,qword ptr [rsp+420h]
fffffa80`045e5590 4889442458      mov     qword ptr [rsp+58h],rax
kd> bp @rcx

Ta bấm “g” để chạy và dừng ngay tại hàm callback.

Giờ ta chỉ quan tâm tới lời gọi “ZwCreateKey” ở trong đoạn if nên: “bp @rip+0x56d, sau đó bấm “g”.

0:000> g
ModLoad: 000007fe`fb640000 000007fe`fb660000   C:\Users\admin\AppData\Local\Microsoft\Credentials\credHelper.dll
Breakpoint 2 hit
fffffa80`045acadd ff15c5050000    call    qword ptr [fffffa80`045ad0a8]

Tham số thứ 5 của hàm ZwCreate là những gì mà ta quan tâm, nên:

kd> dS poi(@rsp+20)
fffff880`06fe2180  "H@n $h0t FiRst!"

Yay, đây cũng chính là Password, giờ ta chỉ việc viết 1 chương trình nhỏ để gọi hàm decrypt flag trong credHelper.dll là xong. Ở đây mình không xài các hàm COM mà dùng thẳng vtable như trong C++ luôn.

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

struct ObjectVTable
{
    void(__fastcall *dontknow)(PVOID pThis);
    void(__fastcall *incRefCount)(PVOID pThis);
    void(__fastcall *decRefCount)(PVOID pThis);
    ULONGLONG(__fastcall *rc4Init)(PVOID pThis, BYTE* pArray);
    ULONGLONG(__fastcall *decryptFlag)(PVOID pThis, BYTE* pArray);
};

struct Object
{
    struct ObjectVTable* vtbl;
    ULONGLONG refCount;
    char pad[0x1000];
};

BOOL setPassword(BYTE* password)
{
    BOOL bRet = TRUE;
    HKEY hSubKey;
    if (RegOpenKeyW(HKEY_CLASSES_ROOT, L"CLSID\\{CEEACC6E-CCB2-4C4F-BCF6-D2176037A9A7}\\Config", &hSubKey) != ERROR_SUCCESS)
    {
        bRet = FALSE;
    }
    else if (RegSetValueExA(hSubKey, "Password", 0, REG_SZ, (const BYTE*)password, (lstrlenA(password) + 1) * sizeof(char)) != ERROR_SUCCESS)
    {
        bRet = FALSE;
    }
    if (bRet)
    {   
        RegCloseKey(hSubKey);
    }
    return bRet;
}

BOOL printFlag()
{
    wchar_t flag[0x100];
    int size = sizeof(flag);
    memset(flag, 0, size);
    BOOL bRet = TRUE;
    if (RegGetValueW(HKEY_CLASSES_ROOT, L"CLSID\\{CEEACC6E-CCB2-4C4F-BCF6-D2176037A9A7}\\Config", L"Flag", RRF_RT_REG_SZ, 0, flag, &size)  != ERROR_SUCCESS)
    {
        bRet = FALSE;
    }
    if (bRet)
    {
        wprintf(L"-> %s\n", flag);
    }
    return bRet;
}

void readFlag(BYTE* password)
{
    HMODULE hModule = LoadLibraryW(L"credHelper.dll");
    ULONG_PTR pBase = (ULONG_PTR)hModule;
    if (setPassword(password) == FALSE)
    {
        wprintf(L"[+] Error setPassword, (%d)\n", (BYTE)password[0]);
        ExitProcess(1);
    }
    //system("pause");
    if (pBase == (ULONG_PTR)NULL)
    {
        wprintf(L"[+] Error LoadLibrary, (%d)\n", (BYTE)password[0]);
        ExitProcess(1);
    }
    BYTE arr[0x1000];
    struct Object* object = (struct Object*)malloc(sizeof(struct Object));
    object->vtbl = (struct ObjectVTable*)malloc(sizeof(struct ObjectVTable));

    memcpy(object->vtbl, (PVOID)(0x17908ULL + pBase), sizeof(struct ObjectVTable)); // copy the vtable

    object->vtbl->incRefCount(object);

    wprintf(L"[+] Ret: 0x%llx\n", object->vtbl->rc4Init(object, arr));
    wprintf(L"[+] Ret: 0x%llx\n", object->vtbl->decryptFlag(object, arr));


    printFlag();

    object->vtbl->decRefCount(object);

    free(object->vtbl);
    free(object);
    if (FreeLibrary(hModule) == FALSE)
    {
        wprintf(L"[+] Free error\n");
    }
}

int wmain(int argc, wchar_t* argv[])
{
    BYTE password[] = { 0x48, 0x40, 0x6e, 0x20, 0x24, 0x68, 0x30, 0x74, 0x20, 0x46 ,0x69, 0x52 ,0x73 ,0x74 ,0x21, 0x00 };
    readFlag(password);
    return 0;
}

Run:

C:\Users\admin\Desktop>9.exe
[+] Ret: 0x0
[+] Ret: 0x0
-> S0_m@ny_cl@sse$_in_th3_Reg1stry@flare-on.com
S0_m@ny_cl@sse$_in_th3_Reg1stry@flare-on.com

[+] Source code dùng để giải cho tất cả các bài nằm ở đây