系统调用与函数地址动态寻找(详解版)

双机调试

F9,进入程序领空,搜索所有用户模块的跨模块调用,F2下断点

x64Dbg:F7单步步入,F8单步步过

进入内核的方式:

  1. int 2E(比较早期)
  2. sysenter(x86)
  3. syscall(x64)

进内核的时候,通常会带一个参数(最基本的是系统服务号)

SSDT表:系统描述符表

系统服务号作用:在内核中的SSDT表中进行对照寻找,实际上就是SSDT的索引

在SSDT中寻找对应的内核函数

x86App:

x86 OS:

  1. exe调用OpenProcess


  2. F7跟进去,kernel32.dll进行中转


  3. kernelbase.dll调用ZwOpenProcess(和NTOpenProcess基本没什么区别)


  4. F7,ntdll.dll里,KiFastSystemCall,F7,sysenter进入内核

x64 OS:

  1. exe调用OpenProcess
    系统调用与函数地址动态寻找(详解版)_第1张图片

  2. F7跟进去,kernel32.dll进行中转
    系统调用与函数地址动态寻找(详解版)_第2张图片

  3. kernelbase.dll调用NtOpenProcess
    系统调用与函数地址动态寻找(详解版)_第3张图片

  4. Ntdll.dll调用Wow64,为64位系统下的32位程序实现一个模拟的32位,转发,调用64位的东西
    系统调用与函数地址动态寻找(详解版)_第4张图片

x64App

  1. exe调用OpenProcess
    系统调用与函数地址动态寻找(详解版)_第5张图片

  2. F7跟进去,kernel32.dll进行中转
    系统调用与函数地址动态寻找(详解版)_第6张图片

  3. kernelbase.dll调用ZwOpenProcess(和NTOpenProcessProcess基本没什么区别)
    系统调用与函数地址动态寻找(详解版)_第7张图片

  4. ntdll.dll调用syscall/int 2E(这里会做判断)
    系统调用与函数地址动态寻找(详解版)_第8张图片

壳:在目标体内加区段,但是我们不能确定目标程序内有没有包含我们需要的头文件或者动态链接库,那么我们就需要自己加载动态链接库,但是我们要加载动态链接库的时候,LoadLibraryAPI 在哪里?我们就要想办法找到API地址

  • TEB(线程环境块)
  • PEB(进程环境块)

描述进程与线程的一些关键成员

  1. 寻找kernel32.dll的地址
  2. 在kernel32.dll的导出表里寻找GetProcAddress

双击调试断下来:

WinDbg输入命令:

  1. ! Process 0 0 (显示简略的系统进程相关信息)
    在这里插入图片描述

    这里的INSTDRV.EXE是我们启动的exe

  2. 复制我们拿到的地址,输入命令:.process /i 0x…(切换到指定进程上下文)

    系统调用与函数地址动态寻找(详解版)_第9张图片

    注意这里执行命令之后摁一下F5

  3. ! process(查看进程信息)
    加粗样式

  4. dt _TEB 0x…(拿到线程(TEB)结构)(dt:查看某一个结构)

    在+0x30位置有一个PEB(所属进程的进程环境块信息)
    系统调用与函数地址动态寻找(详解版)_第10张图片
    在+0x30的位置有我们需要的PEB信息

  5. dt _PEB 0x…(使用拿到的PEB地址,查看PEB结构)
    系统调用与函数地址动态寻找(详解版)_第11张图片

    在+0xC的位置有我们需要的_PEB_LDR_DATA信息

  6. dt _PEB_LDR_DATA ox…(查看_PEB_LDR_DATA结构)
    系统调用与函数地址动态寻找(详解版)_第12张图片

  7. dt _LIST_ENTRY查看模块列表
    在这里插入图片描述

    _LIST_ENTRY:有一个Flink(向下的指针),和一个Blink(向上的指针),双向链表

  8. 查看_LDR_DATA_TABLE_ENTRY结构(第一个节点)(向下的指针)
    系统调用与函数地址动态寻找(详解版)_第13张图片

这样就找到了进程本身

  1. 继续找下一个节点查看 dt _LDR_DATA_TABLE_ENTRY结构(第一个节点)(向下的指针)ntdll.dll

  2. 继续找,找到了kernel32.dll

  3. 这样我们就可以根据kernek32.dll,遍历导出表,就可以找到GetProcAddress函数地址

动态寻找函数地址

我们根据上述调试过程,我们来写代码实现:

x86:

// 函数地址动态查找.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include 
#include 

//获取Kernel32地址
DWORD GetKernel32Address();
//获取API地址
DWORD GrkGetProcessAddress();

//函数指针
EXTERN_C typedef HMODULE
(WINAPI* fnLoadLibraryW)(
	_In_ LPCWSTR lpLibFileName
);

EXTERN_C typedef FARPROC
(WINAPI* fnGetProcAddress)(
	_In_ HMODULE hModule,
	_In_ LPCSTR lpProcName
);

EXTERN_C typedef int
(WINAPI* fnMessageBoxW)(
	_In_opt_ HWND hWnd,
	_In_opt_ LPCWSTR lpText,
	_In_opt_ LPCWSTR lpCaption,
	_In_ UINT uType);

EXTERN_C typedef VOID
(WINAPI* fnExitProcess)(
	_In_ UINT uExitCode
);

int main()
{
	fnGetProcAddress pfnGetProcAddress = (fnGetProcAddress)GrkGetProcessAddress();
	HMODULE hKernel32 = (HMODULE)GetKernel32Address();
	fnLoadLibraryW pfnLoadLibraryW = (fnLoadLibraryW)pfnGetProcAddress(hKernel32, "LoadLibraryW");
	HMODULE hUser32 = pfnLoadLibraryW(L"user32.dll");
	fnMessageBoxW pfnMessageBoxW = (fnMessageBoxW)pfnGetProcAddress(hUser32, "MessageBoxW");
	fnExitProcess pfnExitProcess = (fnExitProcess)pfnGetProcAddress(hKernel32,"ExitProcss");
	pfnMessageBoxW(NULL, L"WdIg", L"Msg", NULL);
	system("pause");
	return 0;
}

DWORD GetKernel32Address() {
	DWORD dwKernel32 = 0;
	//NtCurrentTeb方法返回当前线程的线程环境块(TEB)指针
	_TEB *pTeb = NtCurrentTeb();
	//这里就是从线程环境块中找到进程,在线程环境块的+0x30位置
	PDWORD pPeb = (PDWORD)*(PDWORD)((DWORD)pTeb + 0x30);
	//从进程环境块中找到PEB_LDR_DATA信息,在进程环境块中的+0xC位置
	PDWORD pLdr = (PDWORD)*(PDWORD)((DWORD)pPeb + 0xC);
	//这里就是找到_LIST_ENTRY列表
	PDWORD InLoadOrderModuleList = (PDWORD)((DWORD)pLdr + 0xC);
	//这里是找到exe模块
	PDWORD pModuleExe = (PDWORD)*InLoadOrderModuleList;
	//找到Ntdll模块
	PDWORD pModuleNtdll = (PDWORD)*pModuleExe;
	//找到Kernel32模块
	PDWORD pModuleKernel32 = (PDWORD)*pModuleNtdll;
	dwKernel32 = pModuleKernel32[6];
	return dwKernel32;
}

DWORD GrkGetProcessAddress() {
	//这里是我们自己写的函数,得到Kernel32基址
	DWORD dwBase = GetKernel32Address();
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwBase;
	PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
	PIMAGE_OPTIONAL_HEADER  pOptionalHeader = &pNtHeaders->OptionalHeader;
	//寻找Kernel32的导出表,以找到我们需要的函数
	PIMAGE_DATA_DIRECTORY pExportDir = &(pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);
	PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(dwBase + pExportDir->VirtualAddress);
	//遍历导出表,找到我们需要的函数地址
	DWORD dwFuncCount = pExport->NumberOfFunctions;
	DWORD dwFuncNameCount = pExport->NumberOfNames;
	//导出地址表
	PDWORD pEAT = (PDWORD)(dwBase + pExport->AddressOfFunctions);
	//导出名称表
	PDWORD pENT = (PDWORD)(dwBase + pExport->AddressOfNames);
	//导出序号表
	PWORD pEIT = (PWORD)(dwBase + pExport->AddressOfNameOrdinals);
	for (int i = 0; i < pExport->NumberOfNames; i++)
	{
		if (!pENT[i]) {
			continue;
		}
		char* szFuncName = (char*)(dwBase + (DWORD)pENT[i]);
		DWORD dwOrdinal = 0;
		if (strcmp(szFuncName, "GetProcAddress") == 0) {
			dwOrdinal = pEIT[i];
			DWORD dwFuncAddrOffset = pEAT[dwOrdinal];
			return dwBase + pEAT[dwOrdinal];
		}
	}
	return 0;
}

这里需要格外注意:导出函数名称表是WORD类型的数组

x64:

x64的双机内核调试过程跟x86几乎没有差别

64位由于寻址长度与32位不同,我们将地址长度修改为ULONGLONG即可

你可能感兴趣的:(滴水逆向三期PE文件结构学习,windows,安全)