WIN32汇编: 29.Win32调试API第二部分

第29课: Win32调试API 第二部分


我们继续Win32调试API的话题。在本章中,我们将要学习如何修改被调试程序。
下载 the example

理论:

在前面一章中,我们学会了如何装载被调试的进程以及如何处理进程中发生的事件。为了有实际用途,我们的程序应具有修改被调试程序的能力。有好几个API函数用于这一目的。

  • ReadProcessMemory该函数允许你去读指定的进程的内存。函数原型如下:

    ReadProcessMemory proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD, lpNumberOfBytesRead:DWORD

    hProcess 待读进程的句柄.
    lpBaseAddress 目标进程中待读内存起始地址。例如,如果你想要读目标 进程中从地址401000h开始的4个字节,该参数值应置为401000h。
    lpBuffer 接收缓冲区地址
    nSize 想要读的字节数。
    lpNumberOfBytesRead 记录实际读取的字节数的变量地址。如果对这个值 不关心,填入NULL即可。

  • WriteProcessMemory 是对应于ReadProcessMemory的函数,通过它 可以写目标进程的内存。其参数和ReadProcessMemory 相同。

    理解接下去的两个函数需要一些进程上下文的有关背景知识。在象Windows这样的 多任务操作系统中,同一时间里可能运行着几个程序。Windows分配给每个线程一个 时间片,当时间片结束后,Windows将冻结当前线程并切换到下一具有最高优先级的 线程。在切换之前,Windows将保存当前进程的寄存器的 内容,这样当在该线程再 次恢复运行时,Windows可以恢复最近一次线程运行的*环境*。保存的寄存器内容总 称为进程上下文。
    现在回到我们的主题。当一个调试事件发生时,Windows暂停被调试进程,并保存其 进程上下文。由于进程被暂停运行,我们可以确信其进程上下文内容将保持不变。 可以用GetThreadContext来获取进程上下文内容,并且也可以用GetThreadContext 来修改进程上下文内容。
    这两个函数威力非凡。有了他们,对被调试进程你就具有象VxD的能力: 如改变其寄 存器内容,而在被调试程序恢复运行前,这些值将会写回寄存器中。在进程上下文中 所做的任何改动,将都会反映到被调试程序中。想象一下: 甚至可以改变eip寄存器 的内容,这样你可以让程序运行到你想要的任何地方! 在正常情况下是不可能做到这 一点的。

    GetThreadContext proto hThread:DWORD, lpContext:DWORD

    hThread 你想要获得上下文的线程句柄
    lpContext 函数成功返回时用来保存上下文内容的结构指针。

    SetThreadContext 参数相同。让我们来看看上下文的结构:

  • CONTEXT STRUCT
  • ContextFlags dd ?
    ;----------------------------------------------------------------------------------------------------------
    ;当ContextFlags包含CONTEXT_DEBUG_REGISTERS,返回本部分
  • ;-----------------------------------------------------------------------------------------------------------
    iDr0 dd ?
    iDr1 dd ?
    iDr2 dd ?
    iDr3 dd ?
    iDr6 dd ?
    iDr7 dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ;当ContextFlags包含CONTEXT_FLOATING_POINT,返回本部分
  • ;-----------------------------------------------------------------------------------------------------------
  • FloatSave FLOATING_SAVE_AREA <>
  • ;----------------------------------------------------------------------------------------------------------
    ;当ContextFlags包含CONTEXT_SEGMENTS,返回本部分
  • ;-----------------------------------------------------------------------------------------------------------
  • regGs dd ?
    regFs dd ?
    regEs dd ?
    regDs dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ;当ContextFlags包含CONTEXT_INTEGER,返回本部分
  • ;-----------------------------------------------------------------------------------------------------------
  • regEdi dd ?
    regEsi dd ?
    regEbx dd ?
    regEdx dd ?
    regEcx dd ?
    regEax dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ;当ContextFlags包含CONTEXT_CONTROL,返回本部分
  • ;-----------------------------------------------------------------------------------------------------------
  • regEbp dd ?
    regEip dd ?
    regCs dd ?
    regFlag dd ?
    regEsp dd ?
    regSs dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ;当ContextFlags包含CONTEXT_EXTENDED_REGISTERS,返回本部分
  • ;-----------------------------------------------------------------------------------------------------------
  • ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?) CONTEXT ENDS

    可以看出,该结构中的成员是对实际处理器的寄存器的模仿。在使用该结构之前 要在ContextFlags 中指定哪些寄存器组用来读写。如要访问所有的寄存器, 你可以置ContextFlagsCONTEXT_FULL 。或者只访问regEbp, regEip, regCs, regFlag, regEsp 或 regSs, 应置ContextFlagsCONTEXT_CONTROL

    在使用结构CONTEXT 时还应记住: 它必须是双字对齐的,否则在NT下将得 到奇怪的结果。可以在定义前加上"align dword"。例如:

    align dword
    MyContext CONTEXT <>

例:

第一个例子演示DebugActiveProcess的使用。首先,需要在Windows显示在屏幕上以前运行一个待调试程序win.exe,该程序将处于无限循环运行状态中。然后你运行例子程序,它将把自己与win.exe连接起来,并且修改win.exe的代码,这样win.exe将退出无限循环状态而显示自己的窗口。

.386
.model flat,stdcall
option casemap:none
include /masm32/include/windows.inc
include /masm32/include/kernel32.inc
include /masm32/include/comdlg32.inc
include /masm32/include/user32.inc
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/comdlg32.lib
includelib /masm32/lib/user32.lib

.data
AppName db "Win32 Debug Example no.2",0
ClassName db "SimpleWinClass",0
SearchFail db "Cannot find the target process",0
TargetPatched db "Target patched!",0
buffer dw 9090h

.data?
DBEvent DEBUG_EVENT <>
ProcessId dd ?
ThreadId dd ?
align dword
context CONTEXT <>

.code
start:
invoke FindWindow, addr ClassName, NULL
.if eax!=NULL
    invoke GetWindowThreadProcessId, eax, addr ProcessId
    mov ThreadId, eax
    invoke DebugActiveProcess, ProcessId
    .while TRUE
       invoke WaitForDebugEvent, addr DBEvent, INFINITE
       .break .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          mov context.ContextFlags, CONTEXT_CONTROL
          invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context           
          invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL
          invoke MessageBox, 0, addr TargetPatched, addr AppName, MB_OK+MB_ICONINFORMATION
       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
          .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
             invoke ContinueDebugEvent, DBEvent.dwProcessId,DBEvent.dwThreadId, DBG_CONTINUE
             .continue
          .endif
       .endif
       invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
   .endw
.else
    invoke MessageBox, 0, addr SearchFail, addr AppName,MB_OK+MB_ICONERROR .endif
invoke ExitProcess, 0
end start

;--------------------------------------------------------------------
; The partial source code of win.asm, our debuggee. It's actually
; the simple window example in tutorial 2 with an infinite loop inserted
; just before it enters the message loop.
;----------------------------------------------------------------------

......
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,/ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,/ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,/ hInst,NULL
mov hwnd,eax
jmp $ <---- Here's our infinite loop. It assembles to EB FE
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.while TRUE
   invoke GetMessage, ADDR msg,NULL,0,0
   .break .if (!eax)
   invoke TranslateMessage, ADDR msg
   invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

分析:

invoke FindWindow, addr ClassName, NULL

我们的程序需要用DebugActiveProcess将自己绑定到被调试程序,这需要知道被调试程序的进程Id。用GetWindowThreadProcessId 可以得到该Id,该函数需要窗口句柄作为参数,因此首先需要知道窗口句柄。
FindWindow, 我们先指定窗口类的名称,返回的是该类创建的窗口句柄。如 果返回NULL,则表明当前没有该类的窗口。

.if eax!=NULL
    invoke GetWindowThreadProcessId, eax, addr ProcessId
    mov ThreadId, eax
    invoke DebugActiveProcess, ProcessId

得到进程Id后,我们调用DebugActiveProcess。这样就进入等待调试事件的循环中。

       .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          mov context.ContextFlags, CONTEXT_CONTROL
          invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context           

当得到 CREATE_PROCESS_DEBUG_INFO, 这意味着被调试进程已经被暂停运行了。 我们就可以对该进程动手术了。本例中,我们将用NOPs ( 90h 90h)覆盖被调试进程中的无 限循环指令(0EBh 0FEh) 。
首先,需要得到该指令的地址。由于在我们的程序绑定到被调试程序时,被调试程序已经 处于循环语句中了,eip总是指向该指令。我们所要做的是得到eip的值。我们将使用 GetThreadContext来达到此目的。将上下文结构成员中ContextFlags设置 为CONTEXT_CONTROL ,这样告诉GetThreadContext我们需要它去填充上下 文结构的成员中的"控制"寄存器。

          invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL

得到eip的值以后,可以调用WriteProcessMemory来用NOPs覆盖"jmp $" 指令,这样将使被调试程序退出无限循环。在向用户显示了信息之后,调用ContinueDebugEvent 来恢复被调试程序的运行。由于指令"jmp $"已被Nops覆盖,被调试程序将继续 显示窗口,并进入消息循环。证据是我们在屏幕上观察到了次窗口。

另一个例子与此稍有不同,它是将被调试程序从无限循环中中断。

.......
.......
.if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
   mov context.ContextFlags, CONTEXT_CONTROL
   invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
   add context.regEip,2
   invoke SetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
   invoke MessageBox, 0, addr LoopSkipped, addr AppName, MB_OK+MB_ICONINFORMATION
.......
.......

这里仍调用GetThreadContext来获取eip值,但没有去覆盖"jmp $" 指令,而是将 regEip加2,从而"跳过"该指令。结果是当被调试程序 重新获得控制权时,将恢复执行在"jmp $"后的指令。

现在你可以体会到Get/SetThreadContext的威力了。你也可以修改其他寄存器映象,这些值将直接反映到被调试程序中。甚至你可以把int 3h指令插入到被调试进程中。产生断点。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值