angr符号执行用例解析——asisctffinals2015_fake

源码及二进制文件链接: https://github.com/angr/angr-doc/tree/master/examples/asisctffinals2015_fake

首先拿到这个文件,还是先执行分析一下。

输入一个长整型,输出长整型经过一系列计算后的转化为字符串的结果。

这道题有点脑洞大开,但是也不是很难。

其实就是希望我们输入一个长整数,使得输出为我们需要的flag。

这道题的前提是,已知flag字符串以“ASIS{”开头。所以,原理还是一样,输入一个符号变量,使得输出的字符串前5个字符为“ASIS{”。这道题还有个有意思的地方就是,约束条件需要自己添加。

然而,我们不能直接像ais3_crackme这个用例一样,将输入初始化为一个符号变量,添加约束条件,然后计算这个符号变量的值。

这样利用angr分析会出错。错误的原因,我猜是因为执行fake文件时,命令行输入参数是个字符串。在fake处理逻辑中,会调用strtol函数,将输入的字符串转为长整型数据,然后再进行计算。如果一开始执行文件的参数就是一个符号变量,再将符号变量传入strtol函数,进行求解时会出错。

所以,用例源码中hook了strtol函数,在这一步将函数的返回值改为了一个符号变量,这时候再去求解就可以了。

用例代码:

import angr

unconstrained_number = None

def strtol(state):
    # We return an unconstrained number here
    global unconstrained_number
    unconstrained_number = state.solver.BVS('strtol', 64)
    # Store it to rax
    state.regs.rax = unconstrained_number #  state.regs.rax is the return value of strtol function

def main():
    p = angr.Project("fake", load_options={'auto_load_libs': False})
    p.hook(0x4004a7, strtol, length=5)

    state = p.factory.entry_state(
        args=['fake', '123'], # Specify an arbitrary number so that we can bypass 
                              # the check of argc in program
        env={"HOME": "/home/angr"} # this statement could be omitted
    )
    ex = p.surveyors.Explorer(find=(0x400450, ), # find a path from the state to the addresss 0x400450 that repsents the puts() funciton
                              start=state
                              )
    ex.run()

    found = ex.found[0] 
    # We know the flag starts with "ASIS{"
    flag_addr = found.regs.rsp + 0x8 + 0x38 - 0x38 # how to know the flag_addr is difficult 
    found.add_constraints(found.memory.load(flag_addr, 5) == int("ASIS{".encode("hex"), 16))

    # More constraints: the whole flag should be printable
    for i in xrange(0, 32):
        cond_0 = found.memory.load(flag_addr + 5 + i, 1) >= ord('0')
        cond_1 = found.memory.load(flag_addr + 5 + i, 1) <= ord('9')
        cond_2 = found.memory.load(flag_addr + 5 + i, 1) >= ord('a')
        cond_3 = found.memory.load(flag_addr + 5 + i, 1) <= ord('f')
        found.add_constraints(
            found.solver.Or(
                found.solver.And(cond_0, cond_1),
                found.solver.And(cond_2, cond_3)
            )
        )

    # And it ends with a '}'
    found.add_constraints(found.memory.load(flag_addr + 5 + 32, 1) ==
                                ord('}'))

    # In fact, putting less constraints (for example, only constraining the first 
    # several characters) is enough to get the final flag, and Z3 runs much faster 
    # if there are less constraints. I added all constraints just to stay on the 
    # safe side.

    flag = found.solver.eval(found.memory.load(flag_addr, 8 * 5))
    return hex(flag)[2:-1].decode("hex").strip('\0')

    #print "The number to input: ", found.solver.eval(unconstrained_number)
    #print "Flag:", flag

    # The number to input:  25313971399
    # Flag: ASIS{f5f7af556bd6973bd6f2687280a243d9}

def test():
    a = main()
    assert a == 'ASIS{f5f7af556bd6973bd6f2687280a243d9}'

if __name__ == '__main__':
    print main()

在上述代码中,flag存放的地址是:flag_addr = found.regs.rsp + 0x8 + 0x38 - 0x38

逆向fake,发现puts的参数为:

mov     [rsp+38h+var_18], rdi
mov     rdi, rsp        ; s

call    _puts

其实就是把运算结果往栈里放,从代码里看,flag_addr 就应该是rsp+0开始,然而代码里非要加上0x8,如果改变这个0x8则会报错:

ipy/frontends/composite_frontend.py", line 259, in _ensure_sat
    raise UnsatError("CompositeSolver is already unsat")
angr.errors.SimUnsatError: ('Got an unsat result', , UnsatError('CompositeSolver is already unsat',))

这个错误原因还不清楚,求解器得到一个不饱和的结果?

后来想了想,main函数调用了puts函数,所以此时rsp指向的是返回地址or something。

挖了个坑,非常值得参考的连接:https://zhuanlan.zhihu.com/p/27339191。


你可能感兴趣的:(漏洞挖掘,物联网)