从内核空间获取 API 的用户空间虚拟地址






4.83/5 (9投票s)
如何从 Ring0 获取用户空间进程中 API 的虚拟地址
引言
这篇文章是我之前发布的,收到了很好的反响。现在我将对其进行一些修订。有时,从内核获取用户 land API 的 VA 非常有必要。作为我系统研究工作的一部分,我发现了一种未公开的技术来实现这一点。本文的主要目的是告知您这种未公开但有效的技术,用于获取用户 land 进程导入的 API 的虚拟地址。
为此,我们将使用内核模式设备驱动程序。请注意,这是一种未公开的内容,并且是一个用于教育目的的 POC。请您自担风险实施。抱歉,我没有找到太多时间,它仅在 Windows XP 和 Windows 2k 上进行了测试。
背景
假设读者对汇编语言编程有很好的理解。源代码是用 VC++ 编写的,嵌入了汇编语言,这是我一直以来的最爱之一。为什么?汇编对于所有那些杂技表演都是非常必要的……你知道的。要从内核模式获取用户 land API 的 VA,我们基本上实现了从内核 land 获取用户 land 进程的 PEB 的方法,从而掌握了进程加载的所需 *.dll 的基本加载 VA。一旦我们找到这个加载基址 VA,我们就可以从那里获得 IMAGE_EXPORT_DIRECTORY
结构,从该结构中找到特定的函数名及其对应的 VA。
Using the Code
实现非常简单(真的吗?)。如上所述,我们首先获取感兴趣的用户 land 进程的 PEB,该进程在其地址空间中加载了所需的 *.dll。确保 *.dll 导出了所需的 API(其 VA 存在问题)。为了简单起见,我们以 Kernel32.dll 导出的 "WinExec
" API 为例。我们知道,大多数用户 land 进程都从 Kernel32.dll 导入 API(至少一个),这使得它无处不在。此外,我们谦逊地了解到,Kernel32.dll 始终在任何给定进程中第二个初始化(你知道哪个应该先初始化)。同样为了简单起见,我们假设我们的目标进程是 "explorer.exe"。一旦我们获取了所需 *.dll(此处为 kernel32.dll)的加载基址 VA,我们就会跳转到 IMAGE_EXPORT_DIRECTORY
字段偏移量(注意:PE 结构以及 PEB 结构可能会因版本而异。嘿,在这里要小心,你在内核中,否则会遇到 BSOD)。从这里,我们可以获取 AddressOfNames
数组和 AddressOfNameOrdinals
(请参阅 PE 文档以获取有关此内容的更多详细信息),并通过循环遍历它们,我们可以找到所需的精确 API VA。请记住,在这里我们使用了另一个重要且鲜为人知的内核 API,名为 KeStackAttachProcess
,它允许我们通过将上下文设置为用户 land 进程(在内部相应地设置 CR3 寄存器,除了其他一些检查)从内核 land 访问特定的用户 land 内存详细信息。
关于下面给出的过程的唯一参数。这是所需进程的进程 ID,这里是我们的例子中的 explorer.exe。我们可以很容易地从 PEPROCESS
结构中获取它。PEPROCESS
结构的一个属性是 UniqueProcesssId
。
我在这里没有解释它,因为你可以在网上找到一些这样的内容。
/////////////////////////////////////////////////////////////////////////////
//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;
}
//DPRINTA("\nModule Name is %s & Number of Func. Names is %d",pszModName,
//NumberOfFuncNames);
//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;
}
/////////////////////////////////////
关注点
以类似的方式,人们还可以从内核获取更多用户 land 进程的详细信息。
历史
- 2008 年 12 月 15 日:初始帖子