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