angrのBackward Slicingを使ってみたので、わかったことをとりあえずメモしておきます。と言っても、Backward Slicingの実行方法自体はドキュメントに書いてあるので、知りたかったのはBackward Slicingしたあとにどのようにデータを見ればよいの?ってところですね。
まずは単純なテストプログラムを作成。これに対してBackward Slicingをしてみる。
#include <stdio.h> #include <string.h> #include <stdlib.h> char *correct_password = "test123"; void win(void) { printf("Password is correct!\n"); } void loose(void) { printf("Password is wrong!\n"); } int validate(char *password) { int ret = -1; if (!strcmp(correct_password, password)) { win(); ret = 0; } else { loose(); ret = -1; } return ret; } void usage(char *prog) { printf("Usage: %s <password>\n", prog); exit(-1); } int main(int argc, char **argv) { int ret; if (argc != 2) usage(argv[0]); ret = validate(argv[1]); return ret; }
pythonのコードはこんな感じで。win()をターゲットとしてBackward Slicingを実行。
#!/usr/bin/env python3 import angr import claripy prog = "./test" p = angr.Project(prog, auto_load_libs=False) cfg = p.analyses.CFGEmulated(keep_state = True, state_add_options = angr.sim_options.refs, context_sensitivity_level = 3) cdg = p.analyses.CDG(cfg) ddg = p.analyses.DDG(cfg) target_func = cfg.kb.functions.function(name="win") target_node = cfg.get_any_node(target_func.addr) bs = p.analyses.BackwardSlice(cfg, cdg = cdg, ddg = ddg, targets = [ (target_node, -1) ], control_flow_slice = True) #print(bs.annotated_cfg().dbg_repr()) print("----------") acfg = bs.annotated_cfg() for addr in bs.chosen_statements: node = acfg.get_run(addr) print(f"name: {node.name}, addr: {hex(node.addr)}") for ia in node.instruction_addrs: print(f"instruction_addrs: {hex(ia)}") print("-----------") # 以下はおまけ的な sym_args = claripy.BVS("sym_args", 8 * 8) argv = [p.filename] argv.append(sym_args) state = p.factory.entry_state(args=argv) state.options.add(angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY) sm = p.factory.simulation_manager(state) sm.explore(find = 0x401156, avoid = 0x401167) if len(sm.found): for found in sm.found: print(found.solver.eval(sym_args, cast_to=bytes))
BackwardSliceクラスのannotated_cfg()でannontateされたコントロールグラフが取得できます。BackwardSliceクラスのchosen_statementsはpythonの辞書型で選択されたブロックのデータが入ってます。キーはアドレスです。そこで、chosen_statementsのキーであるアドレスを使ってannotated_cfg()で取得したコントロールグラフよりノードを得ることができます。それがこの部分ですね。
acfg = bs.annotated_cfg() for addr in bs.chosen_statements: node = acfg.get_run(addr) print(f"name: {node.name}, addr: {hex(node.addr)}") for ia in node.instruction_addrs: print(f"instruction_addrs: {hex(ia)}")
このコードを実行すると次のような出力になります(__libc_start_mainなんかは今回気にする必要ないでしょう😀)。
---------- name: win, addr: 0x401156 instruction_addrs: 0x401156 instruction_addrs: 0x401157 instruction_addrs: 0x40115a instruction_addrs: 0x40115f name: validate+0x2d, addr: 0x4011a5 instruction_addrs: 0x4011a5 name: validate+0x29, addr: 0x4011a1 instruction_addrs: 0x4011a1 instruction_addrs: 0x4011a3 name: strcmp, addr: 0x500018 name: None, addr: 0x401050 instruction_addrs: 0x401050 name: validate, addr: 0x401178 instruction_addrs: 0x401178 instruction_addrs: 0x401179 instruction_addrs: 0x40117c instruction_addrs: 0x401180 instruction_addrs: 0x401184 instruction_addrs: 0x40118b instruction_addrs: 0x401192 instruction_addrs: 0x401196 instruction_addrs: 0x401199 instruction_addrs: 0x40119c name: main+0x24, addr: 0x401214 instruction_addrs: 0x401214 instruction_addrs: 0x401218 instruction_addrs: 0x40121c instruction_addrs: 0x40121f instruction_addrs: 0x401222 name: main+0x15, addr: 0x401205 instruction_addrs: 0x401205 instruction_addrs: 0x401209 instruction_addrs: 0x40120c instruction_addrs: 0x40120f name: main, addr: 0x4011f0 instruction_addrs: 0x4011f0 instruction_addrs: 0x4011f1 instruction_addrs: 0x4011f4 instruction_addrs: 0x4011f8 instruction_addrs: 0x4011fb instruction_addrs: 0x4011ff instruction_addrs: 0x401203 name: __libc_start_main, addr: 0x500000 name: _start, addr: 0x401070 instruction_addrs: 0x401070 instruction_addrs: 0x401074 instruction_addrs: 0x401076 instruction_addrs: 0x401079 instruction_addrs: 0x40107a instruction_addrs: 0x40107d instruction_addrs: 0x401081 instruction_addrs: 0x401082 instruction_addrs: 0x401083 instruction_addrs: 0x401086 instruction_addrs: 0x401088 instruction_addrs: 0x40108f ----------- b'test123\x00'
出力結果より"name: "の部分だけ抜き出すとこんな感じです(nameがNoneになってるものもありますが、0x401050はstrcmp@pltのアドレスです)。
name: win, addr: 0x401156 name: validate+0x2d, addr: 0x4011a5 name: validate+0x29, addr: 0x4011a1 name: strcmp, addr: 0x500018 name: None, addr: 0x401050 name: validate, addr: 0x401178 name: main+0x24, addr: 0x401214 name: main+0x15, addr: 0x401205 name: main, addr: 0x4011f0 name: __libc_start_main, addr: 0x500000 name: _start, addr: 0x401070
これとobjdumpの出力を合わせてみると、
0000000000401156 <win>: 401156: 55 push rbp 401157: 48 89 e5 mov rbp,rsp 40115a: bf 18 20 40 00 mov edi,0x402018 40115f: e8 cc fe ff ff call 401030 <puts@plt> 401164: 90 nop 401165: 5d pop rbp 401166: c3 ret 0000000000401178 <validate>: 401178: 55 push rbp 401179: 48 89 e5 mov rbp,rsp 40117c: 48 83 ec 20 sub rsp,0x20 401180: 48 89 7d e8 mov QWORD PTR [rbp-0x18],rdi 401184: c7 45 fc ff ff ff ff mov DWORD PTR [rbp-0x4],0xffffffff 40118b: 48 8b 05 96 2e 00 00 mov rax,QWORD PTR [rip+0x2e96] # 404028 <correct_password> 401192: 48 8b 55 e8 mov rdx,QWORD PTR [rbp-0x18] 401196: 48 89 d6 mov rsi,rdx 401199: 48 89 c7 mov rdi,rax 40119c: e8 af fe ff ff call 401050 <strcmp@plt> 4011a1: 85 c0 test eax,eax 4011a3: 75 0e jne 4011b3 <validate+0x3b> 4011a5: e8 ac ff ff ff call 401156 <win> 4011aa: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0 4011b1: eb 0c jmp 4011bf <validate+0x47> 4011b3: e8 af ff ff ff call 401167 <loose> 4011b8: c7 45 fc ff ff ff ff mov DWORD PTR [rbp-0x4],0xffffffff 4011bf: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 4011c2: c9 leave 4011c3: c3 ret 00000000004011f0 <main>: 4011f0: 55 push rbp 4011f1: 48 89 e5 mov rbp,rsp 4011f4: 48 83 ec 20 sub rsp,0x20 4011f8: 89 7d ec mov DWORD PTR [rbp-0x14],edi 4011fb: 48 89 75 e0 mov QWORD PTR [rbp-0x20],rsi 4011ff: 83 7d ec 02 cmp DWORD PTR [rbp-0x14],0x2 401203: 74 0f je 401214 <main+0x24> 401205: 48 8b 45 e0 mov rax,QWORD PTR [rbp-0x20] 401209: 48 8b 00 mov rax,QWORD PTR [rax] 40120c: 48 89 c7 mov rdi,rax 40120f: e8 b0 ff ff ff call 4011c4 <usage> 401214: 48 8b 45 e0 mov rax,QWORD PTR [rbp-0x20] 401218: 48 83 c0 08 add rax,0x8 40121c: 48 8b 00 mov rax,QWORD PTR [rax] 40121f: 48 89 c7 mov rdi,rax 401222: e8 51 ff ff ff call 401178 <validate> 401227: 89 45 fc mov DWORD PTR [rbp-0x4],eax 40122a: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 40122d: c9 leave 40122e: c3 ret
ここはwin()のアドレスそのまま。
name: win, addr: 0x401156
validate+0x2d(=0x4011a5)はvalidate()の中でwin()を呼んでるアドレス。
name: validate+0x2d, addr: 0x4011a5
validate+0x29(0x4011a1)はstrcmp()の戻り値をチェックしている部分。
name: validate+0x29, addr: 0x4011a1
といった感じで流れが見えます。
instruction_addrsのほうはどんなデータかというと、name: validatの部分で見るとこの前のコントールグラフ(Backward Slicingなので次というか前でしょう)strcmp()が呼ばれるまでの流れになってます。
name: validate, addr: 0x401178 instruction_addrs: 0x401178 instruction_addrs: 0x401179 instruction_addrs: 0x40117c instruction_addrs: 0x401180 instruction_addrs: 0x401184 instruction_addrs: 0x40118b instruction_addrs: 0x401192 instruction_addrs: 0x401196 instruction_addrs: 0x401199 instruction_addrs: 0x40119c
なのでinstruction_addrsの流れというのはコード実行の流れということがわかりますね。
win()に至るパスが複数あった場合はどうなんだというのもありますがそれは別途調べるとして、基本的なところはこれくらいかな。。。