REVERSING WITH IDA FROM SCRATCH (P34)
Năm hết, tết đến… Màn hình điện thoại vụt sáng. Mở máy thấy tài khoản mốc được + xèng kèm theo lời chúc của một bạn sinh viên đến từ KMA. Rất cảm ơn em, lâu lâu mới thấy số dư TK thay đổi 🙂 . Năm mới anh cũng chúc gia đình em mạnh khỏe, vạn sự như ý, chúc em học tập tốt và sớm tìm được công việc phù hợp trong ngành InfoSec.
Trong phần 34 này, thầy Ricardo cung cấp hai ví dụ đã được ông biên dịch sẵn, một có sử dụng DEP và một không. Hai ví dụ đều có mã nguồn giống nhau, nhưng trong trường hợp này, thay vì thay đổi thông qua trình biên dịch thì thầy đã sử dụng API SetProcessDEPPolicy
trực tiếp trong code của chương trình. Một với tham số là 0
(DEP off) và một với tham số là 1
(DEP on).
Nếu tôi chạy cả hai ví dụ này và quan sát chúng trong Process Hacker/ Process Explorer, cả hai đều đang dừng lại hàm gets_s
để chờ đợi dữ liệu nhập vào. Lúc này, code của chương trình đã thực hiện hàm API SetProcessDEPPolicy
, vì vậy DEP được thiết lập trong cả hai ví dụ nhờ vào hàm API này (một được kích hoạt và một thì không).
Các ví dụ trong phần này sẽ đáp ứng mục đích của chúng ta tốt hơn so với phần trước bởi code của chúng tương tự nhau. Ở phần trước không có đủ không gian để có thể thực hiện kĩ thuật ROP. Plugin duy nhất mà tôi và các bạn cần cài đặt là idasploiter, nó được phát triển bởi chuyên gia Peter Kacherginsky, từng làm việc ở FireEye. Plugin này chỉ hoạt động với IDA 6.x.
Plugin được viết hoàn toàn bằng python, bạn chỉ việc tải về và sao chép các .py
files vào thư mục plugins của IDA. Nó sẽ tự động được nạp mỗi khi bạn khởi chạy IDA Pro.
Mã nguồn của chương trình được thầy cung cấp trong hình dưới. Cả hai ví dụ đều sử dụng chung đoạn code này, điểm khác biệt duy nhất chính là ở tham số 0
và khi truyền cho hàm SetProcessDEPPolicy
.
Ta load ví dụ có bật DEP vào IDA. Quan sát code của chương trình, các bạn sẽ thấy các lệnh liên quan đến việc thiết lập DEP:
Chương trình thực hiện kiểm tra xem có nhận tham số hay không, nếu không có sẽ thoát. Nếu có, ta sẽ qua đoạn code thiết lập DEP và tới đây:
Ta thấy, code sử dụng hàm atoi()
để chuyển đổi số mà chúng ta nhập từ bàn phím thành số nguyên, sử dụng số này như là một tham số và lưu nó vào biến size
, là một biến có kiểu signed. Tiếp theo, số được lưu ở biến size
được đem đi so sánh và sử dụng lệnh JGE
để rẽ nhánh chương trình - JGE
chính là dấu hiệu cho ta biết đây là một "Signed comparison". Vì vây, nếu chúng ta nhập vào một số âm nhỏ hơn giá trị 0x300
, thì giá trị này sẽ được sử dụng làm tham số cho hàm saluda()
và bên trong hàm này, nó được sử dụng làm kích thước của hàm gets_s()
và được hiểu như là một giá trị unsigned. Điều này có thể gây ra lỗi tràn bộ đệm bởi vì nó sẽ cho phép ta nhập vào nhiều hơn 0x300 bytes
trong bộ đệm có cùng kích thước.
Giống như ở phần trước, chương trình thực hiện nạp Mypepe.dll
bằng hàm API LoadLibrary()
. Nếu tên hàm ở IDA của bạn trông loằng ngoằng nhiều kí tự, các bạn có thể sử dụng chức Demangled names để làm cho nó trông dễ nhìn hơn như trên hình.
Với những thông tin vừa phân tích ở trên, ta tiếp tục đi sâu vào phân tích hàm saluda()
:
Khi bạn biên dịch code của chương trình thì trình compiler sẽ tạo ra một file .pdb
(symbols) có cùng tên, do đó khi load binary vào IDA kèm theo pdb này thì IDA cũng sẽ nhận diện được biến nombre
là một buffer. Địa chỉ của biến này được truyền làm tham số cho hàm gets_s()
, cùng với kích thước của buffer mà ta nhập vào, đây chính là hai tham số của hàm gets_s()
như các bạn thấy trên hình.
Nhấp đúp chuột vào biến nombre
ta sẽ tới màn hình biểu diễn Stack của hàm saluda()
. Tại đây, ta thấy rằng để có thể gây tràn bộ đệm và ghi đè dữ liệu lên stack thì cần tôi cần 772 bytes
.
Do vậy, ta cần truyền cho hàm gets_s một chuỗi có dạng như sau:
fruit="A" * 772 + struct.pack("<L",0xCCCCCCCC) + shellcode
Ta sẽ xây dựng một script để thực hiện công việc này, đồng thời cũng phải nhập vào một “kích thước âm” làm tham số để “ trigger “ việc overflow.
Tôi kiểm tra script với file NO_DEP.exe trước. Thực thi script này thông qua cmd:
Sau đó sử dụng một instance khác của IDA để attach NO_DEP.exe vào:
Sau khi nhấn OK để attach process, IDA sẽ dừng lại tại đây (trên máy của tôi):
Quay trở lại màn hình cmd và nhấn Enter:
Sau đó quay về IDA và nhấn F9 để thực thi, ta nhận được thông báo:
Như các bạn thấy, tất cả mọi thứ được tính toán chính xác, chương trình sẽ nhảy đến địa chỉ 0xCCCCCCCC
để thực thi lệnh tại đó như chúng ta đã thiết lập trong script.
Nhấn OK, các bạn sẽ nhận được thông tin tương tự như trên hình, thanh ghi ESP lúc này sẽ trỏ đến shellcode của chúng ta trong stack, và do chương trình không có DEP nên nếu thay vì nhảy tới địa chỉ , ta cho nó nhảy tới các lệnh JMP ESP, CALL ESP
hoặc PUSH ESP-RET
thuộc một mô-đun nào đó - không sử dụng cơ chế địa chỉ ngẫu nhiên ( ASLR) thì shellcode của chúng ta sẽ thực thi đúng như mong muốn.
Để làm được ta sẽ nhờ tới sự hỗ trợ của IDA Sploiter, trước tiên ta cần mở cửa sổ chứa danh sách các modules đã được nạp của chương trình, thông qua View > Open subview > Modules hoặc phím tắt là SHIFT+F6. Tại đó, ta có được thông tin như sau:
Như trên hình, ta có toàn bộ danh sách các dll đã được nap, ta thấy rằng Mypepe.dll
không áp dụng các cơ chế bảo vệ và đặc biệt là không sử dụng ASLR ( randomization); tức là khi load lên bộ nhớ bao giờ nó cũng nằm ở một địa chỉ cố định, do đó nó sẽ là lựa chọn tốt để ta tìm lệnh JMP ESP
trong code của dll.
Ta nhấn chuột phải tại dll này và chọn Search Gadgets để tìm kiếm các đoạn mã có kết thúc là lệnh RET
:
Sau khi có được kết quả là toàn bộ các gadgets mà plugin có thể tìm thấy trong code của dll, nhấn CTRL+F để tìm kiếm lệnh PUSH ESP
:
Theo kết quả, tôi có được một địa chỉ là:
7800F7C1 push esp # retn Mypepe.dll 2 -4 one-reg, stack esp
Vì vậy, ta có thể sử dụng địa chỉ đó cho script mà không gặp vấn đề với các byte 0x00
, bởi hàm gets_s()
chấp nhận chúng. Ta sửa lại script như sau:
Kiểm tra lại script, kết quả có được như sau:
OK, shellcode hoạt động chính xác như bạn đã thấy ở phần trước, vì các bạn hình dung rằng, khi thực hiện lệnh retn tại hàm saluda() hai thanh ghi sẽ bị thay đổi là EIP
và ESP
; EIP
lúc đó sẽ chứa địa chỉ (0x7800F7C1
) còn ESP
sẽ chứa địa chỉ trỏ tới shellcode. Khi thực hiện cặp lệnh PUSH ESP
và RET
tại EIP
thì chúng ta sẽ nhảy tới shellcode để thực thi.
Toàn bộ ở trên ta thực hiện với NO_DEP.exe, ở phần tiếp theo chúng ta sẽ thực hành với DEP.exe sử dụng ROP để xem có thể thực thi được ứng dụng Calc giống như trên hay không?
Hẹn gặp lại các bạn ở phần 35!
Xin gửi lời cảm ơn chân thành tới thầy Ricardo Narvaja!
m4n0w4r
Ủng hộ tác giả
Nếu bạn cảm thấy những gì tôi chia sẻ trong bài viết là hữu ích, bạn có thể ủng hộ bằng “bỉm sữa” hoặc “quân huy” qua địa chỉ:
Tên tài khoản: TRAN TRUNG KIEN
Số tài khoản: 0021001560963
Ngân hàng: Vietcombank