移动导出表

1.为什么要移动pe结构中的表
1】这些表是编译器生成的,里面存储了非常重要的信息;
    比如,导出表中粗存了该pe文件有哪些导出函数并且这些函数的地址在哪;
2】在程序启动的时候,系统会根据这些表做初始化的工作:
    比如,将用到的DLL中的函数地址存储到IAT表中.
3】为了保护程序,可以对.exe的二进制代码进行加密操作,
    但问题是:各种表的信息与客户字节的代码和数据都混在一起了,各种表、代码、数据都在pe文件的节中
    如果进行加密,那系统在初始化的时候会出问题!因为加密后表和原来不一样无法正常初始化;
    因此一般会建一个新的节,并将pe中的表移动到新的节中,然后将代码和数据加密;
    注意:加密时pe头是不能动的,否则程序根本无法初始化;
4】总之:学会移动各种表,是对程序加密/破解的基础.
 
2.移动导出表大概步骤
第一步:在DLL中新增一个节,并返回新增后的FOA                    
第二步:复制AddressOfFunctions                     
    长度:4*NumberOfFunctions                
第三步:复制AddressOfNameOrdinals                    
    长度:NumberOfNames*2                
第四步:复制AddressOfNames                    
    长度:NumberOfNames*4                
第五步:复制所有的函数名                    
    长度不确定,复制时直接修复AddressOfNames                
第六步:复制IMAGE_EXPORT_DIRECTORY结构                    
第七步:修复IMAGE_EXPORT_DIRECTORY结构中的                    
    AddressOfFunctions                
    AddressOfNameOrdinals                
    AddressOfNames                
第八步:修复目录项中的值,指向新的IMAGE_EXPORT_DIRECTORY    
 
3.移动导出表的思路
预期目标:
    修改测试用的DllHello.dll,新增一个节 .export 将导出表放入该节;
    DllHello.dll中有4个导出函数,分别用来对两个数做加减乘除并返回结果;
    在main函数中测试修改后的dll,依然能正常使用则修改成功;
 
1)新增一个节
新增节需要的步骤:插入节表、插入节、修改pe头中的NumberOfSectons、修改可选pe头的SizeOfImage;
 
插入节表:
    判断空间是否足够,需要在节表尾部和头部结束地址(可选pe头SizeOfHeaders)之间有两个节表的空间40x2=80个字节,一个用来放新节表,另一个用来放全0的40个字节;
    也就是第一个节表中的属性节的文件偏移 (PointerToRawData - 最后一个节表的末尾地址) >= 80;
    如图:DllHello.dll中节表结束的地址为0298,SizeOfHeaders=1000;1000-0290>80;有足够的空间放节表;
    
      
    然后在最后一个节表的尾部添加新节表的数据即可;
    为了符合pe结构的规则,新节表后面的40个字节补0,可以用memset(新节表尾部地址, 0, 40)函数;
 
插入新的节:
要考虑新节的大小,新的节要能够放入:导出表、地址表、名字表、序号表、通过名字表地址找到的函数名;
    导出表是一个固定的结构,大小 = 40个字节;
    地址表 = 4字节 x 导出表中的导出函数个数NumberOfFunctions;
    名字表 = 4字节 x 导出表中的以名字导出的函数个数NumberOfNames;
    序号表 = 2字节 x NumberOfNames;
    函数名 = 遍历查找每个函数名的长度并累加;注意函数名字符串结尾的结束标志0不能丢;
 
然后再看DllHello.dll的文件对齐和内存对齐:
1000个字节足够放了,且正好满足对齐;
    
因此需要插入一个全0的1000个字节的空间到文件最后;
 
修改相关pe文件头结构属性:
    pe头中的NumberOfSectons + 1;
    可选pe头的SizeOfImage + 1000;       
 
2)移动导出表
将导出表、地址表、名字表、序号表、函数名拷贝到新节;
修改可选pe头中的数据目录中导出表的VirtualAddress执行新的导出表;注意是RVA;
新导出表的AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals ;这里是RVA;
 
3.程序实现导出表的移动    
petools中添加一个函数,用来将FOA转成RVA:
//将文件偏移转为内存偏移
DWORD FoaToRva(IN LPVOID pFileBuffer, IN DWORD foa){
    //定义文件头
    PIMAGE_DOS_HEADER dosHeader = NULL;        //dos头指针
    PIMAGE_NT_HEADERS ntHeader = NULL;        //nt头指针
    PIMAGE_FILE_HEADER peHeader = NULL;        //pe头指针
    PIMAGE_OPTIONAL_HEADER32 opHeader = NULL;    //可选pe头指针
    PIMAGE_SECTION_HEADER sectionHeader = NULL;    //节表指针
 
    //找到文件头
    dosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    ntHeader = (PIMAGE_NT_HEADERS) ((DWORD)pFileBuffer + dosHeader->e_lfanew);
    peHeader = (PIMAGE_FILE_HEADER) ((DWORD)ntHeader + 4);
    opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER);
    sectionHeader = (PIMAGE_SECTION_HEADER) ((DWORD)opHeader + peHeader->SizeOfOptionalHeader);
 
    //1.判断是哪个节
    int sec = -1;
    for(int i=0;i<peHeader->NumberOfSections;i++){
        DWORD fa = (sectionHeader+i)->PointerToRawData;
        DWORD size = (sectionHeader+i)->SizeOfRawData;
        if(foa >=fa && foa<(fa+size) ){
            sec = i;
            //printf("在第%d个节\n",sec);
            break;
        }    
    }
    if(sec<0){
        printf("文件偏移不在任何一个节\n");
        return 0;
    }
 
    //2.转换
    DWORD secOffset = foa - (sectionHeader + sec)->PointerToRawData;
    DWORD rva = (sectionHeader + sec)->VirtualAddress + secOffset;
 
    return rva;    
}

 

移动导出表:
#include "stdafx.h"
#include "PeTool.h"
#include "string.h"
 
#define SRC "C:\\Users\\Administrator\\Desktop\\DllHello.dll"
#define DEST "C:\\Users\\Administrator\\Desktop\\DllHello_new.dll"
 
//新增一个节
DWORD addSec(LPVOID pFileBuffer, LPVOID* pSec){
    //1.定义pe头结构指针
    PIMAGE_DOS_HEADER dosHeader = NULL;        //dos头指针
    PIMAGE_FILE_HEADER peHeader = NULL;        //pe头指针
    PIMAGE_OPTIONAL_HEADER32 opHeader = NULL;    //可选pe头指针
    PIMAGE_SECTION_HEADER seHeader = NULL;    //节表指针
 
    //2.初始化头结构指针
    dosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    if(dosHeader->e_magic != IMAGE_DOS_SIGNATURE){
        printf("不是有效MZ标记\n");
        return 0;
    }
    if(*((PDWORD)((DWORD)pFileBuffer + dosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE){
        printf("不是有效PE标记\n");
        free(pFileBuffer);
        return 0;
    }
    peHeader = (PIMAGE_FILE_HEADER) ((DWORD)pFileBuffer + dosHeader->e_lfanew + 4);
    opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER);
    seHeader = (PIMAGE_SECTION_HEADER) ((DWORD)opHeader + peHeader->SizeOfOptionalHeader);
 
    //3.新增一个节表
    PIMAGE_SECTION_HEADER newSec = seHeader + peHeader->NumberOfSections;    //新节表的指针
    if(((DWORD)pFileBuffer + opHeader->SizeOfHeaders - (DWORD)newSec) < 80){
        printf("空间不足插入新的节表\n");
        return 0;
    }
 
    //4.设置新节表
    strcpy((char*)newSec->Name, ".export");
    newSec->Misc.VirtualSize = 0x1000;                    //新节的内存镜像大小为1000
    newSec->VirtualAddress = opHeader->SizeOfImage;        //新节的内存偏移为内存镜像大小
    newSec->SizeOfRawData = 0x1000;                        //新节的文件镜像大小为1000
    PIMAGE_SECTION_HEADER lastSec = seHeader + (peHeader->NumberOfSections -1);    //最后一个节表
    newSec->PointerToRawData = lastSec->PointerToRawData + lastSec->SizeOfRawData;    //新节的文件偏移紧接最后一个节
    newSec->Characteristics = seHeader->Characteristics;    //新节的属性和第一个节一样即可
    
    //5.设置全0节表
    memset((LPVOID)(newSec+1), 0, 40);
 
    //6.修头信息
    peHeader->NumberOfSections = peHeader->NumberOfSections + 1;
    opHeader->SizeOfImage = opHeader->SizeOfImage + 0x1000;
 
    //7.申请内存
    LPVOID sec = malloc(0x1000);
    if(!sec){
        printf("给新节申请内存失败\n");
        return 0;
    }
    memset(sec, 0, 0x1000);
 
    //8.返回
    *pSec = sec;
    return 0x1000;
}
 
//移动导出表
void moveExport(){
    //定义pe头结构指针
    PIMAGE_DOS_HEADER dosHeader = NULL;        //dos头指针
    PIMAGE_FILE_HEADER peHeader = NULL;        //pe头指针
    PIMAGE_OPTIONAL_HEADER32 opHeader = NULL;    //可选pe头指针
    PIMAGE_DATA_DIRECTORY dataDir = NULL;        //数据目录指针
    PIMAGE_EXPORT_DIRECTORY exportDir = NULL;        //导出表指针
 
    //1.将文件读入内存
    LPVOID pFileBuffer = NULL;
    DWORD fileSize = ReadPEFile(SRC, &pFileBuffer);
    if(!pFileBuffer){
        printf("读取dll文件失败\n");
        return;
    }
 
    //2.新增一个节
    LPVOID newSec = NULL;
    DWORD secSize = addSec(pFileBuffer, &newSec);
    if(!newSec){
        printf("新增节失败\n");
        return;
    }
 
    //3.初始化头结构指针
    dosHeader = (PIMAGE_DOS_HEADER) pFileBuffer;
    peHeader = (PIMAGE_FILE_HEADER) ((DWORD)pFileBuffer + dosHeader->e_lfanew + 4);
    opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER);
    dataDir = opHeader ->DataDirectory;
    exportDir = (PIMAGE_EXPORT_DIRECTORY) ((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, dataDir->VirtualAddress));
 
    //4.复制地址表
    LPVOID copyDest = newSec;
    LPDWORD newFunAddr = (LPDWORD)copyDest;
    LPVOID funAddr= (LPVOID) ((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, exportDir->AddressOfFunctions));
    memcpy(newSec, funAddr, exportDir->NumberOfFunctions * 4);
 
    //5.复制序号表
    copyDest = (LPVOID)((DWORD)newSec + exportDir->NumberOfFunctions * 4);    //复制到哪里
    LPWORD newOrdAddr = (LPWORD)copyDest;
    LPVOID ordAddr = (LPVOID)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, exportDir->AddressOfNameOrdinals));
    memcpy(copyDest, ordAddr, exportDir->NumberOfNames * 2);
 
    //6.复制名字表
    copyDest = (LPVOID) ((DWORD)copyDest + exportDir->NumberOfNames * 2);
    LPDWORD newNameAddr = (LPDWORD) copyDest;
    LPDWORD nameAddr = (LPDWORD)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, exportDir->AddressOfNames));
    memset(copyDest, 0, exportDir->NumberOfNames * 4);    //因为名字表要修改的,先留空间
 
    //7.复制名字同时修复名字表
    copyDest = (LPVOID) ((DWORD)copyDest + exportDir->NumberOfNames * 4);
    for(int i=0;i<exportDir->NumberOfNames;i++){
        LPSTR name = (LPSTR)((DWORD)pFileBuffer + *(nameAddr + i));
        DWORD len = strlen(name) + 1;    //名字长度,需要带上字符串结尾符\0;
        memcpy(copyDest, name, len);
        *(newNameAddr + i) = FoaToRva(pFileBuffer, (fileSize + (DWORD)copyDest - (DWORD)newSec));//修复名字表
        copyDest = (LPVOID)((DWORD)copyDest + len);
    }
 
    //8.复制导出表,并修复新导出表
    memcpy(copyDest, exportDir, dataDir[0].Size);
    PIMAGE_EXPORT_DIRECTORY newExportDir = (PIMAGE_EXPORT_DIRECTORY) copyDest;
    newExportDir->AddressOfFunctions = FoaToRva(pFileBuffer, (fileSize + (DWORD)newFunAddr - (DWORD)newSec));
    newExportDir->AddressOfNameOrdinals = FoaToRva(pFileBuffer, (fileSize + (DWORD)newOrdAddr - (DWORD)newSec));
    newExportDir->AddressOfNames = FoaToRva(pFileBuffer, (fileSize + (DWORD)newNameAddr - (DWORD)newSec));
 
    //9.修复数据目录
    dataDir[0].VirtualAddress = FoaToRva(pFileBuffer, (fileSize + (DWORD)newExportDir - (DWORD)newSec));
 
    //10.写出新文件
    FILE* newFile = fopen(DEST, "a+b");
    if(!newFile){
        printf("打开新文件失败\n");
        free(pFileBuffer);
        free(newSec);
        return;
    }
    size_t m = fwrite(pFileBuffer, fileSize, 1, newFile);
    if(!m){
        printf("写出文件第一部分失败\n");
        fclose(newFile);
        free(pFileBuffer);
        free(newSec);
        return;
    }
    //写出新节
    size_t n = fwrite(newSec, secSize, 1, newFile);
    if(!n){
        printf("写出文件第二部分失败\n");
        fclose(newFile);
        free(pFileBuffer);
        free(newSec);
        return;
    }
 
    //关闭文件并返回
    fclose(newFile);
    free(pFileBuffer);
    free(newSec);
    printf("移动导出表成功\n");
    return;
}
 
//测试用的主函数
int main(int argc, char* argv[])
{
    //移动导出表
    moveExport();
 
    //使用新的dll
    typedef int (__stdcall *lpPlus)(int,int);
    lpPlus myPlus;
    HINSTANCE   hModule = LoadLibrary(DEST);
    myPlus = (lpPlus)GetProcAddress(hModule,   "_Plus@8");
    int a =0;
    a=myPlus(10,2);
    printf("10+2=%d\n",a);
    printf("导出函数可以使用,说明移动导出表成功\n");
    getchar();
}
 
结果:
 
 
 
posted @ 2019-11-13 17:55  L丶银甲闪闪  阅读(817)  评论(1编辑  收藏  举报