1、首先通过FS寄存器获取到TEB的地址
2、在TEB偏移为0x30处的成员是PEB
3、PEB偏移为0xC处的成员是PEB_LDR_DATA结构体指针
4、PEB_LDR_DATA结构体偏移为0x1C处成员为InInitializationOrderModuleList,初始化模块链表,这个成员保存的是模块链表的头部地址。
5、通过InInitializationOrderModuleList模块链表可以获得按照顺序加载到进程内存空间的模块,其中第一个始终是ntdll.dll,根据系统的不同,可能第二个加载的模块是kernel32.dll或者kernelbase.dll。
6、无论加载的是kernel32.dll还是kernelbase.dll,其导出表中都有GetProcAddress函数的地址。
汇编代码对应如下:
mov esi,dword ptr fs:[0x30] //esi = PEB的地址
mov esi,[esi+0xC] //esi = 指向PEB_LDR_DATA的结构体指针
mov esi,[esi+0x1C] //esi = 模块链表指针InInitializationOrderModuleList
mov esi,[esi] //esi = 访问链表中的第二个模块
mov ebx,[esi+0x08] //获取Kernel32.dll基址(访问DllBase成员)
7、从kernel32.dll模块中获取DOS头,NT头等信息
8、获取NT中扩展头最后一个字段数据目录表,然后得到导出表项
9、获取导出表的详细信息
10、循环获取ENT(Export Name Table)中的函数名,暂不考虑以序号导出的情况,先以函数名为基准,传入GetProcAddress与ENT中的函数名比较,匹配成功以后,获取相应的序号值,并以此为索引在EAT(Export Address Table)中获取相应的函数地址。
附上部分关键代码:
DWORD dwFunAddr;
for (DWORD = 0;i < pExport->NumberofNames;i++)
{
PCHAR lpFunName = (PCHAR)(pAddofNames[i] + dwAddrBase);
if(!strcmp(lpFunName,"GetProcAddress"))
{
dwFunAddr = pAddrOfFun[pAddrOfOrdinals[i]] + dwAddrBase;
break;
}
if(i == pExport->NumberOfNames - 1)
return 0;
}
return dwFunAddr;