Soft Function Hooking with windbg and pykd

There are a lot of ways to modify the execution of a program, including at least using Windows Compatibility Toolkit (a good reference is Mark Baggett’s Derbycon talk), modifying the environment, manual patching the binary before it runs, and function hooking.  Function hooking generally refers to any method where you’re able to intercept and modify function calls of a running process. A simple example of a function hook might be “every time the program calls AESEncrypt, first save the plaintext to a file and then call AESEncrypt”.

There are also many different ways to function hook, and in my opinion there isn’t really a “best” way – it just depends on what you’re trying to do. For example, if you’re doing something to try to be sneaky, one of the best ways may be like Joe outlines here:

  1. Reflectively load your DLL using powershell so nothing needs to ever touch disk
  2. In your DLL, write a C function that contains the functionality to execute. Optionally, return control to the original function
  3. Overwrite the first bytes of the function to jump to your DLL

However, if your goal is to change the behavior of a program and you don’t care about stealth (e.g. you’re just using hooking as an aid to testing) there are easier ways to accomplish the same goal. “soft” function hooking usually refers to attaching a debugger to a program and using the debugger’s functionality to modify the behavior. I’ve seen this approach elsewhere – in gray hat python, they use this technique with pydbg and immunitydbg.

I learned about pykd because of mona for windb. I messed with pykd last week, and I like it quite a bit (at least more than windbg plugin alternatives I’ve used like powerdbg). There are pluses and minuses when compared with something like immunity debugger. Pykd doesn’t currently have nearly the number of convenience functions immunitydbg has (for example, you have to store your strings in memory manually). UPDATE:  In this case I was looking for something like remotevirtualalloc and didn’t see it. But @corelanc0d3r pointed me at windbglib, which has these exact convenience functions.   But Windbg is just a more powerful debugger. For example, immunitydbg is awesome, but it doesn’t work with 64 bit processes, following children processes, kernel debugging, etc.

Here is a simple example. I ran into a situation where a team’s test box had a hard coded a test server to listen only on localhost. This can be a pain to debug, because a lot of my tools are on other boxes and plus I can’t do things like see what’s actually going on with wireshark. This is a quick script that modifies the behavior of inet_addr, which is where this binary passed the hard coded localhost to (if you’re wondering why I didn’t just patch it – that was an option too, but there was some other important stuff right next to it in .data and ‘localhost’ was too small to fit my IP). So this hook simply grabs the current IP and passes it as the arg to inet_addr instead of “localhost”

Some things I got a bit stuck on

  1. Use the second argument with setBP to have a callback function on the breakpoints, and then use this to modify things. Note you can’t mess with execution within the function itself. Before going this route I tried to use the EventHandlers (like onBreakPoint) and ended up with weird errors.
  2. Within your callback function, if you return True (or nothing), execution will halt, and if you return False then execution will continue
import pykd
import socket
#pykd script to modify inet_addr calls to a supplied IP address

def getAddress(localAddr):
    res = pykd.dbgCommand("x " + localAddr)
    if res.count("\n") > 1:
        print "[-] Warning, more than one result for", localAddr
    return res.split()[0]

class handle_inet(pykd.eventHandler):
	def __init__(self):
		self.localAddr = socket.gethostbyname(socket.gethostname())
		print "[+] Using ip address: " + self.localAddr

		bp_init = getAddress("WS2_32!inet_addr")
		self.bp_init = pykd.setBp(int(bp_init, 16), self.handle_inet_begin)
		self.bp_end = None

	def handle_inet_begin(self, args):
		print args
		print "[+] At start of inet_addr."
		ow_len = len(self.localAddr) + 1

		#just save our string below us on the stack. We'll restore it on return
		#ret_addr = pykd.dbgCommand("k1").split("\n")[1].split()[1] #k doesn't work in win7, wtf
		self.ret_addr = pykd.dbgCommand("dd esp L1").split()[1]

		print "[+] saving return ptr: " + self.ret_addr

		self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.handle_inet_end)
		self.stack_addr = pykd.reg("esp") + 500

		print "[+] using this stack address to save our string: " + hex(self.stack_addr)

		self.old_stack = pykd.loadBytes(self.stack_addr, ow_len)
		print "[+] Writing over old stack stuff"
		pykd.dbgCommand("ea " + hex(self.stack_addr) + " \"" + self.localAddr + '"')
		#null terminate
		pykd.dbgCommand("eb " + hex(self.stack_addr) + "+" + hex(len(self.localAddr)) + " 00")

		#esp + 4 is the IP address parameter for inet_addr
		pykd.dbgCommand("ed esp+4 " + hex(self.stack_addr))

        #Since this is a conditional bp, this makes the debugger continue
		return False

	def handle_inet_end(self, bp):
		if self.bp_end == bp:
                        print "[+] Call complete"
			old_stack = " ".join([hex(i)[2:] for i in self.old_stack])
			pykd.dbgCommand("eb " + hex(self.stack_addr) + " " + old_stack)
			print "[+] Old stack stuff restored"
			self.bp_end = None
		#Since this is a conditional bp, this makes the debugger continue
		return False

d_handle = handle_inet()

If you know windbg basics and python, this should be really familiar – I have a tiny bit of python to grab the IP, and then inside the handlers I’m pretty much just running windbg commands sequentially. I ran this in the debugger itself. Here’s a side tip. You can run put commands in a textfile for them to run in windbg (similar to gdb’s -x arg). So you can do this to load this pykd script automatically.

> type windbg.txt
.load pykd.pyd
> windbg -c "$$><windbg.txt" server1.exe


Another options would be to run this directly from the command line, which is also doable. Just use the pykd “attachProcess” or “startProcess” functions and go from there.