时间:2025-04-12 22:40
人气:
作者:admin
https://bpsend.net/thread-377-1-1.html
通过cff , depends灯等软件可以看到dll,导出函数的信息,因为dll中本身就存了这些信息,存了dll中有哪些导出函数,导出函数的序号是什么,名字是什么,以及他们的地址是什么,这些东西都存在导出表里面
| 序号 | 地址 |
|---|---|
| 1 | 1005 |
| 2 | 2005 |
| 3 | 3005 |
| 4 | 4005 |
| 5 | 5005 |
| 6 | 6005 |
| 7 | 7005 |
| 8 | 8005 |
但是上面的时间复杂度比较高是线性阶,但是对数阶占的体积比较大,所以只能折中,用常量阶.把地址作为数组,序号作为索引,只存地址,这样不仅速度更加快了,而且体积更小了
但是所以是从0开始的,因此可以数组首项加一个空
这样可以通过序号,直接去找对应数组对应的索引的值
但是上面有一个缺陷,序号不一定从0开始,例如 从 1001开始,这样数组不能前面加1000项0,这样不仅浪费空间,而且还用不上,解决办法是加一个 基址 (最小序号), 后面序号就根据基址来取偏移
例如上面,要拿到 序号1006 的函数地址, 可以 获取 1006对 1001 的偏移 5 ,再去数组取 索引为5的地址
解决了上面基本不从0开始的情况,还有一个问题,就是 序号不连续 ,例如 1001 , 1002 , 1020,1021,1050
这种情况下,数组中间对应的序号就需要填充0,因此会使体积变大,微软并没有解决这个问题,因为这是写dll的人自己的问题
例如
可以看出大小是38kb
可以看到此时dll大小变成了 346KB
序号跟成名2个表是拆开的,并不在一起,因此 基数数 ,导出地址表 ,导出名称表 ,导出序号表 构成了导出表
名称和序号是一一对应的,因此导出名称个数 和导出序号个数 只需要保存一个就可以了
IMAGE_EXPORT_DIRECTORY
// IMAGE_EXPORT_DIRECTORY 导出表结构体,40B
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 无用
DWORD TimeDateStamp; // 时间戳
WORD MajorVersion; // 无用
WORD MinorVersion; // 无用
DWORD Name; // 描述性字段:'模块的名字'
//上面20字节没用,说明性的
DWORD Base; // * 序号base基数,序号导出函数的索引值值从Base开始递增
DWORD NumberOfFunctions; // * 导出地址的个数
DWORD NumberOfNames; // * 导出名称的个数
DWORD AddressOfFunctions; // * 导出地址表的地址RVA
DWORD AddressOfNames; // * 导出名称表的地址RVA,按照ASCII码固定排序
DWORD AddressOfNameOrdinals; // * 导出序号表的地址RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
数据目录第一项指针指向导出表。(第二项是导入表),该项只能说明导出表所在地址,导出表有多大并不是该Size来决定的(**但是它是导出函数是否转发的判断条件之一**)。
可以看出跟我们导出的顺序是不一样的,他进行了 函数名 ascii 码排序
在通过cff去看
此时情况是
这种情况OD可以看到 函数名是通过pdb文件知道的,删掉之后就不知道了
可以看出转发的话,导出地址表存的是一个字符串的地址,而系统需要解析这个字符串拿出dll名和函数名,继续去查
而且该地址和其他地址不一样, 该地址只需要位于 导出表地址 和 导出表地址+导出表大小 中间 那么他就是一个转出函数,如果没有位于这中间,那么他就不是一个转发函数
.586 .model flat,stdcall option casemap:none include windows.inc include user32.inc include kernel32.inc include msvcrt.inc includelib user32.lib includelib kernel32.lib includelib msvcrt.lib WinMain proto :DWORD,:DWORD,:DWORD,:DWORD .data g_szDll db "user32.dll",0 g_szFunc db "MessageBoxA",0 .code ;参数: 句柄 导出函数名 MyGetProcAddress proc hMod:HMODULE, lpProcName:LPCSTR LOCAL @pDosHdr:ptr IMAGE_DOS_HEADER ;dos头 LOCAL @pNTHdr:ptr IMAGE_NT_HEADERS ;Nt头 LOCAL @pExpDir:ptr IMAGE_EXPORT_DIRECTORY ;导出表 LOCAL @pAddrTbl:DWORD ;导出地址表地址 LOCAL @pNameTbl:DWORD ;导出名称表地址 LOCAL @pOrdTbl:DWORD ;导出序号表地址 ;解析 ;dos 头 mov eax, hMod mov @pDosHdr, eax ;nt头 mov esi, @pDosHdr assume esi:ptr IMAGE_DOS_HEADER mov eax, hMod add eax, [esi].e_lfanew mov @pNTHdr, eax mov esi, @pNTHdr assume esi:ptr IMAGE_NT_HEADERS ;获取导出表 mov esi, @pNTHdr assume esi:ptr IMAGE_NT_HEADERS mov eax, [esi].OptionalHeader.DataDirectory[0].VirtualAddress add eax, hMod mov @pExpDir, eax mov esi, @pExpDir assume esi:ptr IMAGE_EXPORT_DIRECTORY ;导出函数地址表 mov eax, [esi].AddressOfFunctions add eax, hMod mov @pAddrTbl, eax ;导出函数名称表 mov eax, [esi].AddressOfNames add eax, hMod mov @pNameTbl, eax ;导入序号表 mov eax, [esi].AddressOfNameOrdinals add eax, hMod mov @pOrdTbl, eax ;判断是序号还是名称 (序号是一个 word,对于 dword来说高位都是0) .if lpProcName & ffff0000h ;名称 mov ebx, @pNameTbl xor ecx, ecx .while ecx < [esi].NumberOfNames ;获取名称地址 mov eax, [ebx+ecx*4] add eax, hMod ;字符串比较 push ecx invoke crt_strcmp, lpProcName, eax pop ecx .if eax == 0 ;找到了, 从导出序号表取出函数地址下标 mov edi, @pOrdTbl movzx eax, word ptr [edi+ecx*2] ;从导入地址表,下标寻址,获取导出函数地址 mov ebx, @pAddrTbl mov eax, [ebx+eax*4] ;判断转发 。。。。解析函数名,递归判断 ;返回地址 .if eax != NULL add eax, hMod ret .endif .endif inc ecx .endw .else ;序号 mov eax, lpProcName sub eax, [esi].nBase ;获取索引值 ;从导入地址表,下标寻址,获取导出函数地址 mov ebx, @pAddrTbl mov eax, [ebx+eax*4] .if eax != NULL add eax, hMod ret .endif .endif xor eax, eax ret MyGetProcAddress endp start: invoke LoadLibrary, offset g_szDll invoke MyGetProcAddress,eax, offset g_szFunc push MB_OK push offset g_szDll push offset g_szFunc push NULL call eax invoke ExitProcess,0 end start