所谓感染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指令的长度)