Unicorn Emulator for String Decryption

Applying uEmu in IDA to effectively decrypt strings to reverse engineer kernel drivers.

Introduction

Generating pseudo code for a binary in IDA to be greeted by this:

v3 = 0;
v30 = 46;
unknownString[0] = (__int128)_mm_load_si128((const __m128i*)&xmmword_1400020F0);
unknownString[2] = (__int128)_mm_load_si128((const __m128i*)&xmmword_1400020D0);
unknownString[1] = (__int128)_mm_load_si128((const __m128i*)&xmmword_1400020E0);
do
{
    v4 = (char*)unknownString + v3;
    v5 = v3++ - 116;
    *v4 ^= v5;
} while (v3 < v30);

is always slightly annoying. However, with the help of emulation technologies such as Unicorn, it is much easier to decrypt strings without attempting to replicate the routine.

What is Unicorn

Unicorn is a CPU emulator written in C which provides functionality for emulating machine code for many architectures. This library provides Python bindings which are used by various IDA plugins in order to make code emulation easier. uEmu is a GUI based plugin for IDA which lets you segments of disassembled code with Unicorn.

Setting up uEmu

  1. Make sure IDA Python is installed
  2. Install Python bindings for Unicorn by using pip install unicorn
  3. Download the script at “https://github.com/alexhude/uEmu/blob/master/uEmu.py
  4. Set USE_AS_SCRIPT to False in the script
  5. Add “import sys” and “sys.path.append(‘C:\Program Files (x86)\IDA\python\3\PyQt5’)” to the script
  6. Place the script into the IDA plugin folder.

Now the plugin is accessible through the IDA menu by going to Edit > Plugins > uEmu

Using eEmu

Time to start ripping apart P2C copy pasted XOR string encryption

  1. Breakpoint the end of the string decryption with help of pseudo code.
  2. Set your cursor to be positioned at the proper instructions before any of the decryption occurs so that each register gets populated properly.
  3. Click Start inside the IDA uEmu plugin window and set registers if necessary. Click OK.
  4. Map memory if required, in my case I did not need to so I filled it with 0’s.
  5. Click run and wait for your breakpoint to be hit. Check the emulated CPU context. Confirm with IDA to see where the string is being stored.
  6. Open the “Show Memory Range” menu and enter the address found inside the CPU Context.
  7. Profit. P2C down. EAC wins.

Making use of decrypted string

Using this string, we can figure out that the driver is hooking the “NtTokenManagerGetAnalogExclusiveSurfaceUpdates” export from dxgkrnl.sys. This is verified with the pseudo code below:

Get Base of dxgkrnl.sys (another decrypted string after first example)

if ((unsigned int) ZwQuerySystemInformation(0xBi64, ModuleInformation, (unsigned int) ModuleNumberOfBytes, & ModuleNumberOfBytes) ||
  (moduleArray = SystemModuleInformationBuffer -> Modules, moduleNumber = 0i64, !SystemModuleInformationBuffer -> NumberOfModules))
{
  LABEL_13: ExFreePoolWithTag(SystemModuleInformationBuffer, 0);
  LABEL_14: dxgkrnlImageBase = 0i64;
  goto LABEL_15;
}
while (1)
{
    dxgKrnlSysString2 = (char*)&dxgKrnlSysString;
    do
    {
        v14 = (unsigned __int8)dxgKrnlSysString2[&moduleArray->FullPathName[moduleArray->OffsetToFileName] -
                                                  (UCHAR*)&dxgKrnlSysString];
        v15 = (unsigned __int8)*dxgKrnlSysString2 - v14;
        if (v15)
            break;
        ++dxgKrnlSysString2;
    } while (v14);
    if (!v15)
        break;
    ++moduleNumber;
    ++moduleArray;
    if (moduleNumber >= SystemModuleInformationBuffer->NumberOfModules)
        goto MODULE_NOT_FOUND;
}
dxgkrnlImageBase = moduleArray->ImageBase;

Find NtTokenManagerGetAnalogExclusiveSurfaceUpdate export

ntHeader = (IMAGE_NT_HEADERS*)((char*)dxgkrnlImageBase +
                                *(unsigned int*)((char*)dxgkrnlImageBase + *((int*)dxgkrnlImageBase + 0xF) + 0x88));
if (ntHeader)
{
    v18 = 0
    i64;
    v19 = (char*)dxgkrnlImageBase + ntHeader->OptionalHeader.SizeOfCode;
    v20 = *(unsigned int*)&ntHeader->OptionalHeader.Magic;
    v21 = (char*)dxgkrnlImageBase + ntHeader->OptionalHeader.SizeOfInitializedData;
    v22 = (char*)dxgkrnlImageBase + ntHeader->OptionalHeader.SizeOfUninitializedData;
    if (*(_DWORD*)&ntHeader->OptionalHeader.Magic)
    {
        while (1) // Find NtTokenManagerGetAnalogExclusiveSurfaceUpdate Export
        {
            NtTokenManagerGetAnalogExclusiveSurfaceUpdates = (char*)FunctionExport;
            do
            {
                v24 = (unsigned __int8)NtTokenManagerGetAnalogExclusiveSurfaceUpdates[(_BYTE*)dxgkrnlImageBase +
                                                                                      *(unsigned int*)&v21[4 * v18] -
                                                                                      (_BYTE*)FunctionExport];
                v25 = (unsigned __int8)*NtTokenManagerGetAnalogExclusiveSurfaceUpdates - v24;
                if (v25)
                    break;
                ++NtTokenManagerGetAnalogExclusiveSurfaceUpdates;
            } while (v24);
            if (!v25)
                break;
            if (++v18 >= v20)
                goto LABEL_26;
        }
        functionAddress = (char*)dxgkrnlImageBase + *(unsigned int*)&v19[4 * *(unsigned __int16*)&v22[2 * v18]];
    }
}

Understanding the shell code

Later in DriverEntry there is a slight amount of confusion. A hook handler exists; however its only referenced once in a variable that stores this address which is not used again.

...
hHookedAddress2 = (__int64)hHooked;
...

//Shellcode is written, but where is the hook address?
CopyToReadonlyMemory(functionAddress, (__int64)&shellCodeBuffer, 13i64);

Although hHookedAddress2 is not directly referenced anywhere else in the binary, checking out shellCodeBuffer reveals that the hook handler address is actually stored after 0x14000301F resulting in it getting copied along with the shellcode as follows.

.data:000000014000301D ; __int64 shellCodeBuffer
.data:000000014000301D shellCodeBuffer db  90h                 ; DATA XREF: DriverEntry+20D↑o
.data:000000014000301E                 db  48h ; H
.data:000000014000301F                 db 0B8h ; ¸
.data:0000000140003020 hHookedAddress2 dq 0                    ; DATA XREF: DriverEntry+233↑w
.data:0000000140003028                 db 0FFh ; ÿ
.data:0000000140003029                 db 0E0h ; à

This shellcode simply translates to the following assembly which jumps to the stored address once the export is syscalled from the usermode module.

0:  90                      nop
1:  48 b8 00 00 00 00 00    movabs rax, 8 BYTE ADDRESS
b:  ff e0                   jmp    rax

The nop instruction is there to throw off AC’s that are scanning for this exact shellcode signature. This results in a simple, and very low effort function hook on NtTokenManagerGetAnalogExclusiveSurfaceUpdate and thousands of dollars in profit for unoriginal knowledge and low effort work.

Conclusion

String decryption can easily be done with the help of proper tools and minimal effort. This technique can be applied to test encryption/decryption functions without having to replicate code in your own project.