1. 程式人生 > >angr符號執行用例解析——CSCI-4968-MBE

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()))