Heap Exploitation
ref
Basic Exploit
Heap Overflow (HOF)
- giống BOF nhưng phạm vi hoạt động lại nằm trong heap
- đơn giản là ta có thể ghi đè địa chỉ ta muốn nhờ vào malloc()
ow size, fw, <win_addr> …
Attack Hook (Hook Overwrite)
Hook
cũng gần tương đồng GOT
và PLT
- nếu
GOT
là nơi chứa địa chỉ hàm để thực thi thì ta sẽ thay đổi thành system() chẳng hạn và đến PLT
sẽ thực thi cái địa chỉ chứa trong GOT
- còn với
Hook
thì sẽ nằm trong 3 hàm free(), malloc() và realloc()
–-> tương ứng là __free_hook
, __malloc_hook
và __realloc_hook
- khi thực thi 1 trong 3 free(), malloc() và realloc() thì sẽ có bước kiểm tra cá giá trị của
_hook
- mặc định giá trị của
_hook
sẽ bằng NULL , nếu giá trị chứa trong _hook
khác 0 –-> thực thi cái bên trong _hook
trước (trỏ về nội dung bên trong _hook
)
- control được
_hook
–-> điều khiển được chương trình
:bulb: idea đơn giản về Attack Hook là ghi đè system() vào __free_hook
và đến hàm free() sẽ nhận con trỏ ptr (ptr của chunk cần free() ở thanh ghi $rdi và mình chỉ cần truyền '/bin/sh' là có shell
:bulb: ngoài ra trên __malloc_hook
có 1 chunk chứa size hợp lệ là 0x70 nằm ở __malloc_hook - 35
- khi ta muốn
__malloc_hook
chứa one_gadget, thì tại chunk fake ở trên ta padding tầm 19 byte rồi 8 byte tiếp theo sẽ là one_gadget (muốn có shell ta trigger DBF)
- khi ta muốn
__malloc_hook
chứa system, thì tại chunk fake ở trên ta padding tầm 19 byte rồi 8 byte tiếp theo sẽ là system (muốn có shell ta phải tạo fake chunk chứa '/bin/sh\0' ở địa chỉ ta sẽ lấy làm size($rdi) khi malloc())
🆘 lưu ý với libc 2.34 trở đi là hook không còn tác dụng
take a look
Double free (DBF)
- DBF và UAF là 2 kĩ thuật khai thác phổ biến
CWE-415: Double Free (4.9)
- bug nằm ở sau khi ta malloc() 1 chunk và free() nó nhưng lại không xoá con trỏ sau khi free()
–-> tiếp tục free cùng 1 con trỏ
–-> tận dụng điều đó để sửa chunk
- DBF thường thấy nhất là free() cùng 1 con trỏ dẫn đến trong các bins sẽ có 2 freed_chunk trỏ lẫn nhau (thường được thấy là
loop_detected
)
- TUY NHIÊN ở những version libc khác nhau sẽ có những cách trigger DBF khác nhau
- với libc 2.26 trở xuống thì không có tcache, chỉ có fastbin thì ta cần free() 1 chunk đệm xen giữa (do cơ chế của fastbin để chặn lỗi DBF)
🎯 : DBF chủ yếu trigger để edit freed chunk và metadata
===> lần malloc() tiếp theo sẽ thay đổi 2 chunk loop lẫn nhau tạo thành 1 fake chunk
Use After Free (UAF)
- nói một cách đơn giản, UAF có nghĩa đen là khi một chunk được giải phóng và reused
- nhưng trên thực tế, đây là những tình huống sau
- Sau khi chunk được free(), con trỏ tương ứng của nó được đặt thành
NULL
, nếu được malloc() sử dụng lại –-> SIGSEGV Fault
- Sau khi chunk được free(), con trỏ tương ứng của nó KHÔNG được đặt thành
NULL
, và sau đó trước khi nó được malloc() vào lần tiếp theo, không có code nào để sửa đổi chunk này, thì program có khả năng chạy bình thường.
- Sau khi chunk được free(), con trỏ tương ứng của nó KHÔNG được đặt thành
NULL
, nhưng trước khi nó được malloc() lần sau, một số code sẽ sửa đổi chunk này, sau đó khi program reused lại chunk này, nó có khả năng xuất hiện BUG.
- các bug UAF mà thường đề cập đến chủ yếu là hai tình huống cuối
- ngoài ra, khi gọi con trỏ chunk không set thành
NULL
, sau khi được free() sẽ ở dưới dạng con trỏ lơ lửng (nằm trong các binning)
- BUG này vẫn relevant và common ngày nay
cwe mitre
🎯 : UAF chủ yếu trigger để edit freed chunk và metadata
===> lần malloc() tiếp theo sẽ tận dụng chunk cũ thể sửa lại dữ liệu mình mong muốn
- hứng thú sâu hơn thì bơi vào link này mà tìm hiểu =)))
Tcache Poisoning
- là kỹ thuật trick tcache để sửa
fd_pointer
trở về nơi mình muốn malloc() lần kế
- từ glibc 2.32 trở đi, ta phải leak thêm địa chỉ heap để fake
new_fd = target ^ (heap >> 12)
- source mẫu:
- về kỹ thuật này chỉ cần đọc source trên cũng hiểu muốn chúng ta làm cái gì
- EXAMBLE:
Off-By-One / Poison Null Byte
- là 1 bug chỉ chuẩn cho libc 2.23
- khá giống với off_by_one trong lỗi BOF nhưng ở đây là mình cố ý thêm
NULL
byte chứ không tận dụng sự tự động thêm từ 1 hàm nhập đầu vào (hoặc program tự động thêm NULL vào cuối mỗi payload)
- thì kỹ thuật này chủ yếu là ghi đè size của chunk tiếp theo thành 1 byte cuối là
NULL
–-> mục đích bypass lỗi DBF trong tcache
–-> fake được sự gộp chunks
sửa next_size và bit INUSE
ví dụ 0x111 -> 0x100
khác với chỉ sửa bit INUSE (House of Enherjar)
- ta cần fake cho chunk ta muốn DBF đã free() rồi thì bây giờ nếu có thể reallocate 1 chunk nào đó (thường là chunk trước chunk muốn trigger DBF) và tận dụng sửa size cho chunk này sau đó free() thôi
- nói trắng ra là làm cho program bị lú lẫn về PREV_SIZE khi free 1 chunk
- EXAMBLE:
House of X
- lưu ý với mỗi glibc khác nhau sẽ có cách exploit khác nhau (có hoặc không có tcache)
- chi tiết hơn xem trong how2heap
- dưới đây là cách exploit mà mình từng làm
House of Orange
- kĩ thuật này chỉ chuẩn cho libc 2.23
- kỹ thuật này là sửa size của top_chunk
- khiến đợt request malloc tiếp theo (lớn hơn size top_chunk) sẽ gọi sysmalloc (do không phân bổ từ top_chunk được)
- nôm na ta sẽ có được 1 block freed chunk (chui vô ubin) mà không cần đến hàm free()
introduce
- với các chunk bé hơn 128KB sẽ được ptmalloc2 dùng phương thức
brk
('break point' - thay đổi vùng nhớ heap)
- với các chunk lớn hơn 128KB sẽ dùng sysmalloc với phương thức
mmap
(cấp phát bộ nhớ vào một vùng nhớ khác heap)
requirements
===> với yêu cầu 1, ta chỉ sửa 1 kí tự của size
(ví dụ 0x4321 –-> resize 0x0321)
- nếu thoả mãn, gọi
_init_free
đưa vào ubin
what's next?
- nếu ta malloc thêm 1 chunk nữa sẽ báo lỗi (Aborted) rồi end
- khi call Abort, nó sẽ setup các file structure:
_IO_flush_all_lockp -> _IO_list_all -> _IO_OVERFLOW (vtable)
- rồi sử dụng
__overflow
(trong vtable) để execute phân đoạn mình muốn

mẫu cho các section trong vtable
vì *vtable là _IO_file_jumps
- ta có thể thay đổi
_IO_list_all->*vtable
(cụ thể là __overflow
trong _IO_OVERFLOW
) thành system() hoặc one_gadget
- khi này ta sử dụng ubin attack, thay bk_pointer bằng
_IO_list_all - 0x10
để khi malloc một chunk mới, nó ow địa chỉ _IO_list_all
how?
setup
note
- why 0xd8 is vtable ?
- ở
_IO_write_ptr
phải lớn hơn _IO_write_base
(đơn giản để 3 > 2)
- content trong *vtable

p64(0)*3 để padding qua __dummy
, __dummy2
và __finish
- xem thêm
- example payload if address of
_IO_list_all
is 0xfacef00d and fake vtable is at 0xcafebabe
examble
- link thêm file libc này để compile
copy libc và ld qua source trên compile
lệnh : $ gcc orange.c -o orange libc_64.so.6 ld-2.23.so
sau đó pwninit
để có thể debug được file orange_patched
House of Enherjar
demo trên glibc 2.27
- là 1 kĩ thuật Posion NULL byte (hay Off_By_one) –-> sửa bit INUSE thành NON_INUSE, đồng thời set PREV_SIZE
=> free sẽ vào ubin
==> consolidate chunk (overlapping chunk)
===> attacker could control data
what happend?

assume tcache [0x100] is filled
0x100 is not suitable in fastbins
assume next_chunk of victim is INUSE or top_chunk
next_chunk is INUSE

next_chunk is top_chunk

exploit
- ta đã có overlapping chunk –-> can control heap metadata
- trong cách khai thác ở libc 2.27, poison tcache –->
free_hook
- allocate chunk (X) từ vùng overlapped
- free X vào tcache
- edit prev_chunk để thay đổi X (next ptr) là
free_hook
- allocate chunk (trả về X), truyền tham số '/bin/sh\0'
- allocate chunk (trả về
free_hook
), truyền tham số system
- free X
examble
House of Spirit
- là một kỹ thuật malloc() 1 chunk từ stack
- khiến cho lần ow tiếp theo nằm trên stack để thực hiện ROP
overview
- khi ta setup đầy đủ cả prev_size và size cho 2 fake_chunks
- khi free() thằng đầu tiên –-> fake_chunk nằm trong bin
–-> rơi vào fastbins hoặc tcache (fastbins sẽ khó hơn vì còn các security check)
===> malloc() đến size mình muốn để reused fake_chunk
restrictions
- có 3 hạn chế ta cần thoã mãn
- tức là để tránh các lỗi như
Invalid pointer
khi free() thì fake_chunk phải là địa chỉ hợp lệ (16-byte aligned)
addr có đuôi 0x0 thay vì 0x8 ( tương tự với lỗi xmm
)
how?
- tất nhiên ở mọi bài heap, ta cần có libc
- đầu tiên cần có ubin
fillup tcache hoặc malloc() size > 0x410
- sau đó cần có con trỏ ptr trỏ về chunk VICTIM ta muốn attack
- rồi free(ptr) –-> vào bin
- lần malloc(size) tiếp theo (với size của fake_chunk) sẽ trả về con trỏ ta muốn
fastbins
- free( 0 ) –> check next_chunk ( 1 )
===> satisfy size chunk(1) from 0x10 -> 0x70
examble
tcache
- securit check sẽ không kiểm tra next_chunk
–-> miễn chỉ cần free() 1 addr hợp lệ
examble
House of Force
- là một kỹ thuật tấn công Top_chunk để lần malloc() sau đó trả về 1 con trỏ tuỳ ý
- chỉ áp dụng cho các phiên bản libc < 2.29
why?
- khi ta có khả năng ow được size của Top_chunk
–-> thay đổi thành 1 size cực đại (ví dụ)
- thì lần request malloc() tiếp theo sẽ bỏ qua mmap mà lấy không gian ngoài heap
how?
- để trả về ptr tuỳ ý mình muốn, phải xác định được offset (size) để malloc() ra
- khi malloc() với lượng size được tính như trên thì sẽ trả về con trỏ tới Top_chunk
- và malloc() tiếp theo nữa sẽ trả về ptr ta muốn
examble
- source: (lấy libc 2.27 mà run thử nhoé)