1. 软钩子
软钩子针对各种不同调试事件发生时调用的,调试事件如断点、异常、加载或卸载dll、创建或销毁进程线程等。
以断点命中时触发钩子LogBpHook为例:
import immlib
from immlib import LogBpHook
import struct
#首先继承LogBpHook定义自己的钩子类,重点实现run函数
class MyHook(LogBpHook):
def __init__(self):
LogBpHook.__init__(self)
def run(self, regs):
#钩子触发时要执行的任务,regs中保存各个寄存器的值
imm = immlib.Debugger()
bytes=imm.readMemory( regs['ESP'] + 4, 0x8) #该处读取8个字节
(format_addr,arg) = struct.unpack("LL", bytes) #解压bytes为两个4字节的数
buf = ""
offset = 0
while True:
byte = imm.readMemory(format_addr+offset,1) #读取1个字节
if byte != '\x00':
buf += byte
offset += 1
continue
else:
break
imm.log("%s %d" % (buf,int(arg))) #输出printf的第一个和第二个参数
#然后在主函数中初始化该类的实例,并绑定函数地址
def main(args):
imm = immlib.Debugger()
hook = MyHook()
func = imm.getAddress("msvcrt.printf") #获得函数printf的地址
imm.log("%08x" % func)
hook.add("hookprintf", func) #绑定函数地址
return 'hook printf'
上面的脚本用于捕获printf函数第一个参数和第二个参数(默认第一个参数为格式化字符串),加载被调试程序后直接运行上面的脚本部署钩子,然后运行程序就可以收集printf参数信息。
值得注意的是,LogBpHook钩子并不会暂停程序,程序会一直执行,断点命中是暗中进行并调用钩子函数完成自动化信息收集。
软钩子的具体用法还可参考PyCommands\hookheap.py
2. 硬钩子
硬钩子适用于对调用频繁的函数下钩子,通过布置一个汇编代码桩(stub)来使得代码执行流在钩子命中时重定向至钩子代码处。
FastLogHook 针对cdecl函数调用约定
STDCALLFastLogHook 针对stdcall函数调用约定
构建硬钩子的框架
imm = immlib.Debugger()
fast = immlib.FastLogHook( imm ) #实例化一个钩子对象
fast.logFunction( address, num_arguments )
num_num_arguments为被下钩子函数的参数个数
要在函数头部设置钩子捕获参数,num_num_arguments设置为相应的值
要在函数出口设置钩子,num_num_arguments设置为0
fast.logRegister( register )
跟踪特定寄存器的值
register可以是"EAX","EBP","ESP"等
fast.logBaseDisplacement( register, offset)
一般适合跟踪栈上参数,register设置为"EBP"或"ESP"
fast.logDirectMemory( address )
记录内存地址address上的值
只要钩子命中,与之配套的log函数就会触发,它会把所有捕获的数据信息放在专为FastLogHook对象分配内存中
fast.logAll() 可以获得这块内存区域的所有数据记录,为嵌套列表类型,格式如下:
{(hook_address,(arg1,agr2,...)),(hook_address,(arg1,agr2,...)), ...}
还是以捕获printf函数参数为例进行说明。
import immlib
import immutils
DESC = "hook printf" #description
def getRet( imm, addr, max_opcodes = 300 ):
for a in range( 0, max_opcodes ):
op = imm.disasmForward( addr )
if op.isRet():
## if op.getImmConst() == 0xC3:
op = imm.disasmBackward( addr, 2)
return op.getAddress()
addr = op.getAddress()
return 0x0
def unHook( imm ):
fast = imm.getKnowledge( "hook_printf" )
if not fast:
imm.log( "hook_printf:未能获取钩子记录" )
return
if not fast.isHooked():
imm.log( "hook_printf:未曾安装钩子" )
return
if fast.unHook():
imm.log( "hook_printf:已卸载全部钩子" )
return
return
def Hook( imm ):
msvcrt = imm.getModule( "msvcrt.dll" )
if not msvcrt:
imm.log( "cannot find msvcrt.dll module" )
return
#暂停调试器
imm.pause()
#获取printf的地址
printf_addr = imm.getAddress( "msvcrt.printf" )
imm.log( "hook printf head at: 0x%08x" % printf_addr)
#确保模块代码已经被调试器分析过
if not msvcrt.isAnalysed():
imm.analyseCode( msvcrt.getCodebase() )
#对于printf因为要获取返回值,所以hook在函数尾部
printf_tail = getRet( imm, printf_addr, 1000 )
imm.log( "hook printf tail at: 0x%08x" % printf_tail)
imm.addKnowledge( "hook_printf", (printf_addr,printf_tail) ) #保存钩子的地址
#获取hook对象
fast = immlib.FastLogHook( imm )
#在printf头部设置钩子
fast.logFunction(printf_addr) #默认两个参数
fast.logBaseDisplacement( "ESP", 0x04 )#arg1
fast.logBaseDisplacement( "ESP", 0x08 )#arg2
#在printf尾部设置钩子
fast.logFunction(printf_tail)
fast.logBaseDisplacement( "EBP", -0x20 )#reture value
#启用钩子
fast.Hook()
#将取到的数据保存,供以后读取
imm.addKnowledge( "my_hook", fast, force_add = 1 )
return
def main(args):
imm = immlib.Debugger()
if len(args) == 0:
fast = imm.getKnowledge( "my_hook" )
if not fast:
imm.log( "hook_printf no log data" )
return
log_list = fast.getAllLog()
(printf_head,printf_tail) = imm.getKnowledge("hook_printf")
for a in log_list:
if a[0] == printf_head:
imm.log("printf(0x%08x, 0x%08x)" % \
(a[1][0],a[1][1])) #arg1和arg2
else:
imm.log("return value is: %d" % a[1][0])
return ""
if args[0] == "unhook":
unHook( imm )
return ""
if args[0] == "hook":
Hook( imm )
return ""
加载程序,运行脚本!hook_printf.py hook,然后运行程序;
程序结束后,再运行脚本!hook_printf.py,在log窗口即可看到结果。
但是在printf函数尾部设置钩子捕获返回值没有成功(printf的返回值为参数个数,在上例中参数个数必须为2)
硬钩子的具体用法还可参考PyCommands\hippie.py
关于ImmDbg非常好一篇教程:Starting to write Immunity Debugger PyCommands : my cheatsheet