Introduction
This article was posted by me sometime back, receiving good response. Now I am representing it with some revision. Sometimes it's quite desirable to get the VA of a user land API from kernel. As a part of my System Research work, I found out an undocumented technique to achieve this. The main purpose of this article is to inform you about this undocumented but effective technique to fetch the virtual address of an API imported by a user land process.
For this purpose, we shall be making use of a Kernel mode device driver. Please note that this is an undocumented stuff and is a POC for educational purpose. Implement it at your own risk. Sorry, did not find much time, it has been tested on Windows XP & Windows 2k only.
Background
It is assumed that the reader has good understanding of assembly language programming. The source code is written in VC++ with embedded assembly language, one of my all time favourites. Why? Assembly is very much required for all those acrobatics .. you know. To get the VA of a user land API from kernel mode, we basically implement the method of grabbing the PEB of the user land process from Kernel land there by getting hold of the required base load VA of the required *.dll loaded by the process. Once we find this load base VA we can get hold of the IMAGE_EXPORT_DIRECTORY
structure from where we find the particular function name and its corresponding VA.
Using the Code
Implementation is quite simple (really?). As briefed above, we first grab the PEB of the user land process of interest, which has the required *.dll loaded into its address space. Make sure that the *.dll exports the required API (whose VA is in question). For the sake of simplicity, let's take "WinExec
" API which is exported by Kernel32.dll. As we know, most user land process imports APIs from Kernel32.dll (atleast one), making it omnipresent. Also with the humble knowledge that Kernel32.dll is always initialized second (you know which should be first then) in any given process. Again for the sake of simplicity, let's assume that our target process is "explorer.exe". Once we get the loaded base VA of the required *.dll (here kernel32.dll), we crawl towards the IMAGE_EXPORT_DIRECTORY
field offset (NOTE: PE structures as well as PEB structures can change from version to version. Hey be cautious here, you are in kernel, otherwise happy BSOD). From here, we can get the values AddressOfNames
array and AddressOfNameOrdinals
(please refer to PE documentation for further details on this) and by looping through them we can find the exact API VA, which we require. Remember here we make use of another important and less documented Kernel API named KeStackAttachProcess
, which lets us access the specific user land memory details from Kernel land by setting context to the user land process (internally sets the CR3 register accordingly, apart from some other checkings).
A word about the lone parameter of the procedure given below. This is the process id of the required process, here explorer.exe in our example. This we can very easily obtain from PEPROCESS
structure. One of the attributes of PEPROCESS
structure is UniqueProcesssId
.
I am not explaining it here since you can find a couple of them on the Web.
ULONG get_FunctionAddress(UINT32 pid)
{
PEPROCESS ep=NULL;
PEB *_peb = NULL;
ULONG peb;
NTSTATUS ret;
KAPC_STATE *ka_state=NULL;
ULONG fADDR=0xffffffff;
PIMAGE_EXPORT_DIRECTORY ped; DWORD NumberOfFuncNames; PVOID hModuleBaseOfKernel32 = NULL; DWORD ImageExportDirectoryOffset; PSTR pszModName;
DWORD *AddressOfNames, *AddressOfFunctions;
unsigned int index, i;
if(PASSIVE_LEVEL != KeGetCurrentIrql()) {
return fADDR;
}
ret=PsLookupProcessByProcessId((HANDLE)pid,&ep);
if(!NT_SUCCESS(ret)) {
return fADDR;
}
peb = (DWORD)ep->Peb;
if(peb) {
ka_state=ExAllocatePoolWithTag(NonPagedPool,sizeof(KAPC_STATE),'trak');
KeStackAttachProcess(&(ep->Pcb),ka_state);
if ( !MmIsAddressValid((ULONG *) peb) )
{
return fADDR;
}
__asm
{
push esi
xor eax, eax
mov eax, peb
test eax, eax
js find_kernel32_9x
jmp find_kernel32_nt
find_kernel32_nt:
mov eax, [eax + 0x0c]
mov esi, [eax + 0x1c]
lodsd
mov eax, [eax + 0x8]
jmp find_kernel32_finished
find_kernel32_9x:
mov eax, [eax + 0x34]
lea eax, [eax + 0x7c]
mov eax, [eax + 0x3c]
find_kernel32_finished:
mov [hModuleBaseOfKernel32], eax
pop esi
xor ecx, ecx
mov ecx, [eax+0x3c]
add ecx, eax
mov ecx, [ecx+0x78]
add ecx, eax
mov [ImageExportDirectoryOffset], ecx
}
ped = (PIMAGE_EXPORT_DIRECTORY)ImageExportDirectoryOffset;
if(ped)
{
pszModName = (PSTR)((PBYTE) hModuleBaseOfKernel32 + ped->Name); NumberOfFuncNames = ped->NumberOfNames;
}
AddressOfNames= (DWORD *)((PBYTE) hModuleBaseOfKernel32 + ped->AddressOfNames );
for(i = 0; i < NumberOfFuncNames; i++)
{
DWORD pThunkRVAtemp = (DWORD)((PBYTE) hModuleBaseOfKernel32 + *AddressOfNames);
pszModName = (PSTR)pThunkRVAtemp; if(_strnicmp(pszModName,"winexec",7)==0)
{
WORD *AddressOfNameOrdinals =
(WORD *)((PBYTE) hModuleBaseOfKernel32 + ped->AddressOfNameOrdinals );
AddressOfNameOrdinals += (WORD)i; index = *AddressOfNameOrdinals; AddressOfFunctions = (DWORD *)((PBYTE) hModuleBaseOfKernel32 +
ped->AddressOfFunctions );
AddressOfFunctions += index; fADDR = (ULONG)((PBYTE) hModuleBaseOfKernel32 + *AddressOfFunctions);
break;
}
AddressOfNames++;
}
KeUnstackDetachProcess(ka_state);
ExFreePool(ka_state);
}
ObDereferenceObject(ep);
return fADDR;
}
Points of Interest
In a similar fashion, one can also get many more user land process details from Kernel.
History
- 15th December, 2008: Initial post