RedpwnCTF 2019 Writeup

この大会は2019/8/13 1:00(JST)~2019/8/17 1:00(JST)に開催されました。
今回もチームで参戦。結果は3001点で926チーム中59位でした。
自分で解けた問題をWriteupとして書いておきます。

Discord (Misc)

Disocordに入り、#flagチャネルのメッセージにフラグが書いてあった。

flag{why_c4nt_th3y_ju5t_us3_irc}

Tux Trivia Show (Misc)

$ nc chall.2019.redpwn.net 6001
Welcome to Tux Trivia Show!!!
What is the capital of Fiji?
Suva
Correct! Next question coming...

What is the capital of Washington?


Time out!

国の首都やアメリカの各州の州都を答えていく問題。https://github.com/icyrockcom/country-capitals/blob/master/data/country-list.csvで首都のリストを入手。アメリカの州都のリストも別途入手する。
ただ、微妙にスペルが違っていたり、2バイト文字を使っていたりするので、調整する。

import socket
import re

capital_dic={
    'Alabama': 'Montgomery',
    'Alaska': 'Juneau',
    'Arizona':'Phoenix',
    'Arkansas':'Little Rock',
    'California': 'Sacramento',
    'Colorado':'Denver',
    'Connecticut':'Hartford',
    'Delaware':'Dover',
    'Florida': 'Tallahassee',
    'Georgia': 'Atlanta',
    'Hawaii': 'Honolulu',
    'Idaho': 'Boise',
    'Illinois': 'Springfield',
    'Indiana': 'Indianapolis',
    'Iowa': 'Des Moines',
    'Kansas': 'Topeka',
    'Kentucky': 'Frankfort',
    'Louisiana': 'Baton Rouge',
    'Maine': 'Augusta',
    'Maryland': 'Annapolis',
    'Massachusetts': 'Boston',
    'Michigan': 'Lansing',
    'Minnesota': 'St. Paul',
    'Mississippi': 'Jackson',
    'Missouri': 'Jefferson City',
    'Montana': 'Helena',
    'Nebraska': 'Lincoln',
    'Nevada': 'Carson City',
    'New Hampshire': 'Concord',
    'New Jersey': 'Trenton',
    'New Mexico': 'Santa Fe',
    'New York': 'Albany',
    'North Carolina': 'Raleigh',
    'North Dakota': 'Bismarck',
    'Ohio': 'Columbus',
    'Oklahoma': 'Oklahoma City',
    'Oregon': 'Salem',
    'Pennsylvania': 'Harrisburg',
    'Rhode Island': 'Providence',
    'South Carolina': 'Columbia',
    'South Dakota': 'Pierre',
    'Tennessee': 'Nashville',
    'Texas': 'Austin',
    'Utah': 'Salt Lake City',
    'Vermont': 'Montpelier',
    'Virginia': 'Richmond',
    'Washington': 'Olympia',
    'West Virginia': 'Charleston',
    'Wisconsin': 'Madison',
    'Wyoming': 'Cheyenne'  
}

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

def get_capital_from_country(s):
    for countryinfo in countries_list:
        country = countryinfo.split(',')[0][1:-1]
        capital = countryinfo.split(',')[1][1:-1]
        if country == s:
            return capital
    return ''

def get_capital_from_state(s):
    if s in capital_dic:
        return capital_dic[s]
    else:
        return 'Not Found!'

with open('country-list.csv', 'r') as f:
    countries_list = f.readlines()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('chall.2019.redpwn.net', 6001))

data = recvuntil(s, '\n').rstrip()
print data

for i in range(1000):
    print 'Round %d' % (i+1)
    data = recvuntil(s, '\n').rstrip()
    print data
    pattern = 'capital of (.+)\?'
    m = re.search(pattern, data)
    country = m.group(1)
    capital = get_capital_from_country(country)
    if capital == '':
        capital = get_capital_from_state(country)
        if capital == '':
            break
    print capital
    s.sendall(capital + '\n')
    data = recvuntil(s, '\n').rstrip()
    print data
    data = recvuntil(s, '\n').rstrip()
    print data

data = s.recv(8192)
print data

実行結果は以下の通り。

    :
Round 1000
What is the capital of Hungary?
Budapest
Correct! Next question coming...

Here is your flag: flag{TUX_tr1v1A_sh0w+m3st3r3d_:D}
flag{TUX_tr1v1A_sh0w+m3st3r3d_:D}

BabbyPwn (Pwn)

ncで接続するだけ。

$ nc chall.2019.redpwn.net 4001
So you want the flag, I see
You should stop
Stealing is wrong
You wouldn't steal a car
Well...
Okay
But know this
You are irredeemably a bad person
flag{st341ing_is_wr0ng}
flag{st341ing_is_wr0ng}

Generic Crackme (Reverse Engineering)

$ ./generic_crackme
plz enter password plz:
aaa
lolno

正しいパスワードがフラグになるはず。IDAで開き、処理を追う。
main関数は以下のようになっていて、sub_1168の結果により分岐する。
f:id:satou-y:20190818162522p:plain
sub_1168関数は以下のようになっていて、先頭からsub_1159関数の結果、以下のようになっていればよい。

e, p, h, h, z

f:id:satou-y:20190818162613p:plain
sub_1159関数は以下のようになっていて、1を足しているだけ。
f:id:satou-y:20190818162701p:plain
以上のことから、パスワードはdoggy

flag{doggy}

Generic Crackme Redux (Reverse Engineering)

$ ./generic_crackme_redux 
Enter access code: 123
Bzzzzrrrppp

正しいアクセスコードがフラグになるはず。IDAで開き、処理を追う。
main関数は以下のようになっていて、sub_1169の結果により分岐する。
f:id:satou-y:20190818162942p:plain
sub_1169関数は以下のようになっていて、処理をした後の結果比較をしている。

1. a = アクセスコード << 2 ; a = アクセスコードの4倍
2. a += アクセスコード ; a = アクセスコードの5倍
3. a += a; a = アクセスコードの10倍
4. aと0xac292(=705170)を比較し、同じならOK

f:id:satou-y:20190818163357p:plain
アクセスコードを求めるためには10で割ればよい。

705170 / 10 = 70517
flag{70517}

Dunce Crypto (Crypto)

シーザー暗号そ推測し、https://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipherで復号する。

Rotation 7:
flag{I_d0nt_w4nt_t0_p4y_my_tax3s}
flag{I_d0nt_w4nt_t0_p4y_my_tax3s}

Super Hash (Crypto)

10回md5を取ると、CD04302CBBD2E0EB259F53FAC7C57EE2になるものがフラグ。md5は英大文字にすることに気を付け、ブルートフォースする。

import itertools
import string
import hashlib

def ten_hash(s):
    res = s
    for i in range(10):
        res = hashlib.md5(res).hexdigest().upper()
    return res

h = 'CD04302CBBD2E0EB259F53FAC7C57EE2'

found = False
for size in range(1, 5):
    for c in itertools.product(string.printable, repeat=size):
        text = ''.join(c)
        if ten_hash(text) == h:
            found = True
            flag = 'flag{%s}' % text
            print flag
            break
    if found:
        break
flag{^}

Trinity (Crypto)

lowercaseで答えるということから、アルファベットの大文字と小文字の区別がないエンコード方式と推測できる。0を., 1を-, 2をスペースにしてモールス信号として変換する。

- . .-. -. .- .-. -.-- .. ... -- --- .-. . .- .-. -.-. .- -. . -... ..- - .. -... . - -.-- --- ..- - .... --- ..- --. .... - --- ..-. .. - ..-. .. .-. ... - 

https://morsecode.scphillips.com/translator.htmlで変換する。

TERNARYISMOREARCANEBUTIBETYOUTHOUGHTOFITFIRST

フラグは小文字で答える必要があるので、小文字にする。

ternaryismorearcanebutibetyouthoughtofitfirst

010000100110100101101110011000010111001001111001 (Crypto)

$ nc chall2.2019.redpwn.net 5001
011101010110110001110100011010010110110101100001011101000110010100100000011001010110111001100011011100100111100101110000011101000110100101101111011011100010000001110011011001010111001001110110011010010110001101100101

0101011101000001010100100100111001001001010011100100011100111010001000000100100100100000011011110110111001101100011110010010000001110011011000010111100100100000001100000010000001101111011100100010000000110001

0010100001001110001011000110010100101001: (110001100101111011100111001001001101011011100101000110010000001010101011010011110010111110011001010001000001001000011111101100000111000001000110101111111011100010001011110111000110101100000101001000000101111000111010100110000101111100111010010001100000100010010110011110001101001111110010110000100000111101100111100100001100101111000100111101001001010101001001100100110011001110011010000001100110100010001010101011011111001110000100100000011011110100101010110110101110000110111001010100000000010000001100011110100001101011110100100101001111010110111101100011011001111011010101100100010101011111010011011000101110010111000110001001111010010010100110111000100101000000010011000101110001011100100101100110101000111111011111000100101100100001101000101110000101001001111111111100110100010011110000011110100011111101100110000000000000111100100010101100010000101111100111101111101110111100111010000101100001101011101000111011000010100110000101011010011001000001110000011011100101100111001100000010101000101110101101001000010000101, 10000000000000001)

0100010101001110010000110101001001011001010100000101010001000101010001000010000001001101010001010101001101010011010000010100011101000101: 10010000010001011010010101101100000000110101011010010110011111001001111101111100100110110011011110010000111000001100110011110011011100101000001011001100101001111100011011010110101110100100111010000101001000111110101010110100101000111101000001101111010001100110000101010100000010001011011010000110010001111011000111010001100110110100000101100000101000001101010110010011011110001110111111001010011011010011100110100110100111100001011001011010100111111101100011110001000000010100001100100100100010101100110000000111011111100101100100000011110101110110000010101000001111100001110011010011100010110010010010110110000111100000000001011011110010110111000010110001101001011001001000110111110011101111000010011000010110100011000000010101100011010011110101010110110111100001101110011101111100101001000011101111000001110000001100000011001101010110011111010110011010111000101100010001101111011101111101110101100110010100010111100000000010001010110111101000000000110100101100001111000101000001000001100101011111100010101011111001100100

> 1
1
> 0
0

0, 1を一部ASCII文字にしてみる。

ultimate encryption service

WARNING: I only say 0 or 1

(N,e): (110001100101111011100111001001001101011011100101000110010000001010101011010011110010111110011001010001000001001000011111101100000111000001000110101111111011100010001011110111000110101100000101001000000101111000111010100110000101111100111010010001100000100010010110011110001101001111110010110000100000111101100111100100001100101111000100111101001001010101001001100100110011001110011010000001100110100010001010101011011111001110000100100000011011110100101010110110101110000110111001010100000000010000001100011110100001101011110100100101001111010110111101100011011001111011010101100100010101011111010011011000101110010111000110001001111010010010100110111000100101000000010011000101110001011100100101100110101000111111011111000100101100100001101000101110000101001001111111111100110100010011110000011110100011111101100110000000000000111100100010101100010000101111100111101111101110111100111010000101100001101011101000111011000010100110000101011010011001000001110000011011100101100111001100000010101000101110101101001000010000101, 10000000000000001)

ENCRYPTED MESSAGE: 10010000010001011010010101101100000000110101011010010110011111001001111101111100100110110011011110010000111000001100110011110011011100101000001011001100101001111100011011010110101110100100111010000101001000111110101010110100101000111101000001101111010001100110000101010100000010001011011010000110010001111011000111010001100110110100000101100000101000001101010110010011011110001110111111001010011011010011100110100110100111100001011001011010100111111101100011110001000000010100001100100100100010101100110000000111011111100101100100000011110101110110000010101000001111100001110011010011100010110010010010110110000111100000000001011011110010110111000010110001101001011001001000110111110011101111000010011000010110100011000000010101100011010011110101010110110111100001101110011101111100101001000011101111000001110000001100000011001101010110011111010110011010111000101100010001101111011101111101110101100110010100010111100000000010001010110111101000000000110100101100001111000101000001000001100101011111100010101011111001100100

RSA暗号で、この後0, 1で数値を指定すると、復号した結果の末尾を0, 1で返してくれると推測できる。LSB decryption oracle attackで復号する。

import socket
from fractions import Fraction
from Crypto.Util.number import long_to_bytes

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

def bin_2_ascii(b):
    a = ''
    for i in range(0, len(b), 8):
        a += chr(int(b[i:i+8], 2))
    return a

def lsb_oracle(s, enc):
    data = recvuntil(s, '> ')
    print data + enc
    s.sendall(enc + '\n')
    data = recvuntil(s, '\n').strip()
    print data
    return int(data)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('chall2.2019.redpwn.net', 5001))

pt = ''

data = recvuntil(s, '\n').rstrip()
print data
pt += bin_2_ascii(data) + '\n'

data = recvuntil(s, '\n').rstrip()
print data
pt += data + '\n'

data = recvuntil(s, '\n').rstrip()
print data
pt += bin_2_ascii(data) + '\n'

data = recvuntil(s, '\n').rstrip()
print data
pt += data + '\n'

data = recvuntil(s, '\n').rstrip()
print data
pt += bin_2_ascii(data.split(':')[0]) + ':' + data.split(':')[1] + '\n'

data = recvuntil(s, '\n').rstrip()
print data
pt += data + '\n'

data = recvuntil(s, '\n').rstrip()
print data
pt += bin_2_ascii(data.split(':')[0]) + ':' + data.split(':')[1] + '\n'

print pt

N = int(pt.split('\n')[-4].split(': (')[1].split(', ')[0], 2)
e = int(pt.split('\n')[-4].split(': (')[1].split(', ')[1][:-1], 2)
c = int(pt.split('\n')[-2].split(': ')[1], 2)
print 'N =', N
print 'e =', e
print 'c =', c

data = recvuntil(s, '\n').rstrip()
print data
pt += data + '\n'

bounds = [0, Fraction(N)]

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

    c2 = (c * pow(2, e, N)) % N
    lsb = lsb_oracle(s, bin(c2)[2:])
    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 = long_to_bytes(m)
print flag

実行結果は以下の通り。

    :
Round 1024
> 101100011100000111000110110010110111000101100011100100111111100000011010111100011011011011011110000100101011110111010100000101000100011010101111111011100000100101001111010000011010000011101011001010110010010010111001100110011100111000001101101100010101000111101011000011011010101001111110001101100011001111100110011101000100110000000100101100111101100000111100110110100010011000011011010110011110010010011011110100101110101000111110000000000111000101110111010010101011001100000011000001110001110000110111101010001101000000000111111001101001001101011101000111010101101001110111100111111110101111111101110000011001101110100111000100001100100000011110010011010001111110011011111111111110101101010111100111001101100110000001101110011111010100101110110000101101100101010001111110111111100010010000010010110101001101010110111001110100000010101111010001000000010001001011001100000010000000111011010111010111100111111111101101110100011001111111111111101100101000110011100001110100111100000010101110100001001011111111111011101000
1
flag{y0u_s0lved_th3_l3ast_s1gn1f1c1nt_RSA!-1123}
flag{y0u_s0lved_th3_l3ast_s1gn1f1c1nt_RSA!-1123}

Survey (Misc)

アンケートに答えたら、フラグが表示された。

flag{thks_for_playing}