## 1. Bối Cảnh :::success * Trong quá trình dạo chơi với rootkit ở windows thì mình cũng đã có kha khá kiến thức về lập trình driver :smiling_face_with_smiling_eyes_and_hand_covering_mouth:. Hôm nay mình tình cờ lướt qua reversing.kr thì mình thấy rằng có 1 bài liên quan đến window kernel nên làm thử và kết quả là bị dập cho sấp mặt :slightly_smiling_face:. Sau 1 buổi chiều loay hoay làm thì mình đã làm ra và học được khá nhiều thứ :smiling_face_with_smiling_eyes_and_hand_covering_mouth:. Giờ thì bắt đầu thôi. ::: ## 2. Phân Tích Bài Toán :diamond_shape_with_a_dot_inside: **Phân tích file thực thi** :::info * Đây là tất cả những gì mà đề bài cho mình để thực hiện khai thác bài này. Tất cả gồm có một file thực thi, một kernel module và một file **ReadMe.txt**. ::: ![](https://hackmd.io/_uploads/HJM59Hyn3.png) :::info * Đây là hint của đề bài, huh nó yêu cầu mình xác thực gì đó. Mình cũng không hiểu lắm :thinking_face:. ::: ![](https://hackmd.io/_uploads/HJ0EFrJhn.png) :::info * Tiếp theo khi mình chạy thử chương trình, nó hiển thị một dialog box và xuất hiện một nút enable. ::: ![](https://hackmd.io/_uploads/S1nz_kFo2.png) :::info * Sau khi ấn nút enable, nó cho phép mình nhập thông điệp vào text box. ::: ![](https://hackmd.io/_uploads/rk9w_1tjn.png) :::info * Mình nhập thử syaoren thì một message box hiển thị lên thông báo là **"Wrong"**". Điều này có nghĩa là thông điệp mình nhập vào text box không đúng với yêu cầu. * Vậy mấu chốt ở đây là ta cần phải nhập đúng thông điệp vào text box. Oce, đầu tiên mình sẽ phân tích file thực thi để xem có gì hay ho :nerd_face:. ::: ![](https://hackmd.io/_uploads/SkY0xEos2.png) :::info * Đầu tiên chương trình sẽ tạo một dialog box với hàm xử lý sự kiện là **DialogFunc()**. Điều này quá rõ ràng rồi :smiling_face_with_smiling_eyes_and_hand_covering_mouth:. ::: ![](https://hackmd.io/_uploads/rJPOjrJ23.png) :::info * Trong hàm xử lý sự kiện DialogFunc() thì ta cần thấy nó gọi 3 hàm lần lượt là **sub_401310()**, **sub401490()**, và **sub40111()**. ::: ![](https://hackmd.io/_uploads/ByuE2H122.png) :::info * Nhìn vào sub_401310() thì có thể thấy chức năng của nó là tạo một service vào kernel. Cụ thể, nó dùng file **WinKer.sys** do đề bài cung cấp để tạo một driver và tải nó vào hệ thống. ::: ![](https://hackmd.io/_uploads/ByH_hr1n3.png) :::info * Trong sub_401490() thì có thể thấy nó gọi API **DeleteService()**, mà API này dùng để xóa driver ra khỏi hệ thống nên có thể chắc chắn sub_401490() được gọi khi ta kết thúc chương trình :smiling_face_with_smiling_eyes_and_hand_covering_mouth:. ::: ![](https://hackmd.io/_uploads/Hk4Jar122.png) :::info * Cuối cùng là **sub_401110()**, hàm này tiết lộ cho ta rất nhiều thông tin quan trọng. Dễ thấy khi kết quả của hàm **sub_401280()** với tham số bằng 0x2000 có giá trị bằng 1 thì một message box với thông điệp "Correct!" được hiển thị lên :smiling_face_with_smiling_eyes_and_hand_covering_mouth:. * Vậy làm thế nào để cho **sub_401280(0x2000) = 1** :thinking_face:? Ta cùng kiểm tra chức năng của hàm sub_401280() nào. ::: ![](https://hackmd.io/_uploads/rJyCdu12h.png) :::info * Và chức năng của sub_401280() thì khá đơn giản, nó dùng API **DeviceIoControl()** để gửi một I/O request xuống cho device **RevKr** với control code là 0x2000. Sau khi driver đi kèm với device RevKr xử lý I/O request này thì nó sẽ trả về 4 byte dữ liệu được lưu trong OutBuffer. Giá trị của OutBuffer sau đó sẽ được trả về. Vậy thì bây giờ nhiệm vụ của ta cần làm là phải tìm cách khiến cho driver trả về giá trị OutBuffer bằng 1 thì lúc này message box với thoogn điệp **"Correct"**" sẽ hiển thị lên màn hình :smile:. * Tiếp theo ta cùng đi dịch ngược driver để hiểu cơ chế hoạt động của nó. ::: ![](https://hackmd.io/_uploads/H17kKu1hn.png) :diamond_shape_with_a_dot_inside: **Phân tích driver** :::info * Mở đầu **"DriverEntry()**" của driver, ta thấy nó thực hiện các chức năng cơ bản như tạo device bằng API **IoCreateDevice()** và tạo một symbolic link bằng API **IoCreateSymbolicLink()**. ::: ![](https://hackmd.io/_uploads/BkGSLUG3h.png) :::info * Tiếp theo, ta thấy nó đăng kí 3 hàm xử lý IRP tương ứng với thao tác create, close, và device control. * Ta chỉ cần quan tâm đến hàm xử lý IRP tương ứng với thao tác device control có control code là 0x2000 thôi. ::: ![](https://hackmd.io/_uploads/rklD8IG22.png) :::info * Dưới đây là các macro đại diện cho các thao tác của một gói IRP. ::: ![](https://hackmd.io/_uploads/SycOvLf2h.png) :::info * Khi mới nhìn vào hàm xử lý **OnDeviceControl()**, mình thật sự khá hoang mang vì nó xử dụng các cấu trúc khá là lạ như là **MasterIrp, Tail.Overlay.PacketType + 12**. Mấy cái kiểu cấu trúc như này mình thật sự chưa gặp bao giờ :tired_face:. ::: ![](https://hackmd.io/_uploads/Sy3FdUMnh.png) :::info * Sau một hồi tìm hiểu thì mình mới nghĩ rằng IDA đã disasembly sai và đã sửa lại source code của API **OnDeviceControl()** cho dễ đọc hơn. * Huh, hóa ra là trong các cấu trúc phức tạp thường hay sử dụng union để biểu diễn các trường. Vì thế nên tại một offset cụ thể nào đó của cấu trúc có thể biểu diễn nhiều trường khác nhau nên IDA mới dịch sai :smile:. Việc ta cần làm là khiến IDA biểu diễn các trường dữ liệu phù hợp với ngữ cảnh hơn :smiling_face_with_smiling_eyes_and_hand_covering_mouth:. * Tới lúc này thì source code đã clean hơn rất nhiều, v2 sẽ chứa control code được gửi từ phía user space và Buffer chứa con trỏ đến buffer sẽ được gửi về cho process ở user space. * Oce, tiếp theo nó thực hiện kiểm tra v2 cho 2 giá trị là 4096 và 0x2000 và ta chỉ quan tâm đến control code bằng 0x2000. Ta thấy rằng nếu control code bằng 0x2000 thì output buffer trả về cho user process sẽ bằng biến toàn cục C và để ra kết quả đúng thì C bắt buộc phải có giá trị bằng 1. * Để có thể kiểm soát giá trị của C bằng 1 thì ta bắt buộc phải biết hàm nào tác động vào nó :grin:. ::: ![](https://hackmd.io/_uploads/HyIGjLzn2.png) :::info * Dựa vào XREF, thì mình biết rằng C được tham chiếu đến bởi hàm **sub_110D0()** và **sub_11116()**. ::: ![](https://hackmd.io/_uploads/SJkP6Uzn2.png) :::info * Sau khi dùng graph để kiểm tra mối liên hệ giữa các hàm thì mình tìm thấy điều khá hay ho :smiling_face_with_smiling_eyes_and_hand_covering_mouth:. Cụ thể, driver mà ta đang phân tích có gọi đến macro **READ_PORT_UCHAR()**. Đây là một hàm macro trong lập trình thiết bị I/O trên các hệ thống như vi điều khiển và nó được sử dụng để đọc một giá trị từ một cổng I/O cụ thể. ::: ![](https://hackmd.io/_uploads/H1xGOvMnh.png) :::info * Trong trường hợp của chúng ta, READ_PORT_UCHAR() đọc giá trị ở cổng 0x60 và trả về scancode của PS/2 keyboard. À á, vậy hóa ra những kí tự mà ta viết trong text box của chương trình đều được driver đọc từ bàn phím bởi macro này. * Các bạn có thể tìm hiểu ở https://wiki.osdev.org/PS/2_Keyboard để xem thử mã scancode trả về bởi macro này sẽ tương ứng với key nào trên bàn phím nha :smile:. * Giá trị được đọc từ macro được lưu trữ ở biến v4 và nó tiếp tục được làm tham số cho **sub_111DC()**. Ta cùng kiểm tra thử xem hàm này xử lý giá trị được đọc từ bàn phím như thế nào. ::: ![](https://hackmd.io/_uploads/Sy_Vvvfh2.png) :::info * Huh, nó đang thực hiện cái gì đó :grin:. Nó tiếp tục gọi đến **sub_11156()** khi B thỏa một điều kiện nào đó. ::: ![](https://hackmd.io/_uploads/ryCSPvMhn.png) :::info * Ỏ nó tiếp tục gọi **sub_11156()** khi B cũng thỏa điều kiện nào đó. ::: ![](https://hackmd.io/_uploads/HJidPwGhn.png) :::info * Yeah, trong sub_110D0() có điều hay ho đây. Hàm này có gán C bằng 1 khi B cũng thõa mãn một điều kiện nào đó :smile:. * Về phần dịch ngược để tìm cách sao cho C bằng 1 thì mình xin dành lại cho bạn đọc, cũng không có khó đâu :grin:. Mình cũng đã trình bày hết ý tưởng của challenge này rồi :sunglasses:. ::: ![](https://hackmd.io/_uploads/B1d2hvG3h.png) :::info * Flag của bài này là **keydinthook**, hãy so sánh đáp án với flag này nhé :grin:. ::: ![](https://hackmd.io/_uploads/BkEjMuMh2.png)