打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
【成果6.1】软件保护壳技术专题
标 题: 【成果6.1】软件保护壳技术专题 - 添加新节
作 者: 玩命
时 间: 2008-06-15,18:09:52
链 接: http://bbs.pediy.com/showthread.php?t=66612


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

名词解释:
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 ShowErrGetFileSizeFailed:     lea eax, g_szGetFileSizeFailed     jmp ShowErr    CreateMapFailed:     lea eax, g_szCreateMapFailed     jmp ShowErrMapFileFailed:     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, pSectionNameCopySectionNameLoop:       lodsb    test al, al    jz EndCopySectionNameLoop    stosb    jmp CopySectionNameLoopEndCopySectionNameLoop:      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    retAddSection endp
添加新节为最基本的感染方式,感染这个词用在这里虽然有些不太恰当,但是我实在想不到用什么更形象词来描述此等行为。感染算法在病毒领域有很多的讨论,感染技术很多配合入口点技术来一起讨论。如果真要详细讨论此技术怕是要在开一个专题讨论才够。
在附件中的代码由于要修改代码节自身,所以添加新节的程序在连接时要用到
/SECTION:.text,ERW的选项.如果在RadAsm中配置连接选项时要改为/SECTION:.text|ERW才可以正常连接。

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

平常很少发帖,在专题文章的质量和可读性上希望大家多多提要求。。。                                 
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Declaring Procedure Parameters with the PROC Directive Calling Procedures with the INVOKE Directive
Sentinel驱动模拟源码
如何写一个简单的病毒程序
逆向工程
如何截获Oracle数据库连接密码
保护进程,进程关闭关机
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服