65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (9投票s)

2008年12月15日

CPOL

3分钟阅读

viewsIcon

27329

downloadIcon

548

如何从 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 日:初始帖子
© . All rights reserved.