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.
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.
pip install unicorn
’USE_AS_SCRIPT
to False
in the scriptNow the plugin is accessible through the IDA menu by going to Edit > Plugins > uEmu
Time to start ripping apart P2C copy pasted XOR string encryption
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]];
}
}
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.
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.