BambooFox CTF Writeup

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

we1c0me (General)

リンク先の動画にフラグが書かれている。
f:id:satou-y:20200111153552p:plain

BAMBOOFOX{we1c0me_t0_B4mbooF0x_CTF}

How2decompyle (Reverse)

ファイルの種類を確認すると、Pythonバイトコードであるとわかるので、デコンパイルする。

$ file decompyle 
decompyle: python 2.7 byte-compiled
$ mv decompyle decompyle.pyc
$ uncompyle6 decompyle.pyc 
# uncompyle6 version 3.6.0
# Python bytecode 2.7 (62211)
# Decompiled from: Python 3.6.8 (default, Oct  9 2019, 14:04:01) 
# [GCC 5.4.0 20160609]
# Embedded file name: decompyle.py
# Compiled at: 2019-09-22 21:18:03
import string
restrictions = [
 'uudcjkllpuqngqwbujnbhobowpx_kdkp_',
 'f_negcqevyxmauuhthijbwhpjbvalnhnm',
 'dsafqqwxaqtstghrfbxzp_x_xo_kzqxck',
 'mdmqs_tfxbwisprcjutkrsogarmijtcls',
 'kvpsbdddqcyuzrgdomvnmlaymnlbegnur',
 'oykgmfa_cmroybxsgwktlzfitgagwxawu',
 'ewxbxogihhmknjcpbymdxqljvsspnvzfv',
 'izjwevjzooutelioqrbggatwkqfcuzwin',
 'xtbifb_vzsilvyjmyqsxdkrrqwyyiu_vb',
 'watartiplxa_ktzn_ouwzndcrfutffyzd',
 'rqzhdgfhdnbpmomakleqfpmxetpwpobgj',
 'qggdzxprwisr_vkkipgftuvhsizlc_pbz',
 'jerzhlnsegcaqzathfpuufwunakdtceqw',
 'lbvlyyrugffgrwo_v_zrqvqszchqrrljq',
 'aiwuuhzbszvfpidwwkl_wynlujbsbhfox',
 'vmhrizxtiegxdxsqcdoiyxkffloudwtxg',
 'tffjnabob_jbf_qiszdsemczghnjysmah',
 'zrqkppvynlkelnevngwlkhgaputhoagtt',
 'nl_oojyafwoqccbedijmigpedkdzglq_f',
 'cksy_skctjlyxktuzchvstunyvcvabomc',
 'ppcxleeguvhvhengmvac_bykhzqohjuei',
 '_clmaicjrrzhwd_fescyaejtbyefxyihy',
 'hhopvwsmjtpjiffzatyhjrev_dwnsidyo',
 'sjevtrmkkk_zjalxrxfovjsbcxjx_pskp',
 'gnynwuuqypddbsylparpcczqimimqmvdl',
 'bxitcmhnmanwuhvjxnqeoiimlegrmkjra']
capital = [
 0, 4, 9, 19, 23, 26]
flag = raw_input('Please tell me something : ').lower()
flag = flag.lower()
if len(flag) != len(restrictions[0]):
    print 'No......You are wrong orzzzzz'
    exit(0)
for f in range(len(flag)):
    for r in restrictions:
        if flag[f] not in string.lowercase + '_' or flag[f] == r[f]:
            print 'No......You are wrong orzzzzzzzzzzzz'
            exit(0)

cap_flag = ''
for f in range(len(flag)):
    if f in capital:
        cap_flag += flag[f].upper()
    else:
        cap_flag += flag[f]

print 'Yeah, you got it !\nBambooFox{' + cap_flag + '}\n'
# okay decompiling decompyle.pyc

以下の条件を満たすものを探す。

・flagはrestrictions[0]と同じ長さ
・以下のいずれか
 ・flagは英小文字や_でない。
 ・restrictionsの各バイト目で使われていない。
import string

def check_no_char(s):
    chars = string.lowercase + '_'
    for c in chars:
        if c not in s:
            return c
    return ''

restrictions = [
    'uudcjkllpuqngqwbujnbhobowpx_kdkp_',
    'f_negcqevyxmauuhthijbwhpjbvalnhnm',
    'dsafqqwxaqtstghrfbxzp_x_xo_kzqxck',
    'mdmqs_tfxbwisprcjutkrsogarmijtcls',
    'kvpsbdddqcyuzrgdomvnmlaymnlbegnur',
    'oykgmfa_cmroybxsgwktlzfitgagwxawu',
    'ewxbxogihhmknjcpbymdxqljvsspnvzfv',
    'izjwevjzooutelioqrbggatwkqfcuzwin',
    'xtbifb_vzsilvyjmyqsxdkrrqwyyiu_vb',
    'watartiplxa_ktzn_ouwzndcrfutffyzd',
    'rqzhdgfhdnbpmomakleqfpmxetpwpobgj',
    'qggdzxprwisr_vkkipgftuvhsizlc_pbz',
    'jerzhlnsegcaqzathfpuufwunakdtceqw',
    'lbvlyyrugffgrwo_v_zrqvqszchqrrljq',
    'aiwuuhzbszvfpidwwkl_wynlujbsbhfox',
    'vmhrizxtiegxdxsqcdoiyxkffloudwtxg',
    'tffjnabob_jbf_qiszdsemczghnjysmah',
    'zrqkppvynlkelnevngwlkhgaputhoagtt',
    'nl_oojyafwoqccbedijmigpedkdzglq_f',
    'cksy_skctjlyxktuzchvstunyvcvabomc',
    'ppcxleeguvhvhengmvac_bykhzqohjuei',
    '_clmaicjrrzhwd_fescyaejtbyefxyihy',
    'hhopvwsmjtpjiffzatyhjrev_dwnsidyo',
    'sjevtrmkkk_zjalxrxfovjsbcxjx_pskp',
    'gnynwuuqypddbsylparpcczqimimqmvdl',
    'bxitcmhnmanwuhvjxnqeoiimlegrmkjra']
capital = [0, 4, 9, 19, 23, 26]

flag = ''
for i in range(len(restrictions[0])):
    s = ''
    for j in range(len(restrictions)):
        s += restrictions[j][i]
    flag += check_no_char(s)

cap_flag = ''
for f in range(len(flag)):
    if f in capital:
        cap_flag += flag[f].upper()
    else:
        cap_flag += flag[f]

print 'Yeah, you got it !\nBambooFox{' + cap_flag + '}\n'
BambooFox{You_Know_Decompyle_And_Do_Reverse}

I can't see you! (Misc)

rarファイルにはパスワードがかかっている。johnでクラックする。

$ rar2john what.rar > hash.txt
$ john --wordlist=dict/rockyou.txt hash.txt --rules
Loaded 1 password hash (RAR5 [PBKDF2-SHA256 128/128 SSE4.1 4x])
Warning: OpenMP is disabled; a non-OpenMP build may be faster
Press 'q' or Ctrl-C to abort, almost any other key for status
blind            (what.rar)
1g 0:00:03:02 DONE (2019-12-31 21:06) 0.005474g/s 105.6p/s 105.6c/s 105.6C/s bre123..benten
Use the "--show" option to display all of the cracked passwords reliably
Session completed

パスワードblindで解凍すると、点字の画像が展開される。

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

点字を文字にデコードする。

BAMBOOFOX
{ YA_YOU_
KNOW_WHAT_
BLIND_
MEANS }
BAMBOOFOX{YA_YOU_KNOW_WHAT_BLIND_MEANS}

Find the Cat (Misc)

$ binwalk cat.png

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 739 x 554, 8-bit/color RGBA, non-interlaced
101           0x65            Zlib compressed data, best compression
371382        0x5AAB6         PNG image, 739 x 554, 8-bit/color RGBA, non-interlaced
371483        0x5AB1B         Zlib compressed data, best compression

後ろにPNGがもう一つ付いている。

$ foremost cat.png
Processing: cat.png
|*|

似たような画像がもう一つ入っていた。差分を白黒で画像にしてみる。

from PIL import Image

img1 = Image.open('00000000.png').convert('RGB')
img2 = Image.open('00000725.png').convert('RGB')
w, h = img1.size

output_img = Image.new('RGB', (w, h), (255, 255, 255))

for y in range(0, h):
    for x in range(0, w):
        r1, g1, b1 = img1.getpixel((x, y))
        r2, g2, b2 = img2.getpixel((x, y))

        if r1 == r2 and g1 == g2 and b1 == b2:
            output_img.putpixel((x, y), (255, 255, 255))
        else:
            output_img.putpixel((x, y), (0, 0, 0))

output_img.save('diff.png')

f:id:satou-y:20200111154552p:plain
QRコードになったので、読み取る。

https://imgur.com/download/Xrv86y2

このURLにアクセスすると、Xrv86y2 - Imgur.jpgがダウンロードできた。バイナリエディタで見るとPrintableな文字ばかり含まれているので、BAMBOOFOXで検索してみると、フラグが見つかった。
f:id:satou-y:20200111154629p:plain

BAMBOOFOX{Y0u_f1nd_th3_h1dd3n_c4t!!!}

oracle (Crypto)

$ nc 34.82.101.212 20001
1) Info
2) Decrypt
3) Exit
> 1
c = 3707379833111040015783163740193874788414803250531435971653144043446898319590407294871874573636048964074059158735677108453718201912319567996556576283315815748346821418011805401072680103982963210417620997185812458739089731859722602065043829112903847812848345496034190632584920309575488320362226972676630275524
n = 96194794049596317702519938191271814417816716400282810425930600761352066370293757401367088699070463576925336421861089418264302022319363405255438953242163929922974851426935044638903429374717691402537243254925572965917089355609141962782853759805533770299159601171220779548891129054688964114370404823325735253993
1) Info
2) Decrypt
3) Exit
> 2
c = 123
m = 1
1) Info
2) Decrypt
3) Exit
> 3

復号した結果を3で割った数がわかる。2で割った数がわかる場合は通常のRSA LSB Decryption Oracle Attackで解けるが、そのままでは解けないので、RSA LSB Decryption Oracle Attackの原理を考え、スクリプトをカスタマイズする。

fを復号関数とする。
a = 3^e mod n
-> f(a^i * c) = (3^i * m mod n) mod 3

i = 1の場合、f(a * c) = (3*m mod n) mod 3

以下の条件を満たす
・3m < 3n
・3m / n = 0 or 1 or 2

★n % 3 == 1

☆f(ac) = 2
 3*m mod n ⇒ n < 3*m < 2*n
 ⇒ n/3 < m < n*2/3

☆f(ac) = 1
 3*m mod n ⇒ 2*n < 3*m < 3*n
 ⇒ n*2/3 < m < n

☆f(ac) = 0
 3*m mod n ⇒ 0 < 3*m < n
 ⇒ 0 < m < n/3

★n % 3 == 2

☆f(ac) = 2
 3*m mod n ⇒ 2*n < 3*m < 3*n
 ⇒ n*2/3 < m < n

☆f(ac) = 1
 3*m mod n ⇒ n < 3*m < 2*n
 ⇒ n/3 < m < n*2/3

☆f(ac) = 0
 3*m mod n ⇒ 0 < 3*m < n
 ⇒ 0 < m < n/3
from fractions import Fraction
from Crypto.Util.number import *
import socket

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

def remainder_oracle(enc):
    data = recvuntil(s, '>')
    print data + '2'
    s.sendall('2\n')
    data = recvuntil(s, '= ')
    print data + str(enc)
    s.sendall(str(enc) + '\n')
    data = recvuntil(s, '\n').rstrip()
    print data
    m = int(data.split(' = ')[1])
    return m

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('34.82.101.212', 20001))

data = recvuntil(s, '>')
print data + '1'
s.sendall('1\n')
data = recvuntil(s, '\n').rstrip()
print data
c = int(data.split(' = ')[1])
data = recvuntil(s, '\n').rstrip()
print data
n = int(data.split(' = ')[1])
e = 65537

bounds = [0, Fraction(n)]

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

    c2 = (c * pow(3, e, n)) % n
    remainder = remainder_oracle(c2)

    diff = bounds[1] -  bounds[0]
    if remainder == 2:
        if n % 3 == 1:
            bounds[0] = bounds[0] + diff/3
            bounds[1] = bounds[1] - diff/3
        elif n % 3 == 2:
            bounds[0] = bounds[1] - diff/3
    elif remainder == 1:
        if n % 3 == 1:
            bounds[0] = bounds[1] - diff/3
        elif n % 3 == 2:
            bounds[0] = bounds[0] + diff/3
            bounds[1] = bounds[1] - diff/3
    else:
        bounds[1] = bounds[0] + diff/3
    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
BAMBOOFOX{SimPlE0RACl3}