WIN32汇编语言教程:第13章 进程控制 · 13.3 进程调试(5)

程序的开头用equ语句定义补丁的地址00401004h,并在dbPatch常量中定义补丁位置的原先代码,在dbPatched常量中定义了要写入的补丁内容。

程序首先使用CreateProcess函数创建Test.exe进程,如果创建成功,则先用ReadProcessMemory函数读出补丁处的原来内容,并验证是否和我们保留的相同。因为用户可能将补丁程序用在不同版本的目标程序中,这时候补丁就会写入错误的地址,这可能会引起目标程序崩溃,首先验证补丁处的原有内容是否正确,就可以防止发生这种错误的发生。通过了内容的校验以后,程序再使用WriteProcessMemory函数将补丁写入正确的位置。

虽然程序很简单,但是读者还要注意两个细节:首先是创建进程的时候最好使用CREATE_SUSPENDED标志,这样目标进程的主线程一开始是被挂起的,可以防止补丁程序在执行补丁代码的过程中被Windows打断,然后切换到目标进程去的情况,当补丁完成以后,使用ResumeThread函数恢复目标进程的执行就可以了;第二点是完成补丁以后,不要忘记使用CloseHandle语句将CreateProcess返回的进程句柄和线程句柄关闭。

例子中使用了CreateProcess函数来创建目标进程,对于使用这种方法得到的目标进程句柄,父进程拥有全部权限,可以自由读写子进程的地址空间。如果使用OpenProcess去获取运行中的目标进程的句柄时,不要忘了指定PROCESS_VM_READ和PROCESS_VM_WRITE权限,否则补丁操作会失败。

13.3.3 调试API的使用

对于上面的内存补丁例子,读者可能会说:这种东西还好意思拿出来亮相,又没有实用价值!只要用16进制编辑器去Test.exe中查找数据串“74h、15h”然后直接改成“90h、90h”就一劳永逸了,何必这样兴师动众写一个补丁程序呢?的确如此,不过使用这个例子是为了介绍ReadProcessMemory和WriteProcessMemory函数的用法,而且可以用来继续引出本节中的例子程序。

稍微接触过加密解密的读者肯定见过“加壳程序”和“脱壳程序”这两个名词。“加壳”指将可执行文件的代码和数据经过某种变换后存储,并在原来的可执行文件中添加一段用于还原的代码,这样在执行程序的时候,这些代码会自动将原来可执行文件的代码和数据还原并执行,用户并不会感觉到程序被改动过,这段用于还原的代码就像是一层壳附在原来文件外面,所以对文件进行变换处理的程序就被叫做“加壳程序”。

“加壳”的原因有两个:压缩和加密。压缩程序可以将可执行文件的内容压缩存储,这样文件占用的磁盘空间可以缩小,这时“壳代码”就是解压缩代码;而加密程序则是为了保证可执行文件的内容不被随便修改(就像上面讲的用16进制编辑器去修改关键代码),这时“壳代码”就是解密代码,一般解密代码中同时包含有反跟踪模块。现在的大部分加密软件同时有压缩的功能,如Aspack,PECompact和ASProtect等软件。在被加壳的文件中,原来的代码和数据已经面目全非了,用16进制编辑器去寻找特征码是根本无法找到的,所以无法用修改文件的方法进行静态补丁。

要对加过壳的软件进行修改必须首先将它脱壳,但大部分的加壳软件都无法用简单的方法去对付,除了一些加密程度不高的“壳”可以利用逆算法完全恢复原来的文件外,有很多“壳”是无法用逆运算对付的。虽然可以用Soft-ICE等跟踪软件跟踪到壳代码的内部,并一直跟踪到壳代码将原来的可执行文件恢复为止,然后再手工去改动内存中的关键代码,但用户不可能为了执行程序而每次都用调试器去载入可执行文件,并且花一段时间去跟踪并做“手工补丁”。

既然无法用算法将文件还原,尝试从已经正常运行的进程中拷贝代码会怎么样呢?这就是ProcDump之类的软件做的事情,这些软件等待壳代码将可执行文件在内存中恢复,然后从内存中拷贝已还原的代码并写成一个可执行文件,但效果并不那么令人满意。

在这种情况下,内存补丁技术就又派上用途了,这时的关键就在于补丁程序必须有调试器的功能,可以模拟手工使用Soft-ICE等软件进行跟踪的过程,一直跟踪到“壳代码”执行完毕,可执行文件的代码被完全恢复以后再去打内存补丁,Win32中的调试API就可以让我们做到这一点。

1. 改进后的内存补丁程序

首先来改造Test.exe,将它加在一层壳里面,为了简化操作,我们使用Upx压缩软件将它压缩。现在用W32Dasm将压缩后的Test.exe反汇编,可以发现入口地址已经不一样了,入口处的代码也不一样了。下面就是Upx生成的动态解压缩代码:

//******************** Program Entry Point ********

:00405120 60                     pushad

:00405121 BE00504000                mov esi, 00405000

:00405126 8DBE00C0FFFF              lea edi, dword ptr [esi+FFFFC000]

:0040512C 57                     push edi

:0040512D 83CDFF                 or ebp, FFFFFFFF

:00405130 EB10                   jmp 00405142

:00405132 90                     nop

:00405133 90                     nop

...

:0040526E 61                     popad

:0040526F E98CBDFFFF                jmp 00401000

注意:整段代码的最后有一句jmp 00401000,00401000h就是Test.exe程序原来的入口地址,实际上执行到这里的时候,解压缩代码已经结束,可执行文件原来的代码和数据已经被恢复。我们要做的事情就是使用调试API将程序执行到解压缩代码结束的地方,再进行内存补丁。

改进后的补丁程序放在所附光盘的Chapter13\Patch2目录中。Patch2.asm文件如下:

          .586

          .model flat, stdcall

         option casemap :none  ; case sensitive

;####################################################################

;     Include 数据

;####################################################################

include        windows.inc

include        user32.inc

include        kernel32.inc

includelib     user32.lib

includelib     kernel32.lib

;####################################################################

BREAK_POINT1      equ    00405120h

BREAK_POINT2      equ    00401000h

PATCH_POSITION equ    00401004h

;####################################################################

;  数据段

;####################################################################

                 .data?

align          dword

stCT             CONTEXT        <?>

stDE             DEBUG_EVENT    <?>

stStartUp      STARTUPINFO            <>

stProcInfo     PROCESS_INFORMATION    <>

szBuffer          db     1024 dup (?)

 

                 .const

dbPatched      db     90h,90h

dbInt3         db     0cch

dbOldByte      db     60h

szExecFilename db     'Test.exe',0

szErrExec     db    '无法装载执行文件!',0

;####################################################################

;     代码段

;####################################################################

                 .code

Start:

;********************************************************************

; 创建进程

;********************************************************************

invoke     GetStartupInfo,addr stStartUp

invoke     CreateProcess,offset szExecFilename,NULL,NULL,NULL,\

             NULL,DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS,NULL,\

             NULL,offset stStartUp,offset stProcInfo

.if        !eax

             invoke MessageBox,NULL,addr szErrExec,NULL,\

                     MB_OK or MB_ICONSTOP

             invoke ExitProcess,NULL

.endif

;********************************************************************

; 调试进程

;********************************************************************

.while     TRUE

              invoke WaitForDebugEvent,addr stDE,INFINITE

             .break .if stDE.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT

;********************************************************************

上页:第13章 进程控制 · 13.3 进程调试(4) 下页:第13章 进程控制 · 13.3 进程调试(6)

第13章 进程控制

版权所有 © 云南伯恩科技 证书:粤ICP备09170368号