源码及二进制文件链接: 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',
这个错误原因还不清楚,求解器得到一个不饱和的结果?
后来想了想,main函数调用了puts函数,所以此时rsp指向的是返回地址or something。
挖了个坑,非常值得参考的连接:https://zhuanlan.zhihu.com/p/27339191。