T
tmnt53
Guest
Khai thác lỗi format string (phần II)
T
- tmnt53
- 5.839 Lượt xem
Ở bài trước – “khai thác lỗi format string (phần I)”, mình đã giới thiệu những nguyên liệu cơ bản để thực hiện khai thác được study case “echoServer”. Hôm nay mình sẽ hoàn thiện khai thác nó. Tóm tắt lại, mình có:
Các file đính kèm (echoServer, exploit_echoServer,…): format_string.zip
Kế hoạch khai thác sẽ như sau:
Bằng IDA, ta lấy được địa chỉ con trỏ hàm GOT[fgets] là 0x0804A004.
Tìm offset của buf khi mà thực hiện gọi hàm printf. Đặt breakpoin tại call printf@plt, lấy địa chỉ của buf – argument0, và của esp, từ đó tính ra offset = (bufAddress – esp)/4.
Như hình trên ta có esp = 0xffffd420, bufAddress = 0xffffd43c => offsetBuf = 7.
Dùng %[imath]s, ta sẽ lấy được nội dung của các con trỏ nằm trên stack. Ví dụ, khi gọi printf(“%10[/imath]s”), và tại offset 10 (dword thứ 10) là 0x08048000, thì nội dung tại địa chỉ 0x08048000 sẽ được in ra. Ta cần in ra nội dung của GOT[fgets] = 0x0804A004, thì ta phải có giá trị này trên stack. Làm sao để có được vậy? Vì buf nằm trên stack, nên ta sẽ nhập buf = “…x04xA0x04x08…” là được. Nhưng các giá trị “x04”, “x08” không nhập được bằng tay, nên giờ ta sẽ sử dụng công cụ pwntools.
Vì offsetBuf = 7, nên ta nhập vào “x04xA0x04x08%7$s”.
Mã nguồn exploit_echoServer.py:
Kết quả chạy exploit_echoServer.py:
Dữ liệu leak được từ con trỏ GOT[fgets] là “xa0lZxf7xe0x87zx7…”, ở đó 4byte “xa0lZf7” chính là địa chỉ hàm fgets trong libc. Vậy là ta đã leak ra được địa chỉ fgets.
Chú ý là địa chỉ này không cố định ở mỗi lần chạy chương trình:
Nên mỗi lần chạy, ta sẽ tính ra được địa chỉ libc khác nhau.
2. Tính địa chỉ của hàm system và xâu “/bin/sh” trong libc
Với nhiều bài khai thác ta đều cần có libc. Để lấy được libc trong máy mình, ta dùng ldd:
Đoạn code tiếp theo trong exploit_echoServer cho phép ta tính ra được địa chỉ của system, và xâu “/bin/sh” trong libc:
Output có được:
3. Ghi đè địa chỉ hàm system
Ta thực hiện ghi đè địa chỉ hàm system vào GOT[‘printf’] - 0x0804A004. Giả sử địa chỉ hàm system là 0xf7619310, thì ta cần ghi:
“x04xA0x04x08%12c%7$hhn”
Ta thấy, giống như “x04xA0x04x08%7[imath]s”, “%7[/imath]hhn” tương ứng với offset thứ 7 trên stack, và nó trùng đúng với địa chỉ buf. Buf được gán “x04xA0x04x08…” nên tại offset thứ 7 đó là địa chỉ 0x0804A004. Như vậy, số ký tự được in ra sẽ được lưu vào địa chỉ này. Xâu “x04xA0x04x08%12c” sẽ in ra 16 ký tự = 0x10, nên 0x10 sẽ được ghi vào địa chỉ 0x0804A004.
Còn để ghi tiếp “x93” vào byte 0x0804A005, ta có payload:
“x04xA0x04x08x05xA0x04x08%8c%7[imath]hhn%131c%8[/imath] hhn”
%8$hhn sẽ tương ứng với offset thứ 8 trên stack, là dữ liệu tại buf+4 = “…x05xA0x04x08…”. Xâu “x04xA0x04x08x05xA0x04x08%8c” in ra 0x10 ký tự => 0x0804A004 được ghi 0x10, phần sau “%131c” ghi thêm 131 ký tự nữa, mà 131+0x10 = 0x93, nên 0x0804A005 được ghi 0x93.
Qua các ví dụ trên, mình đã minh họa ý tưởng để có thể ghi đè được dữ liệu vào. Do địa chỉ system thay đổi sau mỗi lần gọi chương trình, nên ta cần lập trình để tính ra được payload cần thiết.
Output thu được:
Và giờ, khi ta truyền dữ liệu này cho echoServer, thì chương trình bị khai thác, và tựa như đang thực hiện chức năng sau:
Như vậy, khi nhập ls, cat flag.txt,… thì chương trình thực hiện đúng các lệnh ấy như đang trong một shell. Hay ta nhập /bin/sh thì chương trình gọi hẳn một cái shell lên, và ta có thể thực hiện mọi lệnh như đang trong một phiên làm việc shell, chẳng hạn dùng cd để di chuyển giữa các thư mục:
Mình xin kết thúc bài exploit formatstring tại đây. Có gì thắc mắc các bạn cứ hỏi tự nhiên nhé.
- %[imath]s, %[/imath]p để leak dữ liệu
- %$n để ghi đè địa chỉ tùy ý.
- Bảng GOT chứa các con trỏ hàm, trỏ tới libc
Các file đính kèm (echoServer, exploit_echoServer,…): format_string.zip
Kế hoạch khai thác sẽ như sau:
- Leak dữ liệu từ con trỏ hàm GOT[fgets] trong GOT table. Do nó chứa địa chỉ của hàm fgets trong libc, nên mình sẽ lấy địa chỉ của libc.
- Tính ra địa chỉ của libc từ dữ liệu leak được. Sau đó tìm địa chỉ của hàm system.
- Ghi đè con trỏ hàm GOT[printf] bằng địa chỉ hàm system. Như vậy, khi chương trình gọi printf(buf) thì lại là thực hiện system(buf). Ta chỉ cần nhập buf = “/bin/sh” là shell được gọi lên, và ta đã khai thác được chương trình.
Bằng IDA, ta lấy được địa chỉ con trỏ hàm GOT[fgets] là 0x0804A004.
Tìm offset của buf khi mà thực hiện gọi hàm printf. Đặt breakpoin tại call printf@plt, lấy địa chỉ của buf – argument0, và của esp, từ đó tính ra offset = (bufAddress – esp)/4.
Như hình trên ta có esp = 0xffffd420, bufAddress = 0xffffd43c => offsetBuf = 7.
Dùng %[imath]s, ta sẽ lấy được nội dung của các con trỏ nằm trên stack. Ví dụ, khi gọi printf(“%10[/imath]s”), và tại offset 10 (dword thứ 10) là 0x08048000, thì nội dung tại địa chỉ 0x08048000 sẽ được in ra. Ta cần in ra nội dung của GOT[fgets] = 0x0804A004, thì ta phải có giá trị này trên stack. Làm sao để có được vậy? Vì buf nằm trên stack, nên ta sẽ nhập buf = “…x04xA0x04x08…” là được. Nhưng các giá trị “x04”, “x08” không nhập được bằng tay, nên giờ ta sẽ sử dụng công cụ pwntools.
Vì offsetBuf = 7, nên ta nhập vào “x04xA0x04x08%7$s”.
Mã nguồn exploit_echoServer.py:
Kết quả chạy exploit_echoServer.py:
Dữ liệu leak được từ con trỏ GOT[fgets] là “xa0lZxf7xe0x87zx7…”, ở đó 4byte “xa0lZf7” chính là địa chỉ hàm fgets trong libc. Vậy là ta đã leak ra được địa chỉ fgets.
Chú ý là địa chỉ này không cố định ở mỗi lần chạy chương trình:
Nên mỗi lần chạy, ta sẽ tính ra được địa chỉ libc khác nhau.
2. Tính địa chỉ của hàm system và xâu “/bin/sh” trong libc
Với nhiều bài khai thác ta đều cần có libc. Để lấy được libc trong máy mình, ta dùng ldd:
Đoạn code tiếp theo trong exploit_echoServer cho phép ta tính ra được địa chỉ của system, và xâu “/bin/sh” trong libc:
Output có được:
3. Ghi đè địa chỉ hàm system
Ta thực hiện ghi đè địa chỉ hàm system vào GOT[‘printf’] - 0x0804A004. Giả sử địa chỉ hàm system là 0xf7619310, thì ta cần ghi:
- “x10” vào byte 0x0804A004.
- “x93” vào byte 0x0804A005.
- “x61” vào byte 0x0804A006.
- “xf7” vào byte 0x0804A007.
“x04xA0x04x08%12c%7$hhn”
Ta thấy, giống như “x04xA0x04x08%7[imath]s”, “%7[/imath]hhn” tương ứng với offset thứ 7 trên stack, và nó trùng đúng với địa chỉ buf. Buf được gán “x04xA0x04x08…” nên tại offset thứ 7 đó là địa chỉ 0x0804A004. Như vậy, số ký tự được in ra sẽ được lưu vào địa chỉ này. Xâu “x04xA0x04x08%12c” sẽ in ra 16 ký tự = 0x10, nên 0x10 sẽ được ghi vào địa chỉ 0x0804A004.
Còn để ghi tiếp “x93” vào byte 0x0804A005, ta có payload:
“x04xA0x04x08x05xA0x04x08%8c%7[imath]hhn%131c%8[/imath] hhn”
%8$hhn sẽ tương ứng với offset thứ 8 trên stack, là dữ liệu tại buf+4 = “…x05xA0x04x08…”. Xâu “x04xA0x04x08x05xA0x04x08%8c” in ra 0x10 ký tự => 0x0804A004 được ghi 0x10, phần sau “%131c” ghi thêm 131 ký tự nữa, mà 131+0x10 = 0x93, nên 0x0804A005 được ghi 0x93.
Qua các ví dụ trên, mình đã minh họa ý tưởng để có thể ghi đè được dữ liệu vào. Do địa chỉ system thay đổi sau mỗi lần gọi chương trình, nên ta cần lập trình để tính ra được payload cần thiết.
Output thu được:
Và giờ, khi ta truyền dữ liệu này cho echoServer, thì chương trình bị khai thác, và tựa như đang thực hiện chức năng sau:
while(1) { fgets(buf, 256, stdin); system(buf); } |
Như vậy, khi nhập ls, cat flag.txt,… thì chương trình thực hiện đúng các lệnh ấy như đang trong một shell. Hay ta nhập /bin/sh thì chương trình gọi hẳn một cái shell lên, và ta có thể thực hiện mọi lệnh như đang trong một phiên làm việc shell, chẳng hạn dùng cd để di chuyển giữa các thư mục:
Mình xin kết thúc bài exploit formatstring tại đây. Có gì thắc mắc các bạn cứ hỏi tự nhiên nhé.
Chỉnh sửa lần cuối bởi người điều hành: