Tấn công vượt qua cơ chế phòng thủ DEP/NX
Có 2 bước để khai thác lỗi tràn bộ đệm. Bước 1, tận dụng lỗi tràn bộ đệm để điều khiển luồng thực thi của chương trình; Bước 2, chuyển hướng thực thi của chương trình tới shellcode để thực thi.Tuy nhiên, với chế độ DEP hoặc NX, việc thực thi shellcode trong Stack và Heap là điều không thể. Tức là không thể chuyển EIP (địa chỉ trả về - EIP) tới Stack và Heap để thực thi shellcode.
Do vùng thực thi chỉ có thể tồn tại trên các thư viện liên kết động (DLL) và tệp tin nhị phân binary, tệp tin của chương trình, nên ý tưởng chính để thực hiện tấn công là mượn bit hoặc một phần mã thực thi tồn tại sẵn trong tệp tin thực thi của một tiến trình, sau đó sẽ tạo ra một chuỗi các chỉ lệnh để thực hiện tấn công. Tấn công này được gọi là tấn công ROP (Return Oriented Programming). Để hiểu được tấn công ROP, trước tiên chúng ta cần hiểu ret2libc là gì và nó làm việc như thế nào?
Giả sử có chương trình lỗi như sau:
Với tấn công tràn bộ đệm thông thường (Hình 1 mô tả stack frame của func1). Trong hàm func1, nếu con trỏ s trỏ tới chuỗi có độ dài lớn hơn 80 byte, khi thực hiện hàm strcpy, thì EIP sẽ bị ghi đè với giá trị mà kẻ tấn công điều khiển. Khi hàm func1 trả về, EIP sẽ nhảy tới giá trị mà kẻ tấn công đã ghi đè trước đó, luồng thực thi của chương trình bị thay đổi, dẫn đến việc tấn công khai thác thành công.
Hình 1: Stack frame của func1
Khi thiết lập chế độ DEP/NX thì các dữ liệu trên stack không thể thực thi, đồng nghĩa với shellcode không thể chạy được. Với tấn công truyền thống, shellcode sẽ thực thi vì stack bị đánh dấu là non-excutable. Để tấn công, EIP phải trỏ tới một vùng nhớ có quyền thực thi, vùng này nằm trên file binary hoặc shared libraries.
Hình 2: Chế độ DEP/NX
Tuy nhiên, với tấn công ret2libc, ta có thể vượt qua cơ chế DEP/NX bằng cách ghi đè EIP với địa chỉ hàm system(), system là hàm thực thi, có sẵn trong libc của hệ điều hành, sau đó sẽ truyền địa chỉ của chuỗi “/bin/sh” vào làm tham số để hàm system() thực thi – system(“/bin/sh”). Return from system() là địa chỉ trả về sau khi hàm system thực thi xong.
Hình 3: Tấn công Ret2libc
Như vậy, với ret2libc, ta có thể điều khiển được EIP sau khi thực thi một chuỗi lệnh bất kỳ. Trong trường hợp các hàm hệ thống bị loại bỏ như system(), thì vẫn có thể tạo ra một chuỗi các chỉ lệnh opop/pop/let (return-to-function) để thực thi một tấn công sử dụng các gadgets. Hình thức tấn công này được gọi là tấn công ROP.
Hình 4: Thực hiện tấn công ghi đè với một chuỗi các hàm để thực thi một công việc nhất định
Thử nghiệm tấn công ROP với chương trình sau:
Ở ví dụ trên, chương trình chỉ cho người dùng nhập dữ liệu đầu vào và in dữ liệu nhập vào/ra màn hình, không cho người dùng tương tác với shell của hệ điều hành. Tuy nhiên, khi khai thác lỗi bảo mật tràn bộ đệm ở hàm memcpy, hàm này thực hiện sao chép dữ liệu nhập từ người dùng tới một vùng nhớ buf có độ dài 256 byte. Khi khai thác thành công thì kẻ tấn công có thể tương tác được với shell của hệ điều hành.
Với chương trình này, ta sẽ biên dịch với tùy chọn “fno-stack-protector-boundary”, vô hiệu hóa ASLR (Address Space Layout Random).
Ban đầu ta xác định junk-data để trỏ tới địa chỉ trả về là 260 byte. Khi đó, xây dựng một chuỗi các chỉ lệnh để thực hiện tấn công ROP.Ta xây dựng chuỗi ROP như sau:
Vậy, đệm (buffer) như sau:
Sau khi xây dựng được chuỗi các chỉ lệnh (gadgets) ta có thể thực thi để tấn công lấy shellcode của OS.
Kết luận
Kết quả của tấn công trên cho thấy, việc vượt qua cơ chế phòng thủ DEP/NX của hệ điều hành là không khó. Do đó, việc phát triển phần mềm cần chú ý đến các quy định về lập trình để xây dựng được một phần mềm an toàn, tránh sai sót trong việc kiểm soát các luồng dữ liệu và việc khai báo, sử dụng các biến trong chương trình phần mềm.
Tài liệu tham khảo 1. Nguyễn Thành Nam, “Nghệ thuật tận dụng lỗi phần mềm”, NXB Khoa học và Kỹ thuật, 140tr, 2009. 2. LongLe, “Payload-already-inside-data-reuse-for-ROP-exploits-slides”,44tr, 2010. 3. Shaumil Shal, “Dive into ROP - a quick introduction to Return Oriented Programming”, 76tr, 2013. |