在逆向中,使用Hook来解决问题非常的常见,在之前对angr的学习中,我并未关注到hook方法,现在有时间学习整理一遍,我倍感快乐,其实Hook也十分的简单,而且可以将复杂的问题简单化
一个比较简单的例子defcamp_r100/r100
解题代码只有以下短短的几行:
import angr
project = angr.Project("angr-doc/examples/defcamp_r100/r100", auto_load_libs=False)
@project.hook(0x400844)
def print_flag(state):
print("FLAG SHOULD BE:", state.posix.dumps(0))
project.terminate_execution()
project.execute()
官方文档的介绍如下:
我们可以通过@proj.hook(proj.entry)
的方式来Hook任意一个地址。
例子中使用了project.execute()
方法,此方法并不常用,它往往和project.terminate_execution()
结合起来使用,并且通常用在hook时
因此代码大概的执行流程如下:
project.execute()
project.terminate_execution()
符号执行结束此时angr会执行到0x400844
并打印出flag的结果。
这里以tumctf2016_zwiebel
作为例子进行说明。首先看官方文档的说明。
hook_symbol
函数可以根据所给出的符号名,在二进制文件中找寻对应的地址,并且hook该地址。
IDA载入题目
这是一个smc
的题目,对于angr来说为了能在符号执行时进行自解密,需要添加support_selfmodifying_code=True
参数
很明显,我们无法使用sm.explore(find=xxx,avoid=xxx)
的方式来使用angr,同时注意到程序中出现了ptrace
想必一定有反调试,让我们通过hook的方法来绕过反调试。
p.hook_symbol('ptrace', angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'](return_value=0))
因为angr
实现了大量的符号化函数,以此来替代程序中对库函数的外部调用,其中angr.SIM_PROCEDURES
是angr
对符号化函数的字典调用,我们可以采用angr.SIM_PROCEDURES['模块名']['库函数名']()
进行hook
而后便可以通过simulation_manager
进行执行了。
state = p.factory.full_init_state(cadd_options=angr.options.unicorn)
sm = p.factory.simulation_manager(state)
这里只能采用类似step的方法进行解决,效率很低,例子中提供的代码是这样的。
while sm.active:
# in order to save memory, we only keep the recent 20 deadended or
# errored states
#print(len(sm.active))
sm.run(n=20)
if 'deadended' in sm.stashes and sm.deadended:
sm.stashes['deadended'] = sm.deadended[-20:]
if sm.errored:
sm.errored = sm.errored[-20:]
assert sm.deadended
flag = sm.deadended[-1].posix.dumps(0).split(b"\n")[0]
import ipdb; ipdb.set_trace()
return flag
我觉得有点多此一举了,他这段代码的目的就是执行完sm.run()
此时正确的输入应该保存在最后一个deadended
节点的posix.dumps(0)
当中,最后跑了两个小时,我也是醉了。不过至少知道了angr是如何hook,并绕过反调试的。
defcon2016quals_baby-re
这道题作为例子可能会更好一点。
这题其实不用hook也能顺利的解出,只是我们需要对结果进行处理一下,才能得到我们想要的flag。
IDA载入
其实之前我们遇到过类似的题目,不过那时我们采取的方法是:跳过输入部分,直接对内存进行存储,从而进行输入,这里当然也能这么做,只需对[rbp+var_60]
内存进行操作即可
ps:本来只想简单尝试一下,没想到花了几分钟随便写的代码居然跑出结果了,因此这里顺便贴一下代码。
import angr
import claripy
# 最简单的方法,不过需要对结果进行变化
def main():
proj = angr.Project('./baby-re', auto_load_libs=False)
state = proj.factory.entry_state(add_options={angr.options.LAZY_SOLVES})
sm = proj.factory.simulation_manager(state)
sm.explore(find=0x4028E9, avoid=0x402941)
# 跳过程序自身的输入,通过内存控制输入
def main2():
proj = angr.Project('./baby-re', auto_load_libs=False)
flag_chars = [claripy.BVS('flag_%d' % i, 32) for i in range(13)]
state = proj.factory.blank_state(addr=0x4028E0,add_options={angr.options.LAZY_SOLVES})
for i in range:
state.memory.store(state.regs.rbp-0x60+i*4,flag_chars[i])
state.regs.rdi = state.regs.rbp-0x60
sm = proj.factory.simulation_manager(state)
sm.explore(find=0x4028E9, avoid=0x402941)
if __name__ == '__main__':
main2()
当然说了这么多,这里最主要还是想说一下如何使用Hook技术,来控制输入,从而方便我们的输出。
我们可以通过这样的方式进行Hook
proj.hook_symbol('__isoc99_scanf', my_scanf(), replace=True)
我们用自己的my_scanf()
来代替__isoc99_scanf
,我们在保持scanf功能不变的情况下,将我们的符号变量存储进去。
class my_scanf(angr.SimProcedure):
def run(self, fmt, ptr): # pylint: disable=arguments-differ,unused-argument
self.state.mem[ptr].dword = flag_chars[self.state.globals['scanf_count']]
self.state.globals['scanf_count'] += 1
这样程序每次调用scanf
时,其实就是在执行my_scanf
就会将flag_chars[i]
存储到self.state.mem[ptr]
当中,这其中ptr
参数,其实就是本身scanf
函数传递进来的rdi
也就是[rbp+var_60]+i*4
,为了控制下标,我们设置了一个全局符号变量scanf_count
,相信聪明的你一定不难理解。
如此一来,只要angr执行到我们想要到达的分支,那么我们就可以通过solver.eval()
的方式将其打印出来
代码如下:
import angr
import claripy
def main():
proj = angr.Project('./baby-re', auto_load_libs=False)
# let's provide the exact variables received through the scanf so we don't have to worry about parsing stdin into a bunch of ints.
flag_chars = [claripy.BVS('flag_%d' % i, 32) for i in range(13)]
class my_scanf(angr.SimProcedure):
def run(self, fmt, ptr): # pylint: disable=arguments-differ,unused-argument
self.state.mem[ptr].dword = flag_chars[self.state.globals['scanf_count']]
self.state.globals['scanf_count'] += 1
proj.hook_symbol('__isoc99_scanf', my_scanf(), replace=True)
sm = proj.factory.simulation_manager()
sm.one_active.options.add(angr.options.LAZY_SOLVES)
sm.one_active.globals['scanf_count'] = 0
# search for just before the printf("%c%c...")
# If we get to 0x402941, "Wrong" is going to be printed out, so definitely avoid that.
sm.explore(find=0x4028E9, avoid=0x402941)
# evaluate each of the flag chars against the constraints on the found state to construct the flag
flag = ''.join(chr(sm.one_found.solver.eval(c)) for c in flag_chars)
return flag
def test():
assert main() == 'Math is hard!'
if __name__ == '__main__':
print(main())
我感觉Hook的代码还是比较难写的,不过如果学会了,确实可以省下我们写脚本分析的时间,又是一个提高效率的方法。