(writeup) WolvCTF2023
Echo2


- 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
- 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
- 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
- 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

WTML





- kiểm tra format chuỗi nhập vào

- 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
- 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ử
- 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,



- 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)

- ủ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


#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"
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;
map flag_map = {0};
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) {
}
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];
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;
}
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]++;
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



- ở 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ì …
- 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
