angr符号执行用例解析——CSCI-4968-MBE

用例源码以及二进制文件链接: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()))







阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭