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}

HITB CTF Singapore 2017 Writeup

この大会は2017/8/24 11:00(JST)~2017/8/25 17:00(JST)に開催されました。
今回もチームで参戦。結果は803点で185チーム中65位でした。
平日開催のなので、それほど時間を取れず、あまり取り組めなかったのが残念。
自分で解けた問題をWriteupとして書いておきます。

Cephalopod (Misc)

No.75, 77, 115のパケットにflag.pngの文字あり。PNGやENDで検索すると、No.308のパケットにPNGフォーマットがあることがわかる。このパケットのOperation Payloadをエクスポートすると、flag.pngが得られ、フラグが書いてある。
f:id:satou-y:20170827203519p:plain

HITB{95700d8aefdc1648b90a92f3a8460a2c}

SHA2017 CTF Writeup

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

Stack Overflow (Crypto 100)

添付のPythonコードを見ると、カウンタの値が固定。CTRモードの場合、ナンス+カウンタを暗号鍵で暗号化し、平文とXORを取るが、この問題ではナンス+カウンタが固定。つまり、暗号化したタイミングでは16バイトのブロックごとに同じ値になる。
16バイトの不明なXOR鍵で暗号化されているので、PDFのファイルフォーマットと照らし合わせながら、鍵を突き止め、復号する。

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

PDF_0_6 = '%PDF-1.'
PDF_12_15 = '\x0a%%E'
PDF_11 = ' '
PDF_07_10 = 'obj\x0a'

key_0_6 = []
for i in range(len(PDF_0_6)):
    code = ord(PDF_0_6[i]) ^ ord(data[i])
    key_0_6.append(code)

key_12_15 = []
for i in range(len(PDF_12_15)):
    code = ord(PDF_12_15[i]) ^ ord(data[i-7])
    key_12_15.append(code)

key_11 = []
for i in range(len(PDF_11)):
    code = ord(PDF_11[i]) ^ ord(data[i+11])
    key_11.append(code)

key_07_10 = []
for i in range(len(PDF_07_10)):
    code = ord(PDF_07_10[i]) ^ ord(data[i+55])
    key_07_10.append(code)

key = []
for i in range(len(key_0_6)):
    key.append(key_0_6[i])
for i in range(len(key_07_10)):
    key.append(key_07_10[i])
for i in range(len(key_11)):
    key.append(key_11[i])
for i in range(len(key_12_15)):
    key.append(key_12_15[i])

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

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

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

FLAG{15AD69452103C5DF1CF61E9D98893492}

bugs_bunny ctf 2k17 Writeup

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

Not found (Misc 1)

IRCのページからログインしてみる。

[04:19] -ChanServ- [#bugsbunnyctf] Bugs_Bunny{Th1s_1s_0ur_fl4g_f0rm4t}
Bugs_Bunny{Th1s_1s_0ur_fl4g_f0rm4t}

Locked PDF (Misc 40)

pdfcrackでパスワードを解析する。
辞書ファイルは https://wiki.skullsecurity.org/index.php?title=Passwords にあるものを使う。

$ pdfcrack -f Patricia.pdf -w dict/rockyou.txt 

PDF version 1.7
Security Handler: Standard
V: 4
R: 4
P: -516
Length: 128
Encrypted Metadata: False
FileID: 104c940ca5734c71b2e5fa3b26414f5e
U: 857e6a5a82ab5bc8fc9f9d4dfe5ba381684523c72e38fdcf6ec068ae127dc499
O: 7337f340e3f15670a5a5bc1f8a217a0714538ebf23a1a4a15206a4c0c0adff51
Average Speed: 34524.2 w/s. Current Word: 'belky'
Average Speed: 34157.8 w/s. Current Word: 'puba23'
found user-password: 'g00skie'

見つかったパスワード g00skie でPDFを開くと、薄い字だがフラグが見つかった。

Bugs_Bunny{Pdf_Cr4Ck1nG_1s_Ea5y}

Crypto-15 (Crypto 15)

シーザー暗号と推定し、http://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipherでフラグ部分を復号する。ROT1で復号できた。

Bugs_Bunny{C35aR_3NC0D3_4R3_N0T_S3CuR3_AT_4LL}

Crypto-20 (Crypto 20)

Brainf*ck言語。オンラインツール https://sange.fi/esoteric/brainfuck/impl/interp/i.html で実行すると、フラグが表示された。

Bugs_Bunny{Br41N_Fu**}

Scy way (Crypto 45)

タイトルからスキュタレー暗号を推測。http://www.dcode.fr/scytale-cipherブルートフォース
ターン数が2のときに、ISHOULDLEARNMORECIPHERとなり、英文として読める。

Bugs_Bunny{ISHOULDLEARNMORECIPHER}

Crypto-50 (Crypto 50)

かなり長いBase64文字列データ。Base64デコードを繰り返せば良さそう。

with open('enc.txt', 'r') as f:
    data = f.read()

for i in range(50):
    print 'Round %d' % (i+1)
    data = data.decode('base64')
    print data

36回デコードすると、フラグを得ることができた。

Bugs_Bunny{N0T_H4Rd_4T_4ll}

Baby RSA (Crypto 55)

nをfactordbで素因数分解する。

n = 2165121523231 * 9456131321351327

enc.txtの中身は複数行の数値。それぞれ復号した後、ASCIIコードとして文字に置き換える。

n = 20473673450356553867543177537
e = 17
p = 2165121523231
q = 9456131321351327

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

with open('enc.txt', 'r') as f:
    c_list = f.readlines()

flag = ''
for c_str in c_list:
    c = int(c_str.strip())

    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 += chr(m)

print flag
Bugs_Bunny{Baby_RSA_Its_Cool_Lik3_school_haHAha}

RSA2 (Crypto 80)

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

c = 0x217c8bf9b45601267624c3b1ba89ae93d04c8fae32dc15496262f36f48d06c0dc9e178a77b77a33708dcbe1fcd55ea9eb636fe5684c2f0f08df3389f47b36a128636671eba300491c829ed1e252b1bb4dbb3b93bc46d98a10bb5d55347752052ab45e143fd46799be1d06ac3ff7e8b1eb181dfbba8dfac3910202fd0b9a25befe
e = 266524484526673326121255015126836087453426858655909092116029065652649301962338744664679734617977550306567819672969837450223062478394149960243362563995235387971047857994699247277712682103161537347874310994510059329875060868679654080020041070975648626636209785889112656335054840517934593236597457100751820027783
n = 412460203584740978970185080155274765823237615982150661072746604041385717906706098256415230390148737678989448404730885157667896599397615737297545930957425615121654272472589331747646564634264520011009284080299605233265170506809736069720838542498970453928922703911186788239628906189362646418960560442406497717567

p, q = wiener(n, e)

flag = decrypt(p, q, e, c)
print flag
Bugs_Bunny{Baby_Its_Cool_Lik3_school_haHAha}

Nothing here (Web 5)

ソースのコメントにこう書いてある。

QnVnc19CdW5ueXs1MjljNDI5YWJkZTIxNzFkMGEyNTU4NDQ3MmFmODIxN30K

Base64デコードする。

$ echo QnVnc19CdW5ueXs1MjljNDI5YWJkZTIxNzFkMGEyNTU4NDQ3MmFmODIxN30K | base64 -d
Bugs_Bunny{529c429abde2171d0a25584472af8217}
Bugs_Bunny{529c429abde2171d0a25584472af8217}

Web100 (Web 100)

HTMLソースはスクリプトのみで、以下の通り。

<script type="text/javascript">
var generate = function(string) {

    function RT(lValue, iShiftBits) {
        return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
    }

    function AU(lX, lY) {
        var lX4, lY4, lX8, lY8, lResult;
        lX8 = (lX & 0x80000000);
        lY8 = (lY & 0x80000000);
        lX4 = (lX & 0x40000000);
        lY4 = (lY & 0x40000000);
        lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
        if (lX4 & lY4) {
            return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
        }
        if (lX4 | lY4) {
            if (lResult & 0x40000000) {
                return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
            } else {
                return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
            }
        } else {
            return (lResult ^ lX8 ^ lY8);
        }
    }
    function F(x, y, z) { return (x & y) | ((~x) & z); }
    function G(x, y, z) { return (x & z) | (y & (~z)); }
    function H(x, y, z) { return (x ^ y ^ z); }
    function I(x, y, z) { return (y ^ (x | (~z))); }

    function FF(a, b, c, d, x, s, ac) {
        a = AU(a, AU(AU(F(b, c, d), x), ac));
        return AU(RT(a, s), b);
    };

    function GG(a, b, c, d, x, s, ac) {
        a = AU(a, AU(AU(G(b, c, d), x), ac));
        return AU(RT(a, s), b);
    };

    function HH(a, b, c, d, x, s, ac) {
        a = AU(a, AU(AU(H(b, c, d), x), ac));
        return AU(RT(a, s), b);
    };

    function II(a, b, c, d, x, s, ac) {
        a = AU(a, AU(AU(I(b, c, d), x), ac));
        return AU(RT(a, s), b);
    };

    function CTWA(bytes) {
        var lWordCount;
        var lMessageLength = bytes.length;
        var lNumberOfWords_temp1 = lMessageLength + 8;
        var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
        var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
        var lWordArray = Array(lNumberOfWords - 1);
        var lBytePosition = 0;
        var lByteCount = 0;
        while (lByteCount < lMessageLength) {
            lWordCount = (lByteCount - (lByteCount % 4)) / 4;
            lBytePosition = (lByteCount % 4) * 8;
            lWordArray[lWordCount] = (lWordArray[lWordCount] | (bytes[lByteCount] << lBytePosition));
            lByteCount++;
        }
        lWordCount = (lByteCount - (lByteCount % 4)) / 4;
        lBytePosition = (lByteCount % 4) * 8;
        lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
        lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
        lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
        return lWordArray;
    };

    function WordToHex(lValue) {
        var WordToHexValue = "", WordToHexValue_temp = "", lByte, lCount;
        for (lCount = 0; lCount <= 3; lCount++) {
            lByte = (lValue >>> (lCount * 8)) & 255;
            WordToHexValue_temp = "0" + lByte.toString(16);
            WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
        }
        return WordToHexValue;
    };

    function Utf8Encode(string) {
        string = string.replace(/\r\n/g, "\n");
        var result = Array();

        for (var n = 0; n < string.length; n++) {

            var c = string.charCodeAt(n);

            if (c < 128) {
                result.push(c);
            }
            else if ((c > 127) && (c < 2048)) {
                result.push((c >> 6) | 192);
                result.push((c & 63) | 128);
            }
            else {
                result.push((c >> 12) | 224);
                result.push(((c >> 6) & 63) | 128);
                result.push((c & 63) | 128);
            }
        }
        return result;
    };

    var x = Array();
    var k, AA, BB, CC, DD, a, b, c, d;
    var S11 = 7, S12 = 12, S13 = 17, S14 = 22;
    var S21 = 5, S22 = 9, S23 = 14, S24 = 20;
    var S31 = 4, S32 = 11, S33 = 16, S34 = 23;
    var S41 = 6, S42 = 10, S43 = 15, S44 = 21;

    var bytes = Utf8Encode(string);
    x = CTWA(bytes);

    a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;

    for (k = 0; k < x.length; k += 16) {
        AA = a; BB = b; CC = c; DD = d;
        a = FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
        d = FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
        c = FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
        b = FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
        a = FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
        d = FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
        c = FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
        b = FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
        a = FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
        d = FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
        c = FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
        b = FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
        a = FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
        d = FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
        c = FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
        b = FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
        a = GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
        d = GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
        c = GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
        b = GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
        a = GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
        d = GG(d, a, b, c, x[k + 10], S22, 0x2441453);
        c = GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
        b = GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
        a = GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
        d = GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
        c = GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
        b = GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
        a = GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
        d = GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
        c = GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
        b = GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
        a = HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
        d = HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
        c = HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
        b = HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
        a = HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
        d = HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
        c = HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
        b = HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
        a = HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
        d = HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
        c = HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
        b = HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
        a = HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
        d = HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
        c = HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
        b = HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
        a = II(a, b, c, d, x[k + 0], S41, 0xF4292244);
        d = II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
        c = II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
        b = II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
        a = II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
        d = II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
        c = II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
        b = II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
        a = II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
        d = II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
        c = II(c, d, a, b, x[k + 6], S43, 0xA3014314);
        b = II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
        a = II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
        d = II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
        c = II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
        b = II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
        a = AU(a, AA);
        b = AU(b, BB);
        c = AU(c, CC);
        d = AU(d, DD);
    }

    var temp = WordToHex(a) + WordToHex(b) + WordToHex(c) + WordToHex(d);

    return temp.toLowerCase();
}
__seceret = '622b010e27e3f82d0f4e2e69a3785a395767c7a39599aea7114553448239eb41cab90bfecd4a8a0881d0a8128f27c483';
var _=__=___='';
for (var i = 0; i < __seceret.length; i+=3) {
   _+=__seceret[i+0]; 
   __+=__seceret[i+1];
   ___+=__seceret[i+2];
}
var h = prompt("Please enter your passowrd");
if(generate(h[11]+h[8]+h[1]+h[0]+h[9]+h[4]+h[13])==_&&generate(h[15]+h[10]+h[3]+h[5]+h[6])==__&&generate(h[16]+h[12]+h[14]+h[2]+h[7])==___){
    alert('your flag is Bugs_Bunny{'+h+'}');
}else{
    alert('I\'m sorry my son it\' not easy');
}
</script>

Chromeデベロッパーツールを使いながら、変数の値を確認する。

_ = "6b07fd4ea837c39e1542e1bbca01a224"
__ = "20ee80e63596799a1543bc9fd88d8878"
___ = "21232f297a57a5a743894a0e4a801fc3"

またgenerateの動作を確認する。

generate('a')
→"0cc175b9c0f1b6a831c399e269772661"
generate('b')
→"92eb5ffee6ae2fec3ad71c777531578f"
generate('c')
→"4a8a08f09d37b73795649038408b5f33"

この結果からgenerateはmd5を算出していると考えられる。フラグを表示させる条件を考えると、以下のようになっている必要がある。

h[11]+h[8]+h[1]+h[0]+h[9]+h[4]+h[13] = "6b07fd4ea837c39e1542e1bbca01a224" のMD5逆変換
h[15]+h[10]+h[3]+h[5]+h[6] = "20ee80e63596799a1543bc9fd88d8878" のMD5逆変換
h[16]+h[12]+h[14]+h[2]+h[7] = "21232f297a57a5a743894a0e4a801fc3" のMD5逆変換

つまり以下のようになる。

h[11]+h[8]+h[1]+h[0]+h[9]+h[4]+h[13] = 'tunisia'
h[15]+h[10]+h[3]+h[5]+h[6] = 'bunny'
h[16]+h[12]+h[14]+h[2]+h[7] = 'admin'

このことから h = 'inininynusutdamba' であることがわかる。

Bugs_Bunny{inininynusutdamba}

Stego10 (Steganography 10)

EXIFのCommentにフラグあり。

Bugs_Bunny{0258c4a75fc36076b41d02df8074372b}

For25 (Forensics 25)

xxdの情報が書いてあるので、次のコマンドでバイナリに戻す。

$ xxd -r hex > hex.zip

zip解凍するとhex.pngが展開され、フラグが書かれていた。
f:id:satou-y:20170801200957p:plain

Bugs_Bunny{Y0u_D1D_1T_W3ll}

UNKOWN file !! (Forensics 30)

pngファイルのバイナリが逆転しているので、逆にして画像にする。

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

with open('flag.png', 'wb') as f:
    f.write(data[::-1])

結果のPNGファイルを見ると、フラグが逆に書いてある。
f:id:satou-y:20170801201335p:plain

Bugs_Bunny{E4Sy_T4Sk_F0R_H4X0r_L1KeS_Y0u}

Lost data (Forensics 50)

バイナリエディタでファイルを見てみると、flag.arjの文字列が含まれていることがわかる。このファイルはARJ形式の圧縮ファイルが壊れているものであると推定する。ARJ形式の圧縮ファイルは先頭2バイトが60 EAであると決められているので、先頭2バイトを 60 EA に変更する。この変更後の圧縮ファイルを解凍すると、flag.pngが入っていて、フラグが書かれていた。
f:id:satou-y:20170801201826p:plain

Bugs_Bunny{r3m3mb3r_4ll_w4ys_t0_ch3ck_h34d3r_f1l3}

Give me the Flag ! (Forensics 85)

旗の画像の中にQRコードの破片の画像が紛れ込んでいる。
QRコードを組み立て、読み込む。

== 34Sy_P4SSW0Rd_H4X0r ==

読み込んだ文字列をパスワードとして、flag.zipを解凍する。展開したReadmeの中に2進数8桁の文字列がスペース区切りでたくさん書いてある。これをASCIIコードとして読む。

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

codes = data.split(' ')
flag = ''
for code in codes:
    if code != '':
        flag += chr(int(code, 2))

print flag
Bugs_Bunny{2b97263beb70d0f659bdb93cc5291d0a}

ZERO-ONE ! (Programation 45)

ZEROを0、ONEを1に変換し、2進数をASCIIコードとして読む。BASE64エンコード文字列になるので、デコードする。

with open('progTask.txt', 'r') as f:
    data = f.read()

data = data.replace('ZERO ', '0')
data = data.replace('ONE ', '1')

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

flag = b64_flag.decode('base64')
print flag
Bugs_Bunny{05fe8238cfee1e5f04b65339bea4fed2}

Capital (Programation 80)

$ nc 34.253.165.46 11223
Hi, do YOU love math ?!?!
Level 1.: Alaska
Juneau
Great, keep it up
Level 2.: x + 4 = 1119
1115
Great, keep it up

州都を答える問題か方程式を解く問題かどちからかが出題される。州都は対応表を作って、州に対応する州都を取得し、方程式は四則演算の左側がxである前提で計算し、答えていく。

import socket
import re

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

pattern = 'Level (.+)\.: (.+)'

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('34.253.165.46', 11223))

data = s.recv(256)
print data

for i in range(1, 10000):
    data = s.recv(256)
    print data
    m = re.match(pattern, data)
    q = m.group(2)
    if capitals.has_key(q):
        ans = capitals[q]
    else:
        ope = q.split(' ')[1]
        val1 = int(q.split(' ')[2])
        val2 = int(q.split(' ')[4])
        if ope == '+':
            ans = str(val2 - val1)
        elif ope == '-':
            ans = str(val2 + val1)
        elif ope == '*':
            ans = str(val2 / val1)
        elif ope == '/':
            ans = str(val2 * val1)
        else:
            ans = ''
    print ans
    s.sendall(ans + '\n')
    data = s.recv(256)
    print data

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

Bugs_Bunny{M4TH_LO0k!_HarD_But_s0_EA5Y}

CTFZone 2017 Writeup

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

MPRSA (CRYPTO)

Multi-Prime RSAの問題。まず素因数分解したいが、factordbではできなかった。
暗号化のプログラムから、deltaの値(5~15)は総当たりで素数の構成を作り、nと同じ値になるものを探す。数時間かかったが、次のようなプログラムで素因数を割り出すことができた。ちなみに delta=14の場合に該当するnが見つかった。

from gmpy2 import next_prime

def compute_module(primes):
    n = 1
    for prime in primes:
        n *= prime
    return n

with open('public.txt', 'r') as f:
    data = f.read()

n = int(data.split('\n')[1].split(' = ')[1])

delta = 14
p = 177739015005173300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

adjust = pow(2, 1024)
p0 = p
while True:
    #print p
    P = [next_prime(p)]
    for i in range(1, 4):
        P.append(next_prime(P[i-1] * delta))
    mul = compute_module(P)
    print mul
    if mul == n:
        print 'P[0] =', P[0]
        print 'P[1] =', P[1]
        print 'P[2] =', P[2]
        print 'P[3] =', P[3]
        break
    else:
        diff = n - mul
        if diff < 0:
            if adjust == 1:
                print 'Not Found (delta = %d)' % delta
                break
            else:
                adjust /= 2
                p = p0
        else:
            p0 = p
            p = next_prime(p + adjust)

素因数分解の結果は以下の通り。

P[0] = 177739015005173306533752250332928388271426563815093455666936081629571193511808970499241559574326686383600364964248435983786753776661738101198468295487687752622214814554023747926012377837938347566934895426939099916675611018615527164379921811922640194594903892753383803569371101697376356302460080232868430713251
P[1] = 2488346210072426291472531504660997435799971893411308379337105142813996709165325586989381834040573609370405109499478103773014552873264333416778556136827628536711007403756332470964173289731136865937088535977147398833458554260617380301318905366916962724328654498547373249971195423763268988234441123260158029986729
P[2] = 34836846941013968080615441065253964101199606507758317310719471999395953928314558217851345676568030531185671532992693452822203740225700667834899785915586799513954103652588654593498426056235916123119239503680063583668419759648643324218464675136837478140601162979663225499596735932685765835282175725642212419814243
P[3] = 487715857174195553128616174913555497416794491108616442350072607991543354996403815049918839471952427436599401461897708339510852363159809349688597002818215193195357451136241164308977964787302825723669353051520890171357876635081006539058505451915724693968416281715285156994354303057600721693950460158990973877400281

この結果から、Muti-Prime RSAの復号プログラムを書く。

import gmpy

def chinese_remainder(n, a):
    sum = 0
    prod = reduce(lambda a, b: a*b, n)

    for n_i, a_i in zip(n, a):
        p = prod / n_i
        sum += a_i * gmpy.invert(p, n_i) * p
    return sum % prod

with open('data.enc', 'r') as f:
    c = int(f.read())

with open('public.txt', 'r') as f:
    pub = f.read()

e = int(pub.split('\n')[0].split(' = ')[1])
n = int(pub.split('\n')[1].split(' = ')[1])

primes = [
    177739015005173306533752250332928388271426563815093455666936081629571193511808970499241559574326686383600364964248435983786753776661738101198468295487687752622214814554023747926012377837938347566934895426939099916675611018615527164379921811922640194594903892753383803569371101697376356302460080232868430713251,
    2488346210072426291472531504660997435799971893411308379337105142813996709165325586989381834040573609370405109499478103773014552873264333416778556136827628536711007403756332470964173289731136865937088535977147398833458554260617380301318905366916962724328654498547373249971195423763268988234441123260158029986729,
    34836846941013968080615441065253964101199606507758317310719471999395953928314558217851345676568030531185671532992693452822203740225700667834899785915586799513954103652588654593498426056235916123119239503680063583668419759648643324218464675136837478140601162979663225499596735932685765835282175725642212419814243,
    487715857174195553128616174913555497416794491108616442350072607991543354996403815049918839471952427436599401461897708339510852363159809349688597002818215193195357451136241164308977964787302825723669353051520890171357876635081006539058505451915724693968416281715285156994354303057600721693950460158990973877400281]

n_ary = []
a_ary = []
for p in primes:
    phi = p - 1
    d = gmpy.invert(e, phi)
    mk = pow(c, d, p)
    n_ary.append(p)
    a_ary.append(mk)

m = chinese_remainder(n_ary, a_ary)
flag = ('%x' % m).decode('hex')
print flag

実行結果は次の通り。

Mr.D (12:10):
Okey, see you later ;)

Mr.D (19:30):
So can you help me?

Anonymous (19:31):
Yeah, we will have 10,000 falsified voters. Transfer 100000$ to my bank account: ctfzone{3177809746931830}
ctfzone{3177809746931830}

MeePwn CTF 1st 2017 Writeup

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

nub_cryptosystem (CRYPTO)

暗号化のコードが与えられているので、読んでいくとMerkle-Hellmanナップサック暗号であることがわかる。この場合、解読できる条件は秘密鍵が超増加列になっていることだが、これも当然のごとくあてはまる。ということで、復号プログラムを書く。

def load_data():
    with open('pubkey.txt', 'r') as f:
        pub_keys = f.read().strip()[1:-1].split(', ')

    with open('enc.txt', 'r') as f:
        ciphertext = int(long(f.read().strip()))

    return pub_keys, ciphertext

def create_matrix_from_knapsack(ciphertext, pub_keys):
    last_col = []
    for p in pub_keys:
        last_col.append(int(long(p)))

    last_col.append(ciphertext)
    last_row = [1 for i in pub_keys]

    my_matrix = MatrixSpace(ZZ, len(pub_keys))(2)
    m_last_row = matrix(ZZ, 1, len(last_row), last_row)
    m_last_col = matrix(ZZ, len(last_col), 1, last_col)

    my_matrix = my_matrix.stack(m_last_row)
    my_matrix = my_matrix.augment(m_last_col)

    return my_matrix

def is_short_vector(vector):
    for v in vector:
        if v != 1 and v != -1 and v != 0:
            return False
    return True

def find_short_vector(matrix):
    for row in matrix:
        if is_short_vector(row):
            return row

def main():
    pub_keys, cipher = load_data()
    my_matrix = create_matrix_from_knapsack(cipher, pub_keys)

    new_matrix = my_matrix.LLL()

    short_vector = find_short_vector(new_matrix)

    bin_flag = ''
    for v in short_vector:
        if v == 1:
            bin_flag += '0'
        elif v == -1:
            bin_flag += '1'

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

    flag = 'MeePwnCTF{' + flag + '}'
    print flag

if __name__ == '__main__':
    main()

これで復号でき、フラグが得られた。

MeePwnCTF{Merkleee-Hellmannn!}