查看: 73|回复: 0

[Reverse] 深入浅出angr(四)

[复制链接]

22

主题

23

帖子

0

精华

VIP

Rank: 16

学币
10
荣耀
0
rank
0
违规
0

VIP

    发表于 2020-6-15 00:45:16 | 显示全部楼层 |阅读模式

    前言  在逆向中,使用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()
      官方文档的介绍如下:

    深入浅出angr(四)

    深入浅出angr(四)
      我们可以通过@proj.hook(proj.entry)的方式来Hook任意一个地址。
      例子中使用了project.execute()方法,此方法并不常用,它往往和project.terminate_execution()结合起来使用,并且通常用在hook时

    深入浅出angr(四)

    深入浅出angr(四)
      因此代码大概的执行流程如下:
    • 初始化proj
    • hook指定地址的函数
    • 调用project.execute()
    • 当遇到project.terminate_execution()符号执行结束
      此时angr会执行到0x400844并打印出flag的结果。
    hook符号表  这里以tumctf2016_zwiebel作为例子进行说明。首先看官方文档的说明。

    深入浅出angr(四)

    深入浅出angr(四)
      hook_symbol函数可以根据所给出的符号名,在二进制文件中找寻对应的地址,并且hook该地址。
      IDA载入题目

    深入浅出angr(四)

    深入浅出angr(四)
      这是一个smc的题目,对于angr来说为了能在符号执行时进行自解密,需要添加support_selfmodifying_code=True参数

    深入浅出angr(四)

    深入浅出angr(四)
      很明显,我们无法使用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。

    深入浅出angr(四)

    深入浅出angr(四)
      IDA载入

    深入浅出angr(四)

    深入浅出angr(四)
      其实之前我们遇到过类似的题目,不过那时我们采取的方法是:跳过输入部分,直接对内存进行存储,从而进行输入,这里当然也能这么做,只需对[rbp+var_60]内存进行操作即可
      ps:本来只想简单尝试一下,没想到花了几分钟随便写的代码居然跑出结果了,因此这里顺便贴一下代码。

    深入浅出angr(四)

    深入浅出angr(四)
    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存储到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的代码还是比较难写的,不过如果学会了,确实可以省下我们写脚本分析的时间,又是一个提高效率的方法。


    温馨提示:
    1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
    2.回复帖子不仅是对作者的最好奖励,还可以获得学币奖励,请尊重作者的劳动成果,拒绝做伸手党!
    3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
    学逆向论坛-免费的逆向学习论坛
    微信公众号
    快速回复 返回顶部 返回列表