4.3 进程快照
PyDbg 提供了一个非常酷的功能,进程快照。使用进程快照的时候,我们就能够冰冻进 程,获取进程的内存数据。以后我们想要让进程回到这个时刻的状态,只要使用这个时刻的 快照就行了。
4.3.1 获得进程快照
第一步,在一个准确的时间获得一份目标进程的精确快照。为了使得快照足够精确,需 要得到所有线程以及 CPU 上下文,还有进程的整个内存。将这些数据存储起来,下次我们 需要恢复快照的时候就能用的到。
为了防止在获取快照的时候,进程的数据或者状态被修改,需要将进程挂起来,这个任 务由 suspend_all_threads()完成。挂起进程之后,可以用 process_snapshot()获取快照。快照完 成之后,用 resume_all_threads()恢复挂起的进程,让程序继续执行。当某个时刻我们需要将 进程恢复到从前的状态,简单的 process_restore()就行了。这看起来是不是太简单了?
现在新建个 snapshot.py 试验下,代码的功能就是我们输入"snap"的时候创建一个快照, 输入"restore"的时候将进程恢复到快照时的状态。
#snapshot.py
from pydbg import *
from pydbg.defines import *
import threading
import time
import sys
class snapshotter(object):
def init (self,exe_path):
self.exe_path = exe_path
self.pid = None
self.dbg = None
self.running = True
# Start the debugger thread, and loop until it sets the PID
# of our target process
pydbg_thread = threading.Thread(target=self.start_debugger) pydbg_thread.setDaemon(0)
pydbg_thread.start()
while self.pid == None:
time.sleep(1)
# We now have a PID and the target is running; let's get a
# second thread running to do the snapshots
monitor_thread = threading.Thread(target=self.monitor_debugger) monitor_thread.setDaemon(0)
monitor_thread.start()
def monitor_debugger(self):
while self.running == True:
input = raw_input("Enter: 'snap','restore' or 'quit'")
input = input.lower().strip()
if input == "quit":
print "[*] Exiting the snapshotter."
self.running = False self.dbg.terminate_process()
elif input == "snap":
print "[*] Suspending all threads." self.dbg.suspend_all_threads()
print "[*] Obtaining snapshot."
self.dbg.process_snapshot()
print "[*] Resuming operation."
self.dbg.resume_all_threads()
elif input == "restore":
print "[*] Suspending all threads." self.dbg.suspend_all_threads()
print "[*] Restoring snapshot."
self.dbg.process_restore()
print "[*] Resuming operation."
self.dbg.resume_all_threads()
def start_debugger(self): self.dbg = pydbg()
pid = self.dbg.load(self.exe_path)
self.pid = self.dbg.pid
self.dbg.run()
exe_path = "C:\\WINDOWS\\System32\\calc.exe"
snapshotter(exe_path)
那么第一步就是在调试器内部创建一个新线程,并用此启动目标进程。通过使用分开的线程,就能将被调试的进程和调试器的操作分开,这样我们输入不同的快照命令进行操作的 时候,就不用强迫被调试进程暂停。当创建新线程的代码返回了有效的 PID,我们就创建另 一个线程,接受我们输入的调试命令。之后这个线程根据我们输入的命令决定不同的操作(快 照,恢复快照,结束程序)。
我们之所以选择计算器作为例子,是因为通过操作图形界面 ,可以更清晰的看到,快 照的作用。先在计算器里输入一些数据,然后在终端里输入"snap"进行快照,之后再在计算器 里进行别的操作。最后就当的输入"restore",你将看到,计算器回到了最初时快照的状态。 使用这种方法我们能够将进程恢复到任意我们希望的状态。
现在让我们将所有的新学的 PyDbg 知识,创建一个 fuzz 辅助工具,帮助我们找到软件 的漏洞,并自动处理奔溃事件。
4.3.2 组合代码
我们已经介绍了一些 PyDbg 非常有用的功能,接下来要构建一个工具用来根除应用程 序中出现的可利用的漏洞。在我们平常的开发过程中,有些函数是非常危险的,很容易造成 缓冲区溢出,字符串问题,以及内存出错,对这些函数需要重点关注。
工具将定位于危险函数,并跟踪它们的调用。当我们认为函数被危险调用了,就将 4 堆栈中的 4 个参数接触引用,弹出栈,并且在函数产生溢出之前对进程快照。如果这次访问 违例了,我们的脚本将把进程恢复到,函数被调用之前的快照。并从这开始,单步执行,同 时 反 汇 编 每 个 执 行 的 代 码 , 直 到 我 们 也 抛 出 了 访 问 违 例 , 或 者 执 行 完 了 MAX_INSTRUCTIONS(我们要监视的代码数量)。无论什么时候当你看到一个危险的函数 在处理你输入的数据的时候,尝试操作数据 crash 数据都似乎值得。这是创造出我们的漏洞 利用程序的第一步。
开动代码,建立 danger_track.py,输入下面的代码。
#danger_track.py
from pydbg import *
from pydbg.defines import *
import utils
# This is the maximum number of instructions we will log
# after an access violation MAX_INSTRUCTIONS = 10
# This is far from an exhaustive list; add more for bonus points dangerous_functions = {
"strcpy" : "msvcrt.dll",
"strncpy" : "msvcrt.dll",
"sprintf" : "msvcrt.dll", "vsprintf": "msvcrt.dll"
}
dangerous_functions_resolved = {}
crash_encountered = False
instruction_count = 0
def danger_handler(dbg):
# We want to print out the contents of the stack; that's about it
# Generally there are only going to be a few parameters, so we will
# take everything from ESP to ESP+20, which should give us enough
# information to determine if we own any of the data esp_offset = 0
print "[*] Hit %s" % dangerous_functions_resolved[dbg.context.Eip]
print "================================================================="
while esp_offset <= 20:
parameter = dbg.smart_dereference(dbg.context.Esp + esp_offset)
print "[ESP + %d] => %s" % (esp_offset, parameter)
esp_offset += 4
print "=================================================================\n
dbg.suspend_all_threads()
dbg.process_snapshot()
dbg.resume_all_threads()
return DBG_CONTINUE
def access_violation_handler(dbg):
global crash_encountered
# Something bad happened, which means something good happened :)
# Let's handle the access violation and then restore the process
# back to the last dangerous function that was called
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()
if crash_encountered == False:
dbg.suspend_all_threads()
dbg.process_restore()
crash_encountered = True
# We flag each thread to single step
for thread_id in dbg.enumerate_threads():
print "[*] Setting single step for thread: 0x%08x" % thread_id
h_thread = dbg.open_thread(thread_id)
dbg.single_step(True, h_thread)
dbg.close_handle(h_thread)
# Now resume execution, which will pass control to our
# single step handler
dbg.resume_all_threads()
return DBG_CONTINUE
else:
dbg.terminate_process()
return DBG_EXCEPTION_NOT_HANDLED
def single_step_handler(dbg):
global instruction_count
global crash_encountered
if crash_encountered:
if instruction_count == MAX_INSTRUCTIONS:
dbg.single_step(False)
return DBG_CONTINUE
else:
# Disassemble this instruction
instruction = dbg.disasm(dbg.context.Eip)
print "#%d\t0x%08x : %s" % (instruction_count,dbg.context.Eip, instruction)
instruction_count += 1
dbg.single_step(True)
return DBG_CONTINUE
dbg = pydbg()
pid = int(raw_input("Enter the PID you wish to monitor: "))
dbg.attach(pid)
# Track down all of the dangerous functions and set breakpoints
for func in dangerous_functions.keys():
func_address = dbg.func_resolve( dangerous_functions[func],func )
print "[*] Resolved breakpoint: %s -> 0x%08x" % ( func, func_address )
dbg.bp_set( func_address, handler = danger_handler )
dangerous_functions_resolved[func_address] = func
dbg.set_callback( EXCEPTION_ACCESS_VIOLATION, access_violation_handler )
dbg.set_callback( EXCEPTION_SINGLE_STEP, single_step_handler )
dbg.run()
通过之前对 PyDbg 的诸多讲解,这段代码应该看起来不那么难了吧。测试这个脚本的 最好方法,就是运行一个有漏洞价格的程序,然后让脚本附加到进程,和程序交互,尝试 crash 程序。
我们已经对 PyDbg 有了一定的了解,不过这只是它强大功能的一部分,还有更多的东 西,需要你自己去挖掘。再好的东西也满足不了那些"懒惰"的 hacker。PyDbg 固然强大,方 便的扩展,自动化调试。不过每次要完成任务的时候,都要自己动手编写代码。接下来介绍 的 Immunity Debugger 弥补了这点,完美的结合了图形化调试和脚本调试。它能让你更懒, 哈。让我们继续。