もいんもいん

ゆっぴぃ(Yuppy_orz)のメモ書き

TMCTF2015 write-up

トレンドマイクロさんのTMCTF 2015に、チームint0x80にて参加しました。カウントされた1500点中500点分正解。

Cryptography-100

ろば電子さんところの「opensslでRSA暗号と遊ぶ - ろば電子が詰まっている」さんの記事を参考にコマンド叩くだけ。

% openssl rsa -text -pubin < PublicKey.pem
Public-Key: (256 bit)
Modulus:
00:b6:2d:ce:9f:25:81:63:57:23:db:6b:18:8f:12:
f0:46:9c:be:e0:cb:c5:da:cb:36:c3:6e:0c:96:b6:
ea:7b:fc
Exponent: 65537 (0x10001)
writing RSA key
-----BEGIN PUBLIC KEY-----
MDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhALYtzp8lgWNXI9trGI8S8EacvuDLxdrL
NsNuDJa26nv8AgMBAAE=
-----END PUBLIC KEY-----

 ModulesがRSA暗号における「素数の積」で、これを素因数分解することの難易度がRSAの強度に直結。...って末尾が「fc」って素数の積が偶数=片方が2、なんてあり得ない。「Where is the lost 1bit?」って馬鹿にしてるんかw末尾ビットに決まってるw

最終ビットを立てたもの(fd)として読み替えて素因数分解。msieveさんカモーン

% msieve 0x00b62dce9f2581635723db6b188f12f0469cbee0cbc5dacb36c36e0c96b6ea7bfd
% tail msieve.log | grep factor
Sat Sep 26 15:28:28 2015 prp39 factor: 279125332373073513017147096164124452877
Sat Sep 26 15:28:28 2015 prp39 factor: 295214597363242917440342570226980714417

それっぽい「素数の積」であることがわかったので、あとは秘密鍵を作成。

草野さんところの「backdoorCTF 2014 Write-up - kusano_kの日記」を元に秘密鍵のファイルを作成。

% vi genpriv.py
import sys

p = 279125332373073513017147096164124452877
q = 295214597363242917440342570226980714417

e = 65537
n = p*q

def exgcd(x,y):
r0,r1 = x,y
a0,a1 = 1,0
b0,b1 = 0,1
while r1>0:
q1 = r0/r1
r2 = r0%r1
a2 = a0-q1*a1
b2 = b0-q1*b1
r0,r1 = r1,r2
a0,a1 = a1,a2
b0,b1 = b1,b2
return a0,b0,r0

d = exgcd(e,(p-1)*(q-1))[0] + (p-1)*(q-1)
exp1 = d % (p-1)
exp2 = d % (q-1)
coef = pow(q,p-2,p)

def int2bin(d):
t = "%x"%d
return (t if len(t)%2==0 else "0"+t).decode("hex")

def enclen(l):
if l<0x80:
return chr(l)
else:
t = int2bin(l)
return chr(0x80+len(t))+t

def encint(n):
t = int2bin(n)
return "\x02"+enclen(len(t))+t

t = "".join(map(encint,[0,n,e,d,p,q,exp1,exp2,coef]))
t = "\x30"+enclen(len(t))+t

print "-----BEGIN RSA PRIVATE KEY-----"
print t.encode("base64")[:-1]
print "-----END RSA PRIVATE KEY-----"
% genpriv.py > privkey.pem
% cat privkey.pem
-----BEGIN RSA PRIVATE KEY-----
MIGmAgEAAiC2Lc6fJYFjVyPbaxiPEvBGnL7gy8XayzbDbgyWtup7/QIDAQABAiBmFb0WyPl8JTRe
m+CjK8Wfmc4OQE8hvrvpfOO9bceNAQIQ0f2VZdriZPX9V5U9v7noDQIQ3hhDaCqytILM/1BvAs93
sQIQCdB3Rg5n3F4e3BQOkcJnlQIQP59HwBFrPBa0TvdltbJlIQIQcFyJhxTaQVkB9pJqKC2NOQ==
-----END RSA PRIVATE KEY-----

元メッセージをmessageとして保存しopensslでデコード処理。

% openssl enc -in message -out bintxt -d -a
% openssl rsautl -decrypt -in bintxt  -out plaintext -inkey privkey.pem
% cat plaintext
TMCTF{$@!zbo4+qt9=5}
  Analysis-offensive-200

apk解析系。apkstudioに食わせてsmailを読む問題と想定。手元に環境を用意していなかった(polictfでも似たような問題があったが別の端末で解いてた)ので、整備ツイデに並行してAndroid実機にapkをインストール。

なお、apkstidio付属のapktoolはそのままではエラーになるため、2.0.1に更新する必要がありました。

実機確認では1000000回ボタンをクリックしたらクリアになる単純なアプリの様子。

1000000の16進数表現 0x989680 で適当にファイルを探し、発見された \com\tm\ctf\clicker\activity\c.smali の中の該当数値だけを0x10に変えてビルド・インストール・実行。...動作が変わったように見えない。ここでしばらくはまる。

よくよくsmali関連のフォルダを見るとreceiverとか変なフォルダがありScoreBroadcastReveriver.smaliなどというファイルができていた。中を見るとc.smaliと似たような数値が書かれていること(grepの引数をサボって989680ではなく9896までにしたら98967fをたまたま発見)、および、元のc.smailにも0x989680に至るまでに何度か判定があるように見えた。ははーん両方同期して変えないとダメなのね。(このへん、アプリの構造をちゃんと読んでない適当思考である。プロセス間通信して相互チェックしてる、or片方はダミーでもう片方が有効とかなんでしょうが追いかけるのメンドウ)

const/16 v0, 0xeb9
const/16 v0, 0x2717
const v0, 0xe767
const v0, 0x186a3
const v0, 0x78e75
const v0, 0xf4243
const v0, 0x98967f

このへんを

const/16 v0, 0x2
const/16 v0, 0x4
const v0, 0x6
const v0, 0x8
const v0, 0xa
const v0, 0xc
const v0, 0xe

こんな感じに書き換えてアプリを起動。クリック16回くらいで終了。

TMCTF{Congrats_10MClicks}

 

なお、これらの手法をらくらく思いつけたのはSECCON 2015 橫浜大会の見学に行ったおかげです。紹介されたツールフル活用です。実機よりBlueStacksのほうが手間ないし。(とはいうもののCheatEngineは正直使いこなせず役に立ちませんでしたテヘ)

Analysis-defensive-100
% ./vonn
You are on VMM!
TMCTF{ce5d8bb4d5efe86d25098bec300d6954}

......え???w

Programming-100

あーこれって「iGame | 無料の目のテスト」これっすね。液晶スレでは有名です。ロボまで行ったウチの環境(EIZO L887&EV2334W)をなめるなー眼力勝負じゃー

 

...ごめん相手が悪かった。途中からpaint.netの助けも借りて42段階目までは行ったのだけど、さすがに小さすぎて色判別より先にクリック精度の限界w。

sed&awkで学びperl最強~な世代ではグラフィック処理なんてゼンゼン蚊帳の外だったので、write-up勢の書いてる「pythonで余裕っすw」とかから別次元に取り残された私は今回もbashImageMagickさんに頼るのでした。

% vi imgparse.sh
#!/bin/bash

starturl=http://ctfquest.trendmicro.co.jp:43210/click_on_the_different_color
base=http://ctfquest.trendmicro.co.jp:43210


function urlparse {
url=$1
#echo "parsing $url.."
next=$(curl -v $url | grep /img/ | sed -e 's,.*/,,' -e 's,\.png.*,,')
wget ${base}/img/${next}.png

color=$(convert ${next}.png -format %c histogram:info:- |grep -v white | sort -n|sed -e '/^$/d'|head -1 | awk -F# '{print $2}'| awk '{print $1}')

pos=$(convert ${next}.png txt:- | grep ${color} | head -1 | awk -F: '{print $1}')
x=$(echo $pos | awk -F, '{print $1}')
y=$(echo $pos | awk -F, '{print $2}')

newurl=$(printf "%s/%s?x=%d&y=%d" $base $next $x $y)
urlparse $newurl
}

urlparse $starturl

...いやImageMagickさん便利よ? まじで。convertコマンドはこんな感じでヒストグラム取れるし、各座標の値出したりできるのよ?

% convert 6c1c2686c14fcf0e815e37c57ece0282d43cfad7b.png -format %c histogram:info:-
35328: (122,125,121) #7A7D79 rgb(122,125,121)
16: (124,127,123) #7C7F7B rgb(124,127,123)
108297: (255,255,255) #FFFFFF white
% convert 4576c1c2686c14fcf0e815e37c57ece0282d43cfad7b.png txt:-
# ImageMagick pixel enumeration: 379,379,255,rgb
0,0: (255,255,255) #FFFFFF white
1,0: (255,255,255) #FFFFFF white
2,0: (255,255,255) #FFFFFF white
3,0: (255,255,255) #FFFFFF white
4,0: (255,255,255) #FFFFFF white
5,0: (255,255,255) #FFFFFF white
6,0: (255,255,255) #FFFFFF white
7,0: (255,255,255) #FFFFFF white
:
196,29: (122,125,121)  #7A7D79  rgb(122,125,121)
:

上のスクリプトではこのへんをこまめに処理してがんばりました。

というわけでローカルにフォルダにゴミを巻散らかしつつ最後まで行ってエラーになりはじめたら、最後に正解したっぽいURLをブラウザでアクセスしてフラグゲット。ごめん汚い処理で。

Congraturations!! The flag is TMCTF{U must have R0807 3Y3s!}

Cryptography-500

納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない納得いかない

Base64は「Base64 - Wikipedia」にあるように3バイト(×8ビット)を4バイト(×6ビット)に落とし込む変換なので、3バイトまでのアルファベット全てを変換し、条件に合ってしまうものを抽出し、全Base64文字列との差分を取れば終了、のはず。なので

% vi enc.pl   # アルファベット全パターンを出力
#!/usr/bin/perl

#@list = ('AAAA' .. 'ZZZZ');
@list = ('AAA' .. 'ZZZ');

foreach $a (@list){
my $b = lc $a;
my ($a1,$a2,$a3) = split //, $a;
my ($b1,$b2,$b3) = split //, $b;

printf "%s\n", $a1;
printf "%s\n", $b1;

printf "%s%s\n", $a1,$a2;
printf "%s%s\n", $a1,$b2;
printf "%s%s\n", $b1,$a2;
printf "%s%s\n", $b1,$b2;

printf "%s%s%s\n", $a1,$a2,$a3;
printf "%s%s%s\n", $a1,$a2,$b3;
printf "%s%s%s\n", $a1,$b2,$a3;
printf "%s%s%s\n", $a1,$b2,$b3;
printf "%s%s%s\n", $b1,$a2,$a3;
printf "%s%s%s\n", $b1,$a2,$b3;
printf "%s%s%s\n", $b1,$b2,$a3;
printf "%s%s%s\n", $b1,$b2,$b3;
}
% vi b64.pl # 全パターンをbase64 encode
#!/usr/bin/perl
use MIME::Base64;

while(<>){
chomp;
printf "%s",encode_base64($_);
}
% vi sp.pl # スペース区切りに分割(ワンライナーでも書けるけど一応...)
#!/usr/bin/perl

while(<>){
print join " ", split //;
}
% enc.pl > raw
% b64.pl < raw > candidate
% head candidate
QQ==
QUE=
QUFB
QUFC
QUFD
% sp.pl < candidate > o1
% head o1
Q Q = =
Q U E =
Q U F B
Q U F C
Q U F D
% awk '{print $1}' < o1 |sort|uniq> 1 # 問題の条件に合ってしまうものを抽出
% awk '{print $2}' < o1 |sort|uniq> 2
% awk '{print $3}' < o1 |sort|uniq> 3
% awk '{print $4}' < o1 |sort|uniq> 4
% cat 1 2 3 4 | sort | uniq > used # 全てをマージ
% diff -u used b64candidate # base64文字列候補とdiff
@@ -1,3 +1,5 @@
++
+/
0
1
2
@@ -5,6 +7,7 @@
4
5
6
+7
8
9
=
@@ -39,6 +42,7 @@
c
d
e
+f
g
h
i
% echo -n +/7f | sha1 # +が付いてる文字列を拾ってsha1取得。bsdなのでsha1コマンド
67569763552b4e9b8ac950be0d25f446f8470c60

これをTMCTF{}の指定フォーマットでくくってエントリするが弾かれる。(9/27 2:22頃)

base64の亜種(「_」や「-」等も使うやつ)かと思い、試すもNG。翌朝は用事で参加できないのであきらめ。実質的タイムアップ。

 

...他の方のwrite-up調べたら、どうもこれで合ってるようなんですが...。なぜ...orz
こういうのは後処理でスコア修正されるのが普通と思っていただけに残念。

Analysis-defensive-200

x64_dbgで追っかけるとホスト名取得して処理判定があるみたい。手元マシンのホスト名変えるといろいろと運用上の支障が出るので、vmのマシンを用意してホスト名変更して追いかける。

http://ctfquest.trendmicro.co.jp:13106/126ac9f6149081eb0e97c2e939eaad52/top.html

とかいうURLにアクセスしていること、および下記のような文字列を得る。fiddlerやらstoneで違うところに飛ばしてみたりしましたが解には至らず。後で考えるとCookie値操作やら、いろいろ試してみる余地はあったとは思いますが、点数面からCrypto500を優先したため時間的に深追いはできませんでした。(愚痴愚痴ちくちく)

localhost|8888
ctfquest.trendmicro.co.jp|19400
127.0.0.1|12345
Cookie: g1v3m3k3y=%d
g1v3m3k3y=0