钩子编程(hooking),也称作“挂钩”,是计算机程序设计术语,指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码,被称为钩子(hook)。
简单来说就是用我们自己设计的函数去取代被 hook 的函数
一个简单的例子:
>>> @project.hook(0x1234, length=5)
... def set_rax(state):
... state.regs.rax = 1
其中第一个参数即需要 Hook 的调用函数的地址,第二个参数 length
即指定执行引擎在完成挂钩后应跳过多少字节。具体多少字节由 Hook 处地址的指令长度确定,例如本题:
我们需要 Hook 地址的机器指令长度为 5 个字节,故最后的 hook 函数:
@project.hook(0x80486B3, length=5)
老样子先放最后 EXP,再逐一分析:
import angr
import sys
import claripy
def Go():
path_to_binary = "./09_angr_hooks"
project = angr.Project(path_to_binary, auto_load_libs=False)
initial_state = project.factory.entry_state()
check_equals_called_address = 0x80486B3
instruction_to_skip_length = 5
@project.hook(check_equals_called_address, length=instruction_to_skip_length)
def skip_check_equals_(state):
user_input_buffer_address = 0x804A054
user_input_buffer_length = 16
user_input_string = state.memory.load(
user_input_buffer_address,
user_input_buffer_length
)
check_against_string = 'XKSPZSJKJYQCQXZV'
register_size_bit = 32
state.regs.eax = claripy.If(
user_input_string == check_against_string,
claripy.BVV(1, register_size_bit),
claripy.BVV(0, register_size_bit)
)
simulation = project.factory.simgr(initial_state)
def is_successful(state):
stdout_output = state.posix.dumps(1)
if b'Good Job.\n' in stdout_output:
return True
else:
return False
def should_abort(state):
stdout_output = state.posix.dumps(1)
if b'Try again.\n' in stdout_output:
return True
else:
return False
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
for i in simulation.found:
solution_state = i
solution = solution_state.posix.dumps(0)
print("[+] Success! Solution is: {0}".format(solution.decode('utf-8')))
#print(solution0)
else:
raise Exception('Could not find the solution')
if __name__ == "__main__":
Go()
运行一下查看结果:
下面来逐步分析:
由于 Angr 可以处理对 scanf 的初始调用,因此我们可以从头开始
path_to_binary = "./09_angr_hooks"
project = angr.Project(path_to_binary, auto_load_libs=False)
initial_state = project.factory.entry_state()
如之前分析的而言,首先找到需要 Hook 的函数地址 0x080486B3
,然后设定指令长度
check_equals_called_address = 0x80486B3
instruction_to_skip_length = 5
然后我们需要在在 @project.hook
语句之后书写我们的模拟函数。然后如上题一致,我们利用使用 state.memory
的 .load(addr, size)
接口读出 buffer
处的内存数据,与答案进行比较
@project.hook(check_equals_called_address, length=instruction_to_skip_length)
def skip_check_equals_(state):
user_input_buffer_address = 0x804A054
user_input_buffer_length = 16
user_input_string = state.memory.load(
user_input_buffer_address,
user_input_buffer_length
)
check_against_string = 'XKSPZSJKJYQCQXZV'
然后这里的关键是,我们模拟一个函数就是把它视作一个黑盒,能成功模拟输入相对应的输出即可,所以我们需要处理 check 函数的返回值
不难发现这个函数是利用 EAX 寄存器作为返回值,然后成功则返回 1,不成功则返回 0,还需要注意在构建符号位向量的时候 EAX 寄存器是 32 位寄存器
register_size_bit = 32
state.regs.eax = claripy.If(
user_input_string == check_against_string,
claripy.BVV(1, register_size_bit),
claripy.BVV(0, register_size_bit)
)
接下来同之前差不多,不再赘述