入门向:初探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");
}
posted @ 2024-08-16 09:12  aixve  阅读(68)  评论(0)    收藏  举报