Tháng 12 05 2011
Chuỗi ROP nâng cao cho Windows 8
Đầu tiên, tôi cảm ơn Dan Rosenberg vì những thông tin thú vị về cơ chế ngăn chặn ROP trên Windows 8 mà anh ấy đã chia sẻ. Đồng thời, tôi đánh giá cao nỗ lực của Nguyễn Hồng Sơn, anh chàng đồng nghiệp trẻ tuổi của tôi, trong việc cụ thể hóa phương pháp vượt qua cơ chế ngăn chặn ROP của Dan thành một chuỗi ROP chung (có thể chạy trên nhiều hệ điều hành khác nhau). Tuy nhiên, dễ nhận thấy có hai nhược điểm tồn tại trong chuỗi ROP đó:
- Thứ nhất, nó yêu cầu phải có EAX trỏ đến một giá trị stack (ngăn xếp) hợp lệ trước khi vào chuỗi ROP. Nếu không có được điều này, chuỗi ROP sẽ không sử dụng được. Thực tế, có nhiều trường hợp ta không thể có thanh ghi nào lưu giữ lại giá trị của ESP (chẳng hạn các trường hợp thực hiện “pivot stack” – chỉnh lại stack trước khi vào chuỗi ROP – bằng lệnh MOV ESP,R32 chứ không phải XCHG). Thậm chí ngay cả khi có thanh ghi nào đó khác EAX lưu trữ giá trị hợp lệ của ESP, thì việc chuyển nó về EAX bằng các “ROP gadget” (địa chỉ ROP) không phải lúc nào cũng dễ dàng. Như vậy, để có một chuỗi ROP chung có thể dùng phổ biến cho Windows 8, nhược điểm này cần phải được khắc phục.
- Hạn chế nữa nằm ở độ dài của chuỗi ROP. Trong khi hiện nay, các chuỗi ROP chung phổ biến trên Windows 7 rất ngắn (18 dwords với chuỗi ROP mới của Corelanc0d3r, và 22 dwords với chuỗi ROP của Sayonara), thì chuỗi ROP này lại chứa khoảng 100 dwords. Anh bạn của tôi hay bất cứ ai theo phương pháp của Dan có thể sẽ làm cho chuỗi ROP ngắn hơn được chút nữa nếu chú ý tối ưu trong việc sử dụng các “ROP gadget”, nhưng nó vẫn sẽ rất dài nếu đem so với những chuỗi ROP trên Windows 7, tôi tin vậy. Vì đôi khi mã khai thác của chúng ta gặp phải điều kiện ngặt nghèo về kích thước, nên độ dài của chuỗi ROP cũng là một vấn đề đáng quan tâm, giống vấn đề độ dài của shellcode vậy. Thêm nữa, tôi vẫn cho rằng một chuỗi ROP ngắn gọn trông sẽ đẹp đẽ và hoàn hảo hơn.
Tập trung nghĩ về cơ chế bảo vệ của Windows 8, tôi đã tạo ra một chuỗi ROP chung mới theo phương pháp của riêng mình. Và quan trọng là nó khắc phục được hai nhược điểm nêu trên.
chuỗi ROP của tôi cũng được xây dựng dựa trên thư viện msvcr71.dll như các chuỗi ROP chung đã nhắc tới, nó gồm 3 bước sau :
Bước 1: Xác định vùng stack hợp lệ
Bước này sẽ làm cho EAX trỏ đến một vùng stack hợp lệ đối với cách kiểm tra hiện tại của Windows 8. Qua đó, giải quyết được vấn đề đầu tiên mà tôi nói ở trên.
Nhắc lại tư tưởng của Dan Rosenberg trong ví dụ của anh ấy. Anh ấy sẽ khôi phục lại stack cũ (vốn đã bị “pivot stack” trước đó) để qua mặt cơ chế kiểm tra. Đây là vùng stack chuẩn được sử dụng trước đó một cách bình thường, nên hiển nhiên là hợp lệ. Vì thế đây là giải pháp tốt nếu chúng ta có thanh ghi nào đó sao lưu stack cũ để sau đó khôi phục lại. Tuy nhiên, ở đây, tôi đang bàn đến trường hợp không có thanh ghi nào thỏa mãn yêu cần của chúng ta.
Giờ hãy nhìn lại cơ chế kiểm tra của Windows: giá trị của ESP sẽ là hợp lệ nếu nằm trong khoảng “stack min” (FS:[8] ) và “stack max” (FS:[4]). Vậy sao ta không lấy luôn “stack min” để sử dụng ? Rõ ràng là nó sẽ vượt qua được cách kiểm tra kia, và nếu có được nó, tôi sẽ có một vùng stack rộng lớn, hợp lệ để sử dụng. Vậy tôi sẽ đi xác định giá trị này.
Việc tìm kiếm trên msvcr71.dll những “ROP gadget” thao tác trực tiếp đến FS:[8] không đem lại kết quả gì. Điều này là dễ hiểu vì các giá trị StackBase và StackTop thường được truy xuất từ cấu trúc TEB. Do đó, tôi đi tìm các gadget tham chiếu đến &(TEB) ( FS:[18] ), và thu được một kết quả duy nhất tại địa chỉ 0×7c34d38f :
Thoạt nhìn, chuỗi lệnh này có vẻ không phù hợp để trở thành một “ROP gadget” vì có quá nhiều lệnh tính toán và lệnh jump trước lệnh RETN. Tuy nhiên đây là kết quả duy nhất tôi tìm được trên msvcr71.dll có tham chiếu đến FS:[18], nên tôi đã phân tích kỹ nó. Dường như đoạn mã này cũng đang kiểm tra EBX có thuộc khoảng giữa StackBase và StackTop không. May mắn thay, tôi nhận ra rằng, nếu sắp xếp các thanh ghi hợp lý, tôi có thể lưu được giá trị của FS:[18] (hoặc chính StackBase) vào nơi tôi muốn ( [EBP+8] hoặc [EBP-4] ), rồi quay trở lại chuỗi ROP một cách tốt đẹp. Và đây là Bước 1 với 13 dwords:
Bước 2 : Sao chép đoạn ROP cho Windows 7 lên vùng stack hợp lệ, so đó trở lại chuỗi ROP
Ở đây, tôi sẽ giải quyết vấn đề độ dài của chuỗi ROP. Sở dĩ có sự chêch lệch đáng kể giữa chuỗi ROP theo phương pháp của Dan so với các chuỗi ROP cho Win7 hiện có là vì nó phải thực hiện nhiều hơn các việc tính toán, di chuyển dữ liệu giữa các thanh ghi, và ghi dữ liệu lên bộ nhớ. Sự hạn chế của các ROP gadget thường làm các công việc trên trở lên phức tạp hơn. Trong khi với các chuỗi ROP cho Win7 việc tính toán đơn giản hơn, lại có thể tận dụng một cách hợp lý PUSHAD để đẩy dữ liệu lên stack, rồi thực hiện lời gọi hàm.
Bởi vì cơ chế kiểm tra stack của Windows 8 chỉ áp dụng cho các hàm có liên quan đến việc cấp quyền cho vùng nhớ (như VirtualProtect), nên ta hoàn toàn có thể sắp xếp và gọi một hàm sao chép dữ liệu (memcpy chẳng hạn) mà chưa cần bận tâm đến tính hợp lệ của ESP.
Vậy bước này, tôi sẽ tận dụng lệnh PUSHAD (như phong cách của các chuỗi ROP chung cho Win7) để có một đoạn ROP ngắn thực hiện sao chép toàn bộ Bước 3 (chính là một chuỗi ROP cho Win7) cùng shellcode lên vùng stack hợp lệ ta đã có (được trỏ bởi EAX), rồi trở về đó.
Bước 2 này gồm 8 dwords :
Bước 3: Một chuỗi ROP bình thường trên Windows 7
Toàn bộ phần này, và shellcode theo sau nó, sẽ được ghi lên vùng stack hợp lệ, nên việc thực thi nó trên Windows 8 lúc này không khác gì trên các phiên bản từ Windows 7 trở về trước.
Vì thế, cái ta cần ở đây chỉ là một chuỗi ROP có thể chạy mượt mà trên Windows 7. Tôi đã tham khảo chuỗi ROP nhỏ nhất của Corelanc0d3r (bao gồm 18 dwords và được cập nhật vào tháng 10/2011) rồi viết một phiên bản khác chỉ với 14 dwords (thật tuyệt, phải không?). Chuỗi ROP “vạn năng” đó đây:
Kết luận
- Kết hợp 3 bước trên, ta sẽ có một chuỗi ROP “vạn năng” chạy ngon lành trên Windows 8, với độ dài 35 dwords (13 + 8 + 14).
- Trong trường hợp đã có EAX trỏ đến một vùng stack hợp lệ, bỏ qua Bước 1, chuỗi ROP của chúng ta chỉ cần 22 dwords (8+14) là hoạt động được trên Windows 8.
- Nếu chỉ dùng cho các phiên bản Windows 7 trở lại, Bước 3 của tôi với 14 dwords sẽ là một chuỗi ROP chung vô cùng ngắn mà hoàn chỉnh.
File đính kèm là demo khai thác lỗi CVE-2011-0065, đã thử nghiệm với Firefox 3.16 trên Windows 7 và Windows 8.



