ぼくフルスタックエンジニア。

フルにスタックされすぎて積んでる人のブログ

SECCON2019に参加しました

今までwebフロントばっかりやっていましたが、もともとアセンブラ寄りのセキュリティをしたいと思っていたので、SECCONに参加することにしました。

Beginnersに一回参加したきりで、CTF自体にほとんど参加したことがなく、何も分からない状態でした。 Zliとして、サークルのセキュリティに興味がある4人で参加しました。 一緒に参加した仲間は、mizuiro_ivi、くさつ、spookeydokeyの3人です。

f:id:gpioblink:20191020171915p:plain

結果は196位で散々でしたが、手を止めずにいろいろ考えてることができて、楽しかったです。 解けた問題について簡単に書いていきます。

今回解けた問題

  • coffee_break
  • Beeeeeeeeeer

coffee_break

最終的に作ったコードは次のような感じです。 元のコードから、独自の暗号化とAESで暗号化で2つのファイルに分けて作りました。

decsec1.py (独自の暗号化部分)

import sys
from Crypto.Cipher import AES
import base64


def encrypt(key, text):
    s = ''
    for i in range(len(text)):
        a = (ord(text[i]) - 0x20)
        b = (ord(key[i % len(key)]) - 0x20)
        c = (0x7e - 0x20 + 1) # 0x5f
        d = a+b
        e = d%c
        f = e + 0x20
        s += chr(f)
        # print("a", i, hex(a), chr(a))
        # print("b", i, hex(b), chr(b))
        # print("c", i, hex(c), chr(c))
        # print("d", i, hex(d), chr(d))
        # print("e", i, hex(e), chr(e))
        # print("f", i, hex(f), chr(f))
        # print()
    return s

def decrypt(key, text):
    s = ''
    for i in range(len(text)):
        f = ord(text[i])
        c = 0x5f
        e = f - 0x20
        d = e
        if e < 0x32:
            d = e + 0x5f
        b = (ord(key[i % len(key)]) - 0x20)
        a = d - b
        res = a + 0x20
        s += chr(a + 0x20)
        # print("a", i, hex(a), chr(a))
        # print("b", i, hex(b), chr(b))
        # print("c", i, hex(c), chr(c))
        # print("d", i, hex(d), chr(d))
        # print("e", i, hex(e), chr(e))
        # print("f", i, hex(f), chr(f))
        # print("res", i, hex(res), chr(res))
        # print()
    return s


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

# enc1 = encrypt(key1, text)
# print(enc1)
print(decrypt(key1, text))

こういうコードを全く読んだことがなかったので、オリジナルのencryptコードを変数で小分けして少しずつ比較しながらdecryptを書いていきました。 decryptのdで「%」の扱いに戸惑ってしまいました。アルゴリズムというかこんな実装しか出来なくてちょっと悲しいです。

decsec2.py (AESによる暗号化部分)

こっちはネットで使えそうなコードを見つけたので、そのまま採用しました。

symfoware.blog.fc2.com

import sys
import base64
from Crypto.Cipher import AES
enc = sys.argv[1]

key2 = "seccon2019"

# chr(0)で16バイトに不足している長さを埋める
key = key2 + chr(0x00) * (16 - (len(key2) % 16))
decipher = AES.new(key, AES.MODE_ECB)
# b64decodeしてから復号化
dec = decipher.decrypt(base64.b64decode(enc)).decode('utf-8').rstrip(chr(0x04))
print(dec)

Beeeeeeeeeer

「hogefuga」を自分で入れたコードだと思い見落としてしまい、解くまでにめちゃくちゃ時間がかかりました。

まず、問題にあったシェルスクリプトをダウンロードして、まずはコードの読み方を考えていきました。

難読化されていますが、bash -xv Beeeeeeeeeerのような形で実行すれば、実行とともに一行一行見やすくなった状態で表示されます。

How many beepsの問題で、beepがならない環境だったこともあり、xオプションには大変お世話になりました。

このスクリプトは、主に「起動時のスクリプト」。そのコードの中にbase64で内包されている「beep数を聞いてくるスクリプト」。そのコードのさらに中にbase64+aesで暗号化されている「最後のスクリプト」がありました。順番に見ていきます。

そのまま実行するとsleepとかで悩まされるので、alias sleep='echo sleep'のようにしておくことをおすすめします。

起動時のスクリプト

export S1=hogefuga

これは、「beep数を聞いてくるスクリプト」の前までのコードで、難読化を解除し、不要なコードを消したものです。一行になっちゃいました。 ほとんど不要なコードでしたが、このS1という変数がのちのち重要になってくるので注意です。

また、下の方に「コロン」で無効化はされていますが、poweroffなど怪しいコードが書いてあるので、気おつけてください。 ちなみに「:」を行の先頭に書くと、その行はどんな場合でもtrueと解釈され、それ以降の文字は無視されるようです。(間違えてたら教えてください)

beep数を聞いてくるスクリプト

for k in $(seq $((RANDOM % 10 +1)))
do l=$((RANDOM % 10 +1))
 for m in $(seq $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

コードを見ると、最初の数回は完全にランダムですが、最後のビープ数(\aの数)は3固定になっています。よって3を入力することで、最後のスクリプトに行けます。

最後のスクリプト

echo 'n is ' $n
__=' filename [arguments]';
echo $__
set -- z y x w v u t s r q p o n m l k j i h g f e d c b a '`' _ '^' ']' '' '[' Z Y X W V U T S R Q P O N M L K J I H G F E D C B A;
echo $@;
echo 'Enter the password';
read _____ < /dev/tty
 : password is bash
echo a
echo $(echo -n $_____|md5sum|cut -d" " -f1)|grep -q "d574d4bb40c84861791a694a999cce69"&&echo "Good Job!"&&printf "\n\033[?7l%1024s" " "&&echo SECCON{$S1$n$_____};
echo -e '\033[?7h';

難読化を解除すると、ご丁寧にもpassword is bashと書いてあります。ただ、SECCON{$S1$n$_____}のようにフラグには他の2つの変数も重要だったので解析が必要でした。

ところで、このコードの頭に

__=$(. 2>&1);
__=${__##*.};

こんなコードがあったのですが、次のようなことをしています

__=$(. 2>&1); # "."というファイルは実行できないので、"bash: .: filename argument required .: usage: . filename [arguments]"というエラーになり$__に格納される
__=${__##*.}; # $__から最後の「.」以降の文字が出力される

今までは、こういうことを意識したことがなかったので、知れるいい機会になりました。

このサイトがとても参考になりました。

www.ryotosaito.com

挑戦した問題とか

今後一番やりたいと思っているのはpwnの問題ですが、今回は、pwnやcryptoの知識は全くなかったので、web問を中心に解こうとしました。

「Option-Cmd-U」と「SPA」の問題に挑戦しましたが、どちらも分かりませんでした。

特にSPAは、「document.cookie」を見る方法が分からず、せっかくのjs系の問題なのにって感じで悔しかったなぁ

普通に実務にも使えそうな知識でもありそうだし、あとでwriteupをちゃんと読みたい。

みなさんお疲れ様でした。