suanの備忘録

SUANが色々行ったこと等を書いていきます。

SECCON Beginners CTF 2020 WriteUpのようなもの

SECCON Beginners CTF 2020に参加しました!

SECCON Beginners CTF 2020にチームMIS.Wとしてsoeki、musaprg、shiro、tommi、aoiと参加しました。結果は700点、144位(/1009)でした。

f:id:SUAN:20200524153620p:plain
SCORE

WriteUpとか言っていますが、力業のごり押しなので悪しからず。想定解は私以外の人の方がもっと詳しくわかりやすく書いてあると思います。

SECCON Challenges

解けたもの

[MISC] emoemoencode

emoemoencodeをとりあえず

$ cat emoemoencode.txt

してみました。すると絵文字がたくさん現れてきました。

🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽

栗が多い。ま、当然なんのこっちゃわからないのでバイナリエディタを使ってバイナリを見てみました。

f:id:SUAN:20200524160025p:plain
バイナリ
ここで私は最初、全角英数を変換してこのバイナリにしていると思い、ctf4b{のバイナリと比較してみましたが、変換法則が見つからなかったので断念。
次に半角英数を見てみました。ctf4b{のバイナリと比べてみると、いい感じに規則が見つかりました。

F0 9F 8D XXの場合は
XXA0なら全体を60に置き換え、B0なら全体を70に置き換え、のように行いました。
例: F0 9F 8D A060

F0 9F 8C XXの場合は
XXB0なら全体を30に置き換え。
例: F0 9F 8C B030

これによって得られたフラッグは

ctf4b{stegan0graphy_by_em000000ji}

[REVERSING] mask

とりあえず./maskをして実行すると、

$ ./mask 
Usage: ./mask [FLAG]

と返ってくる。ので今度は[FLAG]に文字を入れて実行。

$ ./mask ctf4b{}
Putting on masks...
atd4`qu
c`b bki
Wrong FLAG. Try again.

と返ってきた。何かしら変換を行って2つの何かと比較しているようだ。

$ strings mask

を実行し、文字列を見てみると怪しい2つの文字列を発見。

atd4`qdedtUpetepqeUdaaeUeaqau
c`b bk`kj`KbababcaKbacaKiacki

あとはこれに合うように[FLAG]を決めれば良いと思い、すべてのアルファベットを探索。 ← これを力業という。

元の文字 変化後A 変化後B
a a a
b ` b
c a c
d d `
e e a
f d b
g e c
h ` h
i a i
j ` j
k a k
l d h
m e i
n d j
o e k
p p `
q q a
r p b
s q c
t t `
u u a
v t b
w u c
x p h
y q i
z p j

ここから存在しないものは記号をテキトーに入れていき、得られたフラッグは

ctf4b{dont_reverse_face_mask}

[REVERSING] yakisoba

とりあえず./yakisobaをして実行すると、

$ ./yakisoba 
FLAG: ctf4b{}
Wrong!

FLAGにはとりあえずテキトーに入れてみた。
今回はmaskとは違い、何も出てこない。strings yakisobaをしても出てこない。
何も出てこないのは困るので逆アセンブルしました。
IDAを使用して逆アセンブルをし、グラフとして出てきたもの見ていたら、1文字ずつ正誤判定しているようでした。
ので、グラフをたどっていって無事フラグが手に入りました。
ctf4b{sp4gh3tt1_r1pp3r1n0}

f:id:SUAN:20200524155122p:plain
IDA
↑これをちまちま一つずつ見ていったという、どう考えても時間かかる力業で解きました。

え?アセンブリを読めって?嫌だよ面倒くさい()

解けなかったもの

[REVERSING] siblangs

siblangs.apkだったのでとりあえず手持ちのXperia XZ Premiumにインストール、実行。

f:id:SUAN:20200524162234p:plain
siblangs1
f:id:SUAN:20200524162308p:plain
siblangs2
f:id:SUAN:20200524162333p:plain
siblangs3
ここでVALIDATE AはiOSで動かせと言われる。apkなので当然動くわけない。(持ってないから動かせないしね)
とりあえず動くVALIDATE Bの方を使うことにした。

.apkファイルはただの.zipなので解凍して、そして中のソースファイル(classes.dex)を.jarに変換後、デコンパイルしてみた。
そこで死ぬほど怪しいものを発見。

package es.o0i.challengeapp.nativemodule;

import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class ValidateFlagModule extends ReactContextBaseJavaModule {
  private static final int GCM_IV_LENGTH = 12;
  
  private final ReactApplicationContext reactContext;
  
  private final SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");
  
  private final SecureRandom secureRandom = new SecureRandom();
  
  public ValidateFlagModule(ReactApplicationContext paramReactApplicationContext) {
    super(paramReactApplicationContext);
    this.reactContext = paramReactApplicationContext;
  }
  
  public String getName() {
    return "ValidateFlagModule";
  }
  
  @ReactMethod
  public void validate(String paramString, Callback paramCallback) {
    byte[] arrayOfByte = new byte[43];
    arrayOfByte[0] = 95;
    arrayOfByte[1] = -59;
    arrayOfByte[2] = -20;
    arrayOfByte[3] = -93;
    arrayOfByte[4] = -70;
    arrayOfByte[5] = 0;
    arrayOfByte[6] = -32;
    arrayOfByte[7] = -93;
    arrayOfByte[8] = -23;
    arrayOfByte[9] = 63;
    arrayOfByte[10] = -9;
    arrayOfByte[11] = 60;
    arrayOfByte[12] = 86;
    arrayOfByte[13] = 123;
    arrayOfByte[14] = -61;
    arrayOfByte[15] = -8;
    arrayOfByte[16] = 17;
    arrayOfByte[17] = -113;
    arrayOfByte[18] = -106;
    arrayOfByte[19] = 28;
    arrayOfByte[20] = 99;
    arrayOfByte[21] = -72;
    arrayOfByte[22] = -3;
    arrayOfByte[23] = 1;
    arrayOfByte[24] = -41;
    arrayOfByte[25] = -123;
    arrayOfByte[26] = 17;
    arrayOfByte[27] = 93;
    arrayOfByte[28] = -36;
    arrayOfByte[29] = 45;
    arrayOfByte[30] = 18;
    arrayOfByte[31] = 71;
    arrayOfByte[32] = 61;
    arrayOfByte[33] = 70;
    arrayOfByte[34] = -117;
    arrayOfByte[35] = -55;
    arrayOfByte[36] = 107;
    arrayOfByte[37] = -75;
    arrayOfByte[38] = -89;
    arrayOfByte[39] = 3;
    arrayOfByte[40] = 94;
    arrayOfByte[41] = -71;
    arrayOfByte[42] = 30;
    try {
      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
      GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, arrayOfByte, 0, 12);
      cipher.init(2, this.secretKey, gCMParameterSpec);
      arrayOfByte = cipher.doFinal(arrayOfByte, 12, arrayOfByte.length - 12);
      byte[] arrayOfByte1 = paramString.getBytes();
      for (int i = 0;; i++) {
        if (i < arrayOfByte.length) {
          if (arrayOfByte1[i + 22] != arrayOfByte[i]) {
            paramCallback.invoke(new Object[] { Boolean.valueOf(false) });
            return;
          } 
        } else {
          paramCallback.invoke(new Object[] { Boolean.valueOf(true) });
          return;
        } 
      } 
    } catch (Exception exception) {
      paramCallback.invoke(new Object[] { Boolean.valueOf(false) });
      return;
    } 
  }
}

どうもエンコードされたByte列をデコードしてから入力文字列の23文字目から比較しているらしい。
ということで自前でJavaでその部分だけ実行してみた。

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

class cyph {

    private static final SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");

    public static void main(String args[]) throws Exception {
        byte[] arrayOfByte = new byte[43];
        arrayOfByte[0] = 95;
        arrayOfByte[1] = -59;
        arrayOfByte[2] = -20;
        arrayOfByte[3] = -93;
        arrayOfByte[4] = -70;
        arrayOfByte[5] = 0;
        arrayOfByte[6] = -32;
        arrayOfByte[7] = -93;
        arrayOfByte[8] = -23;
        arrayOfByte[9] = 63;
        arrayOfByte[10] = -9;
        arrayOfByte[11] = 60;
        arrayOfByte[12] = 86;
        arrayOfByte[13] = 123;
        arrayOfByte[14] = -61;
        arrayOfByte[15] = -8;
        arrayOfByte[16] = 17;
        arrayOfByte[17] = -113;
        arrayOfByte[18] = -106;
        arrayOfByte[19] = 28;
        arrayOfByte[20] = 99;
        arrayOfByte[21] = -72;
        arrayOfByte[22] = -3;
        arrayOfByte[23] = 1;
        arrayOfByte[24] = -41;
        arrayOfByte[25] = -123;
        arrayOfByte[26] = 17;
        arrayOfByte[27] = 93;
        arrayOfByte[28] = -36;
        arrayOfByte[29] = 45;
        arrayOfByte[30] = 18;
        arrayOfByte[31] = 71;
        arrayOfByte[32] = 61;
        arrayOfByte[33] = 70;
        arrayOfByte[34] = -117;
        arrayOfByte[35] = -55;
        arrayOfByte[36] = 107;
        arrayOfByte[37] = -75;
        arrayOfByte[38] = -89;
        arrayOfByte[39] = 3;
        arrayOfByte[40] = 94;
        arrayOfByte[41] = -71;
        arrayOfByte[42] = 30;
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, arrayOfByte, 0, 12);
        cipher.init(2, secretKey, gCMParameterSpec);
        arrayOfByte = cipher.doFinal(arrayOfByte, 12, arrayOfByte.length - 12);
        String text = new String(arrayOfByte);
        System.out.println(text);
    }
}

すると
1pt_3verywhere}
とだけ出てきた。後ろ半分はこれでわかった。

f:id:SUAN:20200524164041p:plain
siblangs4
ただ、前半分はわかりませんでした。他の人のWriteUpを見るとReact Nativeがーーーって書いてあってたしかにそんな文字見たなとなりました。

くやちーーーーー

SECCONを終えて

REVERSING、MISCが解けてちょっと嬉しい。前回はMISCしか解けなかったので…
PWNやりたいって言っていた気がするけどまだ実力不足で何もわかりませんでした(´;ω;`)

SECCONに初めて参加した参加記録 【SECCON 2019】

SECCONに参加しました!

SECCON2019に、チームMIS.Wとしてmusaprg、soeki、shiroと一緒に参加しました。結果は456点で148位でした。

SECCON結果
SECCON
ほぼ初めてな人ばかりが集まるチームで「目標は500点くらい取れればいいよね」と言っていたので、良い方ではないかと思っています。

SECCON Challenges

Beeeeeeeeeer

まずはBeeeeeeeeeerです。 Let's decode! と問題分に書かれています。

echo -e "\033#8"
sleep 1
C=$(tput cols)
L=$(tput lines)
for ID in $(seq $(($L * $C * 6))); do
    x=$(($RANDOM % $C))
    y=$(($RANDOM % $L))
    printf "\033[${y};${x}f "
done
for ID in $(seq $(($L * $C * 6))); do
    x=$(($RANDOM % $C))
    y=$(($RANDOM % $L))
    printf "\033[${y};${x}fF"
done
clear
echo TGV0J3MgZGVjb3JkaW5nISjiiafiiIDiiaYqKQo= | base64 -d
read
$'\164\162\141\160' '' $'\61' $'\62' $'\63' $'\x31\x35' $'\x31\x38' $'\u0031\u0039' $(echo MjAK | base64 -d)
echo $- | grep x && exit
$(echo =btB | rev | tr A-Za-z N-ZA-Mn-za-m | base64 -d) | $(echo =bNpyW3M | rev | tr A-Za-z N-ZA-Mn-za-m | base64 -d) $(echo XRKY | rev | tr A-Za-z N-ZA-Mn-za-m | base64 -d) $(echo =ogO | rev | base64 -d) && exit
if [ -z "$1" ]; then ID=$'\x6e\x61\x6e\x64\x6f\x6b\x75'; else ID="$1"; fi
if whoami | grep -e root -e user -e adm -e nobody -e test -e "$ID" >/dev/null; then :; else exit; fi
for i in $($'\x73\x65\x71' $((RANDOM % 10))); do $($'\x65\x63\x68\x6f' c2xlZXAK | $'\x62\x61\x73\x65\x36\x34' -d) $((RANDOM % 300)); done
$'\145\143\150\157' $- | $(echo =oAclJ3Z | rev | base64 -d) $(echo =oAe | rev | base64 -d) && $($'\x65\x63\x68\x6f' ZXhpdAo= | $'\x62\x61\x73\x65\x36\x34' $'\x2d\x64')
$(echo =btB | rev | tr A-Za-z N-ZA-Mn-za-m | base64 -d) | $(echo M3WypNb= | tr A-Za-z N-ZA-Mn-za-m | base64 -d) $(echo YKRX | tr A-Za-z N-ZA-Mn-za-m | base64 -d) $(echo Btb= | tr A-Za-z N-ZA-Mn-za-m | base64 -d) && exit
$'\145\170\160\157\162\164' $'\u0053\u0031'=$(echo aG9nZWZ1Z2EK | base64 -d)

Beeeeeeeeeerを見る限り、Shell Scriptであることがわかるので、実行権限を与えて実行してみると、

Let's decording!(≧∀≦*)

というメッセージが。
また、 read 以降の文が全てエンコードされていることがわかります。なのでとりあえずdecodeします。

read
trap '' 1 2 3 15 18 19 20
echo $- | grep x && exit
: | grep -q : && exit
if [ -z "$1" ]; then ID=nandoku; else ID="$1"; fi
if whoami | grep -e root -e user -e adm -e nobody -e test -e "$ID" >/dev/null; then :; else exit; fi
for i in $(seq $((RANDOM % 10))); do sleep $((RANDOM % 300)); done
echo $- | grep x && exit
: | grep -q : && exit
export S1=hogefuga

いらないコードばっかりですね。必要なのは export S1=hogefuga だけです。 なんで執拗にexitさせてくるんだ
更にその後のコードもデコードしてみると、

: killall sh
: shutdown
$(: pkill sh)
: shutdown
: shutdown
: poweroff
: poweroff
: exit
: poweroff
: shutdown
: shutdown
: shutdown
: pkill sh
: shutdown

こうなっていました。これ以上にこの並びが続いていて、どんだけ電源落としたいんだってなります…
重要なのは export S1=hogefuga とその後のechoの1行だけです。
その場所だけ隔離してbashで実行してみたところ、ビープ音のなった回数をランダムで聞いてくるとても面倒くさいプログラムにぶち当たります。(間違えるとexitされるのでたちが悪い)
| base64 -d | gunzip | bash

| base64 -d | gunzip > Beeeeeeeeeer2.sh
として出力してみると

for k in $(seq $((RANDOM % 10 + 1))); do
    l=$((RANDOM % 10 + 1))
    for m in $($(echo ==gCxV2c | rev | base64 -d) $l); do
        echo -ne '\a'
       sleep 1
    done
    echo "How many beeps?"
    read n </dev/tty
    export n
    if [ "$n" -ne "$l" ]; then exit; fi
done
echo -ne '\a'
sleep 1
echo -ne '\a'
sleep 1
echo -ne '\a'
sleep 1
echo "How many beeps?"
read n </dev/tty
export n

何だこれは!!!私の邪魔をしてくるコードがたくさん
export n だけが必要であり、n=3であることがわかるので、 export 3 だけを残して削除します。 更にその後にもechoがあり、そこがまた重要なのでそこだけを隔離して実行してみます。 Enter the password と出てきますが、パスワードなんて知りません。ので、やっぱり
| base64 -d | openssl aes-256-cbc -d -pass pass:$(echo -n $n | md5sum | cut -c2,3,5,12) -md md5 2>/dev/null | bash

| base64 -d | openssl aes-256-cbc -d -pass pass:cccc -md md5 2>/dev/null > Beeeeeeeeeer3.sh
として見てみます。

__=$(. 2>&1);__=${__##*.};__=$(. 2>&1);__=${__##*.};${__:$(($[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]]+$[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]]+$[$$/$$])):$((___=___^___||++___))}${__:$[$[$$/$$]<<$[$$/$$]<<$[$$/$$]]:$((___=___^___||++___))}${__:$(($[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]]+$[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]])):$((___=___^___||++___))} -- {z..A};${@:$((____=(____^____||++____)+

のような感じで、何もわかりません。が、一番最後に echo SECCON{$S1$n$_____} という文字列を発見。
S1 = hogefuga
n = 3
であるので、最後の____だけわかればいいことになります。
またこれをデコードすると

__=$(. 2>&1)
__=${__##*.}
__=$(. 2>&1)
__=${__##*.}
set -- {z..A}
echo Enter the password
read _____
: password is bash
read _____
echo -e '\033[?7h'

となりました。ちょこっと内容が消え去っていて私はここで戸惑いました。(←ちゃんとデコードしろ)
: password is bash 、神かこれ。ということで Enter the passwordbash と入れると Good Job! と帰ってくるので正解だとわかります。
これらからflagは
SECCON{hogefuga3bash}
となります。

coffee_break

何がコーヒーだ!コーヒー要素なんてないじゃないか!と思われた方、正解です。
とりあえず配布されたpythonコードを見てみる。

import sys
from Crypto.Cipher import AES
import base64


def encrypt(key, text):
    s = ''
    for i in range(len(text)):
        s += chr((((ord(text[i]) - 0x20) + (ord(key[i % len(key)]) - 0x20)) % (0x7e - 0x20 + 1)) + 0x20)
    return s


key1 = "SECCON"
key2 = "seccon2019"
text = sys.argv[1]

enc1 = encrypt(key1, text)
cipher = AES.new(key2 + chr(0x00) * (16 - (len(key2) % 16)), AES.MODE_ECB)
p = 16 - (len(enc1) % 16)
enc2 = cipher.encrypt(enc1 + chr(p) * p)
print(base64.b64encode(enc2).decode('ascii'))

cipherとbase 64を使用したところはすぐにデコードできそうですね。なので、

key1 = "SECCON"
key2 = "seccon2019"
text = sys.argv[1]

cipher = AES.new(key2 + chr(0x00) * (16 - (len(key2) % 16)), AES.MODE_ECB)
dec1 = cipher.decrypt(base64.b64decode(text))

これだけでデコードできます。問題は def encrypt(key, text):エンコードされた文字列。
でもこれ、ただの数値計算なので逆算してあげる。すると

def decrypt(key, text):
    s = ''
    for i in range(len(text)):
        s += chr(ord(text[i]) + 0x20 - (ord(key[i % len(key)]) - 0x20) + 0x3F)
    return s

はい。これでOK。 def encrypt が95で割ったあまりを出力しているので正確にはデコードできないが、推測ができれば問題ないのでOK。
またこれはアルファベットしかないとして仮定した場合である。すると
FyRyZNBO2MG6ncd3hEkC/yeYKUseI/CxYoZiIeV2fe/Jmtwx+WbWmU1gtMX9m905

S�����{�uccess_�ecryption_�eah_�eah_S�����}
になった。これから推測するとflagは
SECCON{Success_Decryption_Yeah_Yeah_SECCON}
となる。

pngbomb

画像データがgzで圧縮されているので解凍して表示しようとすると、IHDRが壊れていることがわかる。これしかわからなかったのでダメです。12時間を溶かした問題。

SECCONを終えて

初めて出場したCTFの大会でしたが、とても楽しく、寝る時間を惜しんでいました。
miscしか解いていないけれど、それが一番楽しかった気がする。
Pwn、メモリリークできないです勉強してきます…。