上一章,我們搭建好了內核雙機調試環境,實體機作爲調試機,虛擬機win7Sp1x86作爲被調試機。現在我們先熟悉一下HackSysExtremeVulnerableDriver這個用來練習內核漏洞利用的項目,後面簡稱(HEVD)下一章我將演示一些漏洞利用的技術。
本次將用到的工具如下:
- 雙機內核調試環境
- HackSys Extreme Vulnerable Driver (HEVD) - 編譯版本,和源碼都要
- OSR Driver Loader(驅動加載器)
- DebugView (from SysInternals Suite)
- Visual Studio 2013(或任何你喜歡的版本)
安裝測試 HEVD
首先,我會介紹如何安裝HEVD,然後配置調試器和被調試機器顯示輸出調試字符串信息以及HEVD項目的符號信息,最後使用HEVD項目自帶的漏洞利用程序,測試一下漏洞是否可以成功觸發!
查看調試字符串
HEVD內置了很多漏洞利用時的調試字符串,我們可以使用Windbg在調試機查看,也可以使用DbgView在被調試機器中查看這些調試信息。
在安裝HEVD之前,首先需要開啓調試輸出配置,然後在被調試機器打開DbgView,才能看到安裝驅動時的調試信息。
配置調試器
建立好雙機調試以後,讓Windbg中斷下來,輸入開啓打印調試字符串命令:
ed nt!Kd_Default_Mask F
然後,讓被調試機再次運行起來,輸入運行命令:
g
配置被調試機器
我們需要以管理員權限
運行DbgView。然後我們選擇菜單:
Capture -> Capture Kernel
安裝內核漏洞驅動
首先我們需要在被調試機器中下載好,預編譯好的二進制文件,以及源碼包。安裝並測試!
我們可以在HackSysTeam的github上release頁面找到最新版本的:
https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/releases
預編譯壓縮包中有兩個版本,我們選擇32位的i386,打開剛纔下載的OSR Driver Loader工具,加載32位的HEVD驅動。
在OSR工具的Service Start項,選擇Automatic,然後點擊下面的Register Service按鈕,提示成功後,再點擊
Start Service按鈕!
如果驅動服務安裝並啓動成功,我們可以看到在windbg或者被調試機器的DbgView中看到打印出的banner信息:
添加符號
預編譯的HEVD包中含有調試符號PDB文件,我們可以將這個PDB文件添加到windbg中,方便我們調試時定位函數以及在源碼中的位置等。
首先我們中斷調試器,然後查看一下所有加載的模塊:
lm
可以使用過濾命令,來找到HEVD模塊:
lm m H*
我們可以看到windbg並沒有使用任何符號,這個很好解決,首先開啓:
!sym noisy
然後使用reload命令查看未加載的符號路徑:
.reload /f
可以看到這裏有兩個符號路徑,我們選擇其中一個:
d:\mss\HEVD.pdb\38ACF1BD8B354E07B7A8C3554683ABD71\HEVD.pdb
比如我就選擇第一個,然後按照這個路徑創建一模一樣的目錄,然後把HEVD.pdb拷貝進去,
然後重新執行.reload命令,隨後可以使用x命令查看關於HEVD的符號信息了:
x HEVD!*
測試漏洞利用
在下載的HEVD壓縮包內有一個漏洞利用測試程序,我們可以傳入不同的參數測試每種類型的漏洞,效果是打開一個system權限的cmd:
如果打開該文件時提示缺少msvcr100.dll類庫,自己拷一個32位的就行過來,放在同目錄下就行!
測試池溢出利用:
我們看到利用成功後彈出了一個system權限的cmd程序,並且windbg中也打印出了相關的漏洞利用調試信息:
Hi Driver,Let’s Talk!
像r3的漏洞利用一樣,我們需要找到一個可以破壞程序執行的輸入點,如果在r3程序就會崩潰,在內核r0,系統就會藍屏。
爲了能和驅動通訊,我們需要使用IOCTL碼,也叫輸入輸出控制碼。可以讓我們從r3發送一些字符串給驅動,這也是我們漏洞利用很重要的一點:
HEVD中包含了各種類型的漏洞,每一個漏洞都可以使用IOCTL加精心構造的輸入Buffer來觸發。其中有一些觸發後,可能會讓你的系統藍屏!
找到Device name & IOCTLs
在開始與驅動通訊之前,我們需要知道兩件事:
- 驅動創建的設備對象名(如果驅動沒有創建任何設備對象,那我們是不可能與驅動通訊的)
- 驅動接收的IOCTLs列表
HEVD是一個開源項目,所以我們可以直接從源碼中閱讀有用的信息。現實生活中的漏洞挖掘大部分情況下我們是沒有源碼的,有時候只能去逆向來獲取相關信息。
來看一下HEVD項目源碼中創建設備的相關代碼
上面顯示了設備對象的名稱,現在我們來找一下IOCTLs, 我們將從IRPs數組中找:
https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Driver/HackSysExtremeVulnerableDriver.c#L106
連接IRP_MJ_DEVICE_CONTOL
的函數用來分發各種IOCTL給驅動,我們需要看一下這個函數:
函數中有一個switch來分發各種IOCTL給相應的處理函數,我們可以在頭文件中找到這些值得定義:
https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Driver/HackSysExtremeVulnerableDriver.h#L57
編寫客戶端程序
現在拿到了所有IOCTLs,我們可以使用我們自己編寫的程序來和驅動通信,把拿到的IOCTL,寫到我們自己程序的頭文件中:
#pragma once
#include <windows.h>
const char kDevName[] = "\\\\.\\HackSysExtremeVulnerableDriver";
// IOCTLs
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW_GS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_NON_PAGED_POOL_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_ALLOCATE_UAF_OBJECT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_USE_UAF_OBJECT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_FREE_UAF_OBJECT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x806, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_ALLOCATE_FAKE_OBJECT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x807, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_TYPE_CONFUSION CTL_CODE(FILE_DEVICE_UNKNOWN, 0x808, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_INTEGER_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x809, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_UNINITIALIZED_STACK_VARIABLE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80B, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_UNINITIALIZED_HEAP_VARIABLE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80C, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_DOUBLE_FETCH CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80D, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_INSECURE_KERNEL_FILE_ACCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80E, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_MEMORY_DISCLOSURE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80F, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_PAGED_POOL_SESSION CTL_CODE(FILE_DEVICE_UNKNOWN, 0x810, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_WRITE_NULL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x811, METHOD_NEITHER, FILE_ANY_ACCESS)
每一個IOCTL控制碼是被一個標準宏創建的,定義在windows頭文件winioctl.h中:
如果你添加了windows.h頭文件,它會自動包含上圖中這個宏的。
現在我們準備寫一個簡單的程序來跟驅動通信。首先,我們使用CreateFile
打開設備對象,然後我們可以用DeviceIoControl
發送IOCTl控制碼。
下面是一個簡單的例子,發送STACK_OVERFLOW ioctl 給驅動程序:
#include <stdio.h>
#include <windows.h>
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)
const char kDevName[] = "\\\\.\\HackSysExtremeVulnerableDriver";
HANDLE open_device(const char* device_name)
{
HANDLE device = CreateFileA(device_name,
GENERIC_READ | GENERIC_WRITE,
NULL,
NULL,
OPEN_EXISTING,
NULL,
NULL
);
return device;
}
void close_device(HANDLE device)
{
CloseHandle(device);
}
BOOL send_ioctl(HANDLE device, DWORD ioctl_code)
{
//prepare input buffer:
DWORD bufSize = 0x4;
BYTE* inBuffer = (BYTE*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufSize);
//fill the buffer with some content:
RtlFillMemory(inBuffer, bufSize, 'A');
DWORD size_returned = 0;
BOOL is_ok = DeviceIoControl(device,
ioctl_code,
inBuffer,
bufSize,
NULL, //outBuffer -> None
0, //outBuffer size -> 0
&size_returned,
NULL
);
//release the input bufffer:
HeapFree(GetProcessHeap(), 0, (LPVOID)inBuffer);
return is_ok;
}
int main()
{
HANDLE dev = open_device(kDevName);
if (dev == INVALID_HANDLE_VALUE) {
printf("Failed!\n");
system("pause");
return -1;
}
send_ioctl(dev, HACKSYS_EVD_IOCTL_STACK_OVERFLOW);
close_device(dev);
system("pause");
return 0;
}
嘗試編譯上面c程序源碼,然後放到虛擬機運行,打開DbgView然後查看驅動輸出的調試字符信息:
正如上圖所示,驅動收到了我們發送的控制碼,然後打印出了調試字符串信息。
實驗:走個崩潰吧~~
嘗試輸入0x1000的Buffer size,直到驅動程序崩潰,因爲虛擬機在調試模式下,崩潰時機器不會藍屏,而是被WinDbg接管異常。
嘗試分析一下崩潰時信息詳細信息,使用以下命令打印出崩潰詳情:
!analyze -v
其他有用的命令:
k - stack trace
kb - stack trace with parameters
r - registers
dd [address]- display data as DWORD starting from the address
想了解更多命令,可以使用windbg提供的幫助文件:
.hh
在我們上面寫的簡單程序中,用戶層輸入的buffer用字母 “A” -> asciii 0x41 填充.
RtlFillMemory(inBuffer, bufSize, 'A');
所以只要我們分析崩潰詳情時看到了這個字母,就說明我們可以從用戶層輸入buffer填充內核層的內存。
附錄
-
http://expdev-kiuhnm.rhcloud.com/2015/05/17/windbg/ – introduction to WinDbg (by Massimiliano Tomassoli)
-
https://github.com/mwrlabs/win_driver_plugin – An IDA Pro plugin to help when working with IOCTL codes or reversing Windows drivers (by Sam Brown)