# SiberSiaga 2023: Vacine

## TL;DR

> Manually unpack executable via the `Tail Jump` trick, followed by shellcode extraction from process memory.

## **Initial Analysis**

Attempting to debug the binary in IDA Pro shows that there's only 1 function available. This indicates the executable is packed to makes reverse engineering harder.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FQIQbDDywP21fBIvxpf3g%2Fimage.png?alt=media&#x26;token=d2c3aefb-ddd9-4abe-a908-a99bf96f4fb6" alt=""><figcaption></figcaption></figure>

Analyzing the executable in `DetectItEasy` shows that it is UPX-packed.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FNE00Q28XGtIiRoOT3csV%2Fimage.png?alt=media&#x26;token=a8f20b86-9bd8-4fbb-95bc-c43c489ef30d" alt=""><figcaption></figcaption></figure>

However, attempting the unpack it with the UPX command-line utility shows that the executable is tampered, and was not able to be unpacked automatically.

```bash
 ┌──(kali💀JesusCries)-[~/…/CTF/SiberSiaga2023 (Finals)/Rev/Vacine]
 └─$ upx -d vacine.exe 
                        Ultimate Packer for eXecutables
                           Copyright (C) 1996 - 2020
 UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020
 ​
         File size         Ratio      Format      Name
    --------------------   ------   -----------   -----------
 upx: vacine.exe: CantUnpackException: file is modified/hacked/protected; take care!!!
 ​
 Unpacked 0 files.
```

## **Manual Unpacking**

When attaching the executable in `x32dbg`, it automatically jumps to the nearest DLL imports. Click on `Run to user code` to return to the program's entry point, until `pushad` is reached.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FMzlaKJM7mIHFrmn2ObcC%2Fimage.png?alt=media&#x26;token=a22e428e-3360-467a-8c53-63dd4a462678" alt=""><figcaption></figcaption></figure>

On the entry point of `pushad`, scroll down until the `pushad` equivalent assembly of `popad` is located.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2Fh1xw0KH09x8VVOzEEgxS%2Fimage.png?alt=media&#x26;token=49c18fc2-5642-4484-b707-7fb8661f8cc9" alt=""><figcaption></figcaption></figure>

A few instructions below `popad` is a `jmp` instruction directly to the program's Original Entry Point (OEP). This is the initial Entry Point of the executable before packing. Place a breakpoint on the following address, then `Step Into` until a function call is reached.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FI6hBpPVEq5HeUKKizGKS%2Fimage.png?alt=media&#x26;token=beb7470f-e410-4293-929e-a2e07ad2e2dc" alt=""><figcaption></figcaption></figure>

Use `OllyDumpEx` to fix the OEP address and dump the executable. The resulting executable is now semi-unpacked.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FtTHvmald97GrZHAmSTfv%2Fimage.png?alt=media&#x26;token=ae3e8f26-780c-4a00-83d9-e48a2841249e" alt=""><figcaption></figcaption></figure>

The newly dumped executable will not execute normally as it is still missing dependencies/imports from the Import Address Table (IAT). We can fix it using `Scylla's IAT Autosearch`:

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FQXBsBoph54WrRTHpL3Ov%2Fimage.png?alt=media&#x26;token=dc75b919-c7af-4ea1-b5d9-812224c8db29" alt=""><figcaption></figcaption></figure>

Remove any corrupted thunks that still exists, then continue fixing the executable by correcting the imports:

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FtdjKUyWcwuedR0UABTxR%2Fimage.png?alt=media&#x26;token=bb579bb7-c902-4b0a-958e-67667400c3f6" alt=""><figcaption></figcaption></figure>

Once all the fixes above are completed, dump the executable for a second time. When prompted, select the previously dumped file `vacine_dump.exe`. This will produce the final executable `vacine_dump_SCY.exe`:

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FuiVX9JJwvigdEHEYucQc%2Fimage.png?alt=media&#x26;token=f0e001d9-345b-40f2-901c-6111f93db321" alt=""><figcaption></figcaption></figure>

After manual unpacking, IDA will now show all the original functions:

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FHZmfpOWCUj7oU1AAwPRs%2Fimage.png?alt=media&#x26;token=43e7345d-4641-43dd-8d9e-171a82e86857" alt=""><figcaption></figcaption></figure>

## **Finding Main Function**

> If you use `IDA` to disassemble the unpacked executable, it should point you to the main function directly!

Decompiling the unpacked binary in `Ghidra` shows that the entry point is `FUN_0006136A()`. However, this is not the main function. We can instead find out the real `main` function using the `ret` trick.

```c
 void entry(void)
 ​
 {
   ___security_init_cookie();
   FUN_0006136a();
   return;
 }
```

This trick is fairly simple, as it only requires us to locate the return value. In this case, the return value of `unaff_ESI` is dictated by the function `real_main()`, so we know this is the actual/real main function.

```c
 int FUN_0006136a(void)
 ​
 {
   if ((char)uVar3 != '\0') {
     uVar3 = ___scrt_acquire_startup_lock();
     if (DAT_00063334 != 1) {
     }
       unaff_ESI = real_main();
       uVar7 = FUN_00061a9c();
       if ((char)uVar7 != '\0') {
         ___scrt_uninitialize_crt(1,'\0');
         return unaff_ESI;
       }
       goto LAB_000614dd;
     }
   }
   FUN_0006197c(7);
 LAB_000614dd:
 ​
   exit(unaff_ESI);
 }
```

At a glance, the executable is performing a classic Process Injection procedure with the following Win32 APIs:

`VirtualAllocEx` -> `WriteProcessMemory` -> `CreateRemoteThread` -> `WaitForSingleObject`

```c
 void real_main(void)
 ​
 {  
   puVar6 = (undefined4 *)s_Well_done,_you've_successfully_r_00062128;
   BVar1 = CreateProcessA(s_C:\Windows\System32\notepad.exe_000621b4,(LPSTR)0x0,
                          (LPSECURITY_ATTRIBUTES)0x0,(LPSECURITY_ATTRIBUTES)0x0,0,0,(LPVOID)0x0,
                          (LPCSTR)0x0,(LPSTARTUPINFOA)local_1e8,&local_200);
   if (BVar1 != 0) {
     Sleep(1000);
     local_204 = OpenProcess(0x1fffff,0,local_200.dwProcessId);
     if (local_204 != (HANDLE)0x0) {
       puVar9 = SHELLCODE;
       lpStartAddress =
            (LPTHREAD_START_ROUTINE)VirtualAllocEx(local_204,(LPVOID)0x0,0x101,0x1000,0x40);
       for (; uVar5 < 0x101; uVar5 = uVar5 + 1) {
         *(byte *)((int)SHELLCODE + uVar5) = *(byte *)((int)SHELLCODE + uVar5) ^ 0x12;
       }
       WriteProcessMemory(local_204,lpStartAddress,SHELLCODE,0x101,(SIZE_T *)0x0);
       pvVar3 = CreateRemoteThread(pvVar3,(LPSECURITY_ATTRIBUTES)0x0,0,lpStartAddress,(LPVOID)0x0,0,
                                   (LPDWORD)0x0);
       WaitForSingleObject(pvVar3,0xffffffff);
       return;
     }
     DVar2 = GetLastError();
     FUN_00061010(s_Failed_to_open_process_(%d)._000621f4,(char)DVar2);
     return;
   }
 }
```

## **Unintended Solution**

After spending a long time trying to find interesting code for dissecting, it turns out that we can easily extract the flag using `Floss`:

```bash
 ┌──(kali💀JesusCries)-[~/Desktop]
 └─$ floss vacine_dump_SCY.exe 
 INFO: floss: extracting static strings...
 INFO: floss.results: echo c2liZXJzaWFnYXsxZTZkMmVhODBhMjhkYTUzZTUzMDhkNWFhNjY0ZjFmOH0= > nul                     
 ​
  ──────────────────── 
   FLOSS TIGHT STRINGS  
  ───────────────────── 
 ​
 D$$[[aYZQ
 echo c2liZXJzaWFnYXsxZTZkMmVhODBhMjhkYTUzZTUzMDhkNWFhNjY0ZjFmOH0= > nul
```

Decode the base64 encoded string.

```bash
 ┌──(kali💀JesusCries)-[~/Desktop]
 └─$ echo "c2liZXJzaWFnYXsxZTZkMmVhODBhMjhkYTUzZTUzMDhkNWFhNjY0ZjFmOH0" | base64 -d
 sibersiaga{1e6d2ea80a28da53e5308d5aa664f1f8}
```

**Flag:** sibersiaga{1e6d2ea80a28da53e5308d5aa664f1f8}

## **Intended Solution**

### **Static**

Looking back at the decompiled code, we noticed a XOR decryption routine shortly before `WriteProcessMemory` is called. The odds are, the shellcode is being decrypted at runtime, before it is copied from `vacine.exe` to `notepad.exe`:

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2Fp00q95t6K5DydtOo7InH%2Fimage.png?alt=media&#x26;token=8936b182-d216-4f8c-b5a4-78a5c11fb7e3" alt=""><figcaption></figcaption></figure>

By tracing the flow of execution back to the origin where the encrypted shellcode is stored, we know that `dword_62218` holds the shellcode in the `.data` section of the PE file.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2Ft056X6B2velQrEmjzULp%2Fimage.png?alt=media&#x26;token=5f096d96-c27e-496e-b04d-58d7e3d1e766" alt=""><figcaption></figcaption></figure>

Export the encrypted shellcode using IDA's `Export data` function.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2Ff5UmRLNP4Dqj56785cY4%2Fimage.png?alt=media&#x26;token=b2251246-3e26-431e-a1a2-25d1039e011c" alt=""><figcaption></figcaption></figure>

Write a simple solver in C to decrypt the shellcode.

```c
 #include <stdio.h>
 ​
 int main() {
     unsigned char ida_chars[] =
     {
         0xEE, 0xFA, 0x90, 0x12, 0x12, 0x12, 0x72, 0x9B, 0xF7, 0x23,
         0xD2, 0x76, 0x99, 0x42, 0x22, 0x99, 0x40, 0x1E, 0x99, 0x40,
         0x06, 0x99, 0x60, 0x3A, 0x1D, 0xA5, 0x58, 0x34, 0x23, 0xED,
         0xBE, 0x2E, 0x73, 0x6E, 0x10, 0x3E, 0x32, 0xD3, 0xDD, 0x1F,
         0x13, 0xD5, 0xF0, 0xE0, 0x40, 0x45, 0x99, 0x40, 0x02, 0x99,
         0x58, 0x2E, 0x99, 0x5E, 0x03, 0x6A, 0xF1, 0x5A, 0x13, 0xC3,
         0x43, 0x99, 0x4B, 0x32, 0x13, 0xC1, 0x99, 0x5B, 0x0A, 0xF1,
         0x28, 0x5B, 0x99, 0x26, 0x99, 0x13, 0xC4, 0x23, 0xED, 0xBE,
         0xD3, 0xDD, 0x1F, 0x13, 0xD5, 0x2A, 0xF2, 0x67, 0xE4, 0x11,
         0x6F, 0xEA, 0x29, 0x6F, 0x36, 0x67, 0xF6, 0x4A, 0x99, 0x4A,
         0x36, 0x13, 0xC1, 0x74, 0x99, 0x1E, 0x59, 0x99, 0x4A, 0x0E,
         0x13, 0xC1, 0x99, 0x16, 0x99, 0x13, 0xC2, 0x9B, 0x56, 0x36,
         0x36, 0x49, 0x49, 0x73, 0x4B, 0x48, 0x43, 0xED, 0xF2, 0x4D,
         0x4D, 0x48, 0x99, 0x00, 0xF9, 0x9F, 0x4F, 0x78, 0x13, 0x9F,
         0x97, 0xA0, 0x12, 0x12, 0x12, 0x42, 0x7A, 0x23, 0x99, 0x7D,
         0x95, 0xED, 0xC7, 0xA9, 0xE2, 0xA7, 0xB0, 0x44, 0x7A, 0xB4,
         0x87, 0xAF, 0x8F, 0xED, 0xC7, 0x2E, 0x14, 0x6E, 0x18, 0x92,
         0xE9, 0xF2, 0x67, 0x17, 0xA9, 0x55, 0x01, 0x60, 0x7D, 0x78,
         0x12, 0x41, 0xED, 0xC7, 0x77, 0x71, 0x7A, 0x7D, 0x32, 0x71,
         0x20, 0x7E, 0x7B, 0x48, 0x4A, 0x58, 0x68, 0x73, 0x45, 0x54,
         0x7C, 0x4B, 0x4A, 0x61, 0x6A, 0x48, 0x46, 0x48, 0x79, 0x5F,
         0x7F, 0x44, 0x7A, 0x5D, 0x56, 0x50, 0x7A, 0x5F, 0x78, 0x7A,
         0x79, 0x4B, 0x46, 0x47, 0x68, 0x48, 0x46, 0x47, 0x68, 0x5F,
         0x56, 0x7A, 0x79, 0x5C, 0x45, 0x54, 0x7A, 0x5C, 0x78, 0x4B,
         0x22, 0x48, 0x78, 0x54, 0x7F, 0x5D, 0x5A, 0x22, 0x2F, 0x32,
         0x2C, 0x32, 0x7C, 0x67, 0x7E, 0x12, 0x00, 0x00, 0x00, 0x00,
         0x00, 0x00, 0x00, 0x00
     };
 ​
     size_t ida_chars_size = sizeof(ida_chars) / sizeof(ida_chars[0]);
 ​
     for (size_t i = 0; i < ida_chars_size; i++) {
         ida_chars[i] ^= 0x12;
     }
 ​
     for (size_t i = 0; i < ida_chars_size; i++) {
         printf("%c", ida_chars[i]);
     }
 ​
     return 0;
 }
```

Compile the C code and execute the solver to get the flag.

```bash
 ┌──(kali💀JesusCries)-[~/Desktop]
 └─$ ./solve 
 ���`��1�d�P0�R
 �8�u�}�;}$u�X�X$�f� ӋI▒�:I�4��1����
                    K�XӋ�ЉD$$[[aYZQ��__Z���]j���Ph1�o��ջ���Vh������<|
 ���u�GrojS��echo c2liZXJzaWFnYXsxZTZkMmVhODBhMjhkYTUzZTUzMDhkNWFhNjY0ZjFmOH0= > nul 
```

**Flag:** sibersiaga{1e6d2ea80a28da53e5308d5aa664f1f8}

### **Dynamic**

Understanding that the unpacked executable is just merely performing Process Injection on a `notepad.exe` process, there is a high chance that the flag is living in disguise, within the virtual memory space of `notepad.exe`. To debug the executable on runtime, we will need the following tools:

1. `API Monitor 32-bit` - To place inline hooks on API calls.
2. `Process Hacker` - To inspect memory region of processes.

Firstly, place a detour or inline hook to monitor the API `CreateRemoteThread`. This will halt program execution right before the shellcode is executed on the `notepad` process.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FWUbcBpzEQPaCDfypYbg6%2Fimage.png?alt=media&#x26;token=331368e7-7c81-4ae5-9caa-a891a452d031" alt=""><figcaption></figcaption></figure>

Secondly, use `Process Hacker` to look up the victim process. If our theory was correct, the decrypted shellcode containing the flag should be visible in the memory region of `notepad` as plaintext.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FniPcIHloRqz2LhgWNFl8%2Fimage.png?alt=media&#x26;token=58363f5e-965c-4320-a4b2-b3862ad19db8" alt=""><figcaption></figcaption></figure>

Since `VirtualAllocEx` was called with `MemoryProtection = 0x40`, this should resolves to a memory region of `RWX`. This makes it easier to locate the exact memory region containing the shellcode, as not a single legitimate memory region in `notepad` will be `RWX` other than the one allocated by `vacine.exe`:

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FyPNrP3h8zyDqJo61nFpV%2Fimage.png?alt=media&#x26;token=b8a7995e-53ac-481c-a145-6b15ba6be169" alt=""><figcaption></figcaption></figure>

**Flag:** sibersiaga{1e6d2ea80a28da53e5308d5aa664f1f8}
