💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 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 弥补了这点,完美的结合了图形化调试和脚本调试。它能让你更懒, 哈。让我们继续。