壳要附加到软件本身,有很多方式进行。在这里使用最常用的一种方式,添加一个新节。这里我们先用文字的形式描述一下添加新 节的算法。然后给出一段代码并在注释中详细给出没条指令的解释。在这之前,首先给出一些名词的解释,以便刚接触的朋友可以熟悉。熟悉的朋友可以直接跳过。

名词解释:
Offset
相对偏移
常指在文件中到文件头的距离。

RVA                     
相当内存偏移                
常指在内存中到内存加载起始地址的距离。

VA                      
虚拟地址                  
常指在内存中确切的地址也就是RVA+内存加载起始地址。

Alignment               
对齐粒度                    
当作"最小单位"就可以了例如:对齐粒度为200h。可以这样理解,一辆装货的车上面装的全是箱子,这箱子的大小是200h。如果最后一箱子没有装满,但是它 仍然占着一个箱子。

    
添加新节相关的PE头属性:
位于IMAGE_NT_HEADERS结构中的属性:
ImageBase(4字节)
SizeOfImage(4字节)
NumberOfSections(2字节)
AddressOfEntryPoint(4字节)
SectionAlignment(4字节)
FileAlignment(4字节)
  
位于IMAGE_SECTION_HEADER结构的属性:
最后节表VirtualSize(4字节)
最后节表的VirtualAddress(4字节)
最后节表的SizeOfRawData(4字节)
最后节表的PointerToRawData(4字节)
最后节表的Characteristics(4字节)
  
添加新节算法描述:
1.建立文件映射
2.判断是否是PE文件
3.移动到最后一个节表
4.添加新节节表
5.设置新节的VirtualAddress,VirtualSize,SizeOfRawData,PointerToRawData,Characteristics等属性
6.将新节的内容写入文件
7.增加NumberOfSections属性
8.设置SizeOfImage,AddressOfEntryPoint属性
9.将内存映射回文件
注:代码中讲解的部分用红色标出
首先是建立文件映射,也可以直接读写文件,不过这样做操作起来会方便一些。

代码:
CryptFile proc szFname : LPSTR    
     LOCAL hFile : HANDLE
     LOCAL hMap : HANDLE
     LOCAL pMem : LPVOID
     LOCAL dwOrigFileSize : DWORD
     LOCAL dwNTHeaderAddr : DWORD
     
     ;; init data
     xor eax, eax
     mov g_bError, al
     mov eax, offset EndNewSection - offset NewSection
     mov g_dwNewSectionSize, eax
     
     ;; open file
      invoke CreateFile, szFname,\
                          GENERIC_WRITE + GENERIC_READ,\
                          FILE_SHARE_WRITE + FILE_SHARE_READ,\
                          NULL,\
                          OPEN_EXISTING,\
                          FILE_ATTRIBUTE_NORMAL,\
                          0
     .IF eax == INVALID_HANDLE_VALUE
         jmp OpenFileFailed                
     .ENDIF
     mov hFile, eax 
     invoke GetFileSize, hFile, NULL
    .IF eax == 0
        invoke CloseHandle, hFile  
        jmp GetFileSizeFailed
    .ENDIF
     mov dwOrigFileSize, eax   

     ;; 这里的dwOrigFileSize 是原始的文件大小
     ;; 因为你要添加新节,所以要多上那么一点
     ;; 点尺寸.这个尺寸就是APPEND_SIZE,如果
     ;; 这个尺寸也许会让你最后添加的程序后有
     ;; 一些多余的数据。也可以没有,不过有一 
     ;; 点点麻烦。这就要计算添加后的
     ;; SizeOfImage了。等到以后讲解吧。
     add eax, APPEND_SIZE

     xchg eax, ecx
     ;; create memory map
     xor ebx, ebx     
     invoke CreateFileMapping, hFile, ebx, PAGE_READWRITE, ebx, ecx, ebx
     .IF eax == 0
         invoke CloseHandle, hFile
         jmp CreateMapFailed                
     .ENDIF
     mov hMap, eax
     ;; map file to memory
     invoke MapViewOfFile, hMap,
                           FILE_MAP_WRITE+FILE_MAP_READ+FILE_MAP_COPY, 
                           ebx, ebx, ebx
     .IF eax == 0
         invoke CloseHandle, hMap
         invoke CloseHandle, hFile
         jmp MapFileFailed
     .ENDIF
     mov pMem, eax                               
     ;; check it's PE file or not ?
     xchg eax, esi
     assume esi : ptr IMAGE_DOS_HEADER
     .IF [esi].e_magic != 'ZM'
         invoke UnmapViewOfFile, pMem
         invoke CloseHandle, hMap
         invoke CloseHandle, hFile
         jmp InvalidPE        
     .ENDIF       
     add esi, [esi].e_lfanew
     assume esi : ptr IMAGE_NT_HEADERS   
     .IF word ptr [esi].Signature != 'EP'
         invoke UnmapViewOfFile, pMem
         invoke CloseHandle, hMap
         invoke CloseHandle, hFile
         jmp InvalidPE        
     .ENDIF
     mov dwNTHeaderAddr, esi

    ;; 在建立映射后
    ;; 这段代码将映射后文件的指针存放在局部变量pMem.
    ;; 而指定的NT结构头指针存放到esi寄存器处。
    ;; 现在我们调用添加节函数添加一个新节,AddSection是
    ;; 我们这节的主要函数,将在下面讲解。
    ;; 增加一个新节
    ;; pMem:文件映射内存指针
    ;; g_szNewSectionName:新节的节名,这里是(.new)
    ;; g_dwNewSectionSize:新节的长度,这里是offset EndNewSection - offset NewSection
    invoke AddSection, pMem, offset g_szNewSectionName, g_dwNewSectionSize
    push eax
    mov esi, dwNTHeaderAddr
    assume esi : ptr IMAGE_NT_HEADERS
    ;; 下面做的就是设置新节的中的原代码入口点,就是真正的入口地址.
    mov ebx, dword ptr [esi].OptionalHeader.AddressOfEntryPoint
    add ebx, dword ptr [esi].OptionalHeader.ImageBase
    ;; OrigAddressOfEntry这个变量在CryptFile底部的NewSection节中
    mov eax, offset OrigAddressOfEntry
    mov dword ptr [eax], ebx
    ;; 更新入口点,将新节的入口点设置到NT头结构的AddressOfEntryPoint
    ;; 哪个节的VirusAddress被设置到AddressOfEntryPoint处,哪个节将会被先执行
    ;; 这也是最通常的入口点技术
    pop eax
    assume eax : ptr IMAGE_SECTION_HEADER    
    push dword ptr [eax].VirtualAddress
    pop dword ptr [esi].OptionalHeader.AddressOfEntryPoint
    ;; 下面的代码利用新节节表将新节的代码写入文件
    mov esi, offset NewSection
    mov edi, dword ptr [eax].PointerToRawData
    add edi, pMem
    mov ecx, g_dwNewSectionSize
    cld
    rep movsb
 LogicShellExit:
     ;; close handle & write it
     invoke UnmapViewOfFile, pMem
     invoke CloseHandle, hMap
     invoke CloseHandle, hFile
     .IF g_bError == 0
         ;; show success message  
         invoke MessageBox, NULL, offset g_szDone, offset g_szDoneCap, MB_ICONINFORMATION
     .ENDIF        
     ret
;; ----- Show error message ----- 
OpenFileFailed:
     lea eax, g_szOpenFileFailed
     jmp ShowErr
GetFileSizeFailed:
     lea eax, g_szGetFileSizeFailed
     jmp ShowErr    
CreateMapFailed:
     lea eax, g_szCreateMapFailed
     jmp ShowErr
MapFileFailed:
     lea eax, g_szMapFileFailed
     jmp ShowErr        
InvalidPE:          
     lea eax, g_szInvalidPE
     jmp ShowErr   
ShowErr:
     invoke MessageBox, NULL, eax, offset g_szErr, MB_ICONERROR
     mov al, 1
     mov g_bError, al
     jmp LogicShellExit
;; ----- 新节代码 -----
NewSection:
   ;; 在这里获取地址
   ;; call指令会将下条指令的地址压入堆栈
   ;; 注意此指令的OPCODE为EB00000000
   ;; 病毒与Shellcode等常用此指令定位
   ;; 杀毒软件的启发式搜索常将此特征作为查找特征
   ;; 聪明的读者可以自己修改定位代码来躲过
   ;; 这类的查杀
   call GetEip
   GetEip:
   ;; eax中有保存着当前的地址,标号为GetEip
   pop eax
   add eax, offset OrigAddressOfEntry - offset GetEip
   ;; 两个偏移的差就是这两个地址之间的距离,它的距离 + 起始地址
   ;; 就为OrigAddressOfEntry的地址
   ;; 最后将OrigAddressOfEntry保存的值,也就是原来的入口节的地址
   ;; 送回eax中。
   mov eax, dword ptr [eax]
   ;; 跳到原入口点地址
   jmp eax
;; ----- 新节数据 -----
OrigAddressOfEntry  dd ?

EndNewSection:

CryptFile endp
上面的新节代码没有任何作用,只是直接跳入到原入口的地址。
下面的代码就是这次主题的主要的代码.
代码:
AddSection proc uses ebx ecx edx esi edi, pMem : LPVOID,
                                          pSectionName : LPVOID,
                                          dwSectionSize : DWORD
;; add a new section
;; ret: eax =  new section table file offset
    LOCAL dwNTHeader : LPVOID
    LOCAL dwLastSecTbl : LPVOID    
    LOCAL dwFileAlig : DWORD
    LOCAL dwSecAlig : DWORD
    
    ;; move to section table
    mov esi, pMem
    ;; 将映射地址传给esi,再将esi设置为PE头的地址
    ;; 这里这个3ch是DOS头中e_lfanew的偏移。e_lfanew保存着
    ;; 从MZ头到PE头的偏移,所以这样的相加使esi指向PE头
    ;; 也可以使用下面同等指令替换
    ;; assume esi : ptr IMAGE_DOS_HEADER
    ;; add esi, dword ptr [esi].e_lfanew
    add esi, dword ptr [esi+3ch]
    mov dwNTHeader, esi  
    assume esi : ptr IMAGE_NT_HEADERS
    ;; update the number of section
    mov cx, word ptr [esi].FileHeader.NumberOfSections
    movzx ecx, cx
    ;; 增加节的数目
    inc word ptr [esi].FileHeader.NumberOfSections
    push dword ptr [esi].OptionalHeader.FileAlignment
    pop dwFileAlig
    push dword ptr [esi].OptionalHeader.SectionAlignment
    pop dwSecAlig        
    ;; move esi point to section table
    ;; 在NT头结构下面跟着的就是N个节表街头.加上NT头结构的长度就为第一个节表
    add esi, sizeof IMAGE_NT_HEADERS
    ;; store the last section table
    ;; 在这里保存最后一个节表的偏移,因为一会在计算新节的RVA和offset要应用。
    mov eax, sizeof IMAGE_SECTION_HEADER
    mov ebx, ecx
    imul ebx
    ;; esi 为新节节表的偏移
    ;; 到这里可以判断下SizeOfHeader大小以便检测是否有空间加入新节,这里没有做判断
    ;; 一般没有加过壳的程序都会有空间加入,这个也是按照File Alignment对齐的所以就会有
    ;; 剩余的空隙加入
    add esi, eax                            ; esi = the end of orig last section fva
    push esi
    ;; 这里保存了原最后节表的偏移,为了设置新节的地址做准备
    sub esi, sizeof IMAGE_SECTION_HEADER    ; esi = the orig last section fva
    mov dwLastSecTbl, esi
    pop esi
    ;; set new section table
    ;; 设置新节节表名
    assume esi : ptr IMAGE_SECTION_HEADER
    ;; set section name
    push esi
    lea edi, [esi].Name1
    mov esi, pSectionName
CopySectionNameLoop:   
    lodsb
    test al, al
    jz EndCopySectionNameLoop
    stosb
    jmp CopySectionNameLoop
EndCopySectionNameLoop:  
    pop esi  
    ;; set section characteristics
    ;; 设置设置节的属性,一般关心的有三个属性,读写执行
    ;; 0E00000E0h为设置可读可写可执行三个属性的或运算的值
    ;; 有些杀毒软件的启发式搜索也会检查入口点节是否存在写权限
    ;; 来判断是否被病毒感染,有些多态病毒需要自解密自身所有需要
    ;; 写权限,在壳被恶意程序利用后,现在的杀毒软件有可能报告为
    ;; Heru.XXX或者XCrypt之类,都是启发式惹的祸,过此类检测也是有
    ;; 方法的,例如启动后将的代码写到栈中执行.现在的病毒做的越来越像壳
    ;; 壳整的越来越像病毒,两者只有目的上的区别了。
    push 0E00000E0h
    pop dword ptr [esi].Characteristics
    ;; set section virtualsize
    ;; 这里设置节的真实大小,VirtualSize表示新节的真实大小,没有经过对齐后的
    push dwSectionSize
    pop dword ptr [esi].Misc.VirtualSize
    ;; set section sizeofrawdata
    ;; 节的SizeOfRawData为此节在文件中经过文件对齐后的大小
    ;; PEAlign函数用于计算节对齐后的大小,将在附件的代码中给出
    invoke PEAlign, dwSectionSize, dwFileAlig
    mov dword ptr [esi].SizeOfRawData, eax
    ;; set section virtualaddress
    ;; 设置新节的内存偏移和文件偏移需要上一节的一个信息
    ;; 新节的内存偏移 = 上一节的内存偏移 + 上一节经过节对齐后的长度
    ;; 新节的文件偏移 = 上一节的文件偏移 + 上一节进过文件对齐后的长度
    mov eax, dwLastSecTbl                       ; eax = orig last section table fva
    assume eax : ptr IMAGE_SECTION_HEADER
    mov ecx, dword ptr [eax].VirtualAddress
    add ecx, dword ptr [eax].Misc.VirtualSize        ; ecx = new section rva
    mov ebx, dword ptr [eax].PointerToRawData
    add ebx, dword ptr [eax].SizeOfRawData           ; ebx = new section fva
    invoke PEAlign, ecx, dwSecAlig
    mov dword ptr [esi].VirtualAddress, eax
    ;; set section pointertorawdata
    invoke PEAlign, ebx, dwFileAlig
    mov dword ptr [esi].PointerToRawData, eax
    ;; update the sizeofimage
    ;; SizeofImage表示从文件到内存映射文件的内容通过节对齐的大小
    ;; 这个值等于当前最后一节的内存偏移 + 当前最后一节的经过节对齐的大小
    ;; 大家可以思考一下。这个值很有用,可以利用此值做一些特殊的感染来躲过启发式
    ;; 搜索。呵呵...
    mov eax, dword ptr [esi].VirtualAddress
    add eax, dword ptr [esi].Misc.VirtualSize
    invoke PEAlign, eax, dwSecAlig
    mov edx, dwNTHeader
    assume edx : ptr IMAGE_NT_HEADERS
    mov dword ptr [edx].OptionalHeader.SizeOfImage, eax
    push dword ptr [esi].PointerToRawData
    pop edi
    add edi, pMem
    ;; clear the new sec
    ;; 在这里做一下清0工作ZeroMemory
    mov ecx, dwSectionSize
    xor eax, eax
    cld
    rep stosb
    
    ;; 此函数的返回值,新节节表的文件偏移
    mov eax, esi
    assume esi : nothing
    assume eax : nothing
    assume edx : nothing
    ret
AddSection endp
添加新节为最基本的感染方式,感染这个词用在这里虽然有些不太恰当,但是我实在想不到用什么更形象词来描述此等行为。感染算法在病毒领域有很多的讨论,感染技术很多配合入口点技术来一起讨论。如果真要详细讨论此技术怕是要在开一个专题讨论才够。
在附件中的代码由于要修改代码节自身,所以添加新节的程序在连接时要用到
/SECTION:.text,ERW的选项.如果在RadAsm中配置连接选项时要改为/SECTION:.text|ERW才可以正常连接。

附件程序的使用为在AddSection同目录下放置一名为target.exe程序直接运行AddSection后即可.可使用ollydbg调试target.exe查看.

平常很少发帖,在专题文章的质量和可读性上希望大家多多提要求。。。
上传的附件 AddSection_src.rar
AddSection_bin.rar