2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

TokyoWesterns CTF 4th 2018 Write-up

Last updated at Posted at 2018-09-03

TokyoWesterns CTF 4th 2018 に チーム m1z0r3 として参加して、1015点で53位でした。

自分は以下の4問を解き、416点を入れました。

  • SimpleAuth (55pt) - Warmup / Web
  • scs7 (112pt) - Warmup / Crypto
  • mondai.zip (95pt) - Warmup / Misc
  • Revolutional Secure Angou (154pt) - Crypto

忘れないうちに Write-up を書きます。

SimpleAuth

問題文のURLにアクセスするとソースコードが見える

if (!empty($_SERVER['QUERY_STRING'])) {
    $query = $_SERVER['QUERY_STRING'];
    $res = parse_str($query); // ココ
    if (!empty($res['action'])){
        $action = $res['action'];
    }
}

ここの parse_str($query) の処理が間違っており、 PHP Manual を読むと、第二引数がない場合は 現在のスコープに勝手に変数をセットする らしい。

というわけで $hashed_password をセットすれば良い。

/?action=auth&hashed_password=c019f6e5cd8aa0bbbcc6e994a54c757e

にアクセスすれば FLAG が降ってくる。

scs7

nc crypto.chal.ctf.westerns.tokyo 14791 をすると、暗号化された FLAG が送られ、その後メッセージを送信すると暗号化して返してくれる。

色々試した結果、以下のことがわかった。

  • 暗号文は、メッセージをHexエンコードしたものを59進法に変換したものである。
  • ただし、59進法で使われる 0-58 をどの記号に割り当てるかは毎回バラバラである。

したがって、はじめに 0-58 がどの記号に対応するか対応表を作ってしまえば、暗号化された FLAG を復号することができる。具体的には chr(59) から chr(117) までを試して、暗号文の最後の桁の文字を見ると、それが 0-58 の数字に対応する。

#!/usr/bin/env python
import socket

HOST = 'crypto.chal.ctf.westerns.tokyo'
PORT = 14791

def sock(ip, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((ip, port))
    return s, s.makefile('rw', bufsize=0)

def read_until(f, delim='\n'):
    data = ''
    while not data.endswith(delim):
        data += f.read(1)
    return data

def main():
    print 'nc %s %s\n' % (HOST, PORT)
    s, f = sock(HOST, PORT)

    result = read_until(f).strip()
    print result
    encrypted_flag = result.split(': ')[1]
    print read_until(f).strip()

    table = {}
    for i in range(59, 118):
        print read_until(f, ': ').strip() + chr(i)
        s.send(chr(i) + '\n')
        result = read_until(f).strip()
        print result
        table[result[-1]] = i - 59

    flag = 0
    for i, s in enumerate(encrypted_flag[::-1]):
        flag += table[s] * (59 ** i)

    print
    print hex(flag)[2:-1].decode('hex')

if __name__ == '__main__':
    main()

mondai.zip

mondai.zip が渡され、解凍すると中からまた Zip ファイル(パスワード付き)が出てきて、パスワードを解読して解凍するとまた Zip ファイルが出てきて… みたいな問題。

ちなみに、1番目のパスワードが一番難しかった… 他の人が2番目まで解いてくれたので残りをやった。

1番目のパスワード

ファイル名がそのままパスワード

2番目のパスワード

capture.pcapng を開くと、不自然なデータを持つ ICMP パケットが並んでおり、データサイズに着目するとパスワードがわかる

$ tshark -n -t e -r capture.pcapng -Y 'icmp and ip.dst == 192.168.11.5' -T fields -e data.len
print(''.join([chr(i) for i in [87, 101, 49, 99, 111, 109, 101]]))

3番目のパスワード

list.txt がパスワードリストで、どれかが正解

from zipfile import ZipFile

password_list = open('list.txt', 'r').read().split('\n')
with ZipFile('mondai.zip') as zf:
    for password in password_list:
        try:
            zf.extractall(pwd=password)
            print '+ Completed! Pass: ' + password
            break
        except:
            continue

4番目のパスワード

ファイル名が MD5 ハッシュ値で、元のメッセージがパスワード

5番目のパスワード

README.txt を見ると、 "password is too short" とのことなので総当たりを試す

解凍すると secret.txt が出てくるので、指示通り ( TWCTF{(2)_(5)_(1)_(4)_(3)} ) に FLAG を組み立てる

Revolutional Secure Angou

:warning: (私は数学弱者なので、以下は数学的に正しくない可能性が大いにあります。ご注意ください)

$ q \times e \equiv 1 \pmod{P} $ より、次のように考えた。

qe = px + 1
x = \frac{q}{p}e - \frac{1}{p} \approx \frac{q}{p}e \left(\because 0 \lt \frac{1}{p} \ll 1 \right)

ここで、 $p$ と $q$ は巨大な素数であり、 $\frac{q}{p}$ の値はそこまで大きくならないと仮定すると、 $x$ の値は $e$ と大幅には変わらない(ブルートフォース可能)と判断した。

また、

qe = px + 1
ne = p^2 x + p
p^2 = \frac{ne - p}{x} \approx \frac{ne}{x} \left(\because ne \gg p \right)

となるので、$x$ を徐々に増加さながら、 $\frac{ne}{x}$ を超えない最大の平方数を計算し、その平方根を元の式に代入して成立するか確かめた。

require 'openssl'

key = OpenSSL::PKey::RSA.new File.read('publickey.pem')
e, n = key.e.to_i, key.n.to_i

# num 以下の最大の平方数を求める
def search_square(num)
  ds = num.to_s.reverse.scan(/..|.$/).reverse.map(&:reverse)
  k, n = 0, 0
  ds.each do |d|
    k = (k.to_s + d.to_s).to_i
    j = 0
    j += 1 while ((n.to_s + j.to_s).to_i + 1) ** 2 <= k
    n = (n.to_s + j.to_s).to_i
  end
  n
end

2.upto(1000000) do |x|
  puts x if x % 1000 == 0
  p = search_square(n * e / x)
  q = n / p
  if p * (p * x + 1) == n * e
    puts "+ p: #{p}"
    puts "+ q: #{q}"
    break
  end
end

# rsatool.py を用いて、秘密鍵 (privatekey.pem) を生成する
#   $ python rsatool.py -f PEM -o privatekey.pem -p [p] -q [q]

key = OpenSSL::PKey::RSA.new File.read('privatekey.pem')
File.binwrite('flag', key.private_decrypt(File.binread('flag.encrypted')))
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?