在系统研究工作中,经常需要从内核模式获取用户模式进程中导入的API的虚拟地址。本文将介绍一种未文档化但有效的技术,用于实现这一目的。请注意,这是一种未公开的技术,仅供教育目的,且风险自担。本文仅在Windows XP和Windows 2000上进行了测试。
假设读者已经对汇编语言编程有良好的理解。源代码是用VC++编写的,其中嵌入了汇编语言,这是一直以来的最爱。汇编语言对于实现各种技巧至关重要。为了从内核模式获取用户模式API的虚拟地址,基本上实现了从内核模式获取用户模式进程的PEB的方法,从而获取所需的基础加载虚拟地址。
实现过程相当简单。如上所述,首先获取用户模式进程的PEB,该进程已将所需的DLL加载到其地址空间中。请确保该DLL导出了所需的API(其虚拟地址正在查询中)。为了简单起见,以"WinExec" API为例,该API由Kernel32.dll导出。众所周知,大多数用户模式进程从Kernel32.dll导入API(至少一个),使其无处不在。同样,知道Kernel32.dll在任何给定进程中总是第二个初始化的(知道哪个应该是第一个)。为了简单起见,假设目标进程是"explorer.exe"。一旦获取了所需的DLL(这里指kernel32.dll)的基础加载虚拟地址,就向IMAGE_EXPORT_DIRECTORY字段偏移量移动(注意:PE结构以及PEB结构可能因版本而异。嘿,要小心,在内核中,否则会很高兴地遇到BSOD)。从这里,可以获取AddressOfNames数组和AddressOfNameOrdinals(有关这些的更多详细信息,请参阅PE文档),并通过循环遍历它们,可以找到所需的确切API虚拟地址。请记住,在这里使用了另一个重要且较少文档化的内核API,名为KeStackAttachProcess,它允许通过将上下文设置为用户模式进程(内部相应地设置CR3寄存器,除了一些其他检查)来访问用户模式内存详细信息。
//////////////////////////////////////////////////////////////////////////
//Developed by E. Murali Kartha
//CopyRight (C) E.M.Kartha, Must be run only from Kernel Mode.
//It's only for Educational purpose and is a POC.
//To be run on Intel 32 bit processors only.
//Here the below procedure is to get the function address of
//"winexec" (can be parameterized for any) from EAT of kernel32.dll
//For this first we get the peb of the process (here explorer.exe)
//then find the module base address of kernel32.dll and then get
//the EAT offset.-Kartha.
//Note the lone argument here pid can be obtained easily from the
//PEPROCESS structure variable like this PEPROCESS pSystemProcess->UniqueProcessId
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;
// go through this structure declaration to find more.
DWORD NumberOfFuncNames;
// Number of functions in the function names array.
PVOID hModuleBaseOfKernel32 = NULL;
// for holding the retrieved loaded base VA.
DWORD ImageExportDirectoryOffset;
// start offset of the IMAGE_EXPORT_DIRECTORY
PSTR pszModName;
DWORD *AddressOfNames, *AddressOfFunctions;
unsigned int index, i;
if (PASSIVE_LEVEL != KeGetCurrentIrql()) {
// ERR DISPLAY TBD
return fADDR;
}
// ep will contain the EPROCESS address after this call
ret=PsLookupProcessByProcessId((HANDLE)pid,&ep);
if (!NT_SUCCESS(ret)) {
// ERR DISPLAY TBD
return fADDR;
}
// Get the PEB value here -Kartha.
peb = (DWORD)ep->Peb;
// Note: Since PEB is a user mode address KeStackAttachProcess
// has to be called before trying
// to get further fields from it-Kartha.
if (peb) {
ka_state=ExAllocatePoolWithTag(NonPagedPool,
sizeof(KAPC_STATE),
'trak');
KeStackAttachProcess(&(ep->Pcb),ka_state);
if (!MmIsAddressValid((ULONG *) peb)) {
// ERR DISPLAY TBD
return fADDR;
}
__asm {
push esi
xor eax, eax
mov eax, peb
// move peb value to eax
test eax, eax
js find_kernel32_9x
jmp find_kernel32_nt
// just to satisfy VC++ compiler
find_kernel32_nt:
mov eax, [eax+0x0c]
mov esi, [eax+0x1c]
lodsd
mov eax, [eax+0x8]
// get the Kernel32.dll loaded base VA
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
// move the base loaded address, for eg 0x7c800000
pop esi
// Finding the Export table RVA from PE Structure.
// Remember currently eax carries the loaded module base V address of
// kernel32.dll.
xor ecx, ecx
mov ecx, [eax+0x3c]
// In PE IMAGE_DOS_HEADER +3c will give us the
// "offset to New Exe header"
add ecx, eax
// Remember the values which we retrieve are always RVA
// so add with base
mov ecx, [ecx+0x78]
// +0x78 takes as to the start of IMAGE_EXPORT_DIRECTORY
add ecx, eax
// ecx contains the VA of IMAGE_EXPORT_DIRECTORY
mov [ImageExportDirectoryOffset], ecx
}
ped = (PIMAGE_EXPORT_DIRECTORY)ImageExportDirectoryOffset;
if (ped) {
pszModName = (PSTR)((PBYTE) hModuleBaseOfKernel32 + ped->Name);
// Fetch the module name
NumberOfFuncNames = ped->NumberOfNames;
}
// AddressOfNames will hold the RVA of an array which holds RVA's of all Function
// Names-Kartha.
AddressOfNames= (DWORD *)((PBYTE) hModuleBaseOfKernel32 + ped->AddressOfNames);
// Loop through to find out each function, corresponding ordinal array value (index)
// and then getting the function name. -Kartha.
for (i = 0; i < NumberOfFuncNames; i++) {
DWORD pThunkRVAtemp = (DWORD)((PBYTE) hModuleBaseOfKernel32 + *AddressOfNames);
pszModName = (PSTR)pThunkRVAtemp;
// address pointing to function name string.
if (_strnicmp(pszModName, "winexec", 7) == 0) {
WORD *AddressOfNameOrdinals = (WORD *)((PBYTE) hModuleBaseOfKernel32 + ped->AddressOfNameOrdinals);
AddressOfNameOrdinals += (WORD)i;
// Go to the required address index
// in the NameOrdinals array
index = *AddressOfNameOrdinals;
// get the value from this array location.
AddressOfFunctions = (DWORD *)((PBYTE) hModuleBaseOfKernel32 + ped->AddressOfFunctions);
AddressOfFunctions += index;
// get to the index location of Function address array.
// Get the func addre RVA and add with base VA
fADDR = (ULONG)((PBYTE) hModuleBaseOfKernel32 + *AddressOfFunctions);
// DPRINTA("\nFound %s at loop index %d, Function address is 0x%08X",
// pszModName, i, FuncAddress);
break;
}
AddressOfNames++;
}
// DPRINTA("\n");
KeUnstackDetachProcess(ka_state);
ExFreePool(ka_state);
}
// cleanup
ObDereferenceObject(ep);
return fADDR;
}
///
//////////////////////////////////