ABOH 2023: Grape

Watch where you step and what you touch.

Disclaimer

This writeup is written from the author's perspective to showcase the challenge design, and may or may not reflect the typical approach or methodology involved to solve the challenge.

Learning Lessons

This challenge serves to educate participants about Execution Guardrails and anti-tamper techniques deployed by Malware Developers to enhance malware resiliency.

  1. Wine Detection: Malware with anti-dynamic capabilities uses Execution Guardrails to prevent code from detonating when executed in a sandbox/emulator environment. Wine is a simple example of emulator used to run Windows applications on Unix-like Operating System.

  2. Code Integrity Checks: To prevent reverse engineers from bypassing these detection checks with binary patching, the integrity of main function is verified during runtime via code checksumming.

Challenge Overview

Running the executable returns a hash, followed by a line of poetry.

C:\Users\JesusCries\Desktop>Grape.exe 
79 0c 9f 8d da ef de 0a 33 71 d4 ac d5 c2 13 e2 45 5c 08 3d cd 9d 15 c1 46 47 8e e9 cd 6c 90 82
The fairest grape is not for every mouth; nor is the sweetest wine for every lip. 

Under a decompiler, the main function contains very minimal functionality (symbols renamed for readability):

undefined8 main(void)

{
  int iVar1;
  
  iVar1 = code_checksum();
  if (iVar1 != 0) {
    wine_detection();
  }
  return 0;
}

In the code_checksum function, we can find the hardcoded hash:

void code_checksum(void)
    
{
  local_168[0] = 0x79;
  local_168[1] = 0xc;
  local_168[2] = 0x9f;
  local_168[3] = 0x8d;
  local_168[4] = 0xda;
  local_168[5] = 0xef;
  local_168[6] = 0xde
  ---redacted---
}

The essence of this challenge is about bypassing the wine_detection logic:

void wine_detection(void)

{
  ---redacted---
  pHVar3 = GetModuleHandleW(L"kernel32.dll");
  pFVar4 = GetProcAddress(pHVar3,"wine_get_unix_file_name");
  if (pFVar4 != (FARPROC)0x0) {
    pHVar3 = GetModuleHandleW(L"ntdll.dll");
    pFVar4 = GetProcAddress(pHVar3,"wine_get_version");
    if (pFVar4 != (FARPROC)0x0) {
      FUN_140008990((undefined *)local_60,L"winlogon.exe");
      iVar1 = GetProcessCount(local_60);
      ~basic_string<>(local_60);
      if (iVar1 == 0) {
        local_20 = (HKEY)0x0;
        LVar2 = RegOpenKeyExW((HKEY)0xffffffff80000001,L"Software\\Wine\\Direct2D",0,0x20019,
                              &local_20);
        if ((LVar2 == 0) &&
           (LVar2 = RegQueryValueExW(local_20,L"max_version_factory",(LPDWORD)0x0,(LPDWORD)0x0,
                                     (LPBYTE)0x0,(LPDWORD)0x0), LVar2 != 2)) {
          RegCloseKey(local_20);
          p_Var5 = (_String_val<> *)FUN_140006030(local_40);
          FUN_14000e9f0(&DAT_140086960,p_Var5);
          ~basic_string<>(local_40);
          abort();
        }
      }
    }
  }
  FUN_14000ea40(&DAT_140086960,
                (longlong)
                "The fairest grape is not for every mouth; nor is the sweetest wine for every lip."
  ---redacted---
  return;
}

The detection logic is broken down into 3 parts:

  1. Library Exports: Identify if wine related functions are exported from Windows kernel32.dll and ntdll.dll libraries.

  2. Active Processes: Check if the winlogon.exe process is alive. For every Windows system, winlogon.exe is initialized during startup/logon; and since Wine does not emulate the logon process, the process count will be 0.

  3. Registry Key: Check if specific registry key that are exclusive to the Wine emulator presents on the system.

Patching

The way these detection logic are implemented (in a nested IF statement) presents an easy opportunity for binary patching. To bypass these detection logic, simply apply an Invert Patch on the following jump instructions:

After applying the patch, the checksum now looks different from the hardcoded hash we have seen previously; and because of this, the poetry is no longer printed because the program is terminated immediately upon a mismatch of checksum.

C:\Users\JesusCries\Desktop>Grape.exe 
94 d0 19 3e ef 90 37 74 53 62 9c 7a 85 f4 2c 01 9c 0b b0 53 bf 51 aa 0d ba 24 87 8b 58 4e ed 9e

However, recall that the code integrity check from the main function is also implemented in a similar manner, which allows us to patch the integrity hook itself.

This way, we managed to bypass all the detection logic in place and still have the executable run as usual despite having a different checksum.

C:\Users\JesusCries\Desktop>Grape.exe 
f8 97 5c cd d4 0b 21 72 be b0 06 de 28 f2 5e 36 66 87 4d 34 18 10 aa 38 ff d7 0e 3c 29 55 5b 63
ABOH{w1n3_15_n07_4n_3mul470r}

Flag: ABOH{w1n3_15_n07_4n_3mul470r}

Alternative Solution

Another alternative to patching, is to fulfil all the wine requirements that are checked against. Since Library Exports and Active Processes are already automatically True when executing in a Wine emulator, we will only have to deal with the last criteria.

Note that, not all versions of Wine comes with the max_version_factory registry key by default, but we can easily re-create this key since Wine supports it's own registry hive.

┌──(kali💀JesusCries)-[~/Desktop/CTF/ABOH2023/rev]
└─$ wine reg add "HKCU\Software\Wine\Direct2D" /v max_version_factory /t REG_DWORD /d 0
reg: The operation completed successfully

Finally, run the executable in Wine without patching it.

┌──(kali💀JesusCries)-[~/Desktop/CTF/ABOH2023/rev]
└─$ wine Grape.exe 
79 0c 9f 8d da ef de 0a 33 71 d4 ac d5 c2 13 e2 45 5c 08 3d cd 9d 15 c1 46 47 8e e9 cd 6c 90 82
ABOH{w1n3_15_n07_4n_3mul470r}00e0:err:seh:NtRaiseException Unhandled exception code c0000409 flags 1 addr 0x14003ef5d

Flag: ABOH{w1n3_15_n07_4n_3mul470r}

Last updated