Mổ xẻ lỗi UAF với C++ và môn Reverse Engineering, chứng kiến impact chiếm quyền!
Heap Ring3 UAF RE Virtual Vprt
Note: Bài viết được thực hiện bởi sinh viên mới học môn RE, nên sai xót chắc chắn có thể xảy ra, nếu nhìn ra lỗi trong logic của mình thì xin hãy nói cho mình biết với nhé !
Lỗi UAF là gì và tại sao cho đến bây giờ một số CVE mới vẫn cứ réo tên nó ? Impact mà UAF để lại nghe rất oai nhưng để hiểu được nó thì rất khoai TT
Luồng flow mà UAF sinh ra trên Heap:
Coder dùng syntax “new” tạo ra 1 object/biến động => Sau khi dùng xong muốn xoá nó thì chỉ xoá = “delete” nhưng quên gán nó về [null] => Attacker lợi dụng.
Note: Bài viết được thực hiện bởi sinh viên mới học môn RE, nên sai xót chắc chắn có thể xảy ra, nếu nhìn ra lỗi trong logic của mình thì xin hãy nói cho mình biết với nhé !
Lỗi UAF là gì và tại sao cho đến bây giờ một số CVE mới vẫn cứ réo tên nó ? Impact mà UAF để lại nghe rất oai nhưng để hiểu được nó thì rất khoai TT
Luồng flow mà UAF sinh ra trên Heap:
Coder dùng syntax “new” tạo ra 1 object/biến động => Sau khi dùng xong muốn xoá nó thì chỉ xoá = “delete” nhưng quên gán nó về [null] => Attacker lợi dụng.
Đúng như các bạn vừa đọc và do mình đang học viết driver nên bắt đầu học về các con trỏ phức tạp này , đây là luồng flow mà bài blog này ra đời:
Thắc mắc về syntax * và & => Học được về Heap => Học về bufferoverflow trên Heap => Lòi ra khái niệm UAF và Virtual => Kết hợp UAF + Virtual => Chứng kiến impact chiếm luồng thực thi chương trình!
Thật sự mà nói thì lỗi này khá nhức đầu nhưng tin mình đi nếu bạn đã làm lab UAF và chứng kiến từng giọt nước chảy ra thì sẽ thấy rất pheee ><, chúng ta sẽ đi từ từ nhất có thể :
SYNTAX EXPLAINT
Đầu tiên ta hãy nói về đoạn code này:
Syntax [*] và Syntax [&] là gì =)))) Nếu bạn lú thì đừng buồn, lần đầu mình học cũng khá khổ sở vì chả hiểu nó dùng để làm gì . Thì để cho dễ hiểu * chính là ta đang truy xuất và đọc/ghi (Read/Write) giá trị bên trong thằng b, còn & chính là địa chỉ chính xác của thằng a bên dưới mã máy như 0x0000.
Dễ hiểu hơn thì [*] giống như ta đang chỉ tay vào nội thất bên trong 1 căn nhà (int b) và [&] chính là ta đang chỉ tay vào địa chỉ nhà (int a) như kiểu: số 100 nguyễn huệ q1.
Oke tiếp theo ta sẽ khám phá lệnh new,delete và virtual
New ở đây tạm thời là ta đang ra lệnh OS cấp phát 1 vùng nhớ heap cho 1 object/biến động!
Delete ở đây là khi ta dùng xong ngôi nhà New , ta ra lệnh cho OS xoá đi ngôi nhà đó!
Còn Virtual thì nó theo hướng đối tượng -> Bảng con trỏ hàm (Vtable), khi ta dùng virtual cho 1 hàm nó sẽ tạo ngay 1 con trỏ vptr (virtual pointer) nhét vào đầu địa chỉ và UAF là ta ghi đè con trỏ đó thành địa chỉ shell code, khi gọi vào nó sẽ nhảy vào shell code.
Delete ở đây là khi ta dùng xong ngôi nhà New , ta ra lệnh cho OS xoá đi ngôi nhà đó!
Còn Virtual thì nó theo hướng đối tượng -> Bảng con trỏ hàm (Vtable), khi ta dùng virtual cho 1 hàm nó sẽ tạo ngay 1 con trỏ vptr (virtual pointer) nhét vào đầu địa chỉ và UAF là ta ghi đè con trỏ đó thành địa chỉ shell code, khi gọi vào nó sẽ nhảy vào shell code.
UAF (use after free) EXPLAINT
Giờ hãy tìm hiểu chi tiết tại sao quên gán nó về null thì lại bị lợi dụng bởi attacker?
Khi dùng lệnh delete cho biến/ đối tượng new, nhưng thật sự bên dưới, hệ điều hành hoàn toàn không xoá sạch cái biến đó như cái tên delete! Why? Vì C++ cần chúng ta phải tự tay xoá = việc gán null. Vì bạn hãy thử nghĩ trong 1 app đã phải chạy quá nhiều chức năng cồng kềnh.
Giờ mà C++ phải tự tay đi xoá sạch sẽ những biến delete không dùng nữa => bên dưới mã máy nó phải tự tay tạo vòng lặp và đi tìm khổ sở các biến delete để gán null => tốn thêm kha khá chu kỳ => giảm hiệu năng app!!!
Vậy nên sau khi delete mà không gán null, dữ liệu cũ của 1 biến hoặc thậm chí là địa chỉ và sự sống của biến đó hoàn toàn tồn tại! Bạn hoàn toàn có thể test đơn giản như bên dưới:
Khi dùng lệnh delete cho biến/ đối tượng new, nhưng thật sự bên dưới, hệ điều hành hoàn toàn không xoá sạch cái biến đó như cái tên delete! Why? Vì C++ cần chúng ta phải tự tay xoá = việc gán null. Vì bạn hãy thử nghĩ trong 1 app đã phải chạy quá nhiều chức năng cồng kềnh.
Giờ mà C++ phải tự tay đi xoá sạch sẽ những biến delete không dùng nữa => bên dưới mã máy nó phải tự tay tạo vòng lặp và đi tìm khổ sở các biến delete để gán null => tốn thêm kha khá chu kỳ => giảm hiệu năng app!!!
Vậy nên sau khi delete mà không gán null, dữ liệu cũ của 1 biến hoặc thậm chí là địa chỉ và sự sống của biến đó hoàn toàn tồn tại! Bạn hoàn toàn có thể test đơn giản như bên dưới:
Như bạn có thể thấy ta tạo ra int* arr và gán giá trị là 5, sau đó ta ngây thơ dùng delete xóa biến arr và tắt máy đi ngủ. Nhưng như các bạn thấy nếu ta gọi nó ra 1 lần nữa nó hoàn toàn sống lại và in ra màn hình (Giá trị đang chứa sau đó có thể là rác/các quy định ngầm của OS)
=> Khái niệm UAF ra đời với từ tiếng anh USE AFTER FREE (dùng sau khi giải phóng)
EXPLOIT UAF ON x32dbg
Trong exploit này có 1 hàm ta cần chú ý đó là virtual. Vì có từ khóa virtual, Trình biên dịch C++ đã âm thầm nhét một biến con trỏ tàng hình vào ngay vị trí đầu tiên (offset 0x0) của Object SinhVien. Người ta gọi nó là vptr (Virtual Pointer).
- vptr này trỏ tới một mảng có sẵn trong bộ nhớ gọi là VTable (Virtual Table).
- Trong VTable chứa địa chỉ thật của hàm HanhDong().
Để cho dễ hiểu mình sẽ nói theo cách nghĩ của mình là khi có từ khóa virtual. Giống như virtual ám chỉ 1 người lớn, vptr - Virtual Pointer (ám chỉ người virtual đó có 1 cái ví tên vptr) và bên trong ví vptr lại chứa địa chỉ nơi ở chính xác của người lớn virtual. Việc mà ta khéo léo thực hiện là mở ví vptr - Virtual Pointer ra và ghi đè vào đó địa chỉ nhà ở của HACKER => Yes sir!
Oke giờ hãy bình tĩnh ta cần ghi chép để dễ theo dõi các con số hex nhảy múa:
Địa chỉ mã độc: 0x00EE1F60
Đầu tiên ta tạo 1 fake_vtable, gán giá trị = địa chỉ shellcode 0x00EE1F60
Tiếp theo ta tạo ra lỗ hỗng bằng new và delete nhưng thiếu việc gán null !!!
Tiếp theo ta lại khai báo prt_Kedo , ta chỉ khai báo new int[1]. Nhưng sự thật bên dưới ta đã vô tình để cho biến mới này và biến ptr_NanNhan ở trên. Trỏ vào cùng 1 địa chỉ. Why? Vì như đã nói ở trên không gán null nên ngay địa chỉ và sự sống của ptr_NanNhan vẫn đang lơ lửng và thằng prt_Kedo bên dưới là dùng chung 1 ví tiền với ptr_NanNhan (nghe lú nhưng đó là sự thật TT)
*ptr_Kedo = (int)fake_vtable;
Với câu lệnh này ta đang cho gán giá trị (địa chỉ mã độc shell code) vào prt_Kedo. Sự thật rằng tại địa chỉ prt_Kedo và ptr_NanNhan lúc này đều đã bị ghi đè hoàn toàn = địa chỉ shell code!
Chỉnh sửa lần cuối bởi người điều hành: