查看: 80|回复: 0

[Reverse] 深入浅出angr(二)

[复制链接]

22

主题

23

帖子

0

精华

VIP

Rank: 16

学币
10
荣耀
0
rank
0
违规
0

VIP

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

    前言
      让我们一起拨开云雾,玩转angr吧!
    此篇将会讲解如何使用angr进行输入、输出以及条件约束
    获取命令行输入  我以ais3_crackme为例,来说明如何进行命令行输入。
      运行程序,提示如下:

    深入浅出angr(二)

    深入浅出angr(二)
      载入IDA,逻辑同样的简单。

    深入浅出angr(二)

    深入浅出angr(二)
      再上一篇也提到过,在angr==8.18.10.25版本中,需要通过claripy模块,来构造输入。claripy是一个符号求解引擎和z3类似,我们完全可以将其当成是z3进行使用。
      claripy关于变量的定义在claripy.ast.bv.BV当中

    深入浅出angr(二)

    深入浅出angr(二)
      通常使用claripy.BVS()创建位向量符号

    深入浅出angr(二)

    深入浅出angr(二)
      使用claripy.BVV()创建位向量值

    深入浅出angr(二)

    深入浅出angr(二)
      argv1 = claripy.BVS("argv1",100*8)
      argv1是符号名称,100*8是长度以bit为单位,这里是输入了100个字节。

    深入浅出angr(二)

    深入浅出angr(二)
      在设置初始SimgrState时可以进行如下设置
    initial_state = p.factory.entry_state(args=["./ais3_crackme",argv1])
      通常来说在做题时,flag的长度还是很好判断的。
      之后初始化simulation_manager,设置find以及avoid

    深入浅出angr(二)

    深入浅出angr(二)
      那么此时我们不能像之前那样通过posix.dump(0)来打印出结果,因为我们是通过命令行传参,输入的数据,那么此时使路径正确的数据保存在哪里呢?
      我们需要继续查看SimState都由哪些属性。

    深入浅出angr(二)

    深入浅出angr(二)
      之前也提到过claripy是类似于z3的符号执行引擎,所以可以看到solver属性
      :ivar solver:       The symbolic solver and variable manager for this state
      同样的我们查看found.solver都有哪些属性和方法。

    深入浅出angr(二)

    深入浅出angr(二)
      为了能正确的将found中保存的符号执行的结果打印出来,我们可以使用eval方法。

    深入浅出angr(二)

    深入浅出angr(二)
      并且可以使用cast_to参数对需要打印的值进行类型转换

    深入浅出angr(二)

    深入浅出angr(二)
      通常来说只要找到了找到了正确的路径,那么打印结果并不是太大的问题。
      完整的脚本如下:
    #!/usr/bin/env python
    
    
    '''
    ais3_crackme has been developed by Tyler Nighswander (tylerni7) for ais3.
    
    It is an easy crackme challenge. It checks the command line argument.
    '''
    
    import angr
    import claripy
    
    
    def main():
    project = angr.Project("./ais3_crackme")
    
    #create an initial state with a symbolic bit vector as argv1
    argv1 = claripy.BVS("argv1",100*8) #since we do not the length now, we just put 100 bytes
    initial_state = project.factory.entry_state(args=["./crackme1",argv1])
    
    #create a path group using the created initial state 
    sm = project.factory.simulation_manager(initial_state)
    
    #symbolically execute the program until we reach the wanted value of the instruction pointer
    sm.explore(find=0x400602) #at this instruction the binary will print(the "correct" message)
    
    found = sm.found[0]
    #ask to the symbolic solver to get the value of argv1 in the reached state as a string
    solution = found.solver.eval(argv1, cast_to=bytes)
    
    print(repr(solution))
    solution = solution[:solution.find(b"\x00")]
    print(solution)
    return solution
    
    def test():
    res = main()
    assert res == b"ais3{I_tak3_g00d_n0t3s}"
    
    
    if __name__ == '__main__':
    print(repr(main()))
    正常输入并设置约束条件  这里我用上一篇刚开始用到的csaw_wyvern作为例题
      IDA载入

    深入浅出angr(二)

    深入浅出angr(二)
      首先映入眼帘的是C++程序,由于angr是只实现了C库,为了深入C++标准库中,我们需要在设置state时需要使用full_init_state方法,并且设置unicorn引擎。
      通过IDA的分析以及猜测,基本上可以确定flag长度为28,因此我们构造长度为28的BVS变量,并在结尾加上\n

    深入浅出angr(二)

    深入浅出angr(二)
      我们通过claripy构造输入变量
    flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(28)]
    flag = claripy.Concat(*flag_chars + [claripy.BVV(b'\n')])
      claripy.Concat方法用于bitVector的连接
      而后在初始化state时设置stdin参数
    st = p.factory.full_init_state(
    args=['./wyvern'],
    add_options=angr.options.unicorn,
    stdin=flag,
    )
      add_options=angr.options.unicorn,是为了设置unicorn引擎
      其实我们现在已经设置好了state,angr已经可以正常工作了,但是为了提高angr的执行效率,我们有必要进行条件约束。
      设置起来并不麻烦。
    for k in flag_chars:
    st.solver.add(k != 0)
    st.solver.add(k != 10)
      而后便可以执行了。这里我先不设置find,直接通过run()方法运行,这样可以得到29个deadended分支。

    深入浅出angr(二)

    深入浅出angr(二)
      这里有必要再说一下SimulationManager的三种运行方式:
      step()每次向前运行一个基本块,并返回进行分类

    深入浅出angr(二)

    深入浅出angr(二)
      run()运行完所有的基本块,然后会出现deadended的状态,此时我们通常访问最后一个状态来获取我们所需要的信息。

    深入浅出angr(二)

    深入浅出angr(二)
      explore()根据find和avoid进行基本块的执行,最后会返回found和avoid状态

    深入浅出angr(二)

    深入浅出angr(二)
      一般来说我们使用explore()方法即可。
      此时的flag应该就在这29个deadended分支中某个分支的stdout中,我们得想办法将其取出,通常来说是在最后一个分支当中。

    深入浅出angr(二)

    深入浅出angr(二)
      当然我们还是通过代码将其取出。
    out = b''
    for pp in sm.deadended:
    out = pp.posix.dumps(1)
    if b'flag{' in out:
    return out[out.find(b"flag{"):]
      如果不用run()方法,而是通过explore()运行,也是可以的。
    在IDA中找到最终正确的分支0x0x4037FD

    深入浅出angr(二)

    深入浅出angr(二)
      如下设置:

    深入浅出angr(二)

    深入浅出angr(二)
      最后在found[0].posix.dumps(0)打印出flag值,但在执行过程中,我明显感觉到CPU在飞速的旋转。(可能是电脑太渣,哈哈!)
      ps:这道题我记得还可以用pintools解决,而且pizza大佬还写过一个去混淆的脚本,总之方法有很多,不过angr算是比较快速的一种。
    对结果进行条件约束  对于angr来说,执行到正确的路径并不难,但对于我们来说,要想正确的打印出flag,恐怕还得飞一番功夫。
      这里以asisctffinals2015_fake为例。
      载入IDA

    深入浅出angr(二)

    深入浅出angr(二)
      从题目来看,其大概逻辑是通过输入正确的值,经过计算,最后会输出由v5 v6 v7 v8 v9所组成的字符串,也就是flag。
      就此题而言,仅仅设置BVS和find是远远不够的,我们需要对found状态下的memory,进行条件约束,从而打印出正确的flag。
      我们跳过前面的命令行输入部分,直接从0x4004AC开始,因为strtol用于将字符串转化为整数,而我们通过claripy.BVS构造的符号变量是一个bit向量,无法使用strtol转换。当然如果你不闲麻烦,可以将strtolnop掉,然后使用之前所说的命令行传参的方法。
      初始化状态如下设置:
    state = p.factory.blank_state(addr=0x4004AC)
    inp = state.solver.BVS('inp', 8*8)
    state.regs.rax = inp
    
    simgr= p.factory.simulation_manager(state)
    simgr.explore(find=0x400684)
    found = simgr.found[0]

    深入浅出angr(二)

    深入浅出angr(二)
      此时的状态是0x400684时,put将要打印edi寄存器的值.

    深入浅出angr(二)

    深入浅出angr(二)
      为了对结果设置条件约束,我们需要如下设置:
    flag_addr = found.regs.rdi
    found.add_constraints(found.memory.load(flag_addr, 5) == int(binascii.hexlify(b"ASIS{"), 16))
      首先根据题目条件可以知道flag的长度应该为38(5+32+1)字节,并且的前5个字节是ASIS{,最后一个字节是}其余也都应该是可打印字符
      这时可以进行如下约束:
    flag = found.memory.load(flag_addr, 40)
    for i in range(5, 5+32):
    cond_0 = flag.get_byte(i) >= ord('0')
    cond_1 = flag.get_byte(i) <= ord('9')
    cond_2 = flag.get_byte(i) >= ord('a')
    cond_3 = flag.get_byte(i) <= ord('f')
    cond_4 = found.solver.And(cond_0, cond_1)
    cond_5 = found.solver.And(cond_2, cond_3)
    found.add_constraints(found.solver.Or(cond_4, cond_5))
    
    found.add_constraints(flag.get_byte(32+5) == ord('}'))
      最后将结果通过eval输出即可.
    flag_str = found.solver.eval(flag, cast_to=bytes)
    总结  以上我们已经了解了如何使用angr进行输入输出以及条件约束,这就掌握angr的基本用法,接下来我们要继续深入,学会如何对内存以及寄存器进行直接的存取。


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