4.2 处理访问违例

当程序尝试访问它们没有权限访问的页面的时候或者以一种不合法的方式访问内存的 时候,就会产生访问违例。导致违例错误的范围很广,从内存溢出到不恰当的处理空指针都 有可能。从安全角度考虑,每一个访问违例都应该仔细的审查,因为它们有可能被利用。

当调试器处理访问违例的时候,需要搜集所有和违例相关的信息,栈框架,寄存器,以及引起违例的指令。接着我们就能够用这些信息写一个利用程序或者创建一个二进制的补丁 文件。

PyDbg 能够很方便的实现一个违例访问处理函数,并输出相关的奔溃信息。这次的测试 目标就是危险的 C 函数 strcpy() ,我们用它创建一个会被溢出的程序。接下来我们再写一个 简短的 PyDbg 脚本附加到进程并处理违例。溢出的脚本 buffer_overflow.py,代码如下:

# buffer_overflow.py 
from ctypes import * 
msvcrt = cdll.msvcrt
# Give the debugger time to attach, then hit a button 
raw_input("Once the debugger is attached, press any key.")
# Create the 5-byte destination buffer 
buffer = c_char_p("AAAAA")
# The overflow string 
overflow = "A" * 100
# Run the overflow 
msvcrt.strcpy(buffer, overflow)

问题出在这句 msvcrt.strcpy(buffer, overflow),接受的应该是一个指针,而传递给函数的是一个变量,函数就会把 overflow 当作指针使用,把里头的值当作地址用(0x41414141414.....)。可惜这个地址是很可能是不能用的。现在我们已经构造了测试案例, 接下来是处理程序了。

# access_violation_handler.py
from pydbg import *
from pydbg.defines import *
# Utility libraries included with PyDbg 
import utils
# This is our access violation handler 
def check_accessv(dbg):
    # We skip first-chance exceptions
    if dbg.dbg.u.Exception.dwFirstChance:
        return DBG_EXCEPTION_NOT_HANDLED
    crash_bin = utils.crash_binning.crash_binning() 
    crash_bin.record_crash(dbg)
    print crash_bin.crash_synopsis() 
    dbg.terminate_process()
    return DBG_EXCEPTION_NOT_HANDLED
pid = raw_input("Enter the Process ID: ") 
dbg = pydbg()
dbg.attach(int(pid)) dbg.set_callback(EXCEPTION_ACCESS_VIOLATION,check_accessv) 
dbg.run()

现在运行 buffer_overflow.py,并记下它的进程号,我们先暂停它等处理完以后再运行。

执行 access_violation_handler.py 文件,输入测试套件的 PID.当调试器附加到进程以后,在测 试套件的终端里按任何键,接下来你应该看到和表 4-1 相似的输出。

python25.dll:1e071cd8 mov ecx,[eax+0x54] from thread 3376 caused access violation when attempting to read from 0x41414195 CONTEXT DUMP
    EIP: 1e071cd8 mov ecx,[eax+0x54] 
    EAX: 41414141 (1094795585) -> N/A
    EBX: 00b055d0 ( 11556304) -> @U`" B`Ox,`O )Xb@|V`"L{O+H]$6 (heap) 
    ECX: 0021fe90 ( 2227856) -> !$4|7|4|@%,\!$H8|!OGGBG)00S\o (stack) 
    EDX: 00a1dc60 ( 10607712) -> V0`w`W (heap)
    EDI: 1e071cd0 ( 503782608) -> N/A
    ESI: 00a84220 ( 11026976) -> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (heap)
    EBP: 1e1cf448 ( 505214024) -> enable() -> NoneEnable automa (stack) 
    ESP: 0021fe74 ( 2227828) -> 2? BUH` 7|4|@%,\!$H8|!OGGBG) (stack)
    +00: 00000000 ( 0) -> N/A
    +04: 1e063f32 ( 503725874) -> N/A
    +08: 00a84220 ( 11026976) -> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (heap)
    +0c: 00000000 ( 0) -> N/A
    +10: 00000000 ( 0) -> N/A
    +14: 00b055c0 ( 11556288) -> @F@U`" B`Ox,`O )Xb@|V`"L{O+H]$ (heap)
disasm around:
    0x1e071cc9 int3 
    0x1e071cca int3 
    0x1e071ccb int3 
    0x1e071ccc int3 
    0x1e071ccd int3 
    0x1e071cce int3 
    0x1e071ccf int3 
    0x1e071cd0 push esi
    0x1e071cd1 mov esi,[esp+0x8] 
    0x1e071cd5 mov eax,[esi+0x4] 
    0x1e071cd8 mov ecx,[eax+0x54] 
    0x1e071cdb test ch,0x40 
    0x1e071cde jz 0x1e071cff 
    0x1e071ce0 mov eax,[eax+0xa4] 
    0x1e071ce6 test eax,eax 
    0x1e071ce8 jz 0x1e071cf4 
    0x1e071cea push esi
    0x1e071ceb call eax 
    0x1e071ced add esp,0x4 
    0x1e071cf0 test eax,eax 
    0x1e071cf2 jz 0x1e071cff
SEH unwind:
    0021ffe0 -> python.exe:1d00136c jmp [0x1d002040] 
    ffffffff -> kernel32.dll:7c839aa8 push ebp

Listing 4-1:PyDbg 捕捉到的奔溃信息

输出了很多有用的信息片断。第一个部分指出了那个指令引发了访问异常以及指令在哪 个块里。这个信息可以帮助 你写出漏洞利用程序或者用静态分析工具分析问题出在哪里。 第二部分转储出了所有寄存器的值,特别有趣的是,我们将 EAX 覆盖成了 0x41414141(0x41 是大写 A 的的十六进制表示)。同样,我们看到 ESI 指向了一个由 A 组成的字符串。和 ESP+08 指向同一个地方。第三部分是在故障指令附近代码的反汇编指令。最后一块是奔溃发生时候 注册的结构化异常处理程序的列表。

用 PyDbg 构建一个奔溃处理程序就是这么简单。不仅能够自动化的处理崩溃,还能在 在事后剖析进程发生的一切。下节,我们用 PyDbg 的进程内部快照功能创建一个进 程 rewinder。