Mở đầu

Đây là kì thi chọn đội tuyển chuẩn bị cho SVATTT 2019 sắp diễn ra.Trong kì thi mình feed quá, cảm thấy trình pwn còn khá cùi cần rèn luyện thêm. 😥😥😥 Mình có hỏi hint anh chung96vn để làm nốt mấy bài còn lại và tổng hợp lại. Nói chung là cũng học được nhiều và cảm giác có động lực try hard hơn cho kì thi sắp tới. 😁😁😁 Nếu không muốn bị tạch lần nữa.

Play Around

Đây là bài pwn đầu tiên. Một dạng bài khá quen thuộc và căn bản khi bước vào học pwnable.

Chúng ta có lỗi tràn, stack thì không có canary nên có thể tràn về địa chỉ trở về hàm vuln .
Công việc đầu tiên là leak được libc. Chúng ta có thể ghi đè lên địa chỉ trở về địa chỉ plt của hàm printf và pass cho nó arguments là got của hàm printf. Nó sẽ in ra địa chỉ của printf trong libc. Từ đó tính toán được địa chỉ của libc. Đồng thời cần trở lại hàm main 1 lần nữa.
Cụ thể payload sẽ có dạng :

payload = "a" * (0x80 + 8) + ret + pop_rdi + p64(0x601018) + p64(bin.sym['printf']) + ret + p64(bin.sym['vuln'])

Trong kì thi không hiểu vì lí do magic gì mà thay địa chỉ của got.printf vào lại không in được. Bỏ vào debug rõ ràng nó có gọi hàm printf rồi 😱😱😱 Test thử bằng các địa chỉ khác thì vẫn in ra được. Thế là mình thử bằng địa chỉ got.setbuf thì lại được. Loay hoay chỗ này cũng tốn kha khá thời gian .
Công việc còn lại chỉ là gọi hàm system thôi.

Dead Note 1

Chương trình có 2 hàm cơ bản adddel :

Hàm add cho phép chúng ta tạo một vùng heap size 0x10 và ghi không quá 8 kí tự lên đó. Sau đó nó được check chỉ được ghi 0x3 kí tự.
Hàm del cho phép chúng phép chúng ta free một con trỏ và sau đó set lại = 0.

Lúc đầu mình tưởng bài này là heap cơ 😂😂😂 Sợ vl. Sau đó nhìn lại có lỗi out of bound ở hàm add. Chúng ta có thể ghi đè lên GOT của 1 số hàm trong để nó trỏ vào heap. Checksec thì thấy NX bị disable. Tức là bài này 90% là 1 bài shellcode. Cơ mà vì nó filter có 3 bytes nên là đến đây mình hết ý tưởng. Ăn hành suốt mấy tiếng. Vì từ trước đến giờ mình suy nghĩ kiểu lập trình viên, logic quá nên luôn thừa nhận và làm theo khuôn khổ của chương trình mà không bao giờ đặt câu hỏi là có thể bypass cái này không ? Có thể làm khác đi được không ? 😁😁😁 Pwn toàn là cú lừa , đôi khi nên nghĩ khác biệt đi tí thì mới ra được.
Sau khi nhận hint từ tác giả thì mình mình đã bypass được hàm strlen để nó luôn trả về 1. Tức là ta có shellcode 8 bytes.

0:  31 c0                   xor    eax,eax
2:  c3                      ret

Đến đây mình lại gặp phải vấn đề 😰😰😰 là từ trước đến giờ toàn cop shellcode trên mạng về chạy mà không hiểu rõ bản chất. Chả có shellcode nào mà ngắn đến độ 8 bytes cả. Thời gian tiếp theo idea của mình là ghi đè lên hàm free shellcode. Mình chia nhỏ shellcode thành các đoạn rồi cuối mỗi đoạn mình đặt lệnh jmp. Tuy nhiên mình lại gặp vấn đề về đưa chuỗi /bin/sh vào binary . Lại tiếp tục feed 😰😰😰
Sau khi nhận tiếp hint từ tác giả thì mình viết lại shellcode 8 bytes để ghi đè lên hàm atoi. Vì hàm atoi được truyền vào con trỏ là nptr , có thể là chuỗi /bin/sh luôn. 😁😁😁 :

0:  50                      push   rax
1:  5a                      pop    rdx
2:  50                      push   rax
3:  5e                      pop    rsi
4:  34 3b                   xor    al,0x3b
6:  0f 05                   syscall

Qua đây mình học được thêm kĩ năng viết shellcode là tận dụng những con trỏ đầu vào để giảm thiểu độ dài của shellcode. Trước khi vào hàm atoi ta có $rax = 0 cho nên ta tận dụng nó để set $rdx, $rsi = 0. Rồi gọi sysexecve là có shell.

Dead Note 2

Tiếp tục với version 2 của bài trên. Bài này filter shellcode của chúng ta chỉ được có ascii character.

Tiếp tục nhận hint từ tác giả 😛😛😛 thì idea là ta phải bypass speical read để có thể ghi đè lên strlen như bài trước.
Chúng ta chỉ có 3 bytes và đều phải là readable. Để có một hàm hoàn chỉnh thì đầu tiên phải có lệnh ret. Mà lệnh ret lại có mã hex là \xc3 tức là kí tự không readble. Tác giả hint là chúng ta có thể có kí tự này ở cái size cuối cùng của top chunk trong heap.

Nếu chúng ta malloc đủ nhiều thì tới một lúc nào đó nó size trên sẽ có chứa kí tự 0xc3. Ta đã có kí tự ret. Giờ cần set rax = 0.
Đặt breakpoint tại 0xE7D trước khi strlen để xem trạng thái của các thanh ghi xem chúng ta có thể tận dụng gì từ đó không.

Ta có rax = 0x7fff149bcda0 như vậy cần set lại rax. Tuy nhiên các lệnh xor rax, rax, mov rax, 0 đếu chứa các kí tự đặc biệt. Đồng thời, chúng ta đã phải tốn 2 bytes để nhảy tới lệnh \xc3. Tức là chúng ta chỉ còn 1 lệnh để set rax. Điều đó là dường như là không thể.
Để ý là rbx = 0, chúng ta có thể thực hiện push rbx, rồi pop rax để set rax = 0.

push rbx 
pop rax

Vẫn cần 2 bytes. Nhưng nó là 2 lệnh riêng biệt nên chúng ta có thể thực hiện chia shellcode ra thành 2 lần jump rồi mới jmp đến \xc3. 😁😁😁 Bùm bypass được strlen. Nhưng strlen này được thực hiện trong điều kiện rất đặc biệt nên chỉ cần malloc 1 lần nữa là tạch. Nên trong lần tiếp theo ta cần overwrite lại hàm strlen như bài trước.
Sau khi bypass được strlen luôn trả về 0 thì hàm special_read không còn check kí tự đặc biệt nữa. Làm như bài trước là ta có shell.

Nobaby

Bài này là một bài heap. Chạy trên ubuntu 16.04 nên không có tcache. Bài này mình còn chẳng phát hiện ra lỗi nào. Bài quá nhiều não và cú lừa.
Chương trình có 3 chức năng chính :

Mình đọc bài này thì không double free, NX thì được bật nên không thực thi shellcode được. Chỉ có một lỗi out of bound thì dễ nhận ra nhất. Nhưng nó cũng không giúp chúng ta có thể thay đổi luồng thực thi của chương trình. Sau khi chai mặt lại đi hỏi tác giả thì mình cũng nhận ra được lỗi tiếp theo : double free.
Mặc dù con trỏ đã được clear nhưng chúng ta có thể tạo ra 2 con trỏ cùng trỏ vào 1 địa chỉ. Dựa trên lỗi chúng ta có thể tràn vào biến node_count. Cụ thể :

  • Đầu tiên malloc 10 vùng nhớ để fill đầy 10 địa chỉ trong ptr.
  • Sau đó free 11 lần để reset node_count = -1.
  • Sau đó add tiếp 2 lần thì ghi đè node_count = heap.
  • Tiến hành free 0x71 lần ptr[0] để node_count = ptr[9]

Đồng thời chúng ta có thể leak địa chỉ libc bằng hàm show. Dùng lỗi out of bound để ptr trỏ về 1 địa chỉ chứa địa chỉ GOT.Lúc đầu do chưa hiểu rõ được cấu trúc file ELF nên mình không biết có cái table này.

Chỉ cần out of bound về đây là leak được địa chỉ libc.
Sau khi có được địa chỉ libc thì việc còn lại chỉ là lỗi fastbin dupinto stack overwrite địa chỉ hàm malloc_hook thành one_gadget thôi. Mình có viết 1 bài tương tự ở đây .

Kết thúc

Nói chung là qua mỗi kì thi mình lại thấy mình phế hơn xong cũng học được nhiều điều hơn. 😉😉😉 Hope là có thể pass được vòng loại SVATTT lần này, mặc dù cơ hội là khá mong manh.