🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 6.1 用 PyDbg 实现 Soft Hooking 第一个例子就是在应用层嗅探加密的网络传输。平时为了明白客户端和服务器之间的工 作流程,我们都会使用一个网络分析器列如 Wireshark。很不幸的是,Wireshark 获得的数据 经常都是加密过的,使得协议分析变得模糊。用 soft hooking 你能够在数据加密前或者接受 并解密后捕获它们。 实验目标就是最流行的开源浏览器 Mozilla Firefox。为了这次实验,我们假设 Firefox 是闭源的(否则会相当没趣)。我们的任务就是在 firefox.exe 进程加密数据前嗅探出数据。 现在最通用的网络加密协议就是 SSL,这次的主要目标就是解决她。 为了跟踪函数的调用(未加密数据的传递),需要使用记录模块间调用的技巧 [http://forum.immunityinc.com/index.php?topic=35.0](http://forum.immunityinc.com/index.php?topic=35.0) )。现在首要解决的问题就是在什么地方 设置 hook。我们先假定将 hook 设置在 PR_Write 函数上(由 nspr4.dll.导出)。当这个函数被 执行的时候,堆栈[ ESP + 8 ]指向 ASCII 字符串(包含我们提交的但未加密的数据)。 ESP + 8 说明它是 PR_Write 的第二个函数,也是我们需要的,记录它,恢复程序。 首先打开 Firefox,输入网址 [https://www.openrce.org/](http://www.openrce.org/)。一旦你接收了 SSl 证书,页面就 加载成功。接着 Immunity 附加到 firefox.exe 进程在 nspr4.PR_Write 设置断点。在 OpenRCE 网站右上角有一个登录窗口,设置用户名为 test 和密码 test,点击 Login 按钮。设置的断点 立刻被触发;再按 F9,断点再次触发。最后,你将在栈看到如下的内容: ``` [ESP + 8] => ASCII "username=test&password=test&remember_me=on" ``` 很好,我们很清晰的看到了用户名和密码。但是如果从网络层看传输的数据,将是一堆 经过 SSL 加密的无意义的数据。这种方法不仅对 OpenRCE 有效。当你浏览任何一个需要传 输敏感数据的网站的时候,这些数据都将很容易的被捕捉到。现在再也不用手工操作调试器 去捕捉了,自动化才是王道。 在用 PyDbg 定义 soft hook 之前,需要先定义一个包含说有 hook 目标的容器。如下初始 化容器: ``` hooks = utils.hook_container() ``` 使用 hook_container 类的 add()方法将我们定义的 hook 加进去。函数原型: ``` add( pydbg, address, num_arguments, func_entry_hook, func_exit_hook ) ``` 第一个参数设置成一个有效的 pydbg 目标,address 参数设置成要安装 hook 的地址, num_arguments 设置成传递给 hook 的参数。func_entry_hook 和 func_exit_hook 都是回调函数。 func_entry_hook 是 hook 被触发后立刻调用的,func_exit_hook 是被 hook 的函数将要退出之 前执行的。entry hook 用于得到函数的参数,exit hook 用于捕捉函数的返回值。 ``` def entry_hook( dbg, args ): # Hook code here return DBG_CONTINUE ``` dbg 参数设置成有效的 pydbg 目标,args 接收一个列表,包含 hook 触发时接收到的参 数。 exit hook 回调函数有一点不同就是多了个 ret 参数,包含了函数的返回值(EAX 的值): def exit_hook( dbg, args, ret ): ``` # Hook code here return DBG_CONTINUE ``` 接下用实例看看如何用 entry hook 嗅探加密前的数据。 ``` #firefox_hook.py from pydbg import * from pydbg.defines import * import utils import sys dbg = pydbg() found_firefox = False # Let's set a global pattern that we can make the hook # search for pattern = "password" # This is our entry hook callback function # the argument we are interested in is args[1] def ssl_sniff( dbg, args ): # Now we read out the memory pointed to by the second argument # it is stored as an ASCII string, so we'll loop on a read until # we reach a NULL byte buffer = "" offset = 0 while 1: byte = dbg.read_process_memory( args[1] + offset, 1 ) if byte != "\x00": buffer += byte offset += 1 continue else: break if pattern in buffer: print "Pre-Encrypted: %s" % buffer return DBG_CONTINUE # Quick and dirty process enumeration to find firefox.exe for (pid, name) in dbg.enumerate_processes(): if name.lower() == "firefox.exe": found_firefox = True hooks = utils.hook_container() dbg.attach(pid) print "[*] Attaching to firefox.exe with PID: %d" % pid # Resolve the function address hook_address = dbg.func_resolve_debuggee("nspr4.dll","PR_Wri if hook_address: # Add the hook to the container. We aren't interested # in using an exit callback, so we set it to None. hooks.add( dbg, hook_address, 2, ssl_sniff, None ) print "[*] nspr4.PR_Write hooked at: 0x%08x" % hook_address break else: print "[*] Error: Couldn't resolve hook address." sys.exit(-1) if found_firefox: print "[*] Hooks set, continuing process." dbg.run() else: print "[*] Error: Couldn't find the firefox.exe process." sys.exit(-1) ``` 代码简洁明了:在 PR_Write 上设置 hook,当 hook 被触发的时候,我们尝试读出第二个 参数指向的字符串。如果有符合的数据就打印在命令行。启动一个新的 Firefox,接着运行 firefox_hook.py 脚本。重复之前的步骤,登录 [https://www.openrce.org/](http://www.openrce.org/),将看到输出如下: ``` [*] Attaching to firefox.exe with PID: 1344 [*] nspr4.PR_Write hooked at: 0x601a2760 [*] Hooks set, continuing process. Pre-Encrypted: username=test&password=test&remember_me=on Pre-Encrypted: username=test&password=test&remember_me=on Pre-Encrypted: username=jms&password=yeahright!&remember_me=on ``` Listing 6-1: How cool is that! 我们能看到未加密前的用户名密码 我们已经看到了 soft hook 的轻量级和强大能力。这种方法能被用于所有类型的调试和 逆向过程。在上面的例子中 soft hook 的工作还算正常,如果遇到有性能限制的函数调用时, 进程马上就会变得缓慢,行为异常,还可能崩溃。只是因为,当 INT3 被触发的时候,会将 执行权限交给我们的 hook 代码之后返回。这回花费非常多的事件,如果函数每秒钟执行数 千次。接下来让我们看看如何通过设置 hard hook 和 instrument low-level heap routines 以解决这个问题。