获取用户模式API的虚拟地址

在系统研究工作中,经常需要从内核模式获取用户模式进程中导入的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; } /// //////////////////////////////////
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485