注:这里的自实现GetProcAddress是不完美的,有一些函数例如 HeapAlloc是获取不到的,具体原因以及解决方法,请给博主一些时间,后续会进行更新
在解析之前我们需要明白PE结构:DOS头、NT头、节等
了解RVA(relative Virtual Address) 相对虚拟地址偏移、(VA (virtual Address) 虚拟地址、FA(RAW)(File Address)文件地址之间的转换
1.首先在Visual stdio中创建一个控制台文件,头文件windows.h
2.我们需要知道PE中存储的地址都是RVA所以需要转换为VA
我们定义一个函数RVATOVA
DWORD RVATOVA(DWORD RVA, DWORD hModule)
{
return RVA + hModule;
}
首先要自实现GetProcAddress 我们需要了解它的结构 ,右键F12进入GetProcAddress()观察它的结构
int main()
{
HMODULE Hmodule = GetModuleHandleA("ntdll.dll");
void *p=GetProcAddress(Hmodule,"RtlValidateHeap");
void* p1 = MyGetProcAddress(Hmodule, "RtlValidateHeap");
printf("%x", p);
std::cout << "Hello World!\n";
}
我们把这部分复制出来
FARPROC
WINAPI
MyGetProcAddress(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
)
函数中两个参数,一个句柄,一个名称
我们再来看一下导出表的结构
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
我们需要重点注意的是DWORD Name、Base(序号) 以及最后三项分别是函数地址、函数名称,函数序号的RVA。
我们先定位到导出表
PIMAGE_DOS_HEADER pIMAGE_DOS_HEADER = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pIMAGE_NT_HEADERS = (PIMAGE_NT_HEADERS)(pIMAGE_DOS_HEADER->e_lfanew + (DWORD)hModule);
PIMAGE_EXPORT_DIRECTORY pIMAGE_EXPORT_DIRECTORY_RVA = (PIMAGE_EXPORT_DIRECTORY)(pIMAGE_NT_HEADERS->OptionalHeader.DataDirectory[0].VirtualAddress);
PIMAGE_EXPORT_DIRECTORY pIMAGE_EXPORT_DIRECTORY = (PIMAGE_EXPORT_DIRECTORY)RVATOVA((DWORD)pIMAGE_EXPORT_DIRECTORY_RVA, (DWORD)hModule);//导出表项
此时我们定位到了导出表, 我们此刻就可以获取到导出模块儿的名称
DWORD MODULE_NAME = RVATOVA(pIMAGE_EXPORT_DIRECTORY->Name, (DWORD)hModule);
//导出函数名称表
DWORD NameAddressRVA = pIMAGE_EXPORT_DIRECTORY->AddressOfNames;
DWORD* NameAddress = (DWORD*)RVATOVA(NameAddressRVA, (DWORD)hModule);
//名称序号表
WORD* NameOrdinalsAddress = (WORD*)RVATOVA(pIMAGE_EXPORT_DIRECTORY->AddressOfNameOrdinals, (DWORD)hModule);
//函数地址表
DWORD* FunctionsAddress = (DWORD*)RVATOVA(pIMAGE_EXPORT_DIRECTORY->AddressOfFunctions, (DWORD)hModule);
用一个FOR循环,i<导出模块中的函数名称个数---- 用strcmp去判断我们得到的函数名称还传入的函数名称是否一致,一致则打印出来它的序号,以及地址
for (size_t i = 0; i < pIMAGE_EXPORT_DIRECTORY->NumberOfNames; i++)
{
char* FunName = (char*)RVATOVA(NameAddress[i], (DWORD)hModule);
if (strcmp(FunName, (char*)lpProcName) == 0)
{
printf("%d\n", NameOrdinalsAddress[i] + pIMAGE_EXPORT_DIRECTORY->Base);
printf("%x\n", RVATOVA(FunctionsAddress[NameOrdinalsAddress[i]], (DWORD)hModule));
}
}
运行,我们即可知道ntdll.dll中的RtlValidateHeap序号为1600 自实现函数打印出来的地址与库函数打印出的地址也一致。
接下来把文件拖入x32dbg中观察是否打印正确
此时我们就实现了通过函数名称获取句柄的操作(通过函数名称比对去获取序号表中获取函数的序号)
那么还可以通过函数序号来获取,我们提前判断一下传入的是名称还是序号---微软中使用的判断方法是看名称字符是否使用了两个字节以上的空间(0xffff),如果是则传入的是字符,否则传入的是序号。
if ((DWORD)lpProcName >= 0xffff)
printf("%x\n", RVATOVA(FunctionsAddress[NameOrdinalsAddress[(WORD)lpProcName-pIMAGE_EXPORT_DIRECTORY->Base]], (DWORD)hModule));
若传入的是序号,那我们直接打印。