以下の内容はhttps://yocchin.hatenablog.com/entry/2024/09/03/081310より取得しました。


CyberSpace CTF 2024 Writeup

この大会は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)

jadx-guiデコンパイルする。

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}



以上の内容はhttps://yocchin.hatenablog.com/entry/2024/09/03/081310より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14