所谓感染PE文件,其实就是修改PE文件,在不改变其原有功能的基础上,添加我们自己的代码,在这里我们将PE文件看作是一般的文件,只是在修改时,要根据PE文件结构
来进行update,否则的话就会破坏原有程序。这里我们不再对PE文件结构进行解释说明,请读者自行百度哈
现在我们说下添加区段的一般步骤
一.修改PE文件头部信息,需要修改的有IMAGE_FILE_HEADER的NumberOfSections(区块数目),IMAGE_OPTIONAL_HEADER的AddressOfEntryPoint,SizeOfImage,以
及SizeOfCode,还有就是记录下原有程序的程序入口地点
二.申请一个IMAGE_SECTION_HEADER的内存模型,该IMAGE_SECTION_HEADER的SizeOfRawData,PointerToRawData,VirtualAddress,Characterics
和.Misc.VirtualSize
2.1 我们会知道要写入汇编代码的长度dwShellLen(该变量的值我们会事先得到)
SizeOfRawData的值就是dwShellLen根据文件对齐值之后的值
PointerToRawData的值就是源程序的最后一个节点的PointerToRawData+最后一个节点的SizeOfRawData
VirtualAddress的值是源程序最后一个节点的VirtualAddress+最后一个节点的根据内存对齐后的区块大小
Characteristic的值改成可读,可写,可执行
Misc.VirtualSize的值就是不经过对齐的值(不经过文件对齐,不经过内存对齐,就是原有数据)
三.需要写入外壳的汇编代码,需要记下的就是将程序入口点修改成新节点的VirtualAddress。
3.1在文件中写入外壳代码,需要注意的就是在PE程序中的偏移量是新节点的PointerToRawData
////////////////////////////////////////////////////////////////////////////// // PE_HACK.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include #include //#include"..\PE_HACK\asm.asm" using namespace std; /////////////////////////////////////////////////////////////////////////////// ///函数描述:根据所给路径检测文件是否是有效的PE文件 ///入口参数:char*描述文件路径 ///返回 值:是PE文件则返回true,否则返回false //////////////////////////////////////////////////////////////////////////////// bool isPe(char* exePath) { bool bIsPE=false; HANDLE hFile=CreateFile(exePath, GENERIC_ALL, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if(INVALID_HANDLE_VALUE==hFile) { MessageBox(NULL,"文件打开失败",0,0); return false; } ::SetFilePointer(hFile,0,NULL,FILE_BEGIN); IMAGE_DOS_HEADER DosHeader={0};//DOS文件头 DWORD dwWrite; ReadFile(hFile,&DosHeader,sizeof(IMAGE_DOS_HEADER),&dwWrite,NULL); if(DosHeader.e_magic==IMAGE_DOS_SIGNATURE) { //ODS头部检测成功,开始检测FILE_HEADER IMAGE_NT_HEADERS NtHeader={0}; //将文件指针移动到IMAGE_NT_HEADER的起始位置 ::SetFilePointer(hFile,DosHeader.e_lfanew,NULL,FILE_BEGIN); ReadFile(hFile,&NtHeader,sizeof(IMAGE_NT_HEADERS),&dwWrite,0); if(NtHeader.Signature==IMAGE_NT_SIGNATURE) { bIsPE=true; } else { bIsPE=false; } } else { bIsPE=false; } if(bIsPE) { CloseHandle(hFile); return true; } else { CloseHandle(hFile); return false; } } //////////////////////////////////////////////////////////////////////////////// ///函数结束 //////////////////////////////////////////////////////////////////////////////// DWORD GetAlign(DWORD size,DWORD align) { DWORD dwResult=0; if(sizee_lfanew); //记录下区块的数目 WORD dwNumberOfSections=pNtHeader->FileHeader.NumberOfSections; IMAGE_SECTION_HEADER LastSection={0}; int nCurNum=0; //记录下原来的OEP DWORD dwOldOEP=pNtHeader->OptionalHeader.AddressOfEntryPoint; DWORD dwWrite; SetFilePointer(hFile,pDosHeader->e_lfanew+sizeof(IMAGE_NT_HEADERS),0,0); DWORD dwTextBase=0; while(nCurNumOptionalHeader.FileAlignment; //获得内存对齐值 DWORD dwSectionAlign=pNtHeader->OptionalHeader.SectionAlignment; DWORD dwShellLen; goto shellend; __asm { shell: PUSHAD PUSHFD POPFD POPAD } shellend: char* pShell; BYTE jmp = 0xE9; __asm { LEA EAX,shell MOV pShell,EAX; LEA EBX,shellend SUB EBX,EAX MOV dwShellLen,EBX } //修改区块的属性 SectionShell.Characteristics=IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_EXECUTE|IMAGE_SCN_MEM_WRITE; //新区块在磁盘中的大小 SectionShell.SizeOfRawData=GetAlign(dwShellLen,dwFileAlign); SectionShell.Misc.VirtualSize=dwShellLen; //对齐最后一个区段后的大小计算壳区段的虚拟地址 memcpy(&SectionShell.Name,".try",4); SectionShell.VirtualAddress=LastSection.VirtualAddress+GetAlign(LastSection.Misc.VirtualSize,dwSectionAlign); SectionShell.PointerToRawData=LastSection.PointerToRawData+LastSection.SizeOfRawData; dwNumberOfSections++; //区块数目加1 pNtHeader->FileHeader.NumberOfSections=dwNumberOfSections; DWORD dwAfterSection=GetAlign(dwShellLen,dwFileAlign); //修改镜像大小 pNtHeader->OptionalHeader.SizeOfImage+=dwAfterSection; pNtHeader->OptionalHeader.SizeOfCode+=dwAfterSection; //重新定位入口地址 pNtHeader->OptionalHeader.AddressOfEntryPoint=SectionShell.VirtualAddress; WriteFile(hFile,&SectionShell,sizeof(SectionShell),&dwWrite,NULL); //将外壳程序写入文件 SetFilePointer(hFile, SectionShell.PointerToRawData ,NULL,FILE_BEGIN); WriteFile(hFile,pShell,dwShellLen,&dwWrite,NULL); WriteFile(hFile,&jmp, sizeof(jmp),&dwWrite,NULL); dwOldOEP=dwOldOEP-(SectionShell.VirtualAddress+dwShellLen)-5; WriteFile(hFile,&dwOldOEP, sizeof(dwOldOEP),&dwWrite,NULL); CloseHandle(hFile); } /////////////////////////////////////////////////////////////////////////////// ///函数结束 //////////////////////////////////////////////////////////////////////////////// int _tmain(int argc, _TCHAR* argv[]) { /*if(::IsDebuggerPresent()) { MessageBox(NULL,"请终止调试",0,0); ExitProcess(0); }*/ if(isPe("D:\\project\\tmp\\Debug\\tmp.exe")) { addNewSection("D:\\project\\tmp\\Debug\\tmp.exe",NULL); } else { MessageBox(NULL,"该文件并非PE文件",0,0); } return 0; }
这里有一个需要记下的就是CreateFileMappingA函数的使用,倒数第二和第三个参数,就是文件映射对象的大小,一般最小值的大小不但应该是文件大小,还应该在此基
础上加上要外壳代码的大小,这样就行了,否则的话,就会出现不是有效的win32程序的错误
PS:笔者是新手,大神路过就好,经过OD测试,已经成功,但是写入的汇编代码不能执行,还在学习,不要见笑,但是如果您的汇编代码是正确的话,应该是没问题的
主要注意的是:应该记下外壳代码的框架
goto shellEnd;
_asm
{
shellCode:
pushad;
pushfd;
popfd;
popad;
}
shellEnd:
char* pShell;
DWORD dwShellLen;
_asm
{
LEA eax,shellCode
MOV pShell,eax;
LEA EBX,shellEnd;
SUB EBX,EAX;
MOV dwShellLen,EBX;
}
这样汇编代码的首地址和汇编代码的长度就被放进了pShell,dwShellLen
还有就是调回原有的起始地点,原有入口地点
dwOldOEP=dwOldOEP-(SectionShell.VirtualAddress+dwShellLen)-5;(5是jmp指令的长度)