yoshikingのがんばる日記

あたまよくないけどがんばります

CyBRICS CTF Quals 2019 Writeup

CyBRICS CTFが7月20~21日に開催されていたので、zer0ptsとして参加しました. 結果は69位で、解いた問題は3つでした.

チームメンバーのWriteup

st98: CyBRICS CTF Quals 2019 の write-up - st98 の日記帳

ptr-yudai: CyBRICS CTF 2019 Writeup - CTFするぞ

[Rev 10pts] Oldman Reverse

以下のアセンブリファイルが渡されます.

.MCALL  .TTYOUT,.EXIT
START:
    mov   #MSG r1 
    mov #0d r2
    mov #32d r3
loop:       
    mov   #MSG r1 
    add r2 r1
    movb    (r1) r0
    .TTYOUT
    sub #1d r3
    cmp #0 r3
    beq     DONE
    add #33d r2
    swab r2
    clrb r2
    swab r2    
    br      loop      
DONE: 
    .EXIT

MSG:
    .ascii "cp33AI9~p78f8h1UcspOtKMQbxSKdq~^0yANxbnN)d}k&6eUNr66UK7Hsk_uFSb5#9b&PjV5_8phe7C#CLc#<QSr0sb6{%NC8G|ra!YJyaG_~RfV3sw_&SW~}((_1>rh0dMzi><i6)wPgxiCzJJVd8CsGkT^p>_KXGxv1cIs1q(QwpnONOU9PtP35JJ5<hlsThB{uCs4knEJxGgzpI&u)1d{4<098KpXrLko{Tn{gY<|EjH_ez{z)j)_3t(|13Y}"
.end START

r3はループ回数に使われてそうで、r1, r2はMSGをごちゃごちゃしてるな~という認識です. 33をr2に足しているので、1文字目、34文字目、67文字目、....とMSGを参照してそうです. あとは、文字列の長さを伸ばしてあげて、pythonでやってあげます.

>>> msg = "cp33AI9~p78f8h1UcspOtKMQbxSKdq~^0yANxbnN)d}k&6eUNr66UK7Hsk_uFSb5#9b&PjV5_8phe7C#CLc#<QSr0sb6{%NC8G|ra!YJyaG_~RfV3sw_&SW~}((_1>rh0dMzi><i6)wPgxiCzJJVd8CsGkT^p>_KXGxv1cIs1q(QwpnONOU9PtP35JJ5<hlsThB{uCs4knEJxGgzpI&u)1d{4<098KpXrLko{Tn{gY<|EjH_ez{z)j)_3t(|13Y}"
>>> (msg*5)[::33]
'cybrics{pdp_gpg_crc_dtd_bkb_php}09|z1Cn'

フラグはcybrics{pdp_gpg_crc_dtd_bkb_php}でした.

[Cyber 10pts] Zakukozh

This image containing flag is encrypted with affine cipher. Scrape it

イメージファイルがアフィン暗号で暗号化されているみたいです. 軽くアフィン暗号について説明すると、


Enc(x) = (ax + b) \bmod{p}

\begin{eqnarray}
Dec(x) &=& a^{-1}(x - b) \bmod{p}
\end{eqnarray}

のような、暗号化、復号が行われます.a, b, pがパラメータとなり、これを見つけると復号できます. アフィン暗号は既知の平文があると、そこからパラメータを推測できるので、イメージファイルのマジックナンバーから推測します. イメージファイルといっても、いくつかあるのですが、暗号化データを見る感じではpngっぽいです.

マジックナンバーに0x00の部分があるので、そこを見るとbが0x59であることがわかります. また、暗号化データの最大値からpも推測でき、pが256であることがわかります. 残るパラメータはaのみです. aはbとqがわかっているので、適当に小さめな値から求めることができます. ここでは、マジックナンバーの0x0aに注目したとすると、暗号化データでは0xefになっていました. そのため、


\begin{eqnarray}
239 &=& (10a + 89) \bmod{256} \\
150 &=& 10a \\
a &=& 15
\end{eqnarray}

となり、aがわかります(modは超えていないだろうという推測で無視しています...). あとは、復号するだけです.

from Crypto.Util.number import inverse

with open("./zakukozh.bin", "rb") as f:
    data = f.read()

a = 15
b = 89
q = 256
inv_a = inverse(a, q)

m = bytearray([])
for c in data:
    m.append(inv_a * (c - b) % q)

with open('./flag.png', 'wb') as f:
    f.write(m)

flag.pngを表示すると、フラグが書いてました. cybrics{W311_C0M3_2_CY13R1C5}

[Forensic 10pts] Tone

youtubeに電話を掛けるときのボタン音が投稿されていました. はじめは、気合で読もうとしたのですが、4,7が低音、2,5,8が中音、3,6,9が高音までしか分からなくて、細かい音の違いが分からなくて泣いていました. そこで、ptr-yudaiがスペクトル解析をしてくれました. スペクトル解析すると、おおよそ正しい数字がわかるので、あとは文字にあてはめます(おおよそなので単語になってなさそうなところをエスパー).

cybrics{secrettonalflag}

録音して波形を見る、という考えになる前に泣いてしまったのが負け

さいごに

はやく夏休みなんねーかな

InnoCTF International 2019 Writeup

 7月13~14日にInnoCTFが開かれていたので、zer0ptsとして参加しました. チームとしては、(多分)9位でした. 終了後スコアサーバーがすぐ落ちたので(は???)、昼頃に見たときの順位です. 僕はwelcom問題含む3問を解きました. 今回Cryptoはguessingばかりだったので、つらかったです.

チームメンバーのWriteup

st98: InnoCTF International 2019 の write-up - st98 の日記帳

[Crypto] One Hundred Time RSA

 nとcが渡されます. 情報はこれだけで、ソースコードはないです.

 nが大きくないので、簡単に素因数分解できます. これでpとqが手に入ったのですが、eがわかりません. 0x10001や3、100(問題名から)を試してもうまくいきません.

 ここで一旦ロシア人になると、eを総当たりしつつ、100回復号を繰り返すという発想に至ります. たしかeが34800とかだった気がします. 結構時間がかかりました.

from Crypto.Util.number import inverse
from binascii import unhexlify

p = 37975227936943673922808872755445627854565536638199
q = 40094690950920881030683735292761468389214899724061
n = p*q
phi = (p-1) * (q-1)

e = 1
while True:
    c = 594147643758126272722748149715320287571901225730250492908477114410071694555274921111773337859009576
    if e % 100 == 0:
        print("[+] now public exponents is :", e)
    d = inverse(e, phi)
    e += 1
    for i in range(100):
        c = pow(c, d, n)
        try:
            m = unhexlify(hex(c)[2:])
            if b"InnoCTF" in m:
                print(m)
        except:
            pass

InnoCTF{cr4ck_rs4_4g41n_7bca}

[Crypto] RF

 暗号文I3_naseincamno_r15Ct_t0T07_}Fnhs{1が渡されます.

 たしか、問題文が「フェンスの上で見つけた」みたいな感じでした. フラグのフォーマットや問題文から推測するに、Rail fence cipherですね. 適当なソルバーに投げてやると、見つかります.

InnoCTF{n0t_ca3sar_7h1s_t1me_in50}

 keyは8でした.

さいごに

 えっっ、guessing過ぎません!? Cryptoはたいていの問題みたんですけど、guessing祭りでした.

 チームメンバー各位お疲れ様でした.

HSCTF 6 Writeup

 6月3~8日にHSCTFが開催されていたので、参加しました. といっても、存在に気付いたのが7日の昼だったので今回はyoshikingdomというチームで参加しました. zer0ptsのメンバーにregister情報投げると、案外みんな参加してくれました. その結果、

f:id:y05h1k1ng:20190608123849p:plain

でした. プロがぽんぽこ解いていって怖かったです. クソみたいな名前じゃなくて、結果zer0ptsでもよかったなと思います.

 登録した張本人はというと、easyなCryptoを2問解いて、寝てました(午後授業フル+バイトだったから許して☆). その2問のWriteupを書いていこうと思います.

チームメンバーのWriteup

theoldmoon0602: HSCTF 6 writeup - ふるつき

ptr-yudai: HSCTF 6 Writeup - CTFするぞ

st98: HSCTF 6 の write-up - st98 の日記帳

[Crypto 130pts] Reverse Search Algorithm

n, e, cが渡されます.

n = 561985565696052620466091856149686893774419565625295691069663316673425409620917583731032457879432617979438142137
e = 65537
c = 328055279212128616898203809983039708787490384650725890748576927208883055381430000756624369636820903704775835777

nが大きくないので、素因数分解してみると、

n = 29 * 19378812610208711050554891591368513578428260883630885898953907471497427917962675301070084754463193723428901453

でした. あとは、やるだけです.

Python 3.5.2 (default, Nov 12 2018, 13:43:14) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from binascii import unhexlify
>>> from Crypto.Util.number import inverse
>>> p = 29
>>> q = 19378812610208711050554891591368513578428260883630885898953907471497427917962675301070084754463193723428901453
>>> e = 0x10001
>>> n = 561985565696052620466091856149686893774419565625295691069663316673425409620917583731032457879432617979438142137
>>> n == p*q
True
>>> c = 328055279212128616898203809983039708787490384650725890748576927208883055381430000756624369636820903704775835777
>>> d = inverse(e, (p-1)*(q-1))
>>> m = pow(c, d, n)
>>> unhexlify(hex(m)[2:])
b'hsctf{y3s_rsa_1s_s0lved_10823704961253}'

hsctf{y3s_rsa_1s_s0lved_10823704961253}

[Crypto 190pts] Super Secure System

 メッセージを投げると、暗号化されたメッセージが返ってくるシステムです.

$ nc crypto.hsctf.com 8111
* * * SUPER SECURE SYSTEM * * *
My encryption system is impossible to crack if used once!
You can use this system to encrypt any of your messages with my super special key!!!
Here is my super secret message: 20235d3e4e162a732e35177c22044e013179111e42530b3b124f31490b753f5c6c7b47347a5f5a112e74772b2c0a776f6c1b680630

Enter the message you want to encrypt: aaa 

Encrypted: 29315f

Enter the message you want to encrypt: bbb

Encrypted: 2a325c

Enter the message you want to encrypt: a

Encrypted: 29

 ちなみに、暗号化は何回でもできます. xorぽいので確認すると、

>>> ord('a')^0x29
72
>>> ord('b')^0x2a
72

 あたりです.また、2文字目、3文字目は別の数字でxorされていることがわかります. 方針としては、フラグの長さ分aを送り、返ってきた値とaでxorを取り復号していきます.

from ptrlib import *

sock = Socket("crypto.hsctf.com", 8111)

sock.recvuntil("Here is my super secret message: ")
c = int(sock.recvline().strip(), 16)

flag_len = len(hex(c)[2:]) // 2
sock.recvuntil("Enter the message you want to encrypt: ")
sock.sendline(b"a"*flag_len)

sock.recvuntil("Encrypted: ")
recv = int(sock.recvline().strip(), 16)
flag = ""
for i in range(0, len(hex(c)[2:]) - 1, 2):
    a = ord('a') ^ int(hex(recv)[2:][i:i+2], 16)
    flag += chr(a ^ int(hex(c)[2:][i:i+2], 16))
print(flag)

hsctf{h0w_d3d_y3u_de3cryP4_th3_s1p3R_s3cuR3_m355a9e?}

 初めxorに気付けなかった...

終わりに

 全然仕事してないじゃん!一応Crypto担当としているので、クビになる前に強くなります. チームメンバー、運営・参加された方々お疲れさまでした.

SECCON Beginners CTF 2019 Writeup

 5月25~26日にSECCON Beginners CTFが開催されてました. チームzer0ptsで参加し、見事1位でした. チームメンバーのスピード感にあたふたしながらも、何とか数問通したのでWriteupを書きます. f:id:y05h1k1ng:20190526150254p:plain

チームメンバーのWriteup

st98: Beginners CTF 2019 の write-up - st98 の日記帳

theoldmoon0602: SECCON Beginners CTF 2019 writeup - ふるつき

ptr-yudai: SECCON Beginners CTF 2019のWriteup - CTFするぞ

[Crypto 363pts] Go RSA

Nだけなくしちゃったんだよなあ……。

 とりあえず、ncしてみます.

$ nc 133.242.17.175 1337
Encrypted flag is: 6344991311564553615862579427487315421208055634102890395723986371495652609153052697621268167305282339617530377087384855394685474792773022115680540686568501138450681688735527617827708821485497498334547179214866381804619441945049231222689303859605328356743173601997078352973035701358512748861024520717173066208645110545270222021655297034426926498311814336661104067007494662082645957734853033496598300417493177589227972753784432427753861158326507152049357862231911441535352804653810629357318640120796228696711675902539564632797542696587638137206651220915784909017728259256279217121403410183718987376151252076850594986072
> 0 
0
> 1
1
> 2
2862334794876318566324057598241408128109425191172133981033760448232814429877972087723511977245286558645814682054000700802565238541629306735791660770705209092054679971683689343593450904080873595244990490120345136595441331711791642822309784005850295255019360549477080451991452042485726954377888628611850312169956235832476482473468997735379482200368532387100203433537314371261639774070163736423425627779689468867325590663448606913878199332557603250135695359944006132143818989945004067114026488158760762115016145779669731641814525334761633123615342046269898802364403424297888529318077961694410182680067006220851010487693
The D was 3108589354283999881366977377816211791659702282566657492492997508648885943813044323050780847180267937096874217339239440259051280553557374969326192368619436355830441614153601856008410159305941711348272515678361389635638005453913489251292139406829869123083572863219302494814837811525563431399822598189396464260524809835126230568531180443134269028794046517626742952434891439389309802663719183434506260830073832328429505252129371886238807490334805430087834068647490477751392145402285002619521810094785728754166749922294644025726513925553921296219135200362480519946364396480504261544279372784914298890836180406341120859329

 数字を与えたら何か返ってきます.また、Encrypted flagDがもらえます.RSAと言われているので、返ってくる数字は


c = (input\ number)^e \bmod{n}

と考えられます.

 残るnさえ見つければ、復号できます. となると、mod演算の力の見せ所です...! mod演算は大雑把に説明すると、余りのことでした. 具体的に小さな値で説明すると、


10 \bmod{8} = 2

となります.つまり、0~7の値ですべての数字を表します.ではマイナスの値はどのように表せられるでしょうか. なんと、


-1 \bmod{8} = 7

となります!!! 詳細な説明はGooglemod 数学とかでググってください.(説明めんどう...)

 ここまでで、-1がn-1になることがわかりました.あとは、Encrypted flag, D, nから復号するだけです.

$ nc 133.242.17.175 1337
Encrypted flag is: 8574265495326376630715344864118702269699156118364751300483334371118983366068335837878430692194643907630947760218074636257962213901840959279695171778233952421546225414267130705377041788556040425374281913702704126363017674911378922866975795108524827197944518379304446564136766368478442131132639926373420800112390754225415097241757980287552353646752237359721950559438294762549429323083064086759103892170016208312683627723372030448129192438463592881852579263558462628295396255591387527275622328216850681535962245066287633466061728308482282796879985854086150426339364856915080205049371087944085742585047633265078786916717
> -1
15542669559227225429825537324581227774031891481936836236023168265768560509534175413897898039939875309341780656581069256476153063968087844810314599683067761887287458414581491790878409323665908229753209802327406269329874867875899296307778465317869542704169327492799825878469046825297855569668076456765118516134730535748246251497708000239072261510836367562449392851385927337215223745929318252139726812616575352609129732978704075275309551532205839627157499897379612485885734926354947463132399491683701371211067204644701324764499164101507557096214031191342098432721010724037945643454866504810914319842027997822183833426928
> 
Bye
The D was 11294445810590008451256715894011877130636049863981565968908667859208720657125830750743450207578873334636815264492070773310043387985660245681773997822701971285230906503746417520618785810004504532306435504005987399035139855466710836433795285598947451084782032325512289357709536384149184028104314710422845174670617305707095050004395521134314125412593577026243869778911744982261668051428824513145971177432162816223685859285472240021048696838191250769292549473790421401957500586651833875804831485999431831427601391163986595878453821597916628888747151831289612576792460488564806552328452459557394329005245982294646297165825
$ python3
Python 3.5.2 (default, Nov 12 2018, 13:43:14) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from binascii import unhexlify
>>> enc_flag = 8574265495326376630715344864118702269699156118364751300483334371118983366068335837878430692194643907630947760218074636257962213901840959279695171778233952421546225414267130705377041788556040425374281913702704126363017674911378922866975795108524827197944518379304446564136766368478442131132639926373420800112390754225415097241757980287552353646752237359721950559438294762549429323083064086759103892170016208312683627723372030448129192438463592881852579263558462628295396255591387527275622328216850681535962245066287633466061728308482282796879985854086150426339364856915080205049371087944085742585047633265078786916717
>>> n_minus_1 = 15542669559227225429825537324581227774031891481936836236023168265768560509534175413897898039939875309341780656581069256476153063968087844810314599683067761887287458414581491790878409323665908229753209802327406269329874867875899296307778465317869542704169327492799825878469046825297855569668076456765118516134730535748246251497708000239072261510836367562449392851385927337215223745929318252139726812616575352609129732978704075275309551532205839627157499897379612485885734926354947463132399491683701371211067204644701324764499164101507557096214031191342098432721010724037945643454866504810914319842027997822183833426928
>>> n = n_minus_1 + 1
>>> d = 11294445810590008451256715894011877130636049863981565968908667859208720657125830750743450207578873334636815264492070773310043387985660245681773997822701971285230906503746417520618785810004504532306435504005987399035139855466710836433795285598947451084782032325512289357709536384149184028104314710422845174670617305707095050004395521134314125412593577026243869778911744982261668051428824513145971177432162816223685859285472240021048696838191250769292549473790421401957500586651833875804831485999431831427601391163986595878453821597916628888747151831289612576792460488564806552328452459557394329005245982294646297165825
>>> flag = pow(enc_flag, d, n)
>>> unhexlify(hex(flag)[2:])
b'ctf4b{f1nd_7he_p4ramet3rs}'

フラグゲット!!!ctf4b{f1nd_7he_p4ramet3rs}

[Crypto 393pts] Bit Flip

平文を1ビットランダムで反転させる能力を手に入れた!

 ソースコードも渡されます.ソースコードを読むと、問題文通りのRSAです. e = 3 は明らかにおかしい(通常はe = 0x10001)ので、low public exponet attack等をやってみますが、条件に合わないのでうまくいきません.

 うーん、これは困りました... いったんe = 3から離れてみましょう.

 ncしたとき、

$ nc 133.242.17.175 31337
11545705109146803081154196731091553481494291928457261149212388528068058378823269333768320005182187108983022445099553663343177812738205610210357260446050457617731702932090378720710803576264258475457572522292489169532629163257089936346472604268088528122978005890263575400712331754280140566518723346561592384959

というふうに、平文をランダムで1ビット反転したものの暗号文がもらえます.再度ncすると、違う場所が反転されたものの暗号文です. となると、1ビット反転後の平文をm'としたとき、m'1m'2はほとんど同じ平文であることがわかります.

 RSAでbitがわかっている(今回はわかっているとは言えない)ときの攻撃といえば、Coppersmith's Attackです. なんか、今回の条件に合う攻撃なかったかな~と調べていると、

inaz2.hatenablog.com

 ありました!!!!Coppersmith’s Short Pad Attack & Franklin-Reiter Related Message Attack(長い)みたいです!!! 攻撃条件は

二つの暗号文について平文の上位bitがnのbit数の (1-1/e2) 程度共通する場合、これらからそれぞれの平文を求めることができる。

みたいです.今回はe = 3、nのbit数が1023bitなので、平文の上位bitが967bit同じ平文m'1m'2を用意しなければなりません. これはあまり難しくなくて、1bitしか変わらないので、いくつか暗号文を用意すれば上位bitがほとんど同じペアがあります. ただ、暗号文からどこのbitが反転しているかはわからないです. そのため10個程度暗号文を用意し、数値が近いもの同士で攻撃してみます.

def short_pad_attack(c1, c2, e, n):
    PRxy.<x,y> = PolynomialRing(Zmod(n))
    PRx.<xn> = PolynomialRing(Zmod(n))
    PRZZ.<xz,yz> = PolynomialRing(Zmod(n))

    g1 = x^e - c1
    g2 = (x+y)^e - c2

    q1 = g1.change_ring(PRZZ)
    q2 = g2.change_ring(PRZZ)

    h = q2.resultant(q1)
    h = h.univariate_polynomial()
    h = h.change_ring(PRx).subs(y=xn)
    h = h.monic()

    kbits = n.nbits()//(2*e*e)
    diff = h.small_roots(X=2^kbits, beta=0.5)[0]  # find root < 2^kbits with factor >= n^0.5

    return diff

def related_message_attack(c1, c2, diff, e, n):
    PRx.<x> = PolynomialRing(Zmod(n))
    g1 = x^e - c1
    g2 = (x+diff)^e - c2

    def gcd(g1, g2):
        while g2:
            g1, g2 = g2, g1 % g2
        return g1.monic()

    return -gcd(g1, g2)[0]


if __name__ == '__main__':
    n = 82212154608576254900096226483113810717974464677637469172151624370076874445177909757467220517368961706061745548693538272183076941444005809369433342423449908965735182462388415108238954782902658438063972198394192220357503336925109727386083951661191494159560430569334665763264352163167121773914831172831824145331
    e = 3

    nbits = n.nbits()
    kbits = nbits//(2*e*e)
    print "upper %d bits (of %d bits) is same" % (nbits-kbits, nbits)

    # ^^ = bit-wise XOR
    # http://doc.sagemath.org/html/en/faq/faq-usage.html#how-do-i-use-the-bitwise-xor-operator-in-sage
    cs = [77806872332913639919230209971392238784617993613323608240759820467908361781292069393342282547351581556259586998986092213607990761433134090374312820694026772204652782705492033343505430039323127158238332166350733150021228707945218465461966055458055161585955918733260995686334094638272681504698713633622376505308, 56989538367235158070306311186976292179966897528478291741508067866076558453602047302097966694208295312681095728980453463065836403911062093169223760418437322965770764311509640241287759696024373113722137027468951246866660964543912817862578633166333098878995510302628735250428938293637106737039139535160299108113, 38904877077171263737628178288521508189937409452080551772429415381447030724554764139002760866443078904692197281533309765344183894882142747076974754239070945859011211448240209079404207383248263468302174482960469183459600520739134108565937096269114702664072319462367117303477702692144066942049118080262294258198, 18583489730966619030615979926193288458958202730544958951381254155250349562324597989826437296289617404778761642054183547194844953317251480423608095521453337512728025268903153005408483464432086619556754592849674435698214322639172504375579000690270700946296005084794493004273617912360198409988034062432904201443, 81779690030003209255009995980102852322918673284543406574532267172257453562542775941820716471921905724303767155906053394193722593095884543297184318525481682039383393411599467185668595266012433381398851647598176014943130868710424785320701066749455904911769968456600407617618999332917394004530335428836578511038, 32723890881319186547872779402620483902697729020200348166442392175021912171894118652683887146846612929006547793375263509134656509739561177352356684588019934405564505733524922144146700314897913117036840607504607821666469911969838394487387376073261438777475081050957502547204126815797206281111078395092049286587, 74515903266795492985898471025229269878024376884435233684330662967167738131179553721410985213658229983853042260704757838616925556197183464709236532593823846170499630895374282114622487171170087091856753282020369482187351762243468042340933214874709271883018014631321412057529269400452647011517326283977015327189, 50884193811664497731364349812345616105175266569747833161903539901598152530392727464246077616701699644190582339965644325919604108036097223534586827791663822181563764715514302285487273629970687924833993670021557569304506460830014722740930334310623881775573664519736313757433300737631028708325665330938763782184, 32723890881319186547872779402620483902697729020200348166442392175021912171894118652683887146846612929006547793375263509134656509739561177352356684588019934405564505733524922144146700314897913117036840607504607821666469911969838394487387376073261438777475081050957502547204126815797206281111078395092049286587, 32723890881319186547872779402620483902697729020200348166442392175021912171894118652683887146846612929006547793375263509134656509739561177352356684588019934405564505733524922144146700314897913117036840607504607821666469911969838394487387376073261438777475081050957502547204126815797206281111078395092049286587, 70349274946614957759744006616813137783981786796010483575643371633588262711107346355531982812397035546281065385276548490062296856185344260769924218825995217554937441693054306002310733683884013698358767818115213188542062367075206824218550649564035135625128570873259781498669780078490598700442802349177736369712, 14980967781796819388041767887889903216468297576583006138007297644760691492596305457996725733919525577152256022483859011858574079640555374613247881143820771398254213825665880164126364722955374272148086280616213121922743671360167002526131898166108921720900263044330025216410582216554968042705705297554547422768, 20084571405797864822888728924121156713191530288959443915037367599696984039639335435370481551716134617000168582476618945013400480262866044903717028607042509827672584798897176555204805945259386570000183369487279952176022447143620009290796257006795499399194469035408992268017445449883256724883866657842304577341, 82212154608576254900096226483113810717974464677637469172151624370076874445177909757467220517368961706061745548693538272183076941444005809369433342423449908965735182462388415108238954782902658438063972198394192220357503336925109727386083951661191494159560430569334665763264352163167121773914831172831824145331]
    cs.sort()
    for i in range(len(cs)-1):
        c1 = cs[i]
        c2 = cs[i+1]
    try:
            diff = short_pad_attack(c1, c2, e, n)
            print "difference of two messages is %d" % diff

            m1 = related_message_attack(c1, c2, diff, e, n)
            print m1
            print m1 + diff
    except:
        pass
$ ./sage attack.sage
upper 967 bits (of 1023 bits) is same
difference of two messages is 0
32723890881319186547872779402620483902697729020200348166442392175021912171894118652683887146846612929006547793375263509134656509739561177352356684588019934405564505733524922144146700314897913117036840607504607821666469911969838394487387376073261438777475081050957502547204126815797206281111078395092049286587
32723890881319186547872779402620483902697729020200348166442392175021912171894118652683887146846612929006547793375263509134656509739561177352356684588019934405564505733524922144146700314897913117036840607504607821666469911969838394487387376073261438777475081050957502547204126815797206281111078395092049286587
difference of two messages is 0
32723890881319186547872779402620483902697729020200348166442392175021912171894118652683887146846612929006547793375263509134656509739561177352356684588019934405564505733524922144146700314897913117036840607504607821666469911969838394487387376073261438777475081050957502547204126815797206281111078395092049286587
32723890881319186547872779402620483902697729020200348166442392175021912171894118652683887146846612929006547793375263509134656509739561177352356684588019934405564505733524922144146700314897913117036840607504607821666469911969838394487387376073261438777475081050957502547204126815797206281111078395092049286587
difference of two messages is 82212154608576254900096226483113810717974464677637469172151624370076874445177909757467220517368961706061745548693538272183076941444005809369433342423449908965735182462388415108238954782902658438063972198394192220357503336925109727386083951661191494159560430569334665763264352163167121773914831172831589264307
16260765149986038884145173876068642724013617302097779293079362876653494069932815072038851668676222848467504538570853507159925860036819304291732134150397319327193122637750054910716746167965635612837962028769149915298230040116567157454495798898178036434538204980608594381468821524975316356796051708170
16260765149986038884145173876068642724013617302097779293079362876653494069932815072038851668676222848467504538570853507159925860036819304291732134150397319327193122637750054910716746167965635612837962028769149915298230040116567157454495798898178036434538204980608594381468821524975316356795816827146

 m'1m'2が同じになってしまうペアがあったようですが、1つうまくいってます. ただ、これはどこかのbitが反転されたものなので、雑プログラムで復号します.

from binascii import unhexlify

m = 16260765149986038884145173876068642724013617302097779293079362876653494069932815072038851668676222848467504538570853507159925860036819304291732134150397319327193122637750054910716746167965635612837962028769149915298230040116567157454495798898178036434538204980608594381468821524975316356796051708170

for i in range(0, 30):
    r = 1 << i
    flag = m ^ r
    try:
        print("[+] {} : {}".format(i, unhexlify(hex(flag)[2:])))
    except:
        pass

 0~30(適当)で雑に調べてますが、特にエラーなく出力されたのでその中で1番うまくいっているのが、

[+] 28 : b'ctf4b{b1tfl1pp1ng_1s_r3lated_m3ss4ge} DUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMY\n'

でした.後ろにDUMMYが入っていたんですね. これでフラグがとれました!ctf4b{b1tfl1pp1ng_1s_r3lated_m3ss4ge}

終わりに

 初めにCryptoのWarmupからやったのですが、全然解けなくて死んでました... theoldmoonにヘルプをだして解いてもらいました... そのときは、Warmupすら解けないの終わりでは...?と思いましたが、結果、きちんと仕事を果たせてよかったです(ほんまか).

 去年はCTFの存在を知っている程度のbabyで、linuxわからない!って感じで何も解けなかった記憶があります(笑). そう考えるとCTFを初めて約1年でよくここまで成長したな~と思います.

 まだまだ、わからないことがたくさんあるので、今後も精進していきたいです.

 最後になりましたが、 チームメンバー、運営・参加者の皆様お疲れさまでした. ここまで、読んでくださってありがとうございます.

angstromCTF 2019 Writeup

 4月20~25日にangstromCTFが開催されてました. チームzer0ptsで参加し、3730点で8位でした. 僕個人は、MiscとCryptoしかやってないです.

 今回は結構たくさんの問題を解くことができたので、うれしいです. Miscの簡単な問題を除いて以下に、Writeupを書いていきます.

チームメンバーのWriteup

theoldmoon0602 : ångstromCTF 2019 writeup - ふるつき

ptr-yudai : angstromCTF 2019 Writeup - CTFするぞ

st98 : ちょいまち angstromCTF 2019 の write-up - st98 の日記帳

[Misc 40] Paper bin

defund accidentally deleted all of his math papers! Help recover them from his computer's raw data.

 paper_bin.datというファイルが渡されます.

 recoverしてくれと言われているので、拡張子をpdfに変更してみます. 普通にpdfが見れるのですが、pdfの中を検索してもフラグが一向に見つかりません. しかし、foremostをするとpdfの中に20個ほどpdfが隠されていることがわかりました.

$ foremost paper_bin.dat  (pdfからでも取れます)
Processing: paper_bin.dat
|*|
$ cd output/pdf/
$ evince *

 雑にpdfを開けて、1つずつactfで検索するとフラグが取れます.

actf{proof_by_triviality}

 この問題は、10ページのpdfに対してサイズがやけに大きいことに感づくべきで、初めは全然気づかなかったです. 実ははじめforemostをpdfに対してしたけど、cd output/を見てpdf以外なんもないじゃんって放置してました... 気づくまで、ddとかで空白を消したりしてました(ddコマンドの練習にはなった...).

[Misc 60] Lithp

My friend gave me this program but I couldn't understand what he was saying - what was he trying to tell me?

 lithp.lispというファイルが渡されます.

;LITHP

(defparameter *encrypted* '(8930 15006 8930 10302 11772 13806 13340 11556 12432 13340 10712 10100 11556 12432 9312 10712 10100 10100 8930 10920 8930 5256 9312 9702 8930 10712 15500 9312))
(defparameter *flag* '(redacted))
(defparameter *reorder* '(19 4 14 3 10 17 24 22 8 2 5 11 7 26 0 25 18 6 21 23 9 13 16 1 12 15 27 20))

(defun enc (plain)
    (setf uwuth (multh plain))
    (setf uwuth (owo uwuth))
    (setf out nil)
    (dotimes (ind (length plain) out)
        (setq out (append out (list (/ (nth ind uwuth) -1))))))
    
(defun multh (plain)
    (cond
        ((null plain) nil)
        (t (cons (whats-this (- 1 (car plain)) (car plain)) (multh (cdr plain))))))

(defun owo (inpth)
    (setf out nil)
    (do ((redth *reorder* (cdr redth)))
        ((null redth) out)
        (setq out (append out (list (nth (car redth) inpth))))))

(defun whats-this (x y)
    (cond
        ((equal y 0) 0)
        (t (+ (whats-this x (- y 1)) x))))

;flag was encrypted with (enc *flag*) to give *encrypted*

 僕はlispわからないマンなので、condとかをググりながら読み解きます. すると.whats-thisがただ単にx * yをしてるだけだったり、oworeorderの順に並び変えているだけだということがわかります.

 ここまでわかったので、自分の書きやすい言語で調べていきます(僕はpython). owoを日本語でうまく説明できないですが(すいません)、

for i in range(len(encrypted)):
    encrypted[reorder.index(i)]

こんな感じで、encryptedを参照すると元の位置のものがわかります.

 ここで、encryptedが何か(lisp読むと平文っぽい)がwhats-thisに入っているのを思い出します. そのため、encryptedは平文actf{と何かを掛けたものだと考えられます. やってみると、

f = 'actf{'
for i, c in enumerate(f):
    a = encrypted[reorder.index(i)] / ord(c)
    print(a)
96.0          <- * ord('a')
98.0          <- * ord('c')
115.0        <- * ord('t')
101.0        <- * ord('f')
122.0        <- * ord('{')

きれいに割れてます.ord('a') = 97であることを考えると、1引いたものだとわかります(lispをきちんと読むとわかる).

 となると、encryptedに格納されている数字はフラグの文字 * (フラグの文字 - 1)とわかります. ここまでわかると、1文字ずつ全探索をするとフラグが得られます.

import string

encrypted = [8930, 15006, 8930, 10302, 11772, 13806, 13340, 11556, 12432, 13340, 10712, 10100, 11556, 12432, 9312, 10712, 10100, 10100, 8930, 10920, 8930, 5256, 9312, 9702, 8930, 10712, 15500, 9312]
reorder = [19, 4, 14, 3, 10, 17, 24, 22, 8, 2, 5, 11, 7, 26, 0, 25, 18, 6, 21, 23, 9, 13, 16, 1, 12, 15, 27, 20]

attack = string.printable

flag = ''
for i in range(len(encrypted)):
    for c in attack:
        if encrypted[reorder.index(i)] == ord(c)*(ord(c)-1):
            print('[+]find : {}'.format(c))
            flag += c

print('[!]flag : {}'.format(flag))

actf{help_me_I_have_a_lithp}

 lispを軽く理解して、あとはエスパー?で補って解いていく感じが楽しかったです. lispを完璧に読むとこんなに手順を踏まなくてもいいですが、CTF感があって楽しかったです.

[Misc 60] Just Letters

Hope you’ve learned the alphabet!

AlphaBeta - Esolang こういう頭がおかしくなる系の言語っぽいです.

$ nc 54.159.113.26 19600
Welcome to the AlphaBeta interpreter! The flag is at the start of memory. You get one line:
> 

 メモリのはじめにフラグがあるみたいです. いろいろ遊んでると、outputの命令を呼ぶとregister3にある値が参照されるっぽいです. なのでこの辺りを使います.

C sets register 3 to the value of register 1
G sets register 1 to the memory at the memory pointer
L outputs a character to the screen
S adds 1 to the register
Y sets the register to 0

 YGCLを送るとaが返ってきます.次の文字からはSGCLで求められます. フラグの長さはわからないので、YGCLSGCLSGCLSGCLSGCL...SGCLをたくさん繰り返したものを送るとフラグを得られます.

actf{esolangs_sure_are_fun!}

[Crypto 50] Half and Half

Mm, coffee. Best served with half and half!

 フラグを半分にしたものをxorしただけです.

from secret import flag

def xor(x, y):
    o = ''
    for i in range(len(x)):
        o += chr(ord(x[i])^ord(y[i]))
    return o

assert len(flag) % 2 == 0

half = len(flag)//2
milk = flag[:half]
cream = flag[half:]

assert xor(milk, cream) == '\x15\x02\x07\x12\x1e\x100\x01\t\n\x01"'

 フラグの形式からmilkの初めがactf{であるので、creamの5文字がtasteであることがわかります. しかしこれでは

milk  = actf{xxxxxx_
cream = tastexxxxxx}

 ここで、残りの文字をエスパーします.

import string

def xor(x, y):
    o = ''
    for i in range(len(x)):
        o += chr(ord(x[i]) ^ ord(y[i]))
    return o

a = '\x15\x02\x07\x12\x1e\x100\x01\t\n\x01"'

b = 'coffee'
for i in range(len(a)):
    print(xor(b, a[i:i+len(b)]))

 これお行儀の悪いプログラムなので、当然怒られますが、bの文字列がいい感じの文字になるか調べられます. 初めはbmilkとかcreamとかを適当に入れてみますが駄目でした. 問題文よりcoffeeを入れてみると、

vmat{u
ahtxuU
d}xvUd
qqvVdl
}Vglo
s_good
SnoldG
おこ

s_goodがいい感じなので、フラグが

milk  = actf{coffee_
cream = tastes_good}

だとわかります.

[Crypto 150] Lattice ZKP

I tried to make a zero knowledge proof, but something isn't right.

 ncすると、なにやら大きな値のA*r + eが得られます. そのあとに、rr + sのどちらかを選べる想定の問題だと思います. しかし、複数回ncしても毎回A*r + eの値は変わりません. そのため、rr + sを得ることができるので、鍵であるs(各自プログラムをよんでほしい)を簡単に求めることができます.

 あとは、暗号文を平文に戻すことを考えるだけです. といっても、sのハッシュとxorしているだけなので、同じことをします. 注意点としては、行列で値を持っていて、それをpackしたものが見えているので、packを戻すこと(unpack)を考えなければならないです. さらにもう一つ注意することがあって、Cryptoモジュールはpycryptoではなくpycryptodomeのものを使いましょう(ややこしい).

from Crypto.Util.strxor import strxor
from Crypto.Hash import SHAKE256
from Crypto.Util.asn1 import DerSequence
import binascii
import numpy as np


pack = lambda x: binascii.hexlify(DerSequence(x.tolist()).encode())
unpack = lambda x: np.array(DerSequence().decode(binascii.unhexlify(x)))

def sub(x, y):
    return np.mod(x-y, q)

with open('./r.txt') as f:
    r = f.read().strip()

with open('./r_plus_s.txt') as f:
    r_plus_s = f.read().strip()

q = 1 << 15
s = sub(unpack(r_plus_s), unpack(r))

print('[+]s : {}'.format(s))

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

raw = bytes(np.mod(s, 256).tolist())
shake = SHAKE256.new()
shake.update(raw)
pad = shake.read(len(enc_flag))
flag = strxor(enc_flag, pad)
print('[!]flag : {}'.format(flag))

actf{deep_into_that_darkness_learning_with_errors_goes}

 僕が手を動かして書いていたのですが、theoldmoon0602にunpack等の助言を得ながら解きました. ややこしいところすべてtheoldmoon0602によって解決したので、ほとんど彼が解いた感じです...

[Crypto 200] Powerball

Introducing ångstromCTF Powerball, where the Grand Prize is a flag! All you need to do is guess 6 ball values, ranging from 0 to 4095. But don't worry, we'll give one for free!

 ptr-yudaiからOblivious TransferでRSAっぽいからやってみて!と言われてやってみた問題です. 申し訳ないですけど、Oblivious Transferが何なのかよく知らないです... が、あふれる想像力でなんとかしました(嘘です).

 neが渡されています.ランダムで生成された6つのxが毎回見れます. 次に好きな数vを入力することができ、各i=0, 1, ... ,6について


k = (v - x_i)^{d} \bmod{n}

m_i = balls_i + k

によって、mが計算され、表示されます.

 これらの情報から、ballsを6つ予想し、すべて合えばフラグが得られるいう問題でした.

 これはRSAが元ということを考えるとうまく解けます. kを求める際、RSAっぽさを感じないですか?? 感じれば勝ちです. つまり、


v - x_i = k^{e} \bmod n

が成り立ちそうです. ここでm_i = balls_i + kよりk = balls_i - m_iなので上式に代入すると、


v - x_i = (m_i - balls_i)^{e} \bmod n

この式で、わからないのはballsだけなので、全探索します(ballsは0~4095の値でしかない).

for m, x in zip(ms, xs):
    for i in range(4096):
        if (max_x - x) == pow(m - i, e, n):
            print('[!]find bull : {}'.format(i))

 vの値として、xの中で一番大きい値を選んでいます(特にこれといった理由はないが、よさそうな感じがしたので...).

 実は、僕がしたのはここまでで、ncでやり取り云々はptr-yudaiに任せました. (僕はすぐに書けないので、pwnで手慣れている彼に投げた.) ここには、自分でやり取りする部分も書いて載せようと思ったのですが、こんなに長いWriteupを書くのは初??なので疲れてしまいました. 回復したら、更新するかもしれないです(しないやつ).

actf{no_more_free_oblivious_transfers}

終わりに

 チームメンバー、運営の皆さん、参加者の皆さんお疲れさまでした. 長期間CTFだったので、いい感じにぼちぼちできて楽しかったです. 最近はASISやPlaidでズタボロにされていたので、この難易度と問題量がよかったと感じました.

 長いWriteupとなりましたが、最後まで読んでくださりありがとうございました.

Sunshine CTF 2019 writeup

 3/30~4/1まで開催されていたSunshine CTFに出ました. チームzer0ptsとして参加し、全体で3255pts獲得して10位でした(すごい!).

[Crypto] WelcomCrypto 50pts

 謎の問題文から解読します.

~C8 =39 A?2V8 73J:C 8FG7 AF?JJC2ECP

DF?LHb=r_>b0%_0520<c8bPN

 シフト系の暗号に見えます. 古典暗号の類はいちいち実装するのは面倒なので、このサイトを使ってごちゃごちゃします.

Org lbh pna'g fbyir guvf punyyratr!

sun{w3lC0m3_T0_da_k4g3!}

 rot47すると取れました.ちなみに上の文字列はさらにrot18するとBet you can't solve this challenge!となります.

終わり

 はい、writeupは以上になります.

短い!!!!!!!!!!もっと解きたい!!!!!!!!

ので、過去問等解いていきます. この頃は、Shiho Midorikawaさんが作られたCrypto Challenge Listを埋めてます.

pastebin.com

 解いて、hackmdで適当にwriteup書いてます. hackmdで細かくタグ管理すると、知見がたまるのですごく良いです. Challenge Listは他にもあるので、ぜひやってみてください. 楽しいです.

 Writeupがあまりにも短くて悲しかったので、ちょっと関係のない話をしましたが、以上です. チームメンバー、運営の皆さん、お疲れさまでした.

Securinets CTF Quals 2019 writeup

 3/24~25にSecurinets CTFが開催されており、参加しました. チームzer0ptsとして参加し、結果は23393ptsで2位でした.

 2位!!!!!

 すごいですね(他人事感).例に漏れず、チームメンバーがつよつよでした. 今回はinsecureではなく、theoldmoon0602, ptr-yudai, st98プロと僕の4人でzer0ptsというチームで出ました.

 僕がフラグを提出した問題は、Sanity Checkぐらいなんですが... まあ、先に解いてるけど、空いた(手をつける問題がない)時間に解いた問題もあるので、それらのwriteupを書こうと思います. あとは、部分的に手伝った問題とかですね.

[Crypto] Useless Admin (869pts)

 OTP(One Time Password)を使ってるよ的な問題文と以下のファイルcipher.jsonが渡されます.

{
    "cipher_list": [
        "1b0605000e14000d1b524802190b410700170e10054c11480807001806004e4f1f4f01480d411400531158141e1c100016535a480c000c031a000a160d421e004113010f13451e0c0100100a020a1a4e165f500d0c1e041a090b001d0515521c0a0410000a4f4b4d1d1c184d071600071c0a521d1706540940",
        "1e10524e001f11481c010010070b13024f0704590903094d0c000e4f0711000615001911454217161a1a45040149000a5218404f1e0012060b1b590a1048171741140c01174c0d49174f0c8d4fc7520211531b0b0c1e4f",
        "1d0c04451352001a000154431b014109450a0a0b000045490403520a1d16490008535848085942071c0d0c57101c0045111c40430c4e111c0b1b1c451d4f071712010508475518061d00060a1b0a1a4c165d",
        "160d074300061d071b524e06190b134e450a0b0a4d4c12411d004f014045491b4649074804001100011d4504520612451e165d53064e164e1d060d0d44541a0041031b0b06540d1a070004001d4b074800531c04101d4f",
        "1a1d524912521548120045021b4e1506490a0859150345531d12521b4e094909030003011148420453074d161e05540b071e4c451b000a084a1d1c04084c0b45060b060a4742070618534218070210484512020043100e191e5956111a1c001c1f0b5c",
        "1a1d5248000154041a1c47430d0b04000005015900140c4f04534f094e08490103000000045442111b11001b1b1d000917535a48004e021d4a0e0b0044491c03080a001a024c11490748074f02040054451a1d150c1b150d020d0e",
        "1a1d5249125215481613500a1b0f0d4e4d0d1c0d000700001d1c001b06004f1d0f5a11480745040a011100181c0c540d13000e44085404404a061716014e010c0308104e084e0d4911450506011853540a5304120a1a154c0a1843001b45541c481607051b431f480d001e0400000c531d01011d00124441010200190d0800000000000e54060001100a1b4d0b040d105347",
        "0a0607000913020d551300041d0f0f0a0003061f154c034f1b53530602004e0c030c541f0454110a1d5a001e0649190419165d00104f104e1b1a101101001b0b1705051b0642040c5341114f0e4b104f0803110b0a060f42",
        "160d074300061d071b524e06190b134e450a0b0a4d4c12411d004f014045491b4649074804001100011d4504520612451e165d53064e16424a1810110c00060d04440e1c02411c0c00544209001953540d165009021a1542",
        "1e10524e001f11481c010010070b13024f0704590903094d0c000e4f0711000615001911454217161a1a45040149000a5218404f1e0012060b1b590a1048171741140c01174c0d49174f4201001f534b0b1c074b",
        "1a49134d4113540a0713490d434e160f541700174f4c11480c53520a1d1100000000190d4549114512544d12000c540402034b4e0d491d40"
    ],
    "cipher_flag": "1a4905410f06110c55064f430a00054e540c0a591603174c0d5f000d1b110006414c1848164516111f1100111d1b54001c17474e0e001c011f1d0a4b"
}

 OTPを使った問題の経験がなかったので、とりあえず調べた結果、こちらのサイト

dann.com.br

を見つけました.サイトを参照してもらえればわかるんですが、ほとんど同じなので今回の問題にしたものにコードを変更します.

import string
import collections
import sets, sys

ciphers = ["1b0605000e14000d1b524802190b410700170e10054c11480807001806004e4f1f4f01480d411400531158141e1c100016535a480c000c031a000a160d421e004113010f13451e0c0100100a020a1a4e165f500d0c1e041a090b001d0515521c0a0410000a4f4b4d1d1c184d071600071c0a521d1706540940",
        "1e10524e001f11481c010010070b13024f0704590903094d0c000e4f0711000615001911454217161a1a45040149000a5218404f1e0012060b1b590a1048171741140c01174c0d49174f0c8d4fc7520211531b0b0c1e4f",
        "1d0c04451352001a000154431b014109450a0a0b000045490403520a1d16490008535848085942071c0d0c57101c0045111c40430c4e111c0b1b1c451d4f071712010508475518061d00060a1b0a1a4c165d",
        "160d074300061d071b524e06190b134e450a0b0a4d4c12411d004f014045491b4649074804001100011d4504520612451e165d53064e164e1d060d0d44541a0041031b0b06540d1a070004001d4b074800531c04101d4f",
        "1a1d524912521548120045021b4e1506490a0859150345531d12521b4e094909030003011148420453074d161e05540b071e4c451b000a084a1d1c04084c0b45060b060a4742070618534218070210484512020043100e191e5956111a1c001c1f0b5c",
        "1a1d5248000154041a1c47430d0b04000005015900140c4f04534f094e08490103000000045442111b11001b1b1d000917535a48004e021d4a0e0b0044491c03080a001a024c11490748074f02040054451a1d150c1b150d020d0e",
        "1a1d5249125215481613500a1b0f0d4e4d0d1c0d000700001d1c001b06004f1d0f5a11480745040a011100181c0c540d13000e44085404404a061716014e010c0308104e084e0d4911450506011853540a5304120a1a154c0a1843001b45541c481607051b431f480d001e0400000c531d01011d00124441010200190d0800000000000e54060001100a1b4d0b040d105347",
        "0a0607000913020d551300041d0f0f0a0003061f154c034f1b53530602004e0c030c541f0454110a1d5a001e0649190419165d00104f104e1b1a101101001b0b1705051b0642040c5341114f0e4b104f0803110b0a060f42",
        "160d074300061d071b524e06190b134e450a0b0a4d4c12411d004f014045491b4649074804001100011d4504520612451e165d53064e16424a1810110c00060d04440e1c02411c0c00544209001953540d165009021a1542",
        "1e10524e001f11481c010010070b13024f0704590903094d0c000e4f0711000615001911454217161a1a45040149000a5218404f1e0012060b1b590a1048171741140c01174c0d49174f4201001f534b0b1c074b",
        "1a49134d4113540a0713490d434e160f541700174f4c11480c53520a1d1100000000190d4549114512544d12000c540402034b4e0d491d40"]

target_cipher = "1a4905410f06110c55064f430a00054e540c0a591603174c0d5f000d1b110006414c1848164516111f1100111d1b54001c17474e0e001c011f1d0a4b"

def strxor(a, b):
    return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])

# To store the final key
final_key = [None]*150
# To store the positions we know are broken
known_key_positions = set()

# For each ciphertext
for current_index, ciphertext in enumerate(ciphers):
    counter = collections.Counter()
    # for each other ciphertext
    for index, ciphertext2 in enumerate(ciphers):
        if current_index != index: # don't xor a ciphertext with itself
            for indexOfChar, char in enumerate(strxor(ciphertext.decode('hex'), ciphertext2.decode('hex'))): # Xor the two ciphertexts
                # If a character in the xored result is a alphanumeric character, it means there was probably a space character in one of the plaintexts (we don't know which one)
                if char in string.printable and char.isalpha(): counter[indexOfChar] += 1 # Increment the counter at this index
    knownSpaceIndexes = []

    # Loop through all positions where a space character was possible in the current_index cipher
    for ind, val in counter.items():
        # If a space was found at least 7 times at this index out of the 9 possible XORS, then the space character was likely from the current_index cipher!
        if val >= 7: knownSpaceIndexes.append(ind)
    #print knownSpaceIndexes # Shows all the positions where we now know the key!

    # Now Xor the current_index with spaces, and at the knownSpaceIndexes positions we get the key back!
    xor_with_spaces = strxor(ciphertext.decode('hex'),' '*150)
    for index in knownSpaceIndexes:
        # Store the key's value at the correct position
        final_key[index] = xor_with_spaces[index].encode('hex')
        # Record that we known the key at this position
        known_key_positions.add(index)

# Construct a hex key from the currently known key, adding in '00' hex chars where we do not know (to make a complete hex string)
final_key_hex = ''.join([val if val is not None else '00' for val in final_key])
# Xor the currently known key with the target cipher
output = strxor(target_cipher.decode('hex'),final_key_hex.decode('hex'))

print "Fix this sentence:"
print ''.join([char if index in known_key_positions else '*' for index, char in enumerate(output)])+"\n"

# WAIT.. MANUAL STEP HERE 
# This output are printing a * if that character is not known yet
# fix the missing characters like this: "Let*M**k*ow if *o{*a" = "cure, Let Me know if you a"
# if is too hard, change the target_cipher to another one and try again
# and we have our key to fix the entire text!

#sys.exit(0) #comment and continue if u got a good key

target_plaintext = "i wanted to end the world, but i'll settle for ending yours."
print "Fixed:"
print target_plaintext+"\n"

key = strxor(target_cipher.decode('hex'),target_plaintext)

print "Decrypted msg:"
for cipher in ciphers:
    print strxor(cipher.decode('hex'),key)

print "\nPrivate key recovered: "+key+"\n"
Fix this sentence:
* *anted to ind t** y*rl*, but i'*l*settle for endin* ****s.

Fixed:
i wanted to end the world, but i'll settle for ending yours.

Decrypted msg:
how often have i said that when you have excluded the imposs
my name is sherlock holmes. it is my business to know what o
never trust to general impressions, my boy, but concentrate 
education never ends, watson. it is a series of lessons with
it is a great thing to start life with a small number of rea
it has long been an axiom of mine that the little things are
it is a capital mistake to theorize before one has data. ins
you have a grand gift for silence, watson. it makes you quit
education never ends, watson. it is a series of lessons, wit
my name is sherlock holmes. it is my business to know what o
i am a brain, watson. the rest of me is a mere appendix.

Private key recovered: sir arthur conan doyale is one of the best writers. i enjoye

 Fixedには上のFix this sentenceを直したものを直接コードに書きます. Fix this sentenceを直すのが大変で、ひたすら調べてi wanted to ind t** y*rl*, but i'll settle for ending yours.までたどり着いたんですが前半がわかりませんでした. 後にptr-yudaiが名言?であることを見つけ、Fixedはi wanted to end the world, but i'll settle for ending yours.であることがわかりました.

Securinets{i wanted to end the world, but i'll settle for ending yours.}

[Crypto] RSA/Encoding error (1000pts) first solve!

 zipファイルが渡されます. 解凍すると、以下のようなpngファイルが出てきます. f:id:y05h1k1ng:20190325160735p:plain  読み取ると、Hint: Every single information mattersで大した情報ではないです. stringsコマンドにかけるとoutput.txtというものが見えるので、foremostコマンドで取り出します. output.txtが取れたので、中身を見ると以下のようなビット列が複数行に渡って書かれています.

11111.11111.00011.10110.00000.00000.00000.10111.10000.10001.00000.11111.10111.01111.00100.00000.10101.10011

 ここからが本当にわからなくて、大変でした. theoldmoon0602とptr-yudaiによるとどうやら反復符号というものらしいです. これでoutput.txtの解釈はできても、RSA要素が全く出てきてません. 色々調べると、はじめのpngexif情報でした. exiftoolにかけると以下になります.

$ exiftool ctf_image.png 
ExifTool Version Number         : 10.80
File Name                       : ctf_image.png
Directory                       : .
File Size                       : 11 kB
File Modification Date/Time     : 2019:03:23 22:53:12+09:00
File Access Date/Time           : 2019:03:25 16:07:35+09:00
File Inode Change Date/Time     : 2019:03:24 13:14:23+09:00
File Permissions                : rw-r--r--
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 887
Image Height                    : 521
Bit Depth                       : 8
Color Type                      : RGB with Alpha
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
SRGB Rendering                  : Perceptual
Gamma                           : 2.2
Pixels Per Unit X               : 3780
Pixels Per Unit Y               : 3780
Pixel Units                     : meters
Comment                         : e = 2^16+1
Warning                         : [minor] Trailer data after PNG IEND chunk
Image Size                      : 887x521
Megapixels                      : 0.462

 これで、e = 2^16+1、あとはエスパー?で画像の縦横のサイズが素数だとわかり、必要なものが揃いました. dを計算し、output.txtを1行ずつ復号してやるとでます.

from Crypto.Util.number import inverse

with open("./output/zip/output.txt", "rb") as f:
    buf = f.read()
#buf = buf.replace(b"\n", ".")

p = 887
q = 521
e = 2**16 + 1
N = p*q
phi = (p-1)*(q-1)

d = inverse(e, phi)

m = ''
for a in buf.split(b"\n"):
    result = ''
    for block in a.split(b'.'):
        if block.count(b'1') > 2:
            result += '1'
        else:
            result += '0'
        c = int(result, 2)
    tmp = pow(c, d, N)
    m += hex(tmp)[2:-1].decode('hex')
print m
A repetition code is a coding scheme that repeats the bits across a channel to achieve error-free communication. Given a stream of data to be transmitted, the data are divided into blocks of bits. Each block is transmitted some predetermined number of times. For example, to send the bit pattern "1011", the four-bit block can be repeated three times, thus producing "1011 1011 1011". However, if this twelve-bit pattern was received as "1010 1011 1011"  where the first block is unlike the other two  it can be determined that an error has occurred.  hope you have understand it, please validate the task with the following Flag : xJbht0oWpsOa1e3WnXo9FDnUj3VZpZsuxMPVlYEN

Securinets{xJbht0oWpsOa1e3WnXo9FDnUj3VZpZsuxMPVlYEN}

[Forensics] Easy Trade (200pts)

 pcapファイルが渡されます. wiresharkで見ると、いくつかのtcpがあるのでtcp streamします(無条件反射). あるパケットの中にPKflag.txtを含んだバイト列があるので、zipだろうということで、摘出します.f:id:y05h1k1ng:20190325163847p:plain  tcp streamで内容を保存できますが、デフォルトではASCIIの保存になるので、下のShow and sava data asrawに変更して保存します. f:id:y05h1k1ng:20190325163716p:plain  これでzipを手に入れることができたので、あとは解凍するだけですが、ここでパスワードを聞かれます. ここは、tcpgive me the keyとあったのでその後にあるパケットのsecurinetsXDを入れると解凍できます. これでflag.txtを手に入れることができます.

Securinets{c2VjdXJpbmV0c3s5NTRmNjcwY2IyOTFlYzI3NmIxYTlmZjg0NTNlYTYwMX0}

 これは、ptr-yudaiが秒で(5分もかからないぐらい)解いたのですが、僕は20分ぐらいかかりました. 圧倒的マッスルですね.

まとめ

 1位、2位の繰り返しで激アツでした. また、今回は初チームということで楽しく24時間過ごせました(4時間ほど寝たけど...).

 最近はCryptoを練習しているので、更にやっていきます.

 チームメンバー、運営の皆様ありがとうございました.お疲れ様です.