angrのBackward Slicingを使ってみる

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()に至るパスが複数あった場合はどうなんだというのもありますがそれは別途調べるとして、基本的なところはこれくらいかな。。。