angr on CTF 超入門

この記事は Harekaze Advent Calender 6日目の記事です。

adventar.org

初めに

この記事ではangrをどうやってCTFで使うか、に重点を起きます。 シンボリック実行やz3などの理論的なことは扱いません。

angrとは

angrは

プログラムの特定位置に到達するための入力値を抽出することができる

(https://ntddk.github.io/2016/08/27/angr-afl-driller/ より引用)

バイナリ自動解析ツールです。 よって主な用途はrevです。

対象バイナリ

angrを試すための簡単なバイナリを用意しました。

target - Google ドライブ

バイナリを動かす

バイナリを動かすとuser:passを聞かれますが、get_flagやlost_flagというシンボルがあることから、user:passが正しいとflagが表示されることが推測できます。 こういう場合angrは、「このアドレスを通らず、このアドレスに到達する入力値を計算してくれ」という使い方をします。 なのでこのバイナリの場合「lost_flag()を通らず、get_flag()に到達する入力値を計算してくれ」という形になります。

solverのベース

import angr
# angrのプロジェクトを作成する
p = angr.Project('./target')
# バイナリのコマンドライン引数や初期状態を指定する
state = p.factory.entry_state(addr, args=[])
# 自動解析の条件を指定する
ex = p.surveyors.Explorer(start=state, find=(,), avoid=(,))
# 自動解析の実行
ex.run()
# 解が見つかった場合表示する
if len(ex.found) > 0:
    s = ex.found[0].state
    print "%r" % s.posix.dumps(0)

コードを足す

solverを足していきます。

まずangrにどこからプログラムが開始するかを伝える必要があるので、mainのアドレスをaddrに指定します。 MAIN_ADDRESSにはobjdumpやradare2で表示されるmainのアドレスを指定してください。

5行目

state = p.factory.entry_state(addr=MAIN_ADDRESS, args=[])

次に、到達したいget_flagと避けたいlost_flagのアドレスをfindとavoidに指定します。 ここの start=state は先程指定したmainのアドレスをプロジェクトに設定しています。

7行目

ex = p.surveyors.Explorer(start=state, find=(GET_FLAG_ADDRESS,), avoid=(LOST_FLAG_ADDRESS,))

自動解析を実行

python solve_base.py

結果

'reo\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01haifuri\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

get_flag()に到達するuser:passが分かるので、バイナリを起動して入力しましょう。

HAREKAZE{This_is_flag}

バイナリのソースコード

#include<stdio.h>
#include<string.h>

void get_flag() {
  printf("HAREKAZE{This_is_flag}");
}

void lost_flag() {
  printf("You can't get the flag :)");
}

int main() {
  char username[256];
  char password[256];
  printf("username: ");
  scanf("%s", username);
  printf("password: ");
  scanf("%s", password);

  if ((strcmp(username, "reo") == 0) && (strcmp(password, "haifuri") == 0)) {
    get_flag();
  } else {
    lost_flag();
  }
  return 0;
}

参考資料

angrでシンボリック実行をやってみる - ももいろテクノロジー
angr on PythonをCTFに使う - saotake’s blog
シンボリック実行に入門しようとした | 一生あとで読んでろ
angr, AFL, Driller | 一生あとで読んでろ
hacktracking: # HITCON CTF 2017 Quals: Sakura - Reversing