I have had hands down the best experiences with PaiMei, or in particular PyDbg for these purposes. The book "Grayhat Python" brought it to my attention and I decided to give it a try.
The script
Here's a somewhat generic skeleton I've been using in my dynamic analysis (dynamic-analysis) efforts. I have adjusted it so it'll hook the Unicode version of the function you are interested in hooking.
However, the answer may not be spot on, since you can achieve breaking conditionally, but you'll have to add the interactive debugger part to the script. If you wanted to vreak instead of continuing, you'd have to tell the debugger to do just that by modifying the line return DBG_CONTINUE
.
import sys
import ctypes
import traceback
try:
from pydbg import *
from pydbg.defines import *
from utils import hooking
except:
print "ERROR: you need pydbg and utils.hooking from PAIMEI."
sys.exit(-1)
reg = None
class reg_pydbg(pydbg):
@staticmethod
def __getlen(mbi, address):
# What's the maximum number of bytes we can read?
_maxlen = 64*1024
absmaxlen = (mbi.BaseAddress + mbi.RegionSize) - address
if absmaxlen > _maxlen:
return _maxlen
return absmaxlen
def rootkey_const(self, key):
if 0x80000000 == key:
return "HKCR"
elif 0x80000001 == key:
return "HKCU"
elif 0x80000002 == key:
return "HKLM"
elif 0x80000003 == key:
return "HKU"
elif 0x80000004 == key:
return "HKEY_PERFORMANCE_DATA"
elif 0x80000050 == key:
return "HKEY_PERFORMANCE_TEXT"
elif 0x80000060 == key:
return "HKEY_PERFORMANCE_NLSTEXT"
elif 0x80000005 == key:
return "HKEY_CURRENT_CONFIG"
elif 0x80000006 == key:
return "HKEY_DYN_DATA"
elif 0x80000007 == key:
return "HKEY_CURRENT_USER_LOCAL_SETTINGS"
return "0x%08X" % (key)
def readmem(self, address, len = 0):
try:
mbi = self.virtual_query(address)
except:
return None, "%08X <invalid ptr>" % (address)
if mbi.Protect & PAGE_GUARD: # no way to display contents of a guard page
return None, "%08X <guard page>" % (address)
if 0 == len: # try to make a good guess then
len = self.__getlen(mbi, address)
try:
explored = self.read_process_memory(address, len)
except:
return None, "%08X <ReadProcessMemory failed>" % (address)
return explored, None
def readstring(self, address, unicodeHint = False, returnNone = False):
if 0 == address:
if returnNone:
return None
return "<nullptr>"
explored, retval = self.readmem(address)
if not explored:
if returnNone:
return None
return retval
explored_string = None
if not unicodeHint:
explored_string = self.get_ascii_string(explored)
if not explored_string:
explored_string = self.get_unicode_string(explored)
if not explored_string:
explored_string = self.get_printable_string(explored)
return explored_string
def exit_RegOpenKeyExW(dbg, args, ret):
keyname = dbg.readstring(args[1], True)
print "RegOpenKeyExW(%s, %s, ...) -> %s (%d)" % (dbg.rootkey_const(args[0]), keyname, ctypes.FormatError(ret), ret)
return DBG_CONTINUE
class reghooks:
fct2hook = {
"advapi32.dll" :
{
"RegOpenKeyExW" : { "args" : 5, "entry" : None, "exit" : exit_RegOpenKeyExW },
},
}
hooked = {}
hookcont = None
dbg = None
def __init__ (self, dbg):
self.hookcont = hooking.hook_container()
self.hooked = {}
self.dbg = dbg
dbg.set_callback(LOAD_DLL_DEBUG_EVENT, self.handler_loadDLL)
def hookByDLL(self, dll):
if not dll.name.lower() in self.hooked:
for key,value in self.fct2hook.items():
if key.lower() == dll.name.lower():
self.hooked[dll.name.lower()] = 1
print "%s at %08x" % (dll.name, dll.base)
for func,fctprops in value.items():
entry = None; exit = None; args = 0
if "entry" in fctprops and None != fctprops["entry"]:
print "\tentry hook " + func
entry = fctprops["entry"]
if "exit" in fctprops and None != fctprops["exit"]:
print "\texit hook " + func
exit = fctprops["exit"]
if "args" in fctprops and None != fctprops["args"]:
args = fctprops["args"]
if None != entry or None != exit:
funcaddr = self.dbg.func_resolve(dll.name, func)
self.hookcont.add(self.dbg, funcaddr, args, entry, exit)
else:
self.hooked[dll.name.lower()] += 1
return
@staticmethod
def handler_loadDLL(dbg):
global reg
dbg.hide_debugger()
last_dll = dbg.get_system_dll(-1)
reg.hookByDLL(last_dll)
return DBG_CONTINUE
def main():
dbg = reg_pydbg()
dbg.load("C:\\Windows\\regedit.exe")
global reg
reg = reghooks(dbg)
dbg.run()
if __name__ == "__main__":
main()
Example output
>C:\Python26\python.exe hookreg.py
advapi32.dll at 75840000
exit hook RegOpenKeyExW
RegOpenKeyExW(HKCU, Software\Microsoft\Windows\CurrentVersion\Applets\Regedit, ...) -> The operation completed successfully. (0)
RegOpenKeyExW(HKLM, SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink, ...) -> The operation completed successfully. (0)
RegOpenKeyExW(HKLM, SOFTWARE\Microsoft\Windows NT\CurrentVersion\LanguagePack\DataStore_V1.0, ...) -> The operation completed successfully. (0)
RegOpenKeyExW(HKLM, SOFTWARE\Microsoft\Windows NT\CurrentVersion\LanguagePack\SurrogateFallback, ...) -> The operation completed successfully. (0)
Explanation
The important parts are:
"RegOpenKeyExW" : { "args" : 5, "entry" : None, "exit" : exit_RegOpenKeyExW },
and the function exit_RegOpenKeyExW
. You could also modify the above to hook only upon entry RegOpenKeyExW
instead of upon exit or to do both. Depends on what you want to achieve.
I have used this method to decode buffers (SCSI_PASS_THROUGH_DIRECT
and SCSI_PASS_THROUGH
) sent via DeviceIoControl
, so the above is by far not the most complex thing you can do.
On another note, I have also hooked stuff like opening files (or registry keys) and kept lists of the returned handles along with the string form of what they are. This way I could implement quite complex human-readable logging scenarios.
Rationale
The reason stuff like the nested dictionary exists is so that it can be easily extended to hook whatever function from whatever DLL I please. Of course it could be hardcoded as well, but I have had scenarios where I was hooking dozens of functions.