この大会は2018/11/9 4:00(JST)~2018/11/15 4:00(JST)に開催されました。
今回もチームで参戦。結果は 70818点で595チーム中21位でした。
自分で解けた問題をWriteupとして書いておきます。
C2: flipping bits (cryptology)
共通のnとe1,c1とe2,c2がわかっているので、Common Modules Attackで復号できる。
import gmpy def commom_modules_attack(c1, c2, e1, e2, n): gcd, s1, s2 = gmpy.gcdext(e1, e2) if s1 < 0: s1 = -s1 c1 = gmpy.invert(c1, n) elif s2 < 0: s2 = -s2 c2 = gmpy.invert(c2, n) v = pow(c1, s1, n) w = pow(c2, s2, n) x = (v*w) % n return x c1 = 13981765388145083997703333682243956434148306954774120760845671024723583618341148528952063316653588928138430524040717841543528568326674293677228449651281422762216853098529425814740156575513620513245005576508982103360592761380293006244528169193632346512170599896471850340765607466109228426538780591853882736654 c2 = 79459949016924442856959059325390894723232586275925931898929445938338123216278271333902062872565058205136627757713051954083968874644581902371182266588247653857616029881453100387797111559677392017415298580136496204898016797180386402171968931958365160589774450964944023720256848731202333789801071962338635072065 e1 = 13 e2 = 15 n = 103109065902334620226101162008793963504256027939117020091876799039690801944735604259018655534860183205031069083254290258577291605287053538752280231959857465853228851714786887294961873006234153079187216285516823832102424110934062954272346111907571393964363630079343598511602013316604641904852018969178919051627 m = commom_modules_attack(c1, c2, e1, e2, n) flag = ('%x' % m).decode('hex') print flag
flag-54d3db5c1efcd7afa579c37bcb560ae0
C3: shredded (image processing)
とりあえずそのままImageMagicで連結してみる。QRコードっぽいことだけわかる。並び替えながら、正しいのを探す。
$ convert +append 0.png 12.png 13.png 5.png 6.png 25.png 16.png 2.png 15.png 26.png 3.png 20.png 10.png 21.png 4.png 19.png 7.png 8.png 1.png 22.png 23.png 24.png 18.png 14.png 11.png 17.png 9.png qr.png
この場合だめだった。まだ何パターンもあるので、わかっている部分以外で総当たりする。
わからない部分: ・左のPositionの3列の黒の箇所 ・真ん中5列(ただし、Timingが黒白黒白黒になることからさらに制限できる) ・右のPositionの3列の黒の箇所
from PIL import Image import itertools from pyzbar.pyzbar import decode FILE_FORMAT = 'shredded/%d.png' WIDTH = 10 HEIGHT = 297 def makeQR(ary): output_img = Image.new('RGB', (WIDTH*27, HEIGHT), (255, 255, 255)) for i in range(27): input_img = Image.open(FILE_FORMAT % ary[i]) output_img.paste(input_img, (WIDTH*i, 0)) output_img.save('flag.png') left = [2, 16, 25] mid_black = [4, 20, 21] mid_white = [10, 19] right = [22, 23, 24] found = False for a in itertools.permutations(left): for b in itertools.permutations(mid_black): for c in itertools.permutations(mid_white): for d in itertools.permutations(right): ary = [0, 12, 13, 5, 6] ary += a ary += [15, 26, 3] ary += [b[0]] ary += [c[0]] ary += [b[1]] ary += [c[1]] ary += [b[2]] ary += [7, 8, 1] ary += d ary += [18, 14, 11, 17, 9] makeQR(ary) try: data = decode(Image.open('flag.png')) print data[0][0].decode('utf-8', 'ignore') print ary found = True break except: continue if found: break if found: break if found: break
実行結果は以下の通り。
GOOD JOB. FLAG-80AD8BCF79 [0, 12, 13, 5, 6, 16, 25, 2, 15, 26, 3, 4, 19, 21, 10, 20, 7, 8, 1, 22, 24, 23, 18, 14, 11, 17, 9]
FLAG-80AD8BCF79
C4: leaky power (cryptology)
AESの相関電力解析 (CPA : Correlation Power Analysis)の問題。
https://wiki.newae.com/Tutorial_B6_Breaking_AES_(Manual_CPA_Attack)を参考にして、鍵を推測しjweを復号するコードを実行する。
import numpy as np from jwcrypto.common import base64url_decode, base64url_encode from jwcrypto import jwk, jwe HW = [bin(n).count("1") for n in range(0,256)] sbox=( 0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16) def intermediate(pt, keyguess): return sbox[pt ^ keyguess] traces = np.load('powertraces.npy') pt = np.load('plaintexts.npy') numtraces = np.shape(traces)[0]-1 numpoint = np.shape(traces)[1] #Use less than the maximum traces by setting numtraces to something #numtraces = 15 bestguess = [0]*16 #Set 16 to something lower (like 1) to only go through a single subkey & save time! for bnum in range(0, 16): cpaoutput = [0]*256 maxcpa = [0]*256 for kguess in range(0, 256): print "Subkey %2d, hyp = %02x: "%(bnum, kguess), #Initialize arrays & variables to zero sumnum = np.zeros(numpoint) sumden1 = np.zeros(numpoint) sumden2 = np.zeros(numpoint) hyp = np.zeros(numtraces) for tnum in range(0, numtraces): hyp[tnum] = HW[intermediate(pt[tnum][bnum], kguess)] #Mean of hypothesis meanh = np.mean(hyp, dtype=np.float64) #Mean of all points in trace meant = np.mean(traces, axis=0, dtype=np.float64) #For each trace, do the following for tnum in range(0, numtraces): hdiff = (hyp[tnum] - meanh) tdiff = traces[tnum,:] - meant sumnum = sumnum + (hdiff*tdiff) sumden1 = sumden1 + hdiff*hdiff sumden2 = sumden2 + tdiff*tdiff cpaoutput[kguess] = sumnum / np.sqrt( sumden1 * sumden2 ) maxcpa[kguess] = max(abs(cpaoutput[kguess])) print maxcpa[kguess] #Find maximum value of key bestguess[bnum] = np.argmax(maxcpa) print "Best Key Guess: " key = '' for b in bestguess: key += "%02x" % b print "%02x" % b, print '' with open('instructions_corrected.jwe', 'r') as f: enc = f.read() key = jwk.JWK(kty = 'oct', k = base64url_encode(key.decode('hex'))) jwetoken = jwe.JWE() jwetoken.deserialize(enc) jwetoken.decrypt(key) payload = jwetoken.payload print payload
実行結果は以下のとおり。
: Best Key Guess: d2 de a0 57 d1 14 5f 45 67 96 96 60 24 a7 03 b2 CONFIDENTIAL To disable C4, you will need: - 6 bits of Dragon Sumac - 1 nibble of Winter Spice - 1 byte of Drake Cardamom - 1 flag with value flag-e2f27bac480a7857de45 - 2 diskfulls of Tundra Chives - 5 forks Grind the Dragon Sumac in a cup, making sure you don't break the cup as it's probably a delicate cup. Add a sprinkle of liquid ice to turn it into a cream-like paste, then add the Winter Spice, first almost everything, then the last tiny remnants. Fill a pan with elemental water, add the mixture and cool it down with how cool you are, then bring the mixture to a boil. Let it cool down to the body temperature of a reptile before adding the Drake Cardamom and Tundra Chives, all at once of one, then half at a time of the other. Bring everything back to a boil, turn of the heat, mix with the forks and let everything cool down. If you touch the liquid and it burns you, it hasn't cooled down enough. Whisk the mixture heavily to aerate it. Stop when it's frothy. Drinking the potion will disable C4. note: A small, but very cold amount is needed for the potion to be effective. Mixing it in a milkshake could work, but be wary of brain freeze.
flag-e2f27bac480a7857de45
C5: de-anonymization (gdpr)
datasetからYakubovicsに関する情報を探す。
■Yakubovicsで検索 1.csv email,job_title,income eyakubovics9t@nih.gov,Captain,96605 2.csv email,state eyakubovics9t@nih.gov,Florida ■96605で検索 4.csv income,state,street,postal 96605,Florida,4 Magdeline,33421 ■"4 Magdeline"で検索 3.csv ssn_last_four,state,street 4484,Florida,4 Magdeline 5.csv first_name,street Elyssa,4 Magdeline
Reset Passwordで以下入力
- First Name: Elyssa - Last Name: Yakubovics - Email Address: eyakubovics9t@nih.gov - What are the last four digits of your SSN?: 4484 - What is the name of the street you grew up on?: 4 Magdeline
Reset Passwordの画面に移るので、HTMLソースを見ると、Current Passwordがフラグになっていた。
<input name='current_password' type='password' value='flag-5ed43dc6356b2b68c689422769952b82' readonly />
flag-5ed43dc6356b2b68c689422769952b82
C8: captcha (math)
HTMLソースを見ると、数式は以下のようになっている。
ggggmt o CW j gggs j gZ j ZWW I ggm o ggp o gn I mtWW o gR I hWWW I EWW o sWW I ggggR o mW j gn o mWW o ggZ j pW I ZWW I gggggp I RW o ggm I sW j gm I mtWWW o ggs o ggs o ZW o gE j gmt j pWWWW o sWW o hW j gC j ggC j EW I gs I gR o pWWWWWWW o gggh o mtW o gggE I EW I EW o RWW j gs I gp j gm o mtWWWWW
表示されている文字はフォント定義により異なる文字を表している。
1回アクセスしたときのbase64文字列からttfを作成しておき、対応する数字のフォントデータを辞書として記憶しておく。それを元に対応する数式に変換し、計算して答える。
import requests import re from fontTools.ttLib import TTFont from fontTools.pens.recordingPen import RecordingPen def make_formula(records_pen, cmap, s): f = s for code in cmap: glyph_name = cmap[code] char = glyph_set[glyph_name] recording_pen = RecordingPen() char.draw(recording_pen) idx = records_pen.index(recording_pen.value) f = f.replace(chr(code), chars[idx]) return f r = requests.get('https://hidden-island-93990.squarectf.com/ea6c95c6d0ff24545cad') body = r.text pattern = 'base64,(.+)\'\);\}.+<p>(.+)</p>.+\"token\" value=\"(\d+)\"' m = re.search(pattern, body) b64_ttf = m.group(1) sign = m.group(2) token = m.group(3) with open('tmp.ttf', 'wb') as f: f.write(b64_ttf.decode('base64')) chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '(', ')', '+', '-', '*'] f = TTFont('base.ttf') glyph_set = f.getGlyphSet() cmap = f.getBestCmap() base_list = [102, 120, 87, 111, 113, 65, 70, 89, 119, 107, 78, 98, 115, 81, 100] records_pen = [] for code in base_list: glyph_name = cmap[code] char = glyph_set[glyph_name] recording_pen = RecordingPen() char.draw(recording_pen) records_pen.append(recording_pen.value) f = TTFont('tmp.ttf') glyph_set = f.getGlyphSet() cmap = f.getBestCmap() formula = make_formula(records_pen, cmap, sign) print formula ans = eval(formula) print ans param = {'token': token, 'answer': str(ans)} r = requests.post('https://hidden-island-93990.squarectf.com/ea6c95c6d0ff24545cad', data = param) print r.text
実行結果は以下の通り。
(((((3 * (2 - 10)) * 8) + ((((3 - (10 - 9)) * 6) * ((7 + 2) - 5)) + (((9 + ((6 - 7) * (8 - (2 + (9 + 4))))) * ((2 - 6) * ((4 - 3) * 8))) - (1 - 1)))) * (((7 + 1) - 10) * (3 + 3))) - ((2 + 8) * ((((1 - 4) + (4 * (7 - (4 - 6)))) * ((3 * ((1 - 1) + 5)) + ((2 * (5 * 2)) + 1))) - ((1 * (7 * 10)) * 9)))) 2292 <html><body><p>If you can see this then you have solved the CAPTCHA.</p><p>If you have solved the CAPTCHA then you are a Charvise.</p><p>If you are a Charvise then you are welcome on Charvis.</p><p>If you are welcome on Charvis then:</p><ol><li>You can disable this system using flag-a76013167fd4c04e3134</li><li><p>You should be given useful information.</p><p>If you should be given useful information then this informs you that there are two layers of defense left, and the last one is trivial.</p></body></html>
flag-a76013167fd4c04e3134