导语:我们该如何获得没有被Windows NT内核所导出的函数和结构体的虚拟地址呢?

获得没有被Windows NT内核所导出的函数和结构体的虚拟地址

众所周知,许多函数和结构体都没有被Windows NT内核所导出,例如PsGetNextProcess函数、KeServiceDescriptorTable等等。那么,我们该如何获得这些函数和结构体的虚拟地址呢?

为此,我们可以通过模式匹配来查找函数内部的特定函数或结构体,但是这种查找方式并不可靠,因为微软一旦对这些函数或结构体进行了修改,原来的模式匹配算法就失效了。

那么,使用Microsoft的Debug Help Library怎么样呢?

我们可以利用它来访问可执行文件(如%systemroot%/system32/ntoskrnl.exe)的符号调试信息,提取所需函数/结构体的RVA并添加到nt的地址。

比如,可以使用Psapi的EnumDeviceDrivers获取ntoskrnl.exe的地址,使用SymFromName获取函数/结构体的符号信息。

如果目标系统没有任何含有调试信息的可执行文件,如symchk.exe,SymSrv.dll等。那么,要获取包含ntoskrnl.exe调试信息的.pdb文件,则需要使用symchk.exe手动下载:

01.png

我们可以在Windows 10的C:\Program Files (x86)\Windows Kits\10\Debuggers\x64目录下找到symchk.exe,它会用到SymbolCheck.dll、SymSrv.dll和DbgHelp.dll。

将所有必要的文件嵌入到一个可执行文件,并在运行时将其提取出来是一个不错的选择。我们需要用到下列可执行文件:DbgHelp.dll、SymbolCheck.dll、symchk.exe和SymSrv.dll

示例源代码如下所示:

// Get ntoskrn.exe address at run-time
VOID* krnlAddr; // First element of the array is base address of ntoskrnl.exe/nt
DWORD retLen;
if (!EnumDeviceDrivers(&krnlAddr, sizeof(krnlAddr), &retLen))
         return GetLastError();
HANDLE procHandle = GetCurrentProcess();
if (!SymInitialize(procHandle, nullptr, FALSE)) {
         auto er = GetLastError();
         printf_s("SymInitialize error: %d\n", er);
         return er;
}
ULONG64 buffer[(sizeof(SYMBOL_INFO) +
         MAX_SYM_NAME * sizeof(TCHAR) +
         sizeof(ULONG64) - 1) /
         sizeof(ULONG64)];
PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
STARTUPINFO sa{ sizeof(STARTUPINFO)};
PROCESS_INFORMATION pi{};
TCHAR execSrv[] = L"./symchk.exe /v c:\\Windows\\System32\\ntoskrnl.exe /oc .";
if (!CreateProcess(nullptr, execSrv, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &sa, &pi)) {
         return GetLastError();
}
WaitForSingleObject(pi.hProcess, INFINITE);
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
pSymbol->MaxNameLen = MAX_SYM_NAME;
CHAR symName[MAX_SYM_NAME] = "KeServiceDescriptorTable"; // change it
auto baseAddr = SymLoadModuleEx(procHandle, nullptr, "%systemroot%\\system32\\ntoskrnl.exe", nullptr, 0, 0, nullptr, 0);
if (!SymFromName(procHandle, symName, pSymbol)) {
         auto er = GetLastError();
         if (er == ERROR_MOD_NOT_FOUND)
                   puts("The specified module could not be found\n");
         printf_s("SymFromName error: %d\n", er);
         return er;
}
auto symAddr = (pSymbol->Address - baseAddr) + (DWORD64)krnlAddr;
printf_s("Virtual address of %s: 0x%llx\n", symName, symAddr);
SymCleanup(procHandle);

本方法的优点:

· 在正确的环境下,我们能得到准确的信息

· 跨平台

本方法的缺点:

· 我们需要用到用户模式进程

· 我们需要互联网连接

· 由于包含多个可执行文件,用户模式应用程序占用空间比较大

感谢您阅读本文…

源链接

Hacking more

...