この大会は2022/12/3 16:00(JST)~2022/12/4 16:00(JST)に開催されました。
今回もチームで参戦。結果は2219点で219チーム中6位でした。
自分で解けた問題をWriteupとして書いておきます。
Discord, anyone? (Hello)
Discordに入り、#announcementsチャネルのメッセージを見ると、フラグのサンプルが記載されている。
flag{d@_Flag_m@y_b3_wr1773n_1n_1337_o_have_random_nums14344555}
2048 (Hello)
https://infosec-2048.chals.io/js/scoreboard.jsを見ると、こう書いてある。
function send_score(score) { var url = "/score.php?score=" + score; //alert("Over111 " + url); var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onload = function () { var users = JSON.parse(xhr.responseText); if (xhr.readyState == 4 && xhr.status == "200") { console.log(users); alert(users.message); //console.log(typeof users) //console.table(users); } else { console.error(users); } } xhr.send(null); }
以下のパスでアクセスできるようだ。
/score.php?score=<score>
scoreに十分高い値を指定してアクセスしてみる。
https://infosec-2048.chals.io/score.php?score=10000000000000にアクセスしたら、以下のように表示された。
{"message":"That is impossible!!!!! flag{Y0R_D4_B35T_1N_GAM35}!"}
flag{Y0R_D4_B35T_1N_GAM35}
Roll (Hello)
Unicodeで最後の籯籵籪籰の部分がflag{になると推測する。
\xe7\xb1\xaf -> f \xe7\xb1\xb5 -> l \xe7\xb1\xaa -> a \xe7\xb1\xb0 -> g \xe7\xb2\x84 -> {
このコードから差分を計算し、復号する。
#!/usr/bin/env python3 with open('ct.txt', 'rb') as f: enc = f.read() flag = '' code = b'' for i in range(len(enc)): if enc[i] != ord(' '): code += bytes([enc[i]]) else: flag += ' ' if len(code) == 3: if code[:2] == b'\xe7\xb0': flag += chr(code[2] - 0x89) elif code[:2] == b'\xe7\xb1': flag += chr(code[2] - 0x49) else: flag += chr(code[2] - 0x9) code = b'' print(flag)
復号結果は以下の通り。
Yep! This is flag{Ju5T_a_R0T8OOO}
flag{Ju5T_a_R0T8OOO}
Characters (Hello)
$ file sc.exe sc.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows
dnSpyでデコンパイルする。
using System; namespace Task1 { // Token: 0x02000002 RID: 2 internal class Program { // Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250 private static void Main(string[] args) { string text = "The flag is flag{5+r1n9Se@rCh15c00l}"; Console.WriteLine("Today's lucky number is " + text.Length.ToString()); } } }
コードにフラグが書かれていた。
flag{5+r1n9Se@rCh15c00l}
Power (Reverse)
$ file enc.exe enc.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows
dnSpyでデコンパイルする。
using System; using System.IO; using System.Linq; using System.Security.Cryptography; namespace Task2 { // Token: 0x02000002 RID: 2 internal class Program { // Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250 private static void Main(string[] args) { try { Console.WriteLine("Input three numbers separated by a space"); string text = Console.ReadLine(); string[] source = text.Split(new char[] { ' ' }); byte[] array = source.Take(3).ToArray<string>().Select(new Func<string, byte>(byte.Parse)).ToArray<byte>(); byte[] array2 = new byte[Program.keyArr.Length + array.Length]; Program.keyArr.CopyTo(array2, 0); array.CopyTo(array2, Program.keyArr.Length); Program.keyArr = array2; } catch (Exception) { return; } Program.EncryptAesManaged(); } // Token: 0x06000002 RID: 2 RVA: 0x000020F4 File Offset: 0x000002F4 private static void EncryptAesManaged() { try { string arg = Program.Decrypt(Program.encStr, Program.keyArr, Program.ivArr); Console.WriteLine("Decrypted data: {0}", arg); } catch (Exception) { } Console.ReadKey(); } // Token: 0x06000003 RID: 3 RVA: 0x0000213C File Offset: 0x0000033C private static byte[] Encrypt(string plainText, byte[] Key, byte[] IV) { byte[] result; using (AesManaged aesManaged = new AesManaged()) { ICryptoTransform transform = aesManaged.CreateEncryptor(Key, IV); using (MemoryStream memoryStream = new MemoryStream()) { using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write)) { using (StreamWriter streamWriter = new StreamWriter(cryptoStream)) { streamWriter.Write(plainText); } result = memoryStream.ToArray(); } } } return result; } // Token: 0x06000004 RID: 4 RVA: 0x000021EC File Offset: 0x000003EC private static string Decrypt(byte[] cipherText, byte[] Key, byte[] IV) { string result = null; using (AesManaged aesManaged = new AesManaged()) { ICryptoTransform transform = aesManaged.CreateDecryptor(Key, IV); using (MemoryStream memoryStream = new MemoryStream(cipherText)) { using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read)) { using (StreamReader streamReader = new StreamReader(cryptoStream)) { result = streamReader.ReadToEnd(); } } } } return result; } // Token: 0x04000001 RID: 1 private static byte[] keyArr = new byte[] { 229, 175, 85, 160, 180, 115, 120, 154, 30, 78, 53, 103, 155, 80, 62, 90, 137, 116, 133, 87, 187, 204, 178, 86, 210, 94, 26, 68, 107 }; // Token: 0x04000002 RID: 2 private static byte[] ivArr = new byte[] { 115, 98, 131, 99, 47, 40, 47, 87, 179, 158, 216, 48, 24, 250, 120, 32 }; // Token: 0x04000003 RID: 3 private static byte[] encStr = new byte[] { 40, 253, 133, 252, 45, 133, 51, 156, 73, 98, 35, 79, 215, 220, 180, 60, 213, 144, 6, 153, 177, 179, 75, 173, 220, 25, 96, 224, 154, 211, 36, 192, 25, 118, 236, 181, 196, 167, 92, 144, 114, 88, 4, 97, 98, 70, 142, 107 }; } }
既知の鍵の長さは29で、鍵の末尾の3バイトは不明。ブルートフォースでAES-CBCの復号をし、フラグを求める。
#!/usr/bin/env python3 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import itertools key = [229, 175, 85, 160, 180, 115, 120, 154, 30, 78, 53, 103, 155, 80, 62, 90, 137, 116, 133, 87, 187, 204, 178, 86, 210, 94, 26, 68, 107] iv = [115, 98, 131, 99, 47, 40, 47, 87, 179, 158, 216, 48, 24, 250, 120, 32] ct = [40, 253, 133, 252, 45, 133, 51, 156, 73, 98, 35, 79, 215, 220, 180, 60, 213, 144, 6, 153, 177, 179, 75, 173, 220, 25, 96, 224, 154, 211, 36, 192, 25, 118, 236, 181, 196, 167, 92, 144, 114, 88, 4, 97, 98, 70, 142, 107] key_base = b''.join([bytes([c]) for c in key]) iv = b''.join([bytes([c]) for c in iv]) ct = b''.join([bytes([c]) for c in ct]) for i in itertools.product(range(256), repeat=3): key = key_base + bytes(list(i)) aes = AES.new(key, AES.MODE_CBC, iv) flag = aes.decrypt(ct) if b'flag{' in flag: flag = unpad(flag, 16).decode() print(flag) break
復号結果は以下の通り。
This is flag flag{EnCryp+10n1257}
flag{EnCryp+10n1257}
factorization_O(1) (Crypto)
factordbでnを素因数分解する。
n = p * q p = 28202319379067501490208047967640223972527628887419121174312069871940762446191037116439778835467062167539975479560808963430713316728657821318091974782177587502977103133562514623190596522401979853897604155978389706065529684964456763671042793097939248363898812226603785142150229952531892483051629343135565017009158169764295572681006147649784770674916373016362742532035032732868305514205472359902274368791400942198050857316423038645187897952552037443809257021152791177709722355552601158212401832663511665683464894274793964666346178953264175424315896294371414865822376705827230948258710334712677553560281415434518989952471 q = 28333216511870237421589680024071479872631128138375694843111245571640079138257082118876936956971626411452714765213235443289844152774726549033287121226853278300931463492944993855419809383702713698075445228165386842813887219933628001930562735500058366809127085676796448036043203541946679120144936025581244148455814036684278066340551598448554713707153731347377919923587961088259591283082595708873570902595973478098800267688164730212308010448744922341861881129811220876277126950484628192066291808850039047023547793278380316222945284141918720028654120002784669476398893169403625089712271909636614227133160810905936848329339
素因数分解できたので、あとは通常通り復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * with open('task.txt', 'r') as f: params = f.read().splitlines() e = int(params[0].split(' ')[-1], 16) n = int(params[1].split(' ')[-1], 16) ct = int(params[2].split(' ')[-1], 16) p = 28202319379067501490208047967640223972527628887419121174312069871940762446191037116439778835467062167539975479560808963430713316728657821318091974782177587502977103133562514623190596522401979853897604155978389706065529684964456763671042793097939248363898812226603785142150229952531892483051629343135565017009158169764295572681006147649784770674916373016362742532035032732868305514205472359902274368791400942198050857316423038645187897952552037443809257021152791177709722355552601158212401832663511665683464894274793964666346178953264175424315896294371414865822376705827230948258710334712677553560281415434518989952471 q = 28333216511870237421589680024071479872631128138375694843111245571640079138257082118876936956971626411452714765213235443289844152774726549033287121226853278300931463492944993855419809383702713698075445228165386842813887219933628001930562735500058366809127085676796448036043203541946679120144936025581244148455814036684278066340551598448554713707153731347377919923587961088259591283082595708873570902595973478098800267688164730212308010448744922341861881129811220876277126950484628192066291808850039047023547793278380316222945284141918720028654120002784669476398893169403625089712271909636614227133160810905936848329339 assert n == p * q phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(ct, d, n) flag = long_to_bytes(m).decode() print(flag)
flag{f4c70rdb_c4n_h3lp_y0u_50lv3_r54_cryp705y573m_745k5}
Vernam (Crypto)
keyはflagと同じ長さ。暗号の1行目はflagとkeyの分だけシフトしたもの。暗号の2行目はflagを右に6シフトしたものとkeyの分だけシフトしたもの。
フラグは"flag{"から始まり、"}"で終わることからkeyの6バイトは割り出せる。さらにflagを右に6シフトしたものからkeyの別の6バイトを割り出せる。割り出したkeyから次のフラグの6バイトを割り出せる。これを繰り返せば、フラグを割り出せる。
#!/usr/bin/env python3 with open('task.txt', 'r') as f: encs = f.read().splitlines() c0 = bytes.fromhex(encs[0]) c1 = bytes.fromhex(encs[1]) flag = list(b'flag{******************************************************}') assert len(flag) == len(c0) key = [0] * len(c0) for i in range(len(c0) // 6 - 1): for j in range(6): key[j - i * 6 - 1] = (c0[j - i * 6 - 1] - flag[j - i * 6 - 1]) % 256 flag[j -i * 6 - 7] = (c1[j - i * 6 - 1] - key[j - i * 6 - 1]) % 256 flag = ''.join([chr(c) for c in flag]) print(flag)
flag{u53_0f_53cr37_k3y_f0r_ww3rn4m_c1ph3r_7w1c3_15_un53cur3}
Never gets old (Crypto)
サーバの処理概要は以下の通り。
・e = 3 ・n: 既知固定値 ・flag: フラグの数値化したもの ・以下繰り返し ・cur_time: UNIXTIMEスタンプ ・m = flag + cur_time ・enc_flag = pow(m, e, n) ・enc_flag表示 ・5秒スリープ
最初の2つのメッセージの差は5とわかっているので、UNIXTIMEスタンプの値がプラスされていることを踏まえ、Franklin-Reiter Related Message Attackで復号する。
#!/usr/bin/env sage import socket import arrow from Crypto.Util.number import * def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) 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] e = 3 n = 56751557236771291682484925205552213395017286856788424728477520869245312923063269575813709372701501266411744107612661617541524170940980758483006610928802060405295040733651568454102696982761234303408607315598889877531472782169525357044937048595117628739355131854220684649309005299064732402206958720387916062449 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('0.cloud.chals.io', 33644)) cur_time = int(arrow.utcnow().timestamp()) diff = 5 data = recvuntil(s, b'\n').rstrip() print(data) c1 = int(data) data = recvuntil(s, b'\n').rstrip() print(data) c2 = int(data) m = int(related_message_attack(c1, c2, diff, e, n)) - cur_time flag = long_to_bytes(m).decode() print(flag)
実行結果は以下の通り。
21054947296945995969188084273175305225334151857723476430540329863698100770359064848331953424161541910154106963594897630059176641956261830307496784230415312468289781039574212563635055810037457682836432822086417 21054947296945995969188084273175305225334151857723476430540329863698215144373519321856619961511537512471055359686360218045850972349978373506547041374157860067287279945158090263184038065455692391435833338524952 flag{r5a_7a5k5_n3v3r_g37_0ld}
flag{r5a_7a5k5_n3v3r_g37_0ld}
Love and Matrices (Crypto)
フラグの各ビットが立っていた場合に行列 y = x * z (zの行列式は1で固定) を計算している。上位2ビット目は立っていることからzは算出でき、各ビットのx, yはわかっているので、zが一致する場合に0としてフラグを復号する。
#!/usr/bin/env sage import numpy enc = numpy.load('enc.npy', allow_pickle=True) x = enc[1][0] y = enc[1][1] z = x.inverse() * y bin_flag = '' for c in enc: x = c[0] y = c[1] if z == x.inverse() * y: bin_flag += '1' else: bin_flag += '0' flag = ''.join([chr(int(bin_flag[i:i+8], 2)) for i in range(0, len(bin_flag), 8)]) print(flag)
flag{M4tr1ce5_4re_8E4UT1fUl}