2014年8月6日 星期三

SSDT Hook

A. SSDT

SSDT 全名為System Service Dispatch Table,從圖來解釋比較快。Windows中Service Descriptor Table有兩種,分別是KeServiceDescriptorTable以及 KeServiceDescriptorTableShadow。每一個Table都是用一個SST Strucute來定義。
而SST中的欄位:
ServiceTableBase: points to the SSDT
ServiceCounterTableBase: Not used
ServiceLimit: number of entries in the SSDT
ArgumentTable: points to the SSDP table (system service Parameter table). 每一個byte儲存每一個SSDT routine其parameter所需用到的bytes數


因此循著ServiceTableBase的address可以找到SSDT的位置。在利用index找到target routine就可以進行hook了!

我們可以用windbg來實際看一下SSDT長什麼樣子
依照前幾篇文章設定好的VS2013+VM target machine的 remote debug
我就透過VS2013直接對我在VM中的windows7進行debug

(1) dd KeServiceDescriptorTable

可以看到KeServiceDescriptorTable的內容被印出來啦!根據前面介紹的概念,紅線標出的部分就是Structure的內容,因此83e7f43c就是SSDT 的address。至於其他內容代表的是什麼還有待查證。
用 dps nt!KeServiceDescriptorTable l4 也可以看到內容。

(指令 dd 的第一個d是代表印出指向指定位置的內容,第二個d代表用DWORD的形式,因此也有db(byte),du(unicode),dc(char)等指令)
(指令 dps代表 display words and symbol,l4是line4,印出4行)

(2) dps nt!KiServiceTable L poi nt!KiServiceLimit
此時會依據KiServiceLimit內容印出KiServiceTable,由於KiServiceLimit是此SSDT的number of entris,因此這行指令代表的就是印出KiServieTable的全部內容啦! 
(這裡的KiServiceTable應該就是structue的name,就是SSDT本人啦)
可以看到每一個SSDT routine以及他們的memory address。
(圖中可以看到第一排的第一個addr 83e7f43c,這不就是我們前一步看到的KeServiceDescriptorTable的內容嗎?沒錯,事實證明83e7f43c就是SSDT的起始address啦~太棒啦)

B. SSDT Hooking

Hook說穿了就是把SSDT中的某一個entry內容改掉,變成指向我們的hook function,為了避免system crash最後在hook function中再call 原先的SSDT routine。 

用A(2)的圖來解釋的話,如果我們想要hook NtAcceptConnectPort,就只要把SSDT[0]的內容改成我們自己的hook function address就可以啦(原本是8407afcf),如此一來每當系統要call這個native api並到SSDT中來找時就會循著address找到我們的hook function。hook function就會依照programmer的設計來做事了。

C. Hooking ntQuerySystemInformation

這一次我想hook的native api是 ntQuerySystemInformation,要找到ntQuerySystemInformation要從 ZWQuerySystemInformation開始,ZWQuerySystemInformation是ntoskrnl.exe中的一個win32 API,只要將ZWQuerySystemInformation的code做disassemble就能夠發現ZWQuerySystemInformation其實是將index value存入eax register,在call KiServiceTable,也就是到SSDT中根據Index value找到ntQuerySystemInformation的address。最後就執行ntQuerySystemInformation。

用windbg command可以用 u nt!ZwQuerySystemInformation看到反組譯後的code,由mov到eax的value可以知道ntQuerySystemInformation的index value是261(10進制)。我們就可以用
SSDT 起始address+4*index來算出ntQuerySystemInformation 存在SSDT中的哪一個位址。

D. Code Explaination

(1) Function Prototypes

MyDriver_UnSupportedFunction: assign給driver object的function,告訴driver object要做哪些事。目前沒有太大的用途囉。
MyDriver_Unload: 當Driver要unload時會call此function
DriverEntry:為此程式的entry point

/* Function Prototypes */
NTSTATUS MyDriver_UnSupportedFunction(PDEVICE_OBJECT DeviceObject, PIRP Irp);
VOID MyDriver_Unload(PDRIVER_OBJECT DriverObject);
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath);




(2) Compile directives

#pragma alloc_text(INIT, DriverEntry)
#pragma alloc_text(PAGE, MyDriver_Unload)
#pragma alloc_text(PAGE, MyDriver_UnSupportedFunction)

#pragma是一個預處理指令,告訴compiler要做些特別的事(如果compiler看得懂的話才會做)
而 #pragma alloc_text 只能處理C function,用來指定section locations.Microsoft 文件有提到"The alloc_text pragma must appear after the declarations of any of the specified functions and before the definitions of these functions"因此要注意#pragma的位置。
這裡指定DriverEntry載入到INIT記憶體區段中,最後Unload之後,可以退出memory。另外兩個function則是指定載入PAGE sections。

(3) System Service Table Structure

/*The structure representing the System Service Table*/
    typedef struct SystemServiceTable{
    UINT32* ServiceTable;
    UINT32* CounterTable;
    UINT32* ServiceLimit;
    UINT32* ArgumentTable;
} SST;

/* Declaration of KeServiceDescriptorTable, which is exported by ntoskrnl.exe */
// KeserviceDescriptorTable symbol is just a memory addr exported by kernel
__declspec(dllimport) SST KeServiceDescriptorTable;


KeServiceDescriptorTable可以由ntoskrnl.exe得到,因此為了得到kernel輸出的symbol,必須透過__declspec(dllimport)的方法。dllimport可以用來告訴compiler,kernel要export一個特定的function,叫compiler不要throw error出來。(一般來說compiler會反應error因為compiler不認識這個function)最後如果我們要用到此symbol時,linker會負責找到這個symbol的address。

因為kernel給我們的symbol 只有一個address,而address裡的內容我們必須要定義一個structure來取出我們要的info。因此我們定義了一個SystemServiceTable的結構以取出該address中的資料。

(4)ZwQuerySystemInformation Definition

/* Required information for hooking ZwQuerySystemInformation */
NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation(
    ULONG SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
);

typedef NTSTATUS(*ZwQuerySystemInformationPrototype)(
    ULONG SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
);

/* global variable is used as a placeholder for saving the old address from SSDT */
ZwQuerySystemInformationPrototype oldZwQuerySystemInformation = NULL;


ZwQuerySystemInformation是我們想要hook的function,負責回傳特定的system information。(此function 在win8上好像已經不能使用?)和上面一樣,我們沒有kernel code,因此只好依著查到的prototype來定義一個自己用的ZwQuerySystemInformation。Structure的parameter說明如下:
(a)SYSTEM_INFORMATION_CLASS:定義了我們想要retrieve的system information的type,可以是SystemBasicInformation、SystemProcessInformation、SystemProcessPerformanceInformation...等,詳細的資訊可以去msdn查。
(b)SystemInformation: 是一個指向buffer的pointer。buffer中存的是retrieve回來的資料。至於buffer的大小和struct視SystemInformationClass 而定。
(c)SystemInformationLength: buffer的size(bytes)
(d)ReturnLength: optional 參數,就是實際被寫入buffer中的bytes

另外我們需要定義一個function pointer oldZwQuerySystemInformation,這個function pointer會儲存舊的native api的address,我們在hook function中會需要此function pointer來指到真正的ZwQuerySystemInformation,讓系統繼續完成他的工作。

這裡的oldZwQuerySystemInformation是一個global variable,當作一個容器儲存被我們hook的native function。

(5) Set Write Access to WP flag

/* Disable the WriteProtect bit in CR0 register */
void DisableWP(){
    __asm{
        push edx;
        mov edx, cr0;
        and edx, 0xFFFEFFFF;
        mov cr0, edx;
        pop edx;
    }
}
/* Enable the WriteProtect bit in CR0 register */
void EnableWP(){
    __asm{
        push edx;
        mov edx, cr0;
        or edx, 0x00010000;
        mov cr0, edx;
        pop edx;
    }

我們對SSDT是沒有write access的,那要如何取得權限呢? reference[1]中有提到3個方法,那我一樣是採取最方便的做法,將CR0的WP flag 設成0,一旦設成0就能夠寫入SSDT新的資料,最後結束hooking時再將CR0的WP flag設回1

(6) HookSSDT  (mainly hooking function!)

PULONG HookSSDT(PUCHAR syscall, PUCHAR hookaddr){

    /* local variables */
    UINT32 index;
    PLONG ssdt;
    PLONG target;
    PULONG ret;

    /* disable WP bit in CR0 to enable writing to SSDT */
    DisableWP();
    //DbgPrint(" The WP flag in CR0 has been disabled.\n");
    DbgPrint(" In HookSSDT().\n");
    /* identify the address of SSDT table */
    ssdt = KeServiceDescriptorTable.ServiceTable;
    DbgPrint(" The system call address is %x\n", syscall);
    DbgPrint(" The hook function address is %x\n", hookaddr);
    DbgPrint(" The address of the SSDT is %x\n", ssdt);
    /* identify 'syscall' index into the SSDT table */
    /* *()means to dereference, to get the content at that addr */
    index = *((PULONG)(syscall + 0x1));
    DbgPrint(" The index into the SSDT table is %d\n", index);
    /* get the address of the service routine in SSDT */
    target = (PLONG)&(ssdt[index]);
    DbgPrint(" The address of the SSDT routine to be hooked is %x\n", target);
    DbgPrint(" The content of ssdt[261] is %x\n", ssdt[index]);
    //ret = (PUCHAR)InterlockedExchange(target, hookaddr);
    ret = (PVOID)InterlockedExchange(&ssdt[index], hookaddr);
    DbgPrint(" exchange!new ssdt[261] is %x\n", ssdt[index]);
    /* hook the service routine in SSDT */
    return ret;
}


HookSSDT的參數是syscall 以及 hookaddr,syscall是要hook的api address,但此api不是native code。前面有提到,這種API一開始就會將index 放入eax中在call KiServiceTable到SSDT中呼叫真正的native api。(ntokrnl.exe中: Zw系列-->call Nt系列)。

簡單來說這裡的sample中我給的參數是ZwQuerySystemInformation的addr,沿著此address找到index value之後我再去SSDT[index]得到我想要的NtQuerySystemInformation,就可以hook NtQuerySystemInformation啦!

HookSSDT做的事情有:
(1)Disable WPflag
(2)利用KeServiceDescriptorTable.ServiceTable取出ssdt的起始address
(3)利用syscall addr+1取出syscall的index(用*取出該address的內容)
(4)取得SSDT[index]的address (用&取得)
(5)用InterlockedExchange()交換兩address的內容
(6)return InterlockedExchange的return value,也就是回傳ntQuerySystemInformation的address

(7) Hook_ZwQuerySystemInformation

/* hook function */
NTSTATUS Hook_ZwQuerySystemInformation(ULONG SystemInformationClass,
PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength){

    /* local variables */
    NTSTATUS status;
    PBYTE buffer;
    /* calling new instructions */
    DbgPrint(" ZwQuerySystemInformation hook called\n");

    /* calling old function */
   status=oldZwQuerySystemInformation(SystemInformationClass,SystemInformation, SystemInformationLength, ReturnLength);
    // if(!NT_SUCCESS(status))
    if (status == STATUS_INFO_LENGTH_MISMATCH) {
        DbgPrint("Error:length mismatch! Allocate new buffer! ");
        //PBYTE buffer = malloc((PULONG)ReturnLength * sizeof(PBYTE));
        PBYTE buffer = ExAllocatePoolWithTag(PagedPool, ReturnLength, 'Tag1');
        if (buffer == NULL){
        DbgPrint(" Allocate Error\n");
        status = STATUS_INSUFFICIENT_RESOURCES;
        return status;
        }
    status = oldZwQuerySystemInformation(SystemInformationClass, (PVOID)buffer, ReturnLength, NULL);
        if (status == STATUS_SUCCESS){
            DbgPrint("--call origin api again and success! \n");
        }
    }else if (NT_SUCCESS(status)){
    DbgPrint("call origin api success! \n");
    }
    return status;
}

HookSystemInformation會call old native api ,也就是ntQuerySystemInformation,來繼續retrieve system information。

(8) DriverUnload


Driver 的unload有分2部分,分別是DriverUnload Routine以及 Driver image實際上的unload,我參考了reference3畫了一個簡易圖示如下



程式碼部分這裡沒有做太多事,只有將SSDT有修改的地方restore回去,也就是將原本ntQuerySystemInformation的address放回SSDT[261]裡去。最後call IODeleteSymbolicLink以及 IODeleteDevice準備unload driver image。

VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
    UNICODE_STRING usDosDeviceName;
    /* restore the hook */
    /* let syscall addr in SSDT point to original syscall addr*/
    if (oldZwQuerySystemInformation != NULL){
   oldZwQuerySystemInformation=(ZwQuerySystemInformationPrototype)HookSSDT((PULONG)ZwQuerySystemInformation, (PULONG)oldZwQuerySystemInformation);
        EnableWP();
        DbgPrint(" The original SSDT function restored\n");
    }

    DbgPrint("Driver unload\n");
    RtlInitUnicodeString(&usDosDeviceName, L"\\DosDevices\\MySSDTHooking");
    IoDeleteSymbolicLink(&usDosDeviceName);
    IoDeleteDevice(DriverObject->DeviceObject);
}

(9) DriverEntry (Driver程式進入點)

DriverEntry中做的事情有
(1)IOCreateDevice:create device object
(2)若create 成功,assign major function給driver object。這裡全都assign同一個function,也就是 MyDriver_UnSupportedFunction,這個function什麼也不做,只是一個簡單的雛形。
(3)assign DriverUnload function,這個動作有點像是callback function,就是告訴system當driver 要 unload時該call哪一個function。
(4)IoCreateSymbolicLink:create symbolic link
(5)call HookSSDT,最重要的step。第一個param是ZwQuerySystemInformation,因為他是我們的目標嘛,第二個param是Hook_ZwQuerySystemInformation,就是我們要replace ZwQuerySystemInformation的function。Call HookSSDT之後,SSDT的第261個entry內容就會指向Hook_ZwQuerySystemInformation,並且return 原來ZwQuerySystemInformation的address。我將他存在oldZwQuerySystemInformation(一個global variable),之後在driver unload以及Hook_ZwQuerySystemInformation裡都會用到此變數,因為我需要call 這個真正的native api以維持system 正常運作。

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath){

    NTSTATUS NtStatus = STATUS_SUCCESS;
    unsigned int uiIndex = 0;
    PDEVICE_OBJECT pDeviceObject = NULL;
    UNICODE_STRING usDriverName, usDosDeviceName;

    DbgPrint("DriverEntry called\n");

    // initialize driver name and device name
    RtlInitUnicodeString(&usDriverName, L"\\Device\\MySSDTHooking");
    RtlInitUnicodeString(&usDosDeviceName, L"\\DosDevices\\MySSDTHooking");
    // create a new device object(type is FILE_DEVICE_UNKNOWN, can only be used by a application)
    NtStatus = IoCreateDevice(pDriverObject, 0, &usDriverName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObject);

    if (NtStatus == STATUS_SUCCESS){
        /* MajorFunction: is a list of function pointers for entry points into the driver */
        for (uiIndex = 0; uiIndex < IRP_MJ_MAXIMUM_FUNCTION; uiIndex++)
           pDriverObject->MajorFunction[uiIndex] = MyDriver_UnSupportedFunction;

        pDriverObject->DriverUnload = DriverUnload;
        pDeviceObject->Flags |= DO_BUFFERED_IO;
        pDeviceObject->Flags &= (~DO_DEVICE_INITIALIZING);

        // Create symbol link
        IoCreateSymbolicLink(&usDosDeviceName, &usDriverName);
        DbgPrint("ZwQuerySystemInformation is at %x\n", ZwQuerySystemInformation);
        oldZwQuerySystemInformation =     (ZwQuerySystemInformationPrototype)HookSSDT((PULONG)ZwQuerySystemInformation, (PULONG)Hook_ZwQuerySystemInformation);
        DbgPrint("HookSSDT return addr is %x\n", oldZwQuerySystemInformation);
    }
    DbgPrint("DriverEntry finished\n");
    return STATUS_SUCCESS;
}

(10) MyDriver_UnSupportedFunction

前面提過啦~這在這裡是一個不重要的function,以後有用到再紀錄

NTSTATUS MyDriver_UnSupportedFunction(PDEVICE_OBJECT DeviceObject, PIRP Irp){
    NTSTATUS NtStatus = STATUS_NOT_SUPPORTED;
    //DbgPrint("MyDriver_UnSupportedFunction called\n");
    return NtStatus;
}

(11) Summary



總結一下這一次的project是hook ZwQuerySystemInformation,但實際上是更改SSDT[261]指向的位置,原本指向NtQuerySystemInformation,我改成指向自己的hook_function,ZwQuerySystemInformation負責將index存入eax再call system service,所以真正回報系統info的是NtQuerySystemInformation。

(12) Reference

以上的程式大部分不是我自己會寫的,而是讀了許多文章學習而來,仍有許多問題還未解決,以後再陸續紀錄新問題與解決之法。
[1] 完整hooking教學
http://resources.infosecinstitute.com/hooking-system-service-dispatch-table-ssdt/
[2]中文教學 
http://www.cnblogs.com/BoyXiao/archive/2011/09/03/2164574.html
[3] Unload driver concept
http://blogs.msdn.com/b/usbcoreblog/archive/2009/10/06/why-doesn-t-my-driver-unload.aspx
[4] alloc text msdn
http://msdn.microsoft.com/en-us/library/sw8ty6zf.aspx
[5]Building and deploying a basic WDF Kernel Mode Driver
http://www.codeproject.com/Articles/13090/Building-and-deploying-a-basic-WDF-Kernel-Mode-Dri
[6]DbgMessage filter
http://msdn.microsoft.com/en-us/library/windows/hardware/ff551519(v=vs.85).aspx

終於寫完了然後code怎麼這麼醜阿~天啊 Q_Q
by BlackCat 


沒有留言:

張貼留言

全國推廣動物認領養平台串聯貼紙

全國推廣動物認領養平台串聯貼紙