用例源码以及二进制文件链接:https://github.com/angr/angr-doc/tree/master/examples/CSCI-4968-MBE/challenges
本次用例代码压轴的crackme0x05非常棒,前面比较简单可以跳过去。
crackme0x00a
这个就是一贯的套路了,确定start_addr,find_addr, avoid_addr,然后创建simulation_manager,explore所有满足路径,求解就可以了。
不过,不过啊。这个用例代码比以前要简单多了,它并没跳过scanf函数,所以说angr是可以执行scanf函数的?那以前为什么还有跳过啊?这点把我搞蒙了。
而且它根本不需要创建什么符号变量,也不需要找start_addr,直接利用sm就可以执行。最后输出满足约束的程序输入。
(看来以前的代码是有可以改进的点的,可以将前面几个用例代码也改写为这样的方式,看能够得到正确结果。不过,一道题肯定有多种解法,它给出这么多解决fangs,主要还是为了让大家深入了解各个方法。)
用例源码:
import angr
FIND_ADDR = 0x08048533 # mov dword [esp], str.Congrats_ ; [0x8048654:4]=0x676e6f43 LEA str.Congrats_ ; "Congrats!" @ 0x8048654
AVOID_ADDR = 0x08048554 # mov dword [esp], str.Wrong_ ; [0x804865e:4]=0x6e6f7257 LEA str.Wrong_ ; "Wrong!" @ 0x804865e
def main():
proj = angr.Project('crackme0x00a', load_options={"auto_load_libs": False})
sm = proj.factory.simulation_manager()
sm.explore(find=FIND_ADDR, avoid=AVOID_ADDR)
return sm.found[0].posix.dumps(0).split('\0')[0] # stdin
def test():
assert main() == 'g00dJ0B!'
if __name__ == '__main__':
print(main())
crackme0x01
与crackme0x00a一样,不多说了。
crackme0x02
与crackme0x00a一样,不多说了。
crackme0x03
与crackme0x00a一样,只是这里面的字符串被混淆了,不能直接看出来,但是它混淆的函数逻辑很简单,所以计算一下就可以看出来哪个是find_addr哪个是avoid_addr了。
crackme0x04
与crackme0x00a一样。不过用例代码有些小变化,可以学习一下,就是确定find_addr时候,利用的cfg(控制流图)。
cfg = proj.analyses.CFG()
FIND_ADDR = cfg.kb.functions.function(name="exit").addr
识别最后的出口点地址。这样我们就不需要去IDA找find_addr,直接让程序运行至结束就好了。
用例代码:
import angr
import subprocess
# from IPython import embed # pop iPython at the end
def main():
proj = angr.Project('crackme0x04', load_options={"auto_load_libs": False})
cfg = proj.analyses.CFG()
FIND_ADDR = cfg.kb.functions.function(name="exit").addr
AVOID_ADDR = 0x080484fb # dword [esp] = str.Password_Incorrect__n ; [0x8048649:4]=0x73736150 LEA str.Password_Incorrect__n ; "Password Incorrect!." @ 0x8048649
sm = proj.factory.simulation_manager()
sm.explore(find=FIND_ADDR, avoid=AVOID_ADDR)
# embed()
#print sm.found[0].posix.dumps(1)
return sm.found[0].posix.dumps(0) # .lstrip('+0').rstrip('B')
def test():
# it SHOULD just be 96 but the way angr models scanf means that it could technically be any number of formats
# so we gotta check against ground truth
with open('input', 'wb') as fp:
fp.write(main())
assert subprocess.check_output('./crackme0x04 < input', shell=True) == 'IOLI Crackme Level 0x04\nPassword: Password OK!\n'
if __name__ == '__main__':
print(repr(main()))
更有趣的是,我们完全可以不用设置avoid_addr。看它的控制流图,avoid_addr是到达不了exit函数的,所以最后找到的路径本身就不包括avoid_addr:
crackme0x05
crackme0x05再次出现新花样,就是寻找find和avoid,不再是传入地址了,而是传入函数。
通过传入的find和avoid函数判断,将这条路径加入find stashes还是avoid stashes。太方便了,再也不需要我们去逆向找find_addr和avoid_addr了!
这个传入的函数参数是state。
用例给出的判断逻辑为:这个程序输入是否存在目标字符串。例如correct函数;
def correct(state):
try:
return 'Password OK' in state.posix.dumps(1)
except:
return False
如果状态输出字符串包含‘Password OK’那么这个state所在的path,就会添加到 found 的path group中。
最后可以通过sm.found来访问。
用例代码:
import angr
import subprocess
def main():
proj = angr.Project('crackme0x05', load_options={"auto_load_libs": False})
def correct(state):
try:
return 'Password OK' in state.posix.dumps(1)
except:
return False
def wrong(state):
try:
return 'Password Incorrect' in state.posix.dumps(1)
except:
return False
sm = proj.factory.simulation_manager()
sm.explore(find=correct, avoid=wrong)
#print sm.found[0].posix.dumps(1)
return sm.found[0].posix.dumps(0) # .lstrip('+0').rstrip('B')
def test():
# it SHOULD just be two numbers but the way angr models scanf means that it could technically be any number of formats
# so we gotta check against ground truth
with open('input', 'wb') as fp:
fp.write(main())
assert subprocess.check_output('./crackme0x05 < input', shell=True) == 'IOLI Crackme Level 0x05\nPassword: Password OK!\n'
if __name__ == '__main__':
print(repr(main()))