It is second part of CrackMe task describing. First one you can find there
In this part I will detail the analysis of driver ReverMe1.sys
File has an anti-reverse tricks, jump to middle of instruction, which was described in previous post, I will not concern this topic here. After patch and decompile init function looks like:
int __stdcall init(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
PDRIVER_OBJECT hDriverObject; // esi@1
UNICODE_STRING SymbolicLinkName; // [sp+8h] [bp-10h]@1
UNICODE_STRING DestinationString; // [sp+10h] [bp-8h]@1
hDriverObject = DriverObject;
DriverObject = 0;
hDriverObject->DriverUnload = (PDRIVER_UNLOAD)&driver_upload_fake;
RtlInitUnicodeString(&DestinationString, &SourceString);
IoCreateDevice(hDriverObject, 0, &DestinationString, 0x22u, 0x22u, 0, (PDEVICE_OBJECT *)&DriverObject);
hDriverObject->MajorFunction[0] = (PDRIVER_DISPATCH)fake;
hDriverObject->MajorFunction[2] = (PDRIVER_DISPATCH)fake;
hDriverObject->MajorFunction[14] = (PDRIVER_DISPATCH)input_strcpy;// DeviceIoControl request handler
RtlInitUnicodeString(&SymbolicLinkName, &uniString);
IoCreateSymbolicLink(&SymbolicLinkName, &DestinationString);
init_decrypt_func();
return 0;
}
There is common driver init function. Driver sets fake handlers (they are always return 0 – success):
hDriverObject->DriverUnload = (PDRIVER_UNLOAD)&driver_upload_fake;
hDriverObject->MajorFunction[0] = (PDRIVER_DISPATCH)fake;
hDriverObject->MajorFunction[2] = (PDRIVER_DISPATCH)fake;
There are two interesting place:
hDriverObject->MajorFunction[14] = (PDRIVER_DISPATCH)input_strcpy;//
DeviceIoControl request handler
MajorFunction[14] – it is The IRP_MJ_DEVICE_CONTROL handler
From MSDN:
“The IRP_MJ_DEVICE_CONTROL request is sent by the I/O Manager and other operating system components, as well as other kernel-mode drivers. Normally this IRP is sent on behalf of a user-mode application that has called the Microsoft Win32 DeviceIoControl function or on behalf of a kernel-mode component that has called ZwDeviceIoControlFile.”
So function input_strcpy() will be call when our user mode program call DeviceIoControl with command line first argument as a parameter.
Second interesting place is init_decrypt_func(); (prev. listing)
The algorithm of driver working sophisticated enough. In init_decrypt_func() it patches IDT (Interrupt Descriptor Table) and hooks int 0Eh interruption (Page Fault).
Further I try to describe step by step:
- Usermode program call DeviceIoControl. Handler function () copy input buffer to own buffer and generate Page Fault
- Because of hook in IDT (int 0Eh) driver intercepts execution and check where interruption was. If it had been between expectable addresses driver would jump to code which decrypt function which calculate hash. Otherwise driver call original exception handler.
- Decrypt function is easy, It just unxors function and jump into it.
unsigned int __stdcall decrypt_function(int a1, char *a2, int a3, int a4, char *a5, char *a6)
{
unsigned int (__stdcall *v8)(int, char *, unsigned __int8 *, int, char *, char *); // esi@1
unsigned int v9; // eax@1
_disable();
__asm { mov eax, cr0 }
_EAX &= 0xFFFEFFFFu;
__asm { mov cr0, eax }
v8 = function;
v9 = 0;
do
{
*(_BYTE *)v8 ^= 0x39u;
++v9;
v8 = (unsigned int (__stdcall *)(int, char *, unsigned __int8 *, int, char *, char *))((char *)v8 + 1);
}
while ( v9 < (char *)encrypt_function – (char *)(int (*)())function );
__asm { mov eax, cr0 }
_EAX = (unsigned int)&_ImageBase | _EAX;
__asm { mov cr0, eax }
return function(a1, a2, (unsigned __int8 *)a3, a4, a5, a6);
}
- function(a1, a2, (unsigned __int8 *)a3, a4, a5, a6) concatenates input string and “HZV_OwnTheWorld7” and calculates hash from resulting string.
unsigned int __stdcall function(int a1, char *HZV_OwnTheWorld7, unsigned __int8 *hash_bin, int hash_str, char *full_str, char *format_string)
{
unsigned int i; // eax@1
unsigned int i1; // esi@3
unsigned int result; // eax@5
int ptmppointer; // [sp+1Ch] [bp+18h]@3
i = 0;
do
{
HZV_OwnTheWorld7[i] ^= 0x37u;
++i;
}
while ( i < 0x10 );
strncat_(full_str, HZV_OwnTheWorld7, 0x80u);
md5_calc(full_str, 4u, hash_bin);
i1 = 0;
ptmppointer = hash_str;
do
{
sprintf_((char *)ptmppointer, format_string, hash_bin[i1]);
ptmppointer += 2;
++i1;
}
while ( i1 < 0x10 );
*(_BYTE *)(hash_str + 32) = 0;
result = 0;
do
{
HZV_OwnTheWorld7[result] ^= 0x37u;
++result;
}
while ( result < 0x10 );
return result;
}
- I would like to remain you that in previous post, question was “What kind of hash does driver use?”. We assumed that this can either be modified md5 or md5 from the changed data. Now we know that the data was changed, but unfortunately result is not equal with md5 from “input_string + HZV_OwnTheWorld7”
Let’s continue to analyze hash algorithm!
int __stdcall md5_calc(const char *full_str, unsigned int input_size, unsigned __int8 *hash_bin)
{
unsigned int len; // eax@1
const char *ptmppointer; // eax@3
unsigned __int8 *v5; // edx@3
unsigned __int8 tmp; // cl@4
unsigned __int8 *md5_state; // [sp+0h] [bp-1Ch]@1
unsigned __int8 *pnew_buff; // [sp+10h] [bp-Ch]@3
unsigned int len_tmp; // [sp+14h] [bp-8h]@3
// memory map:
// 16 bytes – buff_10h // +0h
// 0123456789abcdeffedcba9876543210
// *(_DWORD *)a1 = 0x67452301u;
// *(_DWORD *)(a1 + 4) = 0xEFCDAB89u;
// *(_DWORD *)(a1 + 8) = 0x98BADCFEu;
// *(_DWORD *)(a1 + 12) = 0x10325476u;
// 4 bytes – pointer to new allocated buffer // + 10h
// 4 bytes – len of full_str // +14h
// 4 bytes – 0 // +18h
md5_init(&md5_state);
len = input_size;
if ( !input_size )
len = strlen(full_str);
len_tmp = len;
ptmppointer = full_str;
v5 = (unsigned __int8 *)(pnew_buff – (unsigned __int8 *)full_str);
do
{
tmp = *ptmppointer;
ptmppointer[(_DWORD)v5] = *ptmppointer;
++ptmppointer;
}
while ( tmp );
get_last_3fh_bytes((int)&md5_state);
sub_1119C(hash_bin, (int)&md5_state);
return 0;
}
For us three strings are most important in that function (I made them bold and red).
It checks input parameter and if it is zero function calculates size of string, BUT in our case input parameter is 0x4 and function will calculate hash only from 4 bytes. If we check it will be md5. Further bruteforce md5 from 4 bytes with expectable first 4 bytes (see in usermode program).
Easy, isn’t it ? =)
Also when driver completes its work it crypt them self.
Note: When I analyzed the driver for the first time I did not pay attention to these 3 lines. I had a very deep study hash function before I was finally convinced that it is not modified md5 and started looking elsewhere.
Hence the advice:
- Pay attention to little things – they are always important.
- When analyzing an unknown cryptalgorithm first check the “magic” numbers. It halps always ;) In this case I identified md5 by code:
int __stdcall md5_init(int a1)
{
PVOID v1; // edi@1
int result; // eax@1
v1 = ExAllocatePoolWithTag(0, 0x400u, 0x206B6444u);
result = 0;
*(_DWORD *)(a1 + 0x10) = v1;
memset(v1, 0, 0x400u);
*(_DWORD *)(a1 + 0x14) = 0;
*(_DWORD *)(a1 + 0x18) = 0;
*(_DWORD *)a1 = 0x67452301u;
*(_DWORD *)(a1 + 4) = 0xEFCDAB89u;
*(_DWORD *)(a1 + 8) = 0x98BADCFEu;
*(_DWORD *)(a1 + 12) = 0x10325476u;
return result;
}
It is obvious md5_init. if you google magic number or check source code you will find that.