入门向:初探PE文件
PE文件结构中一些重要的字段
<winnt.h>中含有对应的结构体
DOS头
e-ifanew:标识了NT头的位置
PE头(NT头)
IMAGE_NT_HEADER
ImageBase:内存镜像基址
SectionAlignment:内存对齐
FileAlignment:文件对齐
MajorSubsystemVersion:标识系统版本
MinorSubsystemVersion:标识系统版本
SizeOfImage:整个PE头映射到内存中的大小(SectionAlignment的倍数-对齐)
SizeOfHeaders:所有头+节展开后的大小
CheckSum:校验和
NumbrOfSections:节的数目
后面带有一些版本信息,以及编译的方式,包括dll的属性
解析PE文件
//需要包含头文件#include<winnt.h>
//加载dll
HMODULE hdll = LoadLibraryA("user32.dll");
char* chhdll = (char*)hdll;
//装入DOS头
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)chhdll;
//装入NT头(在原有基础上加上e-ifanew得到NT头位置)
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(chhdll + dosHeader->e_lfanew);
节表
在nt后面是节表
首个节.bss节,运行时才存在
节需要对齐(文件对齐、内存对齐)
Virtual Address是加载入内存后相对于基址的位置(需要考虑上个节的对齐)
Raw Address是文件中的位置(无bss节占位置)
VirtualSize:内存中大小(内存对齐前的长度)
VirtualAddress:内存中偏移
SizeOfRawData:文件中大小(文件对齐后的长度)
PointerToRawData:文件中偏移
对齐、基址、RVA、VA、FA 是什么
对齐:分为内存对齐和文件对齐,操作系统分配空间时(磁盘空间/内存空间)并不是精确到1个字节的,而是会一大块一大块分配
假如磁盘对齐是512,内存对齐为1024
大小为300的内容存入磁盘中,需要耗费512的容量
此内容加载入内存需要耗费1024的内存大小为1200的内容存入磁盘中,需要耗费 513 x 3 = 1536 的容量
此内容加载入内存需要耗费1024 x 2 =2048 的内存
基址:程序加载进内存中时,(默认)会有一个随机的基址,以这个基址为首加载内容
fa:文件地址(磁盘中)
va:虚拟地址(内存中)
rva:相对虚拟地址
rva = va + 基址
C语言读写文件
读文件
//打开文件
char exeFileAddress[] = "C:\\Users\\Tring.exe";
FILE* exe = NULL;
fopen_s(&exe, exeFileAddress, "rb");
//获取长度
fseek(exe, 0, SEEK_END);
long long flen1 = ftell(exe);
fseek(exe, 0, SEEK_SET);
//新建一个buf用来读取文件(malloc)
char* buf = (char*)malloc(flen1);
//读取文件内容
fread(buf, 1, flen1, exe);
fclose(exe);
写文件
char* shellcode = (char*)malloc(flen);
//保存文件
FILE* saveFile = NULL;
fopen_s(&saveFile, "C:\\Users\\2.exe", "wb");
fwrite(shellcode, 1, flen, saveFile);
fclose(saveFile);
C语言操作PE头和节表
解析PE头内容
#include <stdio.h>
#include <Windows.h>
int main()
{
//打开文件
char exeFileAddress[] = "C:\\Users\\Hello.exe";
FILE *exe = NULL;
fopen_s(&exe,exeFileAddress,"rb");
//获取长度
fseek(exe, 0, SEEK_END);
long long flen1 = ftell(exe);
fseek(exe, 0, SEEK_SET);
//新建一个buf用来读取文件(malloc)
char* buf = (char*)malloc(flen1);
//读取文件内容
fread(buf, 1, flen1, exe);
//解析dos头
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)buf;
printf("dos->e_magic: %x\n", dos->e_magic);
printf("dos->e_lfanew: %x\n", dos->e_lfanew);
//解析nt头
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(buf + dos->e_lfanew);
printf("nt->Signature: %x\n",nt->Signature);
//解析节表
PIMAGE_SECTION_HEADER section = (PIMAGE_SECTION_HEADER)(nt + 1);
printf("sum section: %d\n", nt->FileHeader.NumberOfSections);
//打印每个节表名
for (int i = 0; i < nt->FileHeader.NumberOfSections; i++) {
printf("\nsection->Name: %s\n",(section+ i)->Name);
printf("section->VirtualAddress: %x\n", (section + i)->VirtualAddress);
printf("section->Misc.VirtualSize: %x\n", (section + i)->Misc.VirtualSize);
printf("section->SizeOfRawData: %x\n", (section + i)->SizeOfRawData);
printf("section->PointerToRawData: %x\n", (section + i)->PointerToRawData);
printf("section->Characteristics: %x\n", (section + i)->Characteristics);
}
return 0;
}
给末尾节添加内容
#include <stdio.h>
#include <Windows.h>
int main()
{
//打开文件
char exeFileAddress[] = "C:\\Users\\Hello.exe";
FILE *exe = NULL;
fopen_s(&exe,exeFileAddress,"rb");
//获取长度
fseek(exe, 0, SEEK_END);
long long flen1 = ftell(exe);
fseek(exe, 0, SEEK_SET);
//新建一个buf用来读取文件(malloc)
char* buf = (char*)malloc(flen1);
//读取文件内容
fread(buf, 1, flen1, exe);
//解析dos头
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)buf;
printf("dos->e_magic: %x\n", dos->e_magic);
printf("dos->e_lfanew: %x\n", dos->e_lfanew);
//解析nt头
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(buf + dos->e_lfanew);
printf("nt->Signature: %x\n",nt->Signature);
//解析节表
PIMAGE_SECTION_HEADER section = (PIMAGE_SECTION_HEADER)(nt + 1);
printf("sum section: %d\n", nt->FileHeader.NumberOfSections);
//打印每个节表名
for (int i = 0; i < nt->FileHeader.NumberOfSections; i++) {
printf("\nsection->Name: %s\n",(section+ i)->Name);
printf("section->VirtualAddress: %x\n", (section + i)->VirtualAddress);
printf("section->Misc.VirtualSize: %x\n", (section + i)->Misc.VirtualSize);
printf("section->SizeOfRawData: %x\n", (section + i)->SizeOfRawData);
printf("section->PointerToRawData: %x\n", (section + i)->PointerToRawData);
printf("section->Characteristics: %x\n", (section + i)->Characteristics);
}
//打开文件
char addFileAddress[] = "C:\\Users\\add.txt";
FILE* addFile = NULL;
fopen_s(&addFile, addFileAddress, "rb");
//获取长度
fseek(addFile, 0, SEEK_END);
long long flen2=ftell(addFile);
fseek(addFile, 0, SEEK_SET);
//新建一个buf2用来读取文件(VirtualAlloc)
char* buf2=(char*)VirtualAlloc(NULL, flen2, MEM_COMMIT, PAGE_READWRITE);
fread(buf2, 1, flen2, addFile);
//判断是否刚好对齐,并计算出是否多出一个对齐区,adlen为对齐区数量
int adlen =(flen2 + ((nt->OptionalHeader.FileAlignment - (flen2 % nt->OptionalHeader.FileAlignment)) % nt->OptionalHeader.FileAlignment)) / nt->OptionalHeader.FileAlignment;
int duiqi = adlen * nt->OptionalHeader.FileAlignment;
printf(" 文件对齐大小 %d\n", nt->OptionalHeader.FileAlignment);
printf(" 新内容大小 %d\n 新内容需要对齐区 %d 个\n 对齐后总大小 %#.2x\n",flen2, adlen, duiqi);
//修改节表中文件大小的相关字段(section + nt->FileHeader.NumberOfSections-1)为最后一个节
(section + nt->FileHeader.NumberOfSections - 1)->SizeOfRawData += adlen * nt->OptionalHeader.FileAlignment;
(section + nt->FileHeader.NumberOfSections - 1)->Misc.VirtualSize += flen2;
//修改内存载入后的总大小(DWORD SizeOfImage整个PE文件映射到内存中的大小,可能会大于实际的值,需为SectonAlignment的整数倍)
nt->OptionalHeader.SizeOfImage += nt->OptionalHeader.SectionAlignment*(flen2 + ((nt->OptionalHeader.SectionAlignment - (flen2 % nt->OptionalHeader.SectionAlignment)) % nt->OptionalHeader.SectionAlignment)) / nt->OptionalHeader.SectionAlignment;
//buf3为对齐后要新加的数据(新数据进行文件对齐扩充)
char* buf3 = (char*)malloc(duiqi);
memcpy(buf3, buf2, flen2);
//save为扩展后的buf(开辟一个新空间,把老文件和新文件合并在一起)
char* save = (char*)malloc(flen1+duiqi);
//合并
memcpy(save, buf, flen1);
memcpy(save+flen1, buf3, duiqi);
//保存文件
FILE* saveFile = NULL;
fopen_s(&saveFile,"C:\\Users\\2.exe","wb");
fwrite(save,1, flen1 + duiqi,saveFile);
fclose(saveFile);
/*
buf读入exe
buf2读入追加的文件
buf3为buf扩容版(文件对齐)
save用于合并buf和buf3
*/
return 0;
}
C语言rva/va/fa之间转换
rva -> fa:
-
求出rva在第几个节(内存中)
-
rva相对于该节的偏移量 = rva - 该节内存中首地址
-
fa = 该节fa + rva相对于该节的偏移量
fa -> rva:
-
求出fa在第几个节(文件中)
-
fa相对于该节的偏移量 = fa - 该节文件中首地址
-
rva = 该节rva + fa相对于该节的偏移量
va / rva(在未开启随机基址的情况下,直接加减imagesbase即可互相转换)
#include <stdio.h>
#include <Windows.h>
void PEop2();
int rva2fa(long long rva, PIMAGE_SECTION_HEADER pSECTION, PIMAGE_NT_HEADERS nt);
int va2fa (long long rva, PIMAGE_SECTION_HEADER pSECTION, PIMAGE_NT_HEADERS nt);
int fa2rva(long long rva, PIMAGE_SECTION_HEADER pSECTION, PIMAGE_NT_HEADERS nt);
int main()
{
PEop2();
return 0;
}
void PEop2() {
char exeFileAddress[] = "C:\\Users\\name\\Desktop\\12.exe"; //文件名
FILE* exe ; //申明一个文件指针
fopen_s(&exe, exeFileAddress, "rb"); //打开文件
fseek(exe, 0, SEEK_END); //将指针移动到文件末尾
long long flen1 = ftell(exe); //获取指针位置(文件长度)
fseek(exe, 0, SEEK_SET); //指针复位
char* buf = (char*)malloc(flen1); //新建一个buf用来读取文件(malloc)
fread(buf, 1, flen1, exe); //读取文件内容
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)buf; //解析dos头
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(buf + dos->e_lfanew); //解析nt头
PIMAGE_SECTION_HEADER section = (PIMAGE_SECTION_HEADER)(nt + 1); //解析节表
long long rva = 0x3010; //输入的rva
long long va = 0x403010; //输入的va
long long fa = 0x2410; //输入的fa
long long fa1 = rva2fa(rva, section, nt); //rva转化为fa
printf("\nrva to fa:%x\n\n", fa1);
long long fa2= va2fa(va, section, nt); //va转化为fa
printf("\nva to fa:%x\n\n", fa2);
long long rva1 = fa2rva(fa, section, nt); //fa转化为rva
printf("\nfa to rva:%x\n\n", rva1);
fclose(exe); //关闭文件
}
int va2fa(long long va, PIMAGE_SECTION_HEADER pSECTION, PIMAGE_NT_HEADERS nt) {
long long rva = va - nt->OptionalHeader.ImageBase; //将va转化为rva
long long r = rva2fa(rva, pSECTION, nt); //将rva转化为fa
return r;
}
int rva2fa(long long rva, PIMAGE_SECTION_HEADER pSECTION, PIMAGE_NT_HEADERS nt) {
int NumberSections = nt->FileHeader.NumberOfSections; //获取节表的数量
int VAlignment = nt->OptionalHeader.SectionAlignment; //获取内存对齐
int SECTION_len =0; //使用一个变量用来存储对齐后的节大小
for (int i = 0; i < NumberSections; i++) { //遍历节表
if (pSECTION[i].Misc.VirtualSize % VAlignment != 0) { //节长度能够整除内存对齐
SECTION_len = pSECTION[i].Misc.VirtualSize - (pSECTION[i].Misc.VirtualSize % VAlignment) + VAlignment; //节长度向上对齐
}else{ //节长度不能整除内存对齐
SECTION_len = pSECTION[i].Misc.VirtualSize;} //节长度保持不变
if (rva >= pSECTION[i].VirtualAddress && rva <= (pSECTION[i].VirtualAddress + SECTION_len)) { //判断rva是否在本节中
printf("name:%s\n", pSECTION[i].Name); //打印节名
printf("VAlignment:%d\n", VAlignment); //打印内存对齐
printf("pSECTION[i] i=%d\n", i); //打印是第几个节(从0开始)
printf("SECTION_len:%d", SECTION_len); //打印节对齐后的长度
return pSECTION[i].PointerToRawData + (rva - pSECTION[i].VirtualAddress); //返回转化后的fa
}
}
return 0;
}
int fa2rva(long long fa, PIMAGE_SECTION_HEADER pSECTION, PIMAGE_NT_HEADERS nt) {
int NumberSections = nt->FileHeader.NumberOfSections; //获取节表的数量
int FAlignment = nt->OptionalHeader.FileAlignment; //获取文件对齐
int SECTION_len = 0; //使用一个变量用来存储对齐后的节大小
for (int i = 0; i < NumberSections; i++) {
long long SECTION_len =0 ; //对齐后的节大小
if (pSECTION[i].SizeOfRawData % FAlignment != 0) { //节长度能够整除内存对齐
SECTION_len = pSECTION[i].SizeOfRawData - (pSECTION[i].SizeOfRawData % FAlignment) + FAlignment; //节长度向上对齐
}else { //节长度不能整除内存对齐
SECTION_len = pSECTION[i].SizeOfRawData;} //节长度保持不变
if (fa >= pSECTION[i].PointerToRawData && fa <= (pSECTION[i].PointerToRawData + SECTION_len)) { //判断rva是否在本节中
printf("name:%s\n", pSECTION[i].Name); //打印节名
printf("FAlignment:%d\n", FAlignment); //打印内存对齐
printf("pSECTION[i] i=%d\n", i); //打印是第几个节(从0开始)
printf("SECTION_len:%d", SECTION_len); //打印节对齐后的长度
printf("\PointerToRawData:%d", pSECTION[i].PointerToRawData); //节在文件中的偏移值
return pSECTION[i].VirtualAddress + (fa - pSECTION[i].PointerToRawData); //返回转化后的fa
}
}
return 0;
}
DLL导出函数
配置文件方式导出
.def文件的导出方式,导出dllfun函数
可以通过名称导出/序号导出(可以没有名称,但是一定有序号,没有则默认)
EXPORTS
dllfun
导出为2号函数且不导出名称
EXPORTS
dllfun @2 NONAME
定义函数时候导出
此时导出的函数名非原本函数名,会进行名称粉碎:按照参数进行特殊命名
_declspec(dllexport)int dllfun2() {
printf("run dll fun2\n");
return 6;
}
使用DLL中的函数
#include <stdio.h>
#include <Windows.h>
typedef int(_cdecl* pfun)();
int main()
{
HMODULE mydll=LoadLibraryA("D:\\hello.dll");
pfun p=(pfun) GetProcAddress(mydll, "dllfun");
printf("%d", p());
}
导出表
写了内部函数的地址,方便其他程序调用本PE文件中的函数
ps:有函数地址即可调用,导出表只是 “公开” 的函数地址
导出表中包括导出函数地址表、导出函数名称表、导出函数序号表
| 导出函数名称表 | 名称表指向的字符串 | 导出函数序号表 | 导出函数地址表 |
|---|---|---|---|
| 0x011cc2 | GetTitle | 2 | 0x10500 |
| 0x019cc2 | SayHello | 3 | 0x11911 |
| 0x01acbb | ShowName | 0 | 0x12222 |
| 0x13333 |
导出了4个函数,其中有一个名为GetTitle,序号为2,地址在0x12222(地址表第3个)
另外3个函数以此类推
Forward:
当导出函数地址处于导出表中时,表示转发调用,即需要去别的DLL寻找引用的函数,此时该地址存放着对应DLL名称与函数名称,形如NTDLL.RtlZeroMemory就是去NTDLL.dll寻找函数RtlZeroMemory
ps:记录的都是rva需要转化为va才可以正常使用
C语言解析导出表
简单的解析,在LoadLibraryA后
ULONG_PTR dllBaseAddress = (ULONG_PTR)hModule; //获取地址
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)dllBaseAddress; //解析dos头
PIMAGE_NT_HEADERS NtHeader = (PIMAGE_NT_HEADERS)(dllBaseAddress + dosHeader->e_lfanew); //解析nt头
PIMAGE_EXPORT_DIRECTORY ExportForm = (PIMAGE_EXPORT_DIRECTORY)(dllBaseAddress + NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress); //获取导出表地址
DWORD* ExporFuncAddressForm = (DWORD*)(dllBaseAddress + ExportForm->AddressOfFunctions); //获取导出函数地址表的RVA
DWORD* ExporFuncNameForm = (DWORD*)(dllBaseAddress + ExportForm->AddressOfNames); //获取导出函数名称表的RVA
DWORD* ExportNumForm = (DWORD*)(dllBaseAddress + ExportForm->AddressOfNameOrdinals); //获取导出函数序号表的RVA(连接名称表与地址表)
DWORD HaveNameFuncNum = ExportForm->NumberOfNames; //有名称的导出函数的总数量
char* name = (char*)(dllBaseAddress + ExporFuncNameForm[i]); //读取名称数组的内容(RVA要先加基址)
C语言实现GetProcAddress
#include <stdio.h>
#include <Windows.h>
void ParseForwardString(char* str, char* &ToDllName, char* &ToDllFunc); //解析转发调用的字符串
void* Forwar_GetProcAddress(char* str); //通过转发字符串去获取对应的函数地址
void* Ordinal_GetProcAddress(HMODULE hModule, ULONG_PTR ProcID); //根据ID获取Proc地址
void* Name_GetProcAddress(HMODULE hModule, const char* procName); //根据名字获取Proc地址
void* MyGetProcAddress(HMODULE hModule,LPCSTR lpProcName); //自实现GetProcAddress入口
//自实现GetProcAddress入口,根据参数调用不同的方式获取ProcAddress(ID/Name)
void* MyGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
if ((ULONG_PTR)lpProcName >= 0xffff) {
return Name_GetProcAddress(hModule, lpProcName);
}
else{
return Ordinal_GetProcAddress(hModule, (ULONG_PTR)lpProcName);
}
}
//解析转发调用的字符串,形如NTDLL.RtlZeroMemory,传参str为字符串,ToDllName和ToDllFunc为指针,会malloc返回Dll名称与函数名称
void ParseForwardString(char* str, char* &ToDllName, char* &ToDllFunc) {
if (str == NULL)return; //如果指针为空则退出
int LenInfo = strlen(str); //整个信息字符串的长度,信息字符串形如 NTDLL.RtlZeroMemory (末尾有个\0)
int LenName; //Dll名字的长度
int LenFunc; //函数名字的长度
for (LenName = 0; str[LenName] != '.'; LenName++); //遍历到 "." 的位置,前半部分为Dll名字
LenFunc = LenInfo - LenName-1; //剩余的部分为函数名称(此部分包括了"."所以额外-1)
ToDllName = (char*)malloc( sizeof(char)*(LenName+5) ); //开辟空间,名称部分长度+5,用于末尾追加 ".dll\0"
for (int i = 0; i < LenName; i++)ToDllName[i] = str[i]; //将str的前半部分复制给ToDllName
ToDllName[LenName] = '.'; //末尾追加".dll\0"
ToDllName[LenName + 1] = 'd';
ToDllName[LenName + 2] = 'l';
ToDllName[LenName + 3] = 'l';
ToDllName[LenName + 4] = '\0';
ToDllFunc = (char*)malloc( sizeof(char)*(LenFunc+1) ); //开辟空间,函数名称长度+1,用于末尾追加'\0'
for (int i = 0; i < LenFunc ; i++)ToDllFunc[i] = str[LenName + i+1 ]; //将str的后半部分复制给ToDllFunc(+1用于跳过".")
ToDllFunc[LenFunc ] = '\0'; //末尾追加"\0"
//printf("ToDllName:%s\nToDllFunc:%s\n", ToDllName, ToDllFunc); //打印信息,可用于调试
return;
}
//通过转发字符串去获取对应的函数地址
void* Forwar_GetProcAddress(char* str) {
char* ToDllName=NULL; //用于存放跳转的Dll名称
char* ToDllFunc=NULL; //用于存放跳转的函数名称
ParseForwardString(str, ToDllName, ToDllFunc); //解析转发调用的字符串
HMODULE ForwardDll = LoadLibraryA(ToDllName); //加载新的Dll
return Name_GetProcAddress(ForwardDll, ToDllFunc); //获取函数地址
}
//根据ID获取Proc地址
void* Ordinal_GetProcAddress(HMODULE hModule, ULONG_PTR ProcID) {
/*(待改进ForwardOrAPISet)
根据对应模块导出的函数ID获取Address
hModule 被导入的模块(将读取它的导出表)
ProcID ULONG_PTR(4个字节无符号,代表导出表的函数ID)
*/
ULONG_PTR dllBaseAddress = (ULONG_PTR)hModule; //获取地址
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)dllBaseAddress; //解析dos头
PIMAGE_NT_HEADERS NtHeader = (PIMAGE_NT_HEADERS)(dllBaseAddress + dosHeader->e_lfanew); //解析nt头
PIMAGE_EXPORT_DIRECTORY ExportForm = (PIMAGE_EXPORT_DIRECTORY)(dllBaseAddress + NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress); //获取导出表地址
DWORD* ExporFuncAddressForm = (DWORD*)(dllBaseAddress + ExportForm->AddressOfFunctions); //获取导出函数地址表的RVA
DWORD BaseProcID = ExportForm->Base; //获取导出函数的起始编号
if (ProcID < BaseProcID || ProcID >= BaseProcID + ExportForm->NumberOfFunctions) return NULL; //procID错误
if (ExporFuncAddressForm[ProcID - BaseProcID] >= NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress) { //若函数地址在导出表中(大于开头,小于结尾)
if (ExporFuncAddressForm[ProcID - BaseProcID] <= NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress + NtHeader->OptionalHeader.DataDirectory[0].Size) {
//这里还要改,函数转发,apiset
return Forwar_GetProcAddress((char*)(ExporFuncAddressForm[ProcID - BaseProcID])); //转发调用
}
}
else {
return (void*)((ULONG_PTR)hModule + (ExporFuncAddressForm[ProcID - BaseProcID])); //返回proc地址
}
}
//根据名字获取Proc地址
void* Name_GetProcAddress (HMODULE hModule,const char* procName) {
ULONG_PTR dllBaseAddress = (ULONG_PTR)hModule; //获取地址
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)dllBaseAddress; //解析dos头
PIMAGE_NT_HEADERS NtHeader = (PIMAGE_NT_HEADERS)(dllBaseAddress + dosHeader->e_lfanew); //解析nt头
PIMAGE_EXPORT_DIRECTORY ExportForm = (PIMAGE_EXPORT_DIRECTORY)(dllBaseAddress + NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress); //获取导出表地址
DWORD* ExporFuncAddressForm = (DWORD*)(dllBaseAddress + ExportForm->AddressOfFunctions); //获取导出函数地址表的RVA
DWORD* ExporFuncNameForm = (DWORD*)(dllBaseAddress + ExportForm->AddressOfNames); //获取导出函数名称表的RVA
unsigned short* ExportNumForm = (unsigned short*)(dllBaseAddress + ExportForm->AddressOfNameOrdinals); //获取导出函数序号表的RVA(连接名称表与地址表,目标大小为1字节)
DWORD BaseProcID = ExportForm->Base; //获取导出函数的起始编号
DWORD HaveNameFuncNum = ExportForm->NumberOfNames; //有名称的导出函数的总数量
for (int i = 0; i < HaveNameFuncNum; i++) { //遍历全部有名称的导出函数
char* name = (char*)(dllBaseAddress + ExporFuncNameForm[i]); //读取名称数组的内容(RVA要先加基址)
//printf("%s\n", name);
if (strcmp(name, procName) == 0) { //判断是名称表中哪个函数名与传入的函数名相同
unsigned short m = ((unsigned short*)(ExportNumForm))[i];
if (ExporFuncAddressForm[m] >= NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress) { //若函数地址在导出表中(大于开头,小于结尾)
if (ExporFuncAddressForm[m] <= NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress + NtHeader->OptionalHeader.DataDirectory[0].Size) {
void* xx=(void*)((ULONG_PTR)hModule + (DWORD)(ExporFuncAddressForm[m]));
char* yy = (char*)(hModule + ExporFuncAddressForm[m]);
//printf("\nExporFuncAddressForm[m]%x\n", ExporFuncAddressForm[m]);
//printf("hModule %x\n", hModule);
//printf("(void*)((ULONG_PTR)hModule + (DWORD)(ExporFuncAddressForm[m])) %x\n", (void*)((ULONG_PTR)hModule + (DWORD)(ExporFuncAddressForm[m])));
//printf("(char*)(hModule+ExporFuncAddressForm[m])e %x\n", (char*)((char*)hModule + ExporFuncAddressForm[m]));
return Forwar_GetProcAddress((char*)((ULONG_PTR)hModule+ExporFuncAddressForm[m])); //转发调用(转char*是为了使加减法步长为1)
}
}
else {
return (void*)((ULONG_PTR)hModule + (DWORD)(ExporFuncAddressForm[m]));
}
}
}
return NULL;
}
int main()
{
printf("Hello导出表,GetProcAddress...\n");
HMODULE hdll = LoadLibraryA("kernel32"); //加载kernel32.dll
void* pfun0 = GetProcAddress(hdll, (char*)286); //获取ProcAddress
printf("系统GetProcAddress(id) : %016x\n", pfun0); //打印数据
void* pfun2 = GetProcAddress(hdll, "RtlZeroMemory"); //获取ProcAddress
printf("系统GetProcAddress(name) : %016x\n", pfun2); //打印数据
printf("________________________________________________\n");
printf("自实现id的GetProcAddress : %016x\n", Ordinal_GetProcAddress(hdll, 286)); //自实现GetProcAddress
printf("自实现name的GetProcAddress: %016x\n", Name_GetProcAddress(hdll, "RtlZeroMemory")); //自实现GetProcAddress
printf("自实现GetProcAddressc(id) : %016x\n", MyGetProcAddress(hdll, (char*)286)); //自实现GetProcAddress
printf("自实现GetProcAddressc(name): %016x\n", MyGetProcAddress(hdll, "RtlZeroMemory")); //自实现GetProcAddress
return 0;
}
导入表
有一张记录了模块名称和函数名称的表格,并且预留了函数地址的存放位置
在程序开始运行的时候,系统会自动去寻找对应模块的函数地址填入表格中
程序运行过程中可以直接调用对应函数,而无需动态调用GetProcAddress
导入表有多个表项,每个表项对应一个模块(简单理解为1个dll)以及该模块所有被导入的函数信息。最后会有一个全0的表项标识导入表结束
c语言解析导入表
#include <Windows.h>
#include <stdio.h>
int main()
{
HMODULE hModule= GetModuleHandle(NULL); //获取自身模块
ULONG_PTR moduleBaseAddress = (ULONG_PTR)hModule; //获取起始地址(转为ULONG_PTR方便后续运算)
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)moduleBaseAddress; //解析dos头
PIMAGE_NT_HEADERS NtHeader = (PIMAGE_NT_HEADERS)(moduleBaseAddress + dosHeader->e_lfanew); //解析nt头
PIMAGE_IMPORT_DESCRIPTOR FirstImportForm = (PIMAGE_IMPORT_DESCRIPTOR)(moduleBaseAddress + NtHeader->OptionalHeader.DataDirectory[1].VirtualAddress); //获取第一个导入表地址
int NumOfImportForm = 0;
for (NumOfImportForm = 0; (FirstImportForm + NumOfImportForm)->Name != 0; NumOfImportForm++); //获取导入表个数
printf("导入表的个数 %d\n", NumOfImportForm); //解析导出表
PIMAGE_IMPORT_DESCRIPTOR ImportForms = FirstImportForm ; //指向导出表
for (int n = 0; n < NumOfImportForm; n++) {
printf("\n当前导入表序号 %d (由0开始计数)\n", n); //解析导入表
printf("当前导入表的名称的RVA 0x%x\n", ImportForms[n].Name);
printf("当前导入表的名称的值 %s\n", moduleBaseAddress + ImportForms[n].Name);
void** FuncAddressForm = (void**)(moduleBaseAddress+ImportForms[n].FirstThunk); //获取函数地址表(void*为函数地址、void**为地址表首元素地址)
PIMAGE_THUNK_DATA FuncNamesForm = (PIMAGE_THUNK_DATA)(moduleBaseAddress + ImportForms[n].OriginalFirstThunk); //获取第n个导入表的函数名称表数组(数组内容为一个包含函数名称rva的结构体)
for (int i = 0; FuncNamesForm[i].u1.AddressOfData != 0; i++) { //内部字符串rva不为0则说明有下一个函数名(即数组还未结束)
PIMAGE_IMPORT_BY_NAME FuncName = (PIMAGE_IMPORT_BY_NAME)(moduleBaseAddress + FuncNamesForm[i].u1.AddressOfData); //n与i的含义:第n张导入表中的第i个函数
printf(" 函数名称 %s\n", FuncName->Name); //只打印名称,不打印hint
printf(" x64函数地址(8字节) %p\n\n", FuncAddressForm[i]); //%p中#为16进制,p为8字节(在32位程序中应改为%x 4字节)
}
printf("____________________________________________________________");
}
/*******************************************************************************************************************************************************************************************
//第二种获取导入表的方法
PIMAGE_IMPORT_DESCRIPTOR* ImportForms = (PIMAGE_IMPORT_DESCRIPTOR*)malloc(sizeof(PIMAGE_IMPORT_DESCRIPTOR)*NumOfImportForm); //根据导入表个数开辟对应大小的数组
for (int i = 0; i < NumOfImportForm; i++) { //将每个导入表进行装填
ImportForms[i]= (PIMAGE_IMPORT_DESCRIPTOR)(FirstImportForm + i);
}
printf("导入表的个数 %d\n", NumOfImportForm); //解析导入表
printf("第i个导入表的名称的RVA 0x%x\n", ImportForms[1]->Name);
printf("第i个导入表的名称的值 %s\n", moduleBaseAddress + ImportForms[1]->Name);
*******************************************************************************************************************************************************************************************/
system("pause");
}

浙公网安备 33010602011771号