この大会は2024/8/31 1:00(JST)~2024/9/2 1:00(JST)に開催されました。
今回もチームで参戦。結果は450点で830チーム中236位でした。
自分で解けた問題をWriteupとして書いておきます。
Modulus RSA (beginner)
暗号化処理の概要は以下の通り。
・m: フラグを数値化したもの ・p, q, r: 3つの0以上1<<128以下のランダム素数をソートしたもの ・n = p * q * r ・e = 65537 ・c = pow(m, e, n) ・w, x, y = q % p, r % p, r % q ・w, x, y, cを出力
この処理から以下のように表せる。
q = p * X + w r = p * Y + x r = q * Z + y
rは以下の2通りで表せる。
r = p * Y + x r = (p * X + w) * Z + y
このことからr % pは以下の2通りで表せる。
r % p = x r % p = (w * Z + y) % p
Zが1の場合、w + y - xはpで割り切れる。計算すると、素数になるので、pであると推測できる。
X, Yについても小さい数字であると推測して、条件が合うものを探す。あとは通常通り、復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * e = 65537 w = 115017953136750842312826274882950615840 x = 16700949197226085826583888467555942943 y = 20681722155136911131278141581010571320 c = 2246028367836066762231325616808997113924108877001369440213213182152044731534905739635043920048066680458409222434813 assert isPrime(w + y - x) assert w + y - x < 1<<128 p = w + y - x print('[+] p =', p) assert isPrime(p + w) assert p + w < 1<<128 q = p + w print('[+] q =', q) for i in range(1, 10): r = p * i + x r2 = q + y if r == r2: break assert isPrime(r) assert r < 1<<128 print('[+] r =', r) n = p * q * r phi = (p - 1) * (q - 1) * (r - 1) d = inverse(e, phi) m = pow(c, d, n) flag = long_to_bytes(m).decode() print('[*] flag:', flag)
実行結果は以下の通り。
[+] p = 118998726094661667617520527996405244217
[+] q = 234016679231412509930346802879355860057
[+] r = 254698401386549421061624944460366431377
[*] flag: CSCTF{i_l1k3_m0du1us_v3ry_MUCH!!^_^}
CSCTF{i_l1k3_m0du1us_v3ry_MUCH!!^_^}
cipher block clock (beginner)
フラグと既知の文の2つに対して、AES CBCモード暗号化を行っている。それぞれ暗号鍵は分かっており、共通するivがわかっていない。
既知の暗号文からivを"\x00"*16にして復号した結果の1ブロック目と平文の1ブロック目をXORすれば、ivになる。あとはそのIVを使って、フラグを復号すればよい。
#!/usr/bin/env python3 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad from Crypto.Util.strxor import strxor with open('out.txt', 'r') as f: params = f.read().splitlines() key0 = bytes.fromhex(params[0].split(' ')[-1]) ct0 = bytes.fromhex(params[1].split(' ')[-1]) key1 = bytes.fromhex(params[3].split(' ')[-1]) ct1 = bytes.fromhex(params[4].split(' ')[-1]) pt1 = b"Lookie here, someone thinks that this message is unsafe, well I'm sorry to be the bearer of bad news but tough luck; the ciphertext is encrypted with MILITARY grade encryption. You're done kiddo." iv = b'\x00' * 16 aes1 = AES.new(key1, AES.MODE_CBC, iv) tmp_pt1 = unpad(aes1.decrypt(ct1), 32) assert pt1[16:] == tmp_pt1[16:] iv = strxor(pt1[:16], tmp_pt1[:16]) aes0 = AES.new(key0, AES.MODE_CBC, iv) flag = unpad(aes0.decrypt(ct0), 32).decode() print(flag)
CSCTF{d0nt_m1x_up_y0ur_1v_and_k3ys}
encryptor (beginner)
package com.example.encryptor; import android.app.AlertDialog; import android.content.Context; import android.content.res.AssetManager; import android.os.Build; import android.os.Bundle; import android.util.Base64; import android.view.View; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; /* loaded from: classes.dex */ public class MainActivity extends AppCompatActivity { AlertDialog.Builder builder; /* JADX INFO: Access modifiers changed from: protected */ @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity public void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(R.layout.activity_main); this.builder = new AlertDialog.Builder(this); } private String getKey() { return new String(Base64.decode("ZW5jcnlwdG9yZW5jcnlwdG9y".getBytes(), 0)); } private String encryptText(String str) throws InvalidKeyException, UnsupportedEncodingException, NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException { SecretKeySpec secretKeySpec = new SecretKeySpec(getKey().getBytes("UTF-8"), "Blowfish"); Cipher cipher = Cipher.getInstance("Blowfish"); if (cipher == null) { throw new Error(); } cipher.init(1, secretKeySpec); return Build.VERSION.SDK_INT >= 26 ? new String(Base64.encode(cipher.doFinal(str.getBytes("UTF-8")), 0)) : ""; } public void encrypt_onClick(View view) throws UnsupportedEncodingException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { this.builder.setMessage(encryptText(((TextView) findViewById(R.id.input)).getText().toString())).setCancelable(true); AlertDialog create = this.builder.create(); create.setTitle("Here's your encrypted text:"); create.show(); View findViewById = create.findViewById(16908299); if (findViewById instanceof TextView) { ((TextView) findViewById).setTextIsSelectable(true); } } public static String readAssetFile(Context context, String str) { AssetManager assets = context.getAssets(); StringBuilder sb = new StringBuilder(); try { InputStream open = assets.open(str); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(open)); while (true) { try { String readLine = bufferedReader.readLine(); if (readLine == null) { break; } sb.append(readLine).append('\n'); } catch (Throwable th) { try { bufferedReader.close(); } catch (Throwable th2) { th.addSuppressed(th2); } throw th; } } bufferedReader.close(); if (open != null) { open.close(); } } catch (IOException e) { e.printStackTrace(); } return sb.toString(); } public void getflag_onClick(View view) { this.builder.setMessage(readAssetFile(this, "enc.txt")).setCancelable(true); AlertDialog create = this.builder.create(); create.setTitle("Here's the encrypted flag:"); create.show(); View findViewById = create.findViewById(16908299); if (findViewById instanceof TextView) { ((TextView) findViewById).setTextIsSelectable(true); } } }
getKeyメソッドにあるbase64文字列をデコードする。
$ echo ZW5jcnlwdG9yZW5jcnlwdG9y | base64 -d encryptorencryptor
これが鍵となって、Blowfish暗号で暗号化される。
Resources/assets/enc.txtを見ると、以下のようになっている。
OIkZTMehxXAvICdQSusoDP6Hn56nDiwfGxt7w/Oia4oxWJE3NVByYnOMbqTuhXKcgg50DmVpudg=
これをbase64デコードし、先ほどの鍵でBlowfish暗号の復号をすればよい。
#!/usr/bin/env python3 from Crypto.Cipher import Blowfish from Crypto.Util.Padding import unpad from base64 import * key = b'encryptorencryptor' ct = 'OIkZTMehxXAvICdQSusoDP6Hn56nDiwfGxt7w/Oia4oxWJE3NVByYnOMbqTuhXKcgg50DmVpudg=' ct = b64decode(ct) cipher = Blowfish.new(key, Blowfish.MODE_ECB) flag = unpad(cipher.decrypt(ct), Blowfish.block_size).decode() print(flag)
CSCTF{3ncrypt0r_15nt_s4Fe_w1th_4n_h4Rdc0d3D_k3y!}
key (beginner)
Ghidraでデコンパイルする。
undefined8 main(void) { size_t sVar1; long in_FS_OFFSET; uint local_140; int aiStack_138 [32]; int local_b8 [4]; undefined4 local_a8; undefined4 local_a4; undefined4 local_a0; undefined4 local_9c; undefined4 local_98; undefined4 local_94; undefined4 local_90; undefined4 local_8c; undefined4 local_88; undefined4 local_84; undefined4 local_80; undefined4 local_7c; undefined4 local_78; undefined4 local_74; undefined4 local_70; undefined4 local_6c; undefined4 local_68; undefined4 local_64; undefined4 local_60; undefined4 local_5c; undefined4 local_58; undefined4 local_54; undefined4 local_50; undefined4 local_4c; undefined4 local_48; undefined4 local_44; undefined4 local_40; undefined4 local_3c; char local_38 [40]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); local_b8[0] = 0x43; local_b8[1] = 0xa4; local_b8[2] = 0x41; local_b8[3] = 0xae; local_a8 = 0x42; local_a4 = 0xfc; local_a0 = 0x73; local_9c = 0xb0; local_98 = 0x6f; local_94 = 0x72; local_90 = 0x5e; local_8c = 0xa8; local_88 = 0x65; local_84 = 0xf2; local_80 = 0x51; local_7c = 0xce; local_78 = 0x20; local_74 = 0xbc; local_70 = 0x60; local_6c = 0xa4; local_68 = 0x6d; local_64 = 0x46; local_60 = 0x21; local_5c = 0x40; local_58 = 0x20; local_54 = 0x5a; local_50 = 0x2c; local_4c = 0x52; local_48 = 0x2d; local_44 = 0x5e; local_40 = 0x2d; local_3c = 0xc4; printf("Enter the key: "); __isoc99_scanf(&DAT_00102014,local_38); sVar1 = strlen(local_38); if ((int)sVar1 == 0x20) { for (local_140 = 0; ((int)local_140 < 0x20 && (aiStack_138[(int)local_140] = ((int)local_38[(int)local_140] ^ local_140) * ((int)local_140 % 2 + 1), aiStack_138[(int)local_140] == local_b8[(int)local_140])); local_140 = local_140 + 1) { if (local_140 == 0x1f) { printf("Success!"); } } } else { printf("Denied Access"); } if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) { return 0; } /* WARNING: Subroutine does not return */ __stack_chk_fail(); }
フラグは以下の条件を満たすことがわかる。
・長さが0x20 ・各インデックスの文字のASCIIコードが以下を満たす。 (flag[i] ^ i) * (i % 2 + 1) == local_b8[0]以降の各値
この条件を元にフラグを求める。
#!/usr/bin/env python3 ct = [0x43, 0xa4, 0x41, 0xae, 0x42, 0xfc, 0x73, 0xb0, 0x6f, 0x72, 0x5e, 0xa8, 0x65, 0xf2, 0x51, 0xce, 0x20, 0xbc, 0x60, 0xa4, 0x6d, 0x46, 0x21, 0x40, 0x20, 0x5a, 0x2c, 0x52, 0x2d, 0x5e, 0x2d, 0xc4] flag = '' for i in range(len(ct)): flag += chr((ct[i] // (i % 2 + 1)) ^ i) print(flag)
CSCTF{u_g0T_it_h0OrAy6778462123}
Sanity Check (misc)
Discordに入り、#sponsorsチャネルのトピックを見ると、フラグが書いてあった。
CSCTF{54n17y_5ucc355fully_ch3ck3d!}
🅰️ 🦵 🌱 (sponsor)
問題文にある以下のサイトにあるバグ報奨金プログラムのうち、タイトルの絵文字がどれを指しているかを答える問題。
https://go.intigriti.com/programs
真ん中の足、"leg"を頼りに検索してみると、以下が見つかる。
Allegro
Aのマークとも一致するので、これでSubmitすると、フラグが通った。
CSCTF{Allegro}
Social Distancing (forensics)
フォルダ構成からWindows Defenderで隔離されてもののようだ。
https://reversingfun.com/posts/how-to-extract-quarantine-files-from-windows-defender/を確認する。
カスタムRC4暗号で暗号化されているようで、鍵も記載されている。このことを元に3つのデータを復号する。
#!/usr/bin/env python3 from Crypto.Cipher import ARC4 key = [ 0x1E, 0x87, 0x78, 0x1B, 0x8D, 0xBA, 0xA8, 0x44, 0xCE, 0x69, 0x70, 0x2C, 0x0C, 0x78, 0xB7, 0x86, 0xA3, 0xF6, 0x23, 0xB7, 0x38, 0xF5, 0xED, 0xF9, 0xAF, 0x83, 0x53, 0x0F, 0xB3, 0xFC, 0x54, 0xFA, 0xA2, 0x1E, 0xB9, 0xCF, 0x13, 0x31, 0xFD, 0x0F, 0x0D, 0xA9, 0x54, 0xF6, 0x87, 0xCB, 0x9E, 0x18, 0x27, 0x96, 0x97, 0x90, 0x0E, 0x53, 0xFB, 0x31, 0x7C, 0x9C, 0xBC, 0xE4, 0x8E, 0x23, 0xD0, 0x53, 0x71, 0xEC, 0xC1, 0x59, 0x51, 0xB8, 0xF3, 0x64, 0x9D, 0x7C, 0xA3, 0x3E, 0xD6, 0x8D, 0xC9, 0x04, 0x7E, 0x82, 0xC9, 0xBA, 0xAD, 0x97, 0x99, 0xD0, 0xD4, 0x58, 0xCB, 0x84, 0x7C, 0xA9, 0xFF, 0xBE, 0x3C, 0x8A, 0x77, 0x52, 0x33, 0x55, 0x7D, 0xDE, 0x13, 0xA8, 0xB1, 0x40, 0x87, 0xCC, 0x1B, 0xC8, 0xF1, 0x0F, 0x6E, 0xCD, 0xD0, 0x83, 0xA9, 0x59, 0xCF, 0xF8, 0x4A, 0x9D, 0x1D, 0x50, 0x75, 0x5E, 0x3E, 0x19, 0x18, 0x18, 0xAF, 0x23, 0xE2, 0x29, 0x35, 0x58, 0x76, 0x6D, 0x2C, 0x07, 0xE2, 0x57, 0x12, 0xB2, 0xCA, 0x0B, 0x53, 0x5E, 0xD8, 0xF6, 0xC5, 0x6C, 0xE7, 0x3D, 0x24, 0xBD, 0xD0, 0x29, 0x17, 0x71, 0x86, 0x1A, 0x54, 0xB4, 0xC2, 0x85, 0xA9, 0xA3, 0xDB, 0x7A, 0xCA, 0x6D, 0x22, 0x4A, 0xEA, 0xCD, 0x62, 0x1D, 0xB9, 0xF2, 0xA2, 0x2E, 0xD1, 0xE9, 0xE1, 0x1D, 0x75, 0xBE, 0xD7, 0xDC, 0x0E, 0xCB, 0x0A, 0x8E, 0x68, 0xA2, 0xFF, 0x12, 0x63, 0x40, 0x8D, 0xC8, 0x08, 0xDF, 0xFD, 0x16, 0x4B, 0x11, 0x67, 0x74, 0xCD, 0x0B, 0x9B, 0x8D, 0x05, 0x41, 0x1E, 0xD6, 0x26, 0x2E, 0x42, 0x9B, 0xA4, 0x95, 0x67, 0x6B, 0x83, 0x98, 0xDB, 0x2F, 0x35, 0xD3, 0xC1, 0xB9, 0xCE, 0xD5, 0x26, 0x36, 0xF2, 0x76, 0x5E, 0x1A, 0x95, 0xCB, 0x7C, 0xA4, 0xC3, 0xDD, 0xAB, 0xDD, 0xBF, 0xF3, 0x82, 0x53 ] key = bytes(key) DIR = 'Quarantine/' files = [ 'Entries/{80008A1B-0000-0000-7091-E5797219933B}', 'ResourceData/95/957997B71FBF912F2A3E881A13A83E0FAB3ECB47', 'Resources/95/957997B71FBF912F2A3E881A13A83E0FAB3ECB47' ] out_files = ['entries', 'resource_data', 'resources'] for i in range(len(files)): with open(DIR + files[i], 'rb') as f: ct = f.read() cipher = ARC4.new(key) pt = cipher.decrypt(ct) with open(out_files[i], 'wb') as f: f.write(pt)
resource_dataに以下のデータが含まれていることがわかった。
$hidden = @" UEsDBAoAAAAAAOCYuCg8z1FoRAAAAEQAAAAJABwAZWljYXIuY29tVVQJAAOUYCw5y1zNZnV4CwAB BAAAAAAEAAAAAFg1TyFQJUBBUFs0XFBaWDU0KFBeKTdDQyk3fSRFSUNBUi1TVEFOREFSRC1BTlRJ VklSVVMtVEVTVC1GSUxFISRIK0gqUEsDBAoAAAAAAE8HG1mJ3nc0MQAAADEAAAAEABwAZmxhZ1VU CQAD9VzNZtVczWZ1eAsAAQQAAAAABAAAAABDU0NURnt5MHVfdW4tcXU0cmFudDFuM2RfbXlfc2Ny MVB0IV8weDkxYTNlZGZmNn0KUEsBAh4DCgAAAAAA4Ji4KDzPUWhEAAAARAAAAAkAGAAAAAAAAQAA AKSBAAAAAGVpY2FyLmNvbVVUBQADlGAsOXV4CwABBAAAAAAEAAAAAFBLAQIeAwoAAAAAAE8HG1mJ 3nc0MQAAADEAAAAEABgAAAAAAAEAAACkgYcAAABmbGFnVVQFAAP1XM1mdXgLAAEEAAAAAAQAAAAA UEsFBgAAAAACAAIAmQAAAPYAAAAAAA== "@ $decodedBytes = [System.Convert]::FromBase64String($hidden) $zipFilePath = "malicious.zip" [System.IO.File]::WriteAllBytes($zipFilePath, $decodedBytes) Write-Output "File saved as $zipFilePath"
base64データをデコードして、zipとして保存する。
#!/usr/bin/env python3 from base64 import * hidden = ''' UEsDBAoAAAAAAOCYuCg8z1FoRAAAAEQAAAAJABwAZWljYXIuY29tVVQJAAOUYCw5y1zNZnV4CwAB BAAAAAAEAAAAAFg1TyFQJUBBUFs0XFBaWDU0KFBeKTdDQyk3fSRFSUNBUi1TVEFOREFSRC1BTlRJ VklSVVMtVEVTVC1GSUxFISRIK0gqUEsDBAoAAAAAAE8HG1mJ3nc0MQAAADEAAAAEABwAZmxhZ1VU CQAD9VzNZtVczWZ1eAsAAQQAAAAABAAAAABDU0NURnt5MHVfdW4tcXU0cmFudDFuM2RfbXlfc2Ny MVB0IV8weDkxYTNlZGZmNn0KUEsBAh4DCgAAAAAA4Ji4KDzPUWhEAAAARAAAAAkAGAAAAAAAAQAA AKSBAAAAAGVpY2FyLmNvbVVUBQADlGAsOXV4CwABBAAAAAAEAAAAAFBLAQIeAwoAAAAAAE8HG1mJ 3nc0MQAAADEAAAAEABgAAAAAAAEAAACkgYcAAABmbGFnVVQFAAP1XM1mdXgLAAEEAAAAAAQAAAAA UEsFBgAAAAACAAIAmQAAAPYAAAAAAA== ''' zip = b64decode(hidden) with open('hidden.zip', 'wb') as f: f.write(zip)
このzipファイルを解凍すると、eicar.comとflagが展開される。展開されたflagにフラグが書いてあった。
CSCTF{y0u_un-qu4rant1n3d_my_scr1Pt!_0x91a3edff6}
ezcoppersmith (crypto)
暗号化処理の概要は以下の通り。
・flag: ランダム70バイト文字列 + フラグ + ランダム70バイト文字列 ・flag: flagを数値化したもの ・e = 0x10001 ・p: 1024ビット素数 ・q: p + ランダム512ビット整数の次の素数 ・n = p * q ・ct = pow(flag,e,n) ・n, ctを出力
qはpに512ビットの整数をプラスしているだけなので、繰上りを考えると、上位511ビットは同じ。nの平方根の上位511ビットはpの上位511ビットと同じなので、Coppersmithの定理で割り出せる。あとは通常通り、復号すればよい。
#!/usr/bin/env sage from Crypto.Util.number import * from gmpy2 import * e = 0x10001 n = 18644771606497209714095542646224677588981048892455227811334258151262006531336794833359381822210403450387218291341636672728427659163488688386134401896278003165147721355406911673373424263190196921309228396204979060454870860816745503197616145647490864293442635906688253552867657780735555566444060335096583505652012496707636862307239874297179019995999007369981828074059533709513179171859521707075639202212109180625048226522596633441264313917276824985895380863669296036099693167611788521865367087640318827068580965890143181999375133385843774794990578010917043490614806222432751894223475655601237073207615381387441958773717 ct = 814602066169451977605898206043894866509050772237095352345693280423339237890197181768582210420699418615050495985283410604981870683596059562903004295804358339676736292824636301426917335460641348021235478618173522948941541432284037580201234570619769478956374067742134884689871240482950578532380612988605675957629342412670503628580284821612200740753343166428553552463950037371300722459849775674636165297063660872395712545246380895584677099483139705934844856029861773030472761407204967590283582345034506802227442338228782131928742229041926847011673393223237610854842559028007551817527116991453411203276872464110797091619 pbits = 1024 kbits = 511 pbar = int(iroot(n, 2)[0]) & (2^pbits - 2^kbits) PR.<x> = PolynomialRing(Zmod(n)) f = x + pbar p0 = f.small_roots(X=2^kbits, beta=0.3)[0] p = int(p0 + pbar) q = n // p phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(ct, d, n) flag = long_to_bytes(int(m))[70:-70].decode() print(flag)
CSCTF{c0pp3rsm1th_1s_a_m4th_g0d_n0_d0ub7}
flagprinter (crypto)
スクリプトは以下のようになっていて、普通に実行すると、だんだん時間がかかり、フラグ全体はなかなか表示されない。
from out import enc, R from math import prod flag = '' a = [0] for i in range(355): b = [_+1 for _ in a] c = [_+1 for _ in b] a += b + c if i%5 == 0: flag += chr(enc[i//5] ^ prod([a[_] for _ in R[i//5]])) print(flag)
高速化する必要がある。このためにはRが示すaのインデックスの値が算出できれば良い。
i = 0の場合
a = [0, 1, 2]
i = 1の場合
a = [0, 1, 2, 1, 2, 3, 2, 3, 4]
i = 2の場合
a = [0, 1, 2, 1, 2, 3, 2, 3, 4, 1, 2, 3, 2, 3, 4, 3, 4, 5, 2, 3, 4, 3, 4, 5, 4, 5, 6]
i = 3の場合
a = [0, 1, 2, 1, 2, 3, 2, 3, 4, 1, 2, 3, 2, 3, 4, 3, 4, 5, 2, 3, 4, 3, 4, 5, 4, 5, 6,
1, 2, 3, 2, 3, 4, 3, 4, 5, 2, 3, 4, 3, 4, 5, 4, 5, 6, 3, 4, 5, 4, 5, 6, 5, 6, 7,
2, 3, 4, 3, 4, 5, 4, 5, 6, 3, 4, 5, 4, 5, 6, 5, 6, 7, 4, 5, 6, 5, 6, 7, 6, 7, 8]3進数を考えて見てみる。
00(0000) -> 0 01(0001) -> 1 02(0002) -> 2 03(0010) -> 1 04(0011) -> 2 05(0012) -> 3 06(0020) -> 2 07(0021) -> 3 08(0022) -> 4 09(0100) -> 1 10(0101) -> 2 11(0102) -> 3 12(0110) -> 2 13(0111) -> 3 14(0112) -> 4 15(0120) -> 3 16(0121) -> 4 17(0122) -> 5 18(0200) -> 2 19(0201) -> 3 20(0202) -> 4 21(0210) -> 3 22(0211) -> 4 23(0212) -> 5 24(0220) -> 4 25(0221) -> 5 26(0222) -> 6 27(1000) -> 1 28(1001) -> 2 29(1002) -> 3 30(1010) -> 2 31(1011) -> 3 32(1012) -> 4 33(1020) -> 3 34(1021) -> 4 35(1022) -> 5 36(1100) -> 2 37(1101) -> 3 38(1102) -> 4 39(1110) -> 3 40(1111) -> 4 41(1112) -> 5 42(1120) -> 4 43(1121) -> 5 44(1122) -> 6 45(1200) -> 3 46(1201) -> 4 47(1202) -> 5 48(1210) -> 4 49(1211) -> 5 50(1212) -> 6 51(1220) -> 5 52(1221) -> 6 53(1222) -> 7 54(2000) -> 2 55(2001) -> 3 56(2002) -> 4 57(2010) -> 3 58(2011) -> 4 59(2012) -> 5 60(2020) -> 4 61(2021) -> 5 62(2022) -> 6 63(2100) -> 3 64(2101) -> 4 65(2102) -> 5 66(2110) -> 4 67(2111) -> 5 68(2112) -> 6 69(2120) -> 5 70(2121) -> 6 71(2122) -> 7 72(2200) -> 4 73(2201) -> 5 74(2202) -> 6 75(2210) -> 5 76(2211) -> 6 77(2212) -> 7 78(2220) -> 6 79(2221) -> 7 80(2222) -> 8
これを見るとわかるが、3進数の各桁の合計値がRが示すaのインデックスの値になる。これを元にXORの鍵を求め、フラグを復号する。
#!/usr/bin/env python3 from out import enc, R from math import prod def base3(n): if n == 0: return '0' b = '' while n: b += str(n % 3) n //= 3 return b[::-1] def get_a_val(n): s = base3(n) return sum(list(map(int, list(s)))) flag = '' for i in range(len(R)): flag += chr(enc[i] ^ prod([get_a_val(_) for _ in R[i]])) print(flag)
CSCTF{2441d2f22fa1d5b9fc0a850dcfbbccf608cafbc22a1beaae0ac328ff6d218781}