一、任务
打印Windows 2003中ntdll.dll的所有函数名及其地址。
二、流程
1.获取ntdll.dll的基址
(1)汇编代码
unsigned long addr;
__asm{
mov eax, fs:30h;
mov eax, [eax+0ch];
mov ebx, [eax+1ch];
mov eax, [ebx+8];
mov addr, eax;
};
(2)解读
①mov eax, fs:30h;
此时,eax存储了当前进程PEB的首地址。
②mov eax, [eax+0ch];
此时,eax存储了PEB_LDR_DATA的首地址;
③mov ebx, [eax+1ch];
此时,ebx存储了InInitOrderModuleList的首地址。
④mov eax, [ebx+8];
InInitOrderModuleList属于LIST_ENTRY结构(可通俗理解为双链表结构),每个节点表示一个LDR_MODULE。
首个LDR_MODULE包含的是ntdll.dll,其Flink指向的LDR_MODULE包含的是kernel32.dll。
由LIST_ENTRY结构可知,[ebx+8]的内容为ntdll.dll的首地址,存储于eax中。
那要得到kernel32.dll的首地址呢?
mov ebx, [ebx];
mov eax, [ebx + 8];
⑤mov addr, eax;
把ntdll.dll的首地址存储于addr中。
(2)验证正确性
此处的操作在之前的博文中讲过,不再赘述;如有需要,请查看:
Win32的缓冲区溢出攻击(涉及用WinDbg分析 overflow函数的返回地址所在的地址与buffer首地址的距离 OFF_SET)
2.获取ntdll.dll的API
(1)源代码
#include <stdio.h>
#include <stdlib.h>
unsigned long GetNtdllFuncAddr()
{
unsigned long BaseAddrOfModule, NameOfModule;
unsigned long AddressOfFunctions, Address0fNames, NumberOfFunctions;
__asm {
mov edx, fs:30h;
mov edx, [edx+0ch];
mov edx, [edx+1ch];
mov eax, [edx+8];
mov BaseAddrOfModule,eax;
mov ebx, eax;
mov edx,[ebx+3ch];
mov edx,[edx+ebx+78h];
add edx,ebx;
mov esi,edx;
mov edx,[esi+0ch];
add edx,ebx;
mov NameOfModule,edx;
mov edx, [esi + 14h];
mov NumberOfFunctions, edx;
mov edx,[esi+1ch];
add edx,ebx;
mov AddressOfFunctions,edx;
mov edx,[esi+20h];
add edx,ebx;
mov Address0fNames,edx;
}
FILE * fp = fopen("C:\\work\\output.txt","w");
fprintf(fp, "Name of Module:%s\n\tBaseAddr of Moudle=0x%p\n",
(char *)NameOfModule, BaseAddrOfModule);
fprintf(fp, "Number of Functions is %ld\n", NumberOfFunctions);
for(int i = 0; i < NumberOfFunctions; i++)
{
fprintf(fp, "第%d个Function:\n\tAddress=0x%p\n\tName=%s\n", i + 1,
(BaseAddrOfModule + *((unsigned long *)(AddressOfFunctions) + i)),
(char *)(BaseAddrOfModule + *((unsigned long *)(Address0fNames) + i)));
}
fclose(fp);
}
void main(void)
{
GetNtdllFuncAddr();
}
(2)其中的汇编代码解读
1)获取ntdll.dll的基址(见"二、1.")
将ntdll.dll的基址存储在ebx中。
2)第2部分
①从加载地址(ebx存储的地址)开始,内存映像(PE文件的内存映像)存放的是IMAGE_DOS_HEADER结果。(通俗理解,IMAGE_DOS_HEADER的首地址即加载地址)
②mov edx, [edx+3ch];
即把e_lfanew的值存储在edx中,它值为IMAGE_NT_HEADERS的偏移
③mov edx, [edx+ebx+78h];
①由上2张图可知,要指向DataDirectory,则edx + 18h + 60h = edx + 78h;
②由于edx存储的是IMAGE_NT_HEADERS的偏移,故还需要加上基址,即ebx + edx + 78h指向的便是DataDirectory的首地址;
③[edx+ebx+78h]便是DataDirectory[0], 即VirtualAddress,指向IMAGE_EXPORT_DIRECTORY。
④add edx,ebx; mov esi,edx;
但凡是地址,都需要加上基址;完成④中的2条指令后,esi指向的便是IMAGE_EXPORT_DIRECTORY在内存中的首地址。
3)第3部分
如下图所示:
将NameOfModule, NumberOfFunctions, AddressOfFunctions, AddressOfNames读出并存储于变量中。
(3)对输出的一些解读
①图片来源博客地址将在“扩展阅读”中注明。
②以AddressOfname为例,其指向函数名字符串首地址(RVA)构成的数组的首地址,即(unsigned long *)(AddressOfNames),+i表示指向下一个 RVA,故*((unsigned long *)(AddressOfNames) + i)表示的是所指向的第i个RVA的值,即函数名字符串的首地址。
③只要是地址,就需要加上基址。
(4)结果
三、扩展阅读
1.PE文件详解七:IMAGE_EXPORT_DIRECTORY STRUCT导出表(“二、2.(3)的图片来源”)