Đây là bài heap mình chơi tuần trước nhưng không làm được.Tìm đọc writeup vậy 😁😁😁 Qua bài này mình học được rất nhiều kiến thức mới.Do dạo này phải đi học + đi làm nên cũng ko có nhiều thời gian rảnh lắm. Mỗi hôm update tí để hoàn thiện bài này. Hope cuối tuần là xong để chiến giải tiếp.

Phân tích tĩnh IDA

Chương trình rất dễ dịch ngược. Nó có ba hàm cơ bản :

hinh1

Nhưng cũng chính vì đơn giản nên tấn công nó cũng rất phức tạp và đòi hỏi nhiều kĩ thuật.

Double Free

Chúng ta hoàn toàn có thể free một chunk 2 lần liên tiếp. Dù cho con trỏ heap trong node đã bị xóa nhưng vẫn còn con trỏ trong ptr. Đây là một lỗi chúng ta có thể tận dụng.Do trên bản libc 2.27 đã có tcache, tất cả các chunk có size < 0x410 đều được đưa vào tcache bins . Tcache bin sẽ bỏ qua tất cả các security check nên không bị lỗi double free.
Khi tiến hành add 1 node thì heap trở thành :

Tức là ta đã có 1 fastbin. Sau khi double free thì chunk được đặt vào tcache bins.

  • Tcache bins : A -> A

Ta thấy như trên hình thì con trỏ FD trỏ vào chính A.Ta có single linked list. Con trỏ FD này lại vô tình nằm luôn trong phần Userdata của chunk cũ. Sau đó nếu ta tiến hành malloc thì hàm malloc sẽ cấp phát cho chúng ta bộ nhớ tại địa chỉ của A. Chúng ta có quyền sửa đổi FD thành bất kì địa chỉ nào chúng ta muốn. Và tcache bins sẽ trở thành :

  • tcache bins : A -> target_addr

Nhưng chúng ta chưa biết địa chỉ nào vì có PIE mà lại không có hàm nào để leak địa chỉ. Lưu ý một điểm là :

ASLR không thay đổi 3 byte cuối của địa chỉ.  

Tức là 3 byte cuối cùng của địa chỉ heap trong con trỏ FD là không đổi. Vì vậy ta có thể chọn target là địa chỉ heap bằng cách sửa đổi 3 byte này.

Leak libc

Fake unsorted bin

Chúng ta chỉ được quyền malloc fastbin. Nhưng với fastbin thì không thể leak được địa chỉ libc. Vì vậy chúng ta cần tới unsorted bin. Từ cách ghi đè địa chỉ được tìm thấy ở trên, ta sẽ ghi đè lên byte cuối địa chỉ của A thành A_addr-0x10 để nó malloc về vùng nhớ overlap được phần size, pre_size. Ở đây ta tiến hành sửa byte đó từ 0x51 -> 0x91. Ta được 1 chunk unsorted bin.Nhưng đối với unsorted bin thì các cơ chế bảo vệ được check hết sức chặt chẽ. 🌝🌝🌝 Cho nên cần fake một số chunk ở sau để pass các cơ chế bảo vệ. Cụ thể là :

add("a")      # 0  
add("b" * 0x30 + p64(0) + p64(0x51))    # 1  
add("c" * 0x30 + p64(0) + p64(0x1))     # 2 
# hai chunk B, C là để pass được security check về sau
delete(2) 
delete(1) 
delete(0) 
delete(0) 
# tcache bins[4] : 0x556a2e57e670 <- 0x556a2e57e670

# allocate to fix size 
add("\x70")       # tcache bins[3] : 0x556a2e57e670 <- 0x556a2e57e670
add("\x60")       # tcache bins[2] : 0x556a2e57e670 <- 0x556a2e57e660
add("\x60")       # tcache bins[1] : 0x556a2e57e660 
add("\x00")       # malloc to 0x556a2e57e660

Có thể debug để thấy rõ được điều trên.Cách để bypass PIE.
Free chunk 1,2 chỉ mang tính hình thức để việc malloc chunk phía sau thuận lợi. Cũng có thể chỉ free 1 chunk thì bớt 1 add, hoặc ko free chunk nào thì vẫn phải add 3 chunk mới thì mới vô đc cái 0x556a2e57e660. 😬😬😬 Rất magic nhưng cứ debug mà nhìn thôi.
Sau khi vào được chunk như trên ta tiến hành overwrite size :

add(p64(0) + p64(0x91)) 

Và kết quả :

Giờ khi free cái unsorted bin này thì chunk B, C là nhân tố gank tem để bypass được security check. Chunk C để bypass double free(!prev) :

Vì khi size của chunk A được fix thành 0x91 thì nextchunk sẽ trỏ tới phần userdata của chunk B, nơi ta đã fake thành p64(0) p64(0x51). Bit cuối đã được set thành 1 nên hoàn toàn thỏa mãn check trên.

Chunk D để prevent việc unlink :

Fake size trong userdata của B phải là 0x51 vì nó phải trỏ đến fakesize trong userdata của C để bypass được check trên ko đi vào unlink. Như vậy sẽ bypass được . Sau khi free ta có được địa chỉ của main_arena trong heap.Fakesize của chunk C chỉ cần có pre_bit_inuse = 1 là ok, không quan trọng là bao nhiêu.

Sau khi fake để bypass security, chúng ta tiến hành free unsoredted bins chunk vừa tạo. Lưu ý là cần fill đầy tcache bins thì chunk mới được đẩy vào unsorted bins :

for i in range(7) : 
  delete(0) 
delete(0) 

Sau đó ta được libc ờ FD, BK :

Đó là 0x7fee7ff60ca0 <main_arena+96>: 0x00005615ded5d750

Dup into stdout -> leak libc

Sau khi có được libc trong stack thì việc chúng ta hướng tới là kĩ thuật của angel boy, được đề cập trong bài viết này . Mình cũng sẽ nghiệm lại sau :v
Có một điểm là để dup được về stdout là địa chỉ được lưu ở FD, BK của chunk A, thì nó cần ở trong tcache bins có size là 0x51. Do chúng ta chỉ được quyền malloc chunk có size là 0x51 thôi. Và do đó là chúng ta cần sửa lại size của chunk A về lại như cũ theo đúng cách trên ta đã làm.
Đồng thời thì chunk A phải nằm trong tcache bins. Nhưng sau khi thực hiện các bước trên thì tcache bins của chúng ta hiện đang trống :

Cho nên chúng ta cần free chunk A để đưa chunk A vào tcache bins size 0x51. Nhưng nếu chúng ta free sau khi free unsorted bin thì FD, BK sẽ bị ghi đè và mất libc 😱😱😱 Cho nên trước bước edit size đầu tiên ta phải free chunk A. Chúng ta cần một lỗi double free để có thể dup into stack được. Mình học là vậy chứ cũng chưa rõ tại sao lắm.
Tổng kết lại để dup được lên địa chỉ mong muốn thì script phải có dạng :

--malloc to 0x556a2e57e660--  
delete(0)   # double free 
delete(0) 
for i in range(7) : 
  delete(1)   # fills tcache bins 
delete(1)     # free into unsorted bin   

Trở lại đoạn code trên để malloc tại sao lại cần free theo thứ tự : 2 -> 1 -> 0 -> 0 . Để chúng ta có được 3 con trỏ cùng chỉ tới chunk A :

add("\x70")       # 0x556a2e57e670  : 0 
add("\x60")       # 0x556a2e57e670  : 1 
add("\x60")       # 0x556a2e57e670  : 2 
  • Chunk 0 : Dùng để free A về tcache bins size 0x51
  • Chunk 1 : Dùng để free A về unsorted bin
  • Chunk 2 : Dùng để edit giá trị

Ok giờ có thể dup into libc được roài. Trở lại với kĩ thuật của angelboy. Chúng ta cần dup về địa chỉ của stdout. Bằng cách dùng edit(2) để sửa lại vài bytes cuối của libc ta sẽ được stdout. ASLR không thay đổi 3 bytes cuối. Mà địa chỉ của libc main_arena chỉ khác 4 byte so với địa chỉ của stdout. Trên server là 4 byte nhưng trên local của mình thì 5 bytes :vv Do đó trên server chúng ta chỉ cần bruteforce 1 byte là có thể dễ dàng sửa thành stdout.

Sau khi dup into stdout, tiến hành ghi đè lên stdout->flags -> 0xfbad1800, đồng thời ghi giá trị NULL lên _IO_read_ptr, _IO_read_end, _IO_read_base và last bytes của _IO_write_base.

Cụ thể payload khi ghi đè có dạng :

p64(0xfbad1800) + 3 * p64(0) + "\x00"

Sau đó chúng ta leak được địa chỉ của libc :

Địa chỉ của libc bắt đầu bằng \x7f trừ đi 1 số offset nào đó.

Overwrite free hook to get shell

Sau khi đã leak được libc thì chúng ta tận dụng kĩ thuật tcachebin dupinto stack để overwrite free_hook bằng địa chỉ của system.
Có một điểm khác của tcache so với fastbin là : khi free thì con trỏ FD trỏ vào vùng user data của chunk chứ không phải từ vùng size như trong fastbin. Cho nên tcachebin dupinto stack cũng không cần phải trừ đi kích thước vùng size như trong fastbin dupinto stack. Cũng không cần phải fakesize .
Sau khi đã ghi đè được như trên thì khi tiến hành free 1 chunk có con trỏ FD = “/bin/sh\x00” thì chúng ta có shell 😁😁😁

Angel Boy leak libc

Sau khi ngâm cứu blog kia mình cũng hình dung được cách để leak địa chỉ khi thực hiện script như trên.
Nói chúng trong hàm puts có 1 function như này :

int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      f->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
  /* If currently reading or no buffer allocated. */
  if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
    {
      :
      :
    }
  if (ch == EOF)
    return _IO_do_write (f, f->_IO_write_base,  // our target
			 f->_IO_write_ptr - f->_IO_write_base);

Đây là target : _IO_do_write (f, f->_IO_write_base, // our target f->_IO_write_ptr - f->_IO_write_base);
Thực hiện ghi đè lên stdout-> flags = 0xfbad1800 để qua tất cả các check để đến được target.
Sau đó chúng ta ghi đè 1 bytes “\x00” lên bytes cuối cùng của _IO_write_base để nó trỏ sang một địa chỉ trước địa chỉ cần in. Từ đó in thêm cho chúng ta một số thông tin về địa chỉ của libc.

Như ta thấy trên hình thì bên cạnh những địa chỉ bytes linh tinh thì nó cũng in ra kí tự đáng lẽ phải in : done!.

Kết

Bài này khá là dài và mình học được khá nhiều thứ sau bài này.Mình cũng đã viết thử script để attack khi tắt PIE. :))) Nhưng chưa làm với server nên cũng chưa tìm offset các kiểu :)) Cái này mình còn yếu vl.