-
Notifications
You must be signed in to change notification settings - Fork 69
Enumerate Process Modules
So the driver has a lots of commands that make us advantage.
In this case, we are able to enumerate modules that loaded in the target process by process id and a number which specifies we want to get.
I'll explain herewith below how I made it managed to work it with reverse engineering.
The implementation can be found at mhyprot.cpp#L343.
First of all, As you can see there is cmp ecx, 82054000h
as I defined in mhyprot.hpp as MHYPROT_IOCTL_ENUM_PROCESS_MODULES
.
And it calls:
__int64 __fastcall sub_FFFFF800188C26D0(unsigned int a1, __int64 a2, __int64 a3)
{
__int64 v3; // rsi
unsigned int v4; // ebx
bool v5; // di
unsigned int v7; // ebx
PVOID Object; // [rsp+58h] [rbp+20h]
v3 = a2;
Object = 0i64;
v4 = a3;
v5 = (int)PsLookupProcessByProcessId(a1, &Object, a3) >= 0;
if ( !Object )
return 0i64;
v7 = sub_FFFFF800188C27D4(Object, v3, v4);
if ( Object )
{
if ( v5 )
ObfDereferenceObject(Object);
}
return v7;
}
As you can see, the function checks is process 32-bit
or 64-bit
by PsGetProcessWow64Process()
since PEB
is different between 32 and 64-bit processes.
In this case, I only talk about for 64-bit process.
After that, the function attaches from kernel using KeStackAttachProcess
. the second parameter is PKAPC_STATE
.
Then, call PsGetProcessPeb
and get the PEB belongs to the target process.
LDR_MODULE
is undocumented structure.
typedef struct _LDR_MODULE {
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID BaseAddress;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
} LDR_MODULE, *PLDR_MODULE;
And the function pseudocode for sub_FFFFF800188C27D4
is like:
__int64 __fastcall sub_FFFFF800188C27D4(
__int64 a1, // pEPROCESS
__int64 a2, // pointer to the buffer that sent from usermode
unsigned int a3 // max count to get
)
{
...
if ( !a1 )
return 0i64;
v9 = ((__int64 (*)(void))PsGetProcessWow64Process)() != 0;
KeStackAttachProcess(v5, &v30);
if ( !v9 ) // the process is 64-bit
{
v17 = PsGetProcessPeb(v5); // Lookup PEB
v18 = v17;
if ( v17 )
{
v19 = *(_QWORD *)(v17 + 24); // PEB->Ldr
if ( v19 )
{
for ( j = *(__int64 **)(v19 + 16);
j != (__int64 *)(*(_QWORD *)(v18 + 24) + 16i64); // PEB->Ldr->InMemoryOrderModuleList.Flink
j = (__int64 *)*j )
{
if ( v7 < v3 ) // if the counter less than a number what we want to get
{
v21 = 928i64 * v7; // [IMPORTANT] we can see output structure is 0x3A0 alignment
sub_FFFFF800188C7900(v21 + v4 + 12, 0i64, 256i64); // fill memory by 0 sizeof 0x100
sub_FFFFF800188C7900(v21 + v4 + 268, 0i64, 520i64); // fill memory by 0 sizeof 0x208
*(_QWORD *)(v21 + v4) = j[6];
*(_DWORD *)(v21 + v4 + 8) = *((_DWORD *)j + 16);
v22 = *((_WORD *)j + 44);
v23 = 127i64;
if ( v22 <= 0x7Fu )
v23 = v22;
sub_FFFFF800188C75C0(v21 + v4 + 12, j[12], v23); // copy BaseDllName to the buffer
v24 = *((_WORD *)j + 36);
v25 = v24;
if ( v24 > 0x103u )
v25 = 259i64;
sub_FFFFF800188C75C0(v21 + v4 + 268, j[10], v25); // copy FullDllName to the buffer
*(_QWORD *)(v21 + v4 + 792) = *((unsigned int *)j + 32);
v3 = v32;
}
++v6; // counter
++v7; // counter
}
}
}
} else { ... /* 32-bit PEB (Redacted) */ }
KeUnstackDetachProcess(&v30); // detach
return v6;
}
We got a much information from it as follows:
- We can get
BaseDllName
andFullDllName
using this ioctl command - What we need is only
ProcessId
andMaxCount
- The output buffer will overrided in the request buffer
- The output buffer also must have
0x3A0
size alignment per module
Definition of structure for the payload be like: (This is defined in mhyprot.hpp as well.)
typedef struct _MHYPROT_ENUM_PROCESS_MODULES_REQUEST
{
uint32_t process_id;
uint32_t max_count;
} MHYPROT_ENUM_PROCESS_MODULES_REQUEST, * PMHYPROT_ENUM_PROCESS_MODULES_REQUEST;
By:
if (uVar1_ControlCode == 0x82054000) {
uVar6 = GetModuleListByProcessId_FUN_000126d0
// process id
(*(uint *)puVar3_RequestContext,
// out buffer, the output will be stored with overriding max count...
(longlong)(uint *)((longlong)puVar3_RequestContext + 4),
// max count
*(uint *)((longlong)puVar3_RequestContext + 4)
);
iVar3 = (int)uVar6;
}
...
// mhyprot overrides first 4byte of the payload buffer to identify success or fail
*(int *)puVar3_RequestContext = iVar3;
What we need is:
-
- Allocate memory for payload and its result,
0x3A0
*MaxCount
- Allocate memory for payload and its result,
-
- Send the payload with the ioctl code
0x82054000
- Send the payload with the ioctl code
-
- Check for the first 4byte