STEM CTF: Cyber Challenge 2017 Writeup

この大会は2017/9/16 4:00(JST)~2017/9/17 4:00(JST)に開催されました。
今回もチームで参戦。結果は50点でした。終了後スコアボードは見れず、順位はわからずじまいです。
自分で解けた問題をWriteupとして書いておきます。

Forgotten (Forensics 50)

smtpTCP Streamで見る。

EHLO default-VirtualBox.localdomain
MAIL FROM:<default@default-VirtualBox> SIZE=770
RCPT TO:<ctf@mitre.org> ORCPT=rfc822;ctf@mitre.org
DATA
250 2.1.0 Ok
250 2.1.5 Ok
354 End data with <CR><LF>.<CR><LF>
Received: by default-VirtualBox.localdomain (Postfix, from userid 1000)
	id 17CE26F300; Tue, 16 May 2017 14:21:32 -0400 (EDT)
Content-type: text/html;
Subject: Forgot Your Password?
To: <ctf@mitre.org>
X-Mailer: mail (GNU Mailutils 2.99.99)
Message-Id: <20170516182132.17CE26F300@default-VirtualBox.localdomain>
Date: Tue, 16 May 2017 14:21:32 -0400 (EDT)
From: default@default-VirtualBox (default)

<!DOCTYPE html>
<html>
<head>
	<title>Password Reset Email</title>
</head>
<body>
	We hear you have forgotten your password.<br>
	Not to worry.  You can click <a href="https://www.aSuperRealWebsite.notFake/users/password/edit?resetPasswordToken=TUNBezU4MDc2MjY2NzZ9">this link</a> to reset your password!<br><br>
	Have a great day!
</body>
</html>
.
QUIT
250 2.0.0 Ok: queued as B705C6CC00F
221 2.0.0 Bye

URLに含まれるBase64文字列をデコードする。

$ echo TUNBezU4MDc2MjY2NzZ9 | base64 -d
MCA{5807626676}
MCA{5807626676}

SEC-T CTF Writeup

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

Handle (Misc 10)

freenodeで#sect-ctfチャネルに入る。

06:04 *topic : SEC-T CTF - FLAG:SECT{CRASH_OVERR1D3} - DOWNLOAD SERVER: - http://178.62.89.173/ - https://sect.ctf.rocks - https://ctftime.org/event/499 - Wednesday 13/9 15:00 UTC - Friday 15/9 14:30 UTC - Hackers of the world unite!
SECT{CRASH_OVERR1D3}

Report (Misc 200)

PDFStreamDumperで与えられたPDFを開き、最後の 0 HLen: 0xC3A の箇所を見る。

0000000000 65535 f 
0000013999 00000 n 
0000000019 00000 n 
0000001039 00000 n 
0000022799 00000 n 
0000014372 00000 n 
0000014507 0007d n 
0000014635 00000 n 
0000014772 00000 n 
0000014900 00059 n 
0000015028 00000 n 
0000015164 00000 n 
0000015293 00000 n 
0000015429 00000 n 
0000015558 00000 n 
0000015694 00000 n 
0000015823 00000 n 
0000015959 00000 n 
0000016088 00000 n 
0000016226 00043 n 
0000016355 00000 n 
0000016491 00000 n 
0000016620 00000 n 
0000016757 00000 n 
0000016886 00000 n 
0000017024 00000 n 
0000017155 00000 n 
0000017294 00055 n 
0000017425 00000 n 
0000017564 00000 n 
0000017695 00000 n 
0000017834 00000 n 
0000017965 00000 n 
0000018104 00000 n 
0000018235 00000 n 
0000018374 00000 n 
0000018513 00000 n 
0000018644 0004c n 
0000018783 00000 n 
0000018914 00000 n 
0000019053 00000 n 
0000019184 00000 n 
0000019323 00000 n 
0000019454 00000 n 
0000019593 00000 n 
0000019724 00000 n 
0000019863 00000 n 
0000019994 00000 n 
0000020133 00000 n 
0000020264 00000 n 
0000020403 00000 n 
0000020534 0005f n 
0000020673 0004b n 
0000014160 00041 n 
0000001059 00033 n 
0000001648 00000 n 
0000020804 00000 n 
0000020943 00000 n 
0000021075 00052 n 
0000021214 00000 n 
0000021346 00000 n 
0000021485 00000 n 
0000021617 00042 n 
0000021758 00000 n 
0000021890 00000 n 
0000022029 00000 n 
0000022168 0005f n 
0000022307 00000 n 
0000022439 00000 n 
0000022578 00030 n 
0000022710 00054 n 
0000023720 0005f n 
0000001669 00000 n 
0000012723 00000 n 
0000012746 00000 n 
0000012941 00033 n 
0000013505 00043 n 
0000013911 00000 n 
0000013944 00000 n 
0000023310 00000 n 
0000014323 00000 n 
0000014458 00031 n 
0000014586 0004e n 
0000014723 00000 n 
0000014851 00000 n 
0000014979 00000 n 
0000015115 00000 n 
0000015244 00000 n 
0000015380 00000 n 
0000015509 00000 n 
0000015645 00000 n 
0000015774 00000 n 
0000015910 0005f n 
0000016039 00000 n 
0000016177 00000 n 
0000016306 00000 n 
0000016442 00000 n 
0000016571 00000 n 
0000016708 00000 n 
0000016837 00000 n 
0000016974 00000 n 
0000017105 00000 n 
0000017244 00037 n 
0000017375 00000 n 
0000017514 00000 n 
0000017645 00000 n 
0000017784 00000 n 
0000017915 00000 n 
0000018054 00000 n 
0000018185 00000 n 
0000018324 00000 n 
0000018463 00000 n 
0000018594 00000 n 
0000018733 00000 n 
0000018864 00000 n 
0000019003 00000 n 
0000019134 00000 n 
0000019273 00000 n 
0000019404 00000 n 
0000019543 00000 n 
0000019674 00000 n 
0000019813 00000 n 
0000019944 00030 n 
0000020083 00000 n 
0000020214 0004e n 
0000020353 00000 n 
0000020484 00000 n 
0000020623 00000 n 
0000020754 00000 n 
0000020893 00000 n 
0000021025 00000 n 
0000021164 00000 n 
0000021296 00000 n 
0000021435 0007b n 
0000021567 00000 n 
0000021708 00000 n 
0000021840 00000 n 
0000021979 00054 n 
0000022118 00000 n 
0000022257 00000 n 
0000022389 00043 n 
0000022528 00045 n 
0000022660 00000 n 
0000023428 00000 n 
0000023827 00053 n 
0000023998 00000 n 

これを c3a.txtに保存する。2列目の16進数をASCIIコードとして、0以外の値を下から順に文字として読む。

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

flag = ''
for line in lines:
    data = line.strip().split(' ')
    if data[2] == 'n':
        code = int(data[1], 16)
        if code != 0:
            flag += chr(code)

flag = flag[::-1]
print flag
SECT{N07_N1C3_T0_BR3AK_LUCY}

Acid burn (Misc 200)

https://convertio.co/ja/でwebpからpngに変換。Stegsolveで開き、Red plane 2でなんとなくフラグが見える。
f:id:satou-y:20170918203030p:plain
隠れて見えない部分は文章になるように推定しながらフラグを組み立てる。

SECT{I_LOVE_CRASH_OVERFLOW_BUT_I_CAN_NOT_TELL_HIM_HOW_I_FEEL_ABOUT_HIM}

Joeys screenshot (Misc 50)

TweakPNGで見ると、たくさんのiTXtチャンクが入っている。そのテキスト情報を並べると、以下のようになっている。

_36
327
C2
134
524
N22
H41
{4
_11
_33
_13
021
E1
530
342
H43
038
535
P26
U25
G37
415
39
B19
S0
U29
R28
R32
D14
212
_23
D5
D39
_40
G17
K8
Y10
344
!45
520
T3
331
118
46
}46
_16
_7

フラグの最初が"SECT{"となることから、[文字(1バイト)][順番]という構成になっていることに気づく。順番に並べてみる。

S 0
E 1
C 2
T 3
{ 4
D 5
4 6
_ 7
K 8
3 9
Y 10
_ 11
2 12
_ 13
D 14
4 15
_ 16
G 17
1 18
B 19
5 20
0 21
N 22
_ 23
5 24
U 25
P 26
3 27
R 28
U 29
5 30
3 31
R 32
_ 33
1 34
5 35
_ 36
G 37
0 38
D 39
_ 40
H 41
3 42
H 43
3 44
! 45
} 46
SECT{D4_K3Y_2_D4_G1B50N_5UP3RU53R_15_G0D_H3H3!}

ASIS CTF Finals 2017 Writeup

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

V.I.R (Warm-up)

Rulesのページの最下部にフラグが書いてある。

Good luck, ASIS{_rUL35_4r3_4Lw4y5_ImP0rt4nt}
ASIS{_rUL35_4r3_4Lw4y5_ImP0rt4nt}

Vivid Spying (Forensics, Network)

pcapファイルが与えられている。FQDNで0,1の名前を1文字ずつ長くしてDNSに問い合わせしている。続きを行い、0,1を2進数として文字列にすることを考える。

import dns.resolver

r = dns.resolver.Resolver()
r.nameservers = ['95.85.26.168']

domain = '1110.000011101100101011111010110111101100101010010010110010101000001.asisctf.com'

while True:
    if len(domain) % 64 == 11:
        domain = '.' + domain
    print domain

    finish = False
    for i in [0, 1]:
        try_domain = str(i) + domain
        try:
            rdata = r.query(try_domain, 'A')
            domain = try_domain
            break
        except:
            if i == 0:
                continue
            else:
                finish = True
    if finish:
        break

enc = domain.replace('.asisctf.com', '').replace('.', '')
enc = '0' + enc[::-1]

flag = ''
for i in range(0, len(enc), 8):
    flag += chr(int(enc[i:i+8], 2))

print flag
ASIS{_Spying_with_DNS_!}

Cryptocurrency Coding (PPC)

$ nc 178.62.22.245 58901
Hi all, let's go to BTCing!!
Are you ready? [Y]es or [N]o:
y
Send us a valid BTC address with same length of given damaged address that correct it :)
----------------------------------------------------------------------------------------
the damaged BTC address is: 1CnmjSXKaPZRNb7pUR8rNGduP5UEfs5yBb
Send the correted BTC:

Bitcoinアドレスとして間違っているので、訂正する問題。チェックディジットを修正するプログラムを書く。

import socket
import re
import base58
import hashlib

def correct(btc):
    btcbytes = base58.b58decode(btc)
    chk = hashlib.sha256(hashlib.sha256(btcbytes[-25:-4]).digest()).digest()[:4]
    correct_btcbytes = btcbytes[:-4] + chk
    correct_btc = base58.b58encode(correct_btcbytes)
    return correct_btc

pattern = 'the damaged BTC address is: (.+)\n'

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('178.62.22.245', 58901))

data = s.recv(256)
print data
print 'Y'
s.sendall('Y\n')
data = s.recv(256)
print data

i = 1
while True:
    print 'Round %d' % i
    data = s.recv(256)
    print data
    if data.startswith('Good job'):
        data = s.recv(256)
        print data
    m = re.match(pattern, data)
    dmg_btc = m.group(1)
    crct_btc = correct(dmg_btc)
    print crct_btc
    s.sendall(crct_btc + '\n')
    i += 1

data = s.recv(256)
print data
ASIS{!0h_BTC_h4S_aT_leas7_3_3rr0r_cOrr3ct1ng_fe4tur3!}

Simple Crypto (Crypto)

flagをkeyで繰り返し、XORしている。flagに戻すとPNGのフォーマットなっていることがわかるので、それを踏まえコードを書く。

KEY = 'musZTXmxV58UdwiKt8Tp'
key = KEY.encode('hex')

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

flag = ''
for i in range(len(enc)):
    flag += chr(ord(enc[i]) ^ ord(key[i%len(key)]))

with open('flag.png', 'wb') as f:
    f.write(flag.decode('hex'))

f:id:satou-y:20170912221548p:plain

ASIS{juSt_S!mpl3_Cryp7o_f0r_perFect_guy5_l1ke_You!}

Hash collision (Crypto)

phishで定義されたハッシュはコードをよく読むと、X[-1]の値が同じだと同じ結果になる。さらによく考えると、p, g, sの値に関係なく、ある文字列にパディング文字 '/' を追加したそのハッシュもほぼ同じ結果になる。出題者の意図とは違う気がするが、このことからコードを書き、フラグをゲットすることができた。

import socket
import re
import base58
import hashlib
import itertools
import string

def get_btc(s):
    h = hashlib.new('ripemd160')
    h.update(s)
    ripemd160 = h.digest()

    btcbytes_head = '\x00' + ripemd160
    chk = hashlib.sha256(hashlib.sha256(btcbytes_head).digest()).digest()[:4]
    btcbytes = btcbytes_head + chk

    return base58.b58encode(btcbytes)

pattern = '\| Send a BTC valid address that starts with (.+)\:'

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('66.172.27.77', 11019))

data = s.recv(256)
print data
print 'Y'
s.sendall('Y\n')
data = s.recv(256)
print data
m = re.match(pattern, data)
start = m.group(1)

for c in itertools.product(string.printable, repeat=4):
    text = ''.join(c)
    try_btc = get_btc(text)
    if try_btc.startswith(start):
        print try_btc
        s.sendall(try_btc + '\n')
        break

data = s.recv(1024)
print data
data = s.recv(1024)
print data
print 'P'
s.sendall('P\n')
data = s.recv(1024)
print data
data = s.recv(1024)
print data
print 'R'
s.sendall('R\n')
data = s.recv(1024)
print data
data = s.recv(1024)
print data
print 'S'
s.sendall('S\n')
data = s.recv(1024)
print data
print 'ab'
s.sendall('ab\n')
data = s.recv(1024)
print data
print 'ab/'
s.sendall('ab/\n')
data = s.recv(1024)
print data
ASIS{ce8a30c725bdc9fea1da21102fdb480f}

Tokyo Westerns CTF 3rd 2017 Writeup

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

Welcome!! (Misc, Warmup)

問題で書かれているフラグを投入するだけ。

TWCTF{Welcome_To_TWCTF2017!!}

Palindromes Pairs - Coding Phase - (PPC, Warmup)

$ nc ppc1.chal.ctf.westerns.tokyo 8765
####################################
#        Palindromes Pairs         #
####################################

A word is called "palindrome" if it reads the same backward as forward.
For instance, "cba", "a", "caab" and "deaed" are palindromes.

Given the list of words s_1, s_2, s_3, ..., s_n, 
your task is the count pair (i, j) that the concatenation of s_i and s_j is palindrome.


Input Format:
The first line contains n.
The second line contains s_1, s_2, s_3, ..., s_n separated by space. 
n
s_1 s_2 s_3 ... s_n

Output Format:
Your program must output the number of pairs in one line.

Conditions:
 * n <= 50
 * |TestCase| = 50
 * Each word only contains lower alphabets.

Example Input 1:
3
a ba cab

Example Output 1:
3

Explanation of Example1:
'aa' (1,1), 'aba' (1,2), 'bacab' (2,3)

Example Input 2:
5
a aa aaa aaaa aaaaa

Example Output 2:
25


----- START -----
Input 1/50
50
xxx xxc x rx rcr sm c rs mm m s cr rmr xx cc mmx s m xx x m s c r sx cs r rrx r cs rx rx cs rrr xc sxr s s m cr sc scs r x x xmr rc rsm rmm xxm

与えられた文字から2つを組み合わせて回文がいくつできるかを答える。

import socket
import re
import itertools

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('ppc1.chal.ctf.westerns.tokyo', 8765))

data = s.recv(1024)
print data

for i in range(50):
    data = s.recv(1024)
    print data

    pattern = 'Input (.+)\n(.+)\n(.+)'
    m = re.search(pattern, data)
    chars = m.group(3).split(' ')

    count = 0
    for c in itertools.product(chars, repeat=2):
        text = ''.join(c)
        if text == text[::-1]:
            count += 1

    print count
    s.sendall(str(count) + '\n')
    data = s.recv(1024)
    print data

data = s.recv(1024)
print data
TWCTF{find_favorite_smell}

BabyDLP (Crypto)

与えられたコードから暗号化は以下のようになっていることがわかる。

c = pow(g, m^s, p)
・c: 暗号
・g, pは固定値で値はわかっている。
・m: フラグを数値化したもの
・s: 指定可能

0を指定した時はm^sはフラグを数値化したものになる。下位ビットから1,2,4,8,16,…と順に1ビットずつ判定する。

・取得した暗号がフラグの暗号のg**(g**i)倍(mod p)であれば、フラグの該当ビットは立っていない。
・フラグの暗号が取得した暗号のg**(g**i)倍(mod p)であれば、フラグの該当ビットは立っている。
import socket
import re

p = 160634950613302858781995506902938412625377360249559915379491492274326359260806831823821711441204122060415286351711411013883400510041411782176467940678464161205204391247137689678794367049197824119717278923753940984084059450704378828123780678883777306239500480793044460796256306557893061457956479624163771194201
g = 2

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('ppc2.chal.ctf.westerns.tokyo', 28459))

print '0'
s.sendall('0\n')
base = s.recv(1024)
print base
base = int(base[2:], 16)

bin_flag = ''
flag = ''
i = 0
times = g**(g**i)
while True:
    bit_str = hex(g**i)[2:].replace('L', '')
    print bit_str
    s.sendall(bit_str + '\n')
    data = s.recv(1024)
    print data
    data = int(data[2:], 16)
    if i != 0:
        times = (times**2) % p
    if (base * times) % p == data:
        bin_flag = '0' + bin_flag
    elif (data * times) % p == base:
        bin_flag = '1' + bin_flag
    i += 1
    if i % 8 == 0:
        flag = chr(int(bin_flag, 2)) + flag
        bin_flag = ''
    if flag.startswith('TWCTF{'):
        break

print 'flag: ' + flag
TWCTF{0f97c1c3ac2aedbd7fb8cd39d50f2b561d31f770}

HackIT CTF 2017 Writeup

この大会は2017/8/25 23:00(JST)~2017/8/27 23:00(JST)に開催されました。
今回もチームで参戦。結果は220点で1406チーム中123位でした。
自分で解けた問題をWriteupとして書いておきます。

USB ducker (Foren 100)

USBの通信パケットをキャプチャしたpcapファイルが与えられる。DESCRIPTOR Response DECICEを探してキーボードの種類を調べると、No.267のパケットから Aluminum Keyboard (ISO)とわかる。
そこでhttp://www.usb.org/developers/hidpage/Hut1_12v2.pdfを参考に押したキーを割り当てていく。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from scapy.all import *

keymap = { 0x04: ('a', 'A'), 0x05: ('b', 'B'), 0x06: ('c', 'C'),
           0x07: ('d', 'D'), 0x08: ('e', 'E'), 0x09: ('f', 'F'),
           0x0a: ('g', 'G'), 0x0b: ('h', 'H'), 0x0c: ('i', 'I'),
           0x0d: ('j', 'J'), 0x0e: ('k', 'K'), 0x0f: ('l', 'L'),
           0x10: ('m', 'M'), 0x11: ('n', 'N'), 0x12: ('o', 'O'),
           0x13: ('p', 'P'), 0x14: ('q', 'Q'), 0x15: ('r', 'R'),
           0x16: ('s', 'S'), 0x17: ('t', 'T'), 0x18: ('u', 'U'),
           0x19: ('v', 'V'), 0x1a: ('w', 'W'), 0x1b: ('x', 'X'),
           0x1c: ('y', 'Y'), 0x1d: ('z', 'Z'), 0x1e: ('1', '!'),
           0x1f: ('2', '@'), 0x20: ('3', '#'), 0x21: ('4', '$'),
           0x22: ('5', '%'), 0x23: ('6', '^'), 0x24: ('7', '&'),
           0x25: ('8', '*'), 0x26: ('9', '('), 0x27: ('0', ')'),
           0x28: ('[Enter]', '[Enter]'), 0x29: ('\x1b', '\x1b'),
           0x2a: ('\x08', '\x08'), 0x2b: ('\x09', '\x09'),
           0x2c: ('\x20', '\x20'), 0x2d: ('-', '_'),
           0x2e: ('=', '+'), 0x2f: ('[', '{'), 0x30: (']', '}'),
           0x31: ('\\', '|'), 0x32: ('#', '~'), 0x33: (';', ':'),
           0x34: ('\'', '\"'), 0x35: ('`', '~'), 0x36: (',', '<'),
           0x37: ('.', '>'), 0x38: ('/', '?'), 
           0x51: ('[Down]', '[Down]'), 0x52: ('[Up]', '[Up]') }

def read_usb_data_from_pcap():
    packets = rdpcap('task.pcap')
    usb_data = []
    for p in packets:
        buf = p['Raw'].load
        if buf[19] == '\x03' and buf[22] == '\x01':
            usb_data.append(buf[27:])
    return usb_data

def analyze_usb_data(usb_data):
    flag = ''
    for d in usb_data:
        if d[2] == '\x00' or not('\x00' in d[3:8]):
            continue
        if d[0] == '\x02' or d[0] == '\x20':
            c = keymap[ord(d[2])][1]
            flag += c
        else:
            c = keymap[ord(d[2])][0]
            flag += c
    print flag

def main():
    data = read_usb_data_from_pcap()
    analyze_usb_data(data)

if __name__ == '__main__':
    main()

これを実行すると、以下のようなキー入力があったことがわかる。

w[Enter]k[Enter]f[Enter]b[Enter]3'[Up][[Up]l[Up]#[Up]{w$[Down]>b[Down]ag[Down][e[Down]ci.[[Up][f[Up]{k[Up]n$[Up]ju}[Down]:[Down]3[Down]u[Down]%=[Up]~[Up]y[Up]6[Up],'[Down]p[Down]b[Down]7[Down]%&[Up]d[Up]0[Up]j[Up]pt[Down]i[Down]a[Down][[Down]k([Up]=[Up]r[Up]m[Up]]=[Down]0[Down]d[Down]>[Down]lc[Up]*[Up]_[Up]{[Up]j%[Down]u[Down]s[Down]([Down]*2[Up]0[Up]n[Up]'[Up];9[Down]h[Down]4[Down]][Down]y4[Up]'[Up]k[Up];[Up]+p[Down]f[Down]e[Down]$[Down]!}[Up]1[Up]_[Up]k[Up]s&[Down]s[Down]2[Down]c[Down]%q[Up]$[Up].[Up]![Up]#,[Down]s[Down]0[Down]c[Down]z3[Up]e[Up]}[Up]-[Up]i

上記の結果通りキーを押していくと、3段目にフラグが出来上がる。

w{w$ju},'pt]=j%;9+ps&#,i
k#>bn$:6pjim0{u'h;fks!s-
flag{k3yb0ard_sn4ke_2.0}
b[[e[fu~7d[=>*(0]'$1c$ce
3'ci.[%=%&k(lc*2y4!}%qz3
flag{k3yb0ard_sn4ke_2.0}

HackCon 2017 Writeup

この大会は2017/8/26 6:00(JST)~2017/8/27 6:00(JST)に開催されました。
今回もチームで参戦。結果は981点で345チーム中4位でした。
自分で解けた問題をWriteupとして書いておきます。

Rotate it (Bacche 2)

シーザー暗号。ROT13で復号できた。

d4rk{wh0_give$_ca3sar_in_CTF???}c0de

High Bass (Bacche 3)

Base64デコードする。

$ echo VGhpcyB3YXMgaW4gYmFzZS02NDogZDRya3t0aGF0XyRpbXBsXzNuMHVnaDRfVX1jMGRl | base64 -d
This was in base-64: d4rk{that_$impl_3n0ugh4_U}c0de
d4rk{that_$impl_3n0ugh4_U}c0de

File (Bacche 3)

実行するだけ。

$ ./one
d4rk{s1mpl_linux_execUt4ble}c0de
d4rk{s1mpl_linux_execUt4ble}c0de

Needle (Bacche 3)

テキストファイルをd4rkで検索

d4rk{n33dle_in_a_h4ystck}c0de

ALL CAPS (Bacche 5)

換字式暗号。quipqiupで復号する。

IN CRYPTOGRAPHY, A S??STIT?TION CIPHER IS A METHOD OF ENCODING ?Y WHICH ?NITS OF PLAINTEXT ARE REPLACED WITH CIPHERTEXT, ACCORDING TO A FIXED SYSTEM; THE "?NITS" MAY ?E SINGLE LETTERS (THE MOST COMMON), PAIRS OF LETTERS, TRIPLETS OF LETTERS, MIXT?RES OF THE A?O?E, AND SO FORTH. THE RECEI?ER DECIPHERS THE TEXT ?Y PERFORMING THE IN?ERSE S??STIT?TION. THAN?S FOR READING THAT, HERE'S YO?R FLAG: D4R?{TRY_FACCH3_IFTHIS_TOO_SIMPEL}C0DE
D4RK{TRY_FACCH3_IFTHIS_TOO_SIMPEL}C0DE

RSA - 1 (Bacche 10)

p, q がわかっているので、そのまま復号する。

p = 152571978722786084351886931023496370376798999987339944199021200531651275691099103449347349897964635706112525455731825020638894818859922778593149300143162720366876072994268633705232631614015865065113917174134807989294330527442191261958994565247945255072784239755770729665527959042883079517088277506164871850439

q = 147521976719041268733467288485176351894757998182920217874425681969830447338980333917821370916051260709883910633752027981630326988193070984505456700948150616796672915601007075205372397177359025857236701866904448906965019938049507857761886750656621746762474747080300831166523844026738913325930146507823506104359

c = 8511718779884002348933302329129034304748857434273552143349006561412761982574325566387289878631104742338583716487885551483795770878333568637517519439482152832740954108568568151340772337201643636336669393323584931481091714361927928549187423697803637825181374486997812604036706926194198296656150267412049091252088273904913718189248082391969963422192111264078757219427099935562601838047817410081362261577538573299114227343694888309834727224639741066786960337752762092049561527128427933146887521537659100047835461395832978920369260824158334744269055059394177455075510916989043073375102773439498560915413630238758363023648

e = 65537

n = p * q

a = (p - 1) * (q - 1)

x = 0
while True:
    if (a * x + 1) % e == 0:
        d = (a * x + 1) / e
        break
    x = x + 1

m = pow(c, d, n)

flag = ('%x' % m).decode('hex')
print flag
d4rk{s1mpl3_rsa_n0t_th1s_34sy_next_time}c0de

RSA - 2 (Crypto 50)

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])

n = 109676931776753394141394564514720734236796584022842820507613945978304098920529412415619708851314423671483225500317195833435789174491417871864260375066278885574232653256425434296113773973874542733322600365156233965235292281146938652303374751525426102732530711430473466903656428846184387282528950095967567885381

e = 49446678600051379228760906286031155509742239832659705731559249988210578539211813543612425990507831160407165259046991194935262200565953842567148786053040450198919753834397378188932524599840027093290217612285214105791999673535556558448523448336314401414644879827127064929878383237432895170442176211946286617205

c = 103280644092615059984518332609100925251130437801342718478803923990158474621180283788652329522078935869010936203566024336697568861166241737937884153980866061431062015970439320809653170936674539901900312536610219900459284854811622720209705994060764318380465515920139663572083312965314519159261624303103692125635

p, q = wiener(n, e)

flag = decrypt(p, q, e, c)
print flag
d4rk{1_70ld_y0u_th15_would_8e_more_difficult}c0de

VizHash (Crypto 100)

暗号化の概要は以下の通り。

・flagをBase64エンコードする。
・flagのBase64文字列分、以下の処理を行い、結合する。
 ・先頭1文字をencode(逆にしてASCIIコードで4つプラス)
 ・先頭2文字をencode
    :
 ・全文字をencode
・結合したものを1文字ずつmd5を取る。
・md5の文字について以下のような処理で色にして画像にする。

R (128 + ord('0')) ^ ord('x')
G 128 + ord('x') ^ ord(2番目)
B 128 + ord(2番目) ^ ord(3番目)

R (128 + ord(3番目)) ^ ord(4番目)
G 128 + ord(4番目) ^ ord(5番目)
B 128 + ord(5番目) ^ ord(6番目)

:

R (128 + ord(30番目)) ^ ord(31番目)
G 128 + ord(31番目) ^ ord(32番目)
B 128 + ord(32番目) ^ ord(33番目)

一つ一つ逆算していけば元に戻せそう。

import hashlib
from PIL import Image

BODY_DATA_NUM = 12936

def decode(s):
        return (''.join([ chr(ord(c)-4) for c in s[::-1] ]))

hash_dict = {}
for i in range(32, 127):
    h = hashlib.md5(chr(i)).hexdigest()
    hash_dict[h] = chr(i)

img = Image.open('digest.png').convert('RGB')
pixel_list = img.getdata()

md5_list = []
for i in range(BODY_DATA_NUM / 11):
    h = []
    for j in range(11):
        if j != 0:
            h.append(pixel_list[i*11+j][0] ^ (h[-1] + 128))
            h.append((pixel_list[i*11+j][1] - 128) ^ h[-1])
        else:
            h.append((pixel_list[i*11+j][1] - 128) ^ ord('x'))
        h.append((pixel_list[i*11+j][2] - 128) ^ h[-1])
    md5str = ''
    for i in range(32):
        md5str += chr(h[i])
    if md5str[-1:] == 'L':
        md5str = '0' + md5str[:-1]
    md5_list.append(md5str)

encrypted_flag = ''
for md5_val in md5_list:
    encrypted_flag += hash_dict[md5_val]

len_b64 = 0
while True:
    if len_b64 * (len_b64 + 1) == len(encrypted_flag) * 2:
        break
    len_b64 += 1

b64_flag = decode(encrypted_flag[-len_b64:])
flag = b64_flag.decode('base64')
print flag
d4rk{no00000oo_not0x_myfaUltXXX}c0de

White (Steg 50)

PNGファイルが添付されているが、PNGフォーマットの後にBase64の文字列が入っている。Base64の文字列をデコードすると、またPNGフォーマットとその後にBase64文字列のデータが入っている。繰り返し処理してPNGファイルを取り出す。

def div_png_and_b64dec(data):
    PNG_TAIL = '\x49\x45\x4e\x44\xae\x42\x60\x82'
    index = data.find(PNG_TAIL)
    enc = data[index+8:]
    if enc == '':
        return True, data, ''
    else:
        final = False
        dec = enc.decode('base64')
        return final, data[:index+8], dec

with open('final.png', 'rb') as f:
    data = f.read()

i = 1
while True:
    print 'Round %d' % i
    final, png, data = div_png_and_b64dec(data)
    file = 'png\\%02d.png' % i
    if final:
        with open(file, 'wb') as f:
            f.write(png)
        break
    else:
        with open(file, 'wb') as f:
            f.write(png)
    i += 1

取り出した画像の中にフラグの断片が入っているので、結合する。

$ convert +append 01.png 02.png 03.png 04.png 05.png 06.png flag01.png
$ convert +append 07.png 08.png 09.png 10.png 11.png 12.png flag02.png
$ convert +append 13.png 14.png 15.png 16.png 17.png 18.png flag03.png
$ convert +append 19.png 20.png 21.png 22.png 23.png 24.png flag04.png
$ convert +append 25.png 26.png 27.png 28.png 29.png 30.png flag05.png
$ convert -append flag*.png flag.png

f:id:satou-y:20170831221156p:plain

d4rk{1mag3_m4n1pul4t10n_F7W}c0d3

WhiteHat Challenge 04 Writeup

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

misc01 (Miscellaneous 15)

Pythonスクリプトが与えられているが、Base64データになっている。デコードすると、exec関数でASCIIコードで文字列が連結されている。その文字列はまたBase64文字列になっていて、デコードするとまたexec関数でASCIIコードで文字列が連結されている。execの中身をデコードすることを繰り返す。

def exec_decode(data):
    if data.startswith('exec(') == False:
        return ''
    data = data.replace('exec(chr(', '')
    data = data.replace(')+chr(', ' ')
    data = data.replace('))', '')
    data = data.split(' ')

    dec = ''
    for i in range(len(data)):
        dec += chr(int(data[i]))
    return dec

with open('python_beginner.py', 'r') as f:
    data = f.read()

data = data.decode('base64')

while True:
    dec = exec_decode(data)
    if dec == '':
        print data
        break
    data = dec
flag = "scr1pt_w1th_pyth0n_1s_ez"
priint flag
$ echo -n scr1pt_w1th_pyth0n_1s_ez | sha1sum
6c67866ad716bc4bb5ff9e7a09d15cb04707f238  -
WhiteHat{6c67866ad716bc4bb5ff9e7a09d15cb04707f238}

crypto01 (Cryptography 10)

Googleの画像検索で調べる。
http://forum.artemis-fowl.com/viewtopic.php?t=5982
ここに対応表が載っているので、1.jpgから復号する。

FLAG IS HAVE FUN
$ echo -n 'HAVE FUN' | sha1sum
78fd26881b713984b6a49a74d8621ae7ae8ccde0  -
WhiteHat{78fd26881b713984b6a49a74d8621ae7ae8ccde0}
→これはダメだった。

小文字にしてみる。

$ echo -n 'have fun' | sha1sum
eeb5dd49132e82a02cacec1c0fc4889bd6373192  -
WhiteHat{eeb5dd49132e82a02cacec1c0fc4889bd6373192}

crypto02 (Cryptography 20)

ある暗号方法でBase64文字列を入力すると、暗号化文字列を返すシステムがある。ソースコードもダウンロードできて、次のようになっている。

import sys
from Crypto.Hash import SHA256
from Crypto.Cipher.AES import AESCipher
from flag import flag as create
def encrypt(m):
	flag = create()
	key = SHA256.new(flag).digest()
	s = 'something!' + m.decode('base64') + flag
	p = 16 - (len(s) % 16)
	s = s + (chr(p) * p)
	return AESCipher(key).encrypt(s).encode("base64")

平文は「something![text][flag]」という形式。ブロック暗号であることを利用してパディング文字数を調整しながら、先頭からフラグを探り当てる。
以下のようなことを繰り返すイメージ。

123456789012345
something!#####[FLAG]
something!#####[FLAG1文字目をBFで回す]

something!####[FLAG]
something!####E[FLAG2文字目をBFで回す]

  :
import urllib
import urllib2
import re
import string

def query(text):
    pattern = '<p> (.+)\n</p>'
    url = 'http://chall04-crypto02.wargame.whitehat.vn/'
    req = {'crypto': text.encode('base64')}
    params = urllib.urlencode(req)
    request = urllib2.Request(url, params)
    response = urllib2.urlopen(request)
    data = response.read()
    m = re.search(pattern, data, re.DOTALL)
    enc = m.group(1).decode('base64')
    return enc

header = 'something!'
flag = ''
while True:
    padding = '#' * ((- len(header + flag) - 1) % 16)
    size = len(header + padding + flag) + 1
    correct = query(padding)[0:size]
    found = False
    for c in string.printable:
        try_str = query(padding + flag + c)[0:size]
        if try_str == correct:
            found = True
            flag += c
            print c,
            break
    if found == False:
        break

print flag

実行結果は次の通り。

E a s y _ p o i n t _ c 1 3 Easy_point_c13
$ echo -n Easy_point_c13 | sha1sum
1ffe5f92c181a89dd267cfd4b6ec2c80c6202391  -
WhiteHat{1ffe5f92c181a89dd267cfd4b6ec2c80c6202391}