系統調用篇——總結與提升

寫在前面

  此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章後面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統內核——簡述 ,方便學習本教程。

  看此教程之前,問幾個問題,基礎知識儲備好了嗎?保護模式篇學會了嗎?系統調用篇學會了嗎?練習做完了嗎?沒有的話就不要繼續了。


🔒 華麗的分割線 🔒


篇章總結

  由於本篇章十分簡單,沒學APC,完整的系統調用流程無法講解。我們就用簡單總結一下我們所學:我們首先直接調用我們的函數比如OpenProcess,它會經過層層參數校驗,然後通過將服務號賦給eax,將堆棧賦給esp,通過sysenter或者中斷門進入內核,經過層層處理,調用真正的內核函數,通過APC返回。既然學習了本篇,我們就做一個項目,實現進程只能通過自己關閉,不能通過他人調用關閉,也就是實現所謂的SSDT Hook。比如你打開一個記事本,只能通過點擊關閉按鈕或者菜單退出可以,而不能通過其他程序調用TerminateProcess關閉它。

項目代碼分析

  在分析項目源碼之前,我們來看看效果:

  如果沒有寫代碼的話,就不要繼續了。代碼可以不和我一樣,只要實現它的功能即可,本項目代碼僅供參考。
  我們的核心功能肯定需要在驅動上,單純的3環是做不到的,實現SSDT Hook,還要修改它,為了代碼的可讀性和方便性,我們首先定義一下它的結構體和導入變量:

struct SSDT_ITEM
{
    PULONG funcTable;
    ULONG count;
    ULONG limit;
    PUCHAR paramTable;
};

extern struct SSDT_ITEM* KeServiceDescriptorTable;

  我們阻止他人關閉要保護的程序,首先得知道它是怎樣關閉程序的,如下是任務管理器關閉程序的關鍵函數的偽代碼:

BOOL __thiscall CProcPage::KillProcess(CProcPage *this, DWORD a2, int a3)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  dwMessageId = 0;
  v4 = FindProcInArrayByPID(*(this + 3), a2);
  v5 = v4;
  if ( !v4 || CProcPage::IsSystemProcess(this, a2, v4) )
    return 0;
  v7 = *(v5 + 35);
  if ( v7 )
    v8 = *(v7 + 8);
  else
    v8 = *(v5 + 2);
  v9 = *(v5 + 72);
  dwProcessId = v8;
  if ( !a3 && CProcPage::QuickConfirm(this, 0x2717u, 0x2719u) != 6 )
    return 0;
  if ( v7 )
    return VDMTerminateTaskWOW(dwProcessId, v9);
  CPrivilegeEnable::CPrivilegeEnable(v13, L"SeDebugPrivilege");
  v10 = OpenProcess(1u, 0, a2);
  v11 = v10;
  if ( v10 )
  {
    if ( TerminateProcess(v10, 1u) )
      (*(*this + 24))(this);
    else
      dwMessageId = GetLastError();
    CloseHandle(v11);
  }
  else
  {
    dwMessageId = GetLastError();
  }
  if ( dwMessageId )
  {
    DisplayFailureMsg(*(this + 1), 0x2721u, dwMessageId);
    v12 = 0;
  }
  else
  {
    v12 = 1;
  }
  CPrivilegeEnable::~CPrivilegeEnable(v13);
  return v12;
}

  可以知道,任務管理器是通過TerminateProcess來關閉這個進程,如果失敗會彈窗顯示錯誤原因。要想Hook,就必須知道在SSDT的服務號,如下是跟蹤流程,見如下分析,我們先定位到該函數:

; BOOL __stdcall TerminateProcess(HANDLE hProcess, UINT uExitCode)
                public _TerminateProcess@8
_TerminateProcess@8 proc near           ; CODE XREF: .text:7C839AB2↓j
                                        ; ConsoleIMERoutine(x)+102↓p
                                        ; DATA XREF: ...

hProcess        = dword ptr  8
uExitCode       = dword ptr  0Ch

                mov     edi, edi
                push    ebp
                mov     ebp, esp
                cmp     [ebp+hProcess], 0
                jnz     short loc_7C801E2E
                push    6               ; dwErrCode
                call    _SetLastError@4 ; SetLastError(x)
                jmp     short loc_7C801E49
; ---------------------------------------------------------------------------

loc_7C801E2E:                           ; CODE XREF: TerminateProcess(x,x)+9↑j
                push    [ebp+uExitCode] ; ExitStatus
                push    [ebp+hProcess]  ; ProcessHandle
                call    ds:__imp__NtTerminateProcess@8 ; NtTerminateProcess(x,x)
                test    eax, eax
                jl      short loc_7C801E43
                xor     eax, eax
                inc     eax
                jmp     short loc_7C801E4B
; ---------------------------------------------------------------------------

loc_7C801E43:                           ; CODE XREF: TerminateProcess(x,x)+22↑j
                push    eax             ; Status
                call    _BaseSetLastNTError@4 ; BaseSetLastNTError(x)

loc_7C801E49:                           ; CODE XREF: TerminateProcess(x,x)+12↑j
                xor     eax, eax

loc_7C801E4B:                           ; CODE XREF: TerminateProcess(x,x)+27↑j
                pop     ebp
                retn    8
_TerminateProcess@8 endp

  上面的函數在kernel32.dll裏面,可以看到它又調用了NtTerminateProcess,這個函數又在ntdll.dll中,如下所示:

; Exported entry 348. NtTerminateProcess
; Exported entry 1157. ZwTerminateProcess

; =============== S U B R O U T I N E =======================================


; __stdcall ZwTerminateProcess(x, x)
                public _ZwTerminateProcess@8
_ZwTerminateProcess@8 proc near         ; CODE XREF: LdrpGenericExceptionFilter(x,x)+9107↓p
                                        ; ___report_gsfailure+E1↓p ...
                mov     eax, 101h       ; NtTerminateProcess
                mov     edx, 7FFE0300h
                call    dword ptr [edx]
                retn    8
_ZwTerminateProcess@8 endp

  最終,我們找到了它的服務號是0x101,剩下的我們就開始寫代碼了。
  既然修改SSDT,我們就必須具有寫權限,修改自身模塊我們可以想改就改沒啥問題,但是要修改其他模塊的,就得小心行事了,因為我們不能確保那塊內存我們具有寫的權限。一種方式我們可以修改物理頁屬性,這個我就不贅述了。本項目通過修改CR0WP位實現繞過只讀,如果忘卻,請自行複習 保護模式篇——中斷與異常和控制寄存器 中的控制寄存器的CR0,具體代碼如下:

//恢復內存保護
void PageProtectOn() 
{
    __asm
    {
        mov eax, cr0;
        or eax, 10000h;
        mov cr0, eax;
        sti; //恢復中斷
    }
}

//去掉內存保護
void PageProtectOff()
{
    __asm 
    {
        cli; //屏蔽中斷
        mov eax, cr0;
        and eax, not 10000h;
        mov cr0, eax;
    }
}

  下面我只需要寫個實現Hook的和接管Hook的函數就行了,如下所示:

typedef  NTSTATUS(__stdcall *NtTerminateProcess)(HANDLE ProcessHandle, NTSTATUS ExitStatus);

NtTerminateProcess oldNtTerminateProcess = NULL;

NTSTATUS __stdcall HookNtTerminateProcess(HANDLE ProcessHandle, NTSTATUS ExitStatus)
{
    if (protectedProcess != NULL)
    {
        PEPROCESS pro = IoGetCurrentProcess();
        PEPROCESS p;
        if (ObReferenceObjectByHandle(ProcessHandle, NULL,  *PsProcessType, KernelMode, &p, NULL) == STATUS_SUCCESS)
        {
            if (p == protectedProcess && pro != protectedProcess)
            {
                return STATUS_ACCESS_DENIED;
            }
        }
    }
    return oldNtTerminateProcess(ProcessHandle, ExitStatus);
}

void HookTerminateProcess()
{
    PageProtectOff();
    oldNtTerminateProcess = KeServiceDescriptorTable->funcTable[TerminateProcessIndex];
    KeServiceDescriptorTable->funcTable[TerminateProcessIndex] = HookNtTerminateProcess;
    PageProtectOn();
}

  看HookNtTerminateProcess函數,這個函數是用來實現Hook函數接管的,為什麼我還要調用原來的NtTerminateProcess函數呢?是因為所有的3環程序都需要用到它,我必須調用它才行。
  剩下的代碼我就不再贅述了,如下是該項目的完整代碼:

🔒 點擊查看驅動代碼 🔒
#include <ntifs.h>
#include <wdm.h>
#include <ntddk.h>

#define TerminateProcessIndex 0x101

UNICODE_STRING Devicename;
UNICODE_STRING SymbolLink;

#define DEVICE_NAME L"\\Device\\ProcessHook"
#define SYMBOL_LINK L"\\??\\ProcessHook"

//操作碼:0x0-0x7FF 被保留,0x800-0xFFF 可用
#define Hook CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define UnHook CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)

NTSTATUS DEVICE_CONTROL_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp);
NTSTATUS DEVICE_CREATE_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp);
NTSTATUS DEVICE_CLOSE_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp);

PEPROCESS protectedProcess = NULL;

struct SSDT_ITEM
{
    PULONG funcTable;
    ULONG count;
    ULONG limit;
    PUCHAR paramTable;
};

extern struct SSDT_ITEM* KeServiceDescriptorTable;

typedef  NTSTATUS(__stdcall *NtTerminateProcess)(HANDLE ProcessHandle, NTSTATUS ExitStatus);

NtTerminateProcess oldNtTerminateProcess = NULL;

//恢復內存保護
void PageProtectOn() 
{
    __asm
    {
        mov eax, cr0;
        or eax, 10000h;
        mov cr0, eax;
        sti;
    }
}

//去掉內存保護
void PageProtectOff()
{
    __asm 
    {
        cli;
        mov eax, cr0;
        and eax, not 10000h;
        mov cr0, eax;
    }
}

NTSTATUS __stdcall HookNtTerminateProcess(HANDLE ProcessHandle, NTSTATUS ExitStatus)
{
    if (protectedProcess != NULL)
    {
        PEPROCESS pro = IoGetCurrentProcess();
        PEPROCESS p;
        if (ObReferenceObjectByHandle(ProcessHandle, NULL, *PsProcessType, KernelMode, &p, NULL) == STATUS_SUCCESS)
        {
            if (p == protectedProcess && pro != protectedProcess)
            {
                return STATUS_ACCESS_DENIED;
            }
        }
    }
    return oldNtTerminateProcess(ProcessHandle, ExitStatus);
}

void HookTerminateProcess()
{
    PageProtectOff();
    oldNtTerminateProcess = KeServiceDescriptorTable->funcTable[TerminateProcessIndex];
    KeServiceDescriptorTable->funcTable[TerminateProcessIndex] = HookNtTerminateProcess;
    PageProtectOn();
}

PDEVICE_OBJECT dobj;

void UnloadDriver(PDRIVER_OBJECT DriverObject)
{
    IoDeleteSymbolicLink(&SymbolLink);
    IoDeleteDevice(dobj);
    if (oldNtTerminateProcess!=NULL)
    {
        KeServiceDescriptorTable->funcTable[TerminateProcessIndex] = oldNtTerminateProcess;
    }
    DbgPrint("卸載成功!!!\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{

    DriverObject->DriverUnload = UnloadDriver;

    RtlInitUnicodeString(&Devicename, DEVICE_NAME);

    IoCreateDevice(DriverObject, 0, &Devicename, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &dobj);
    RtlInitUnicodeString(&SymbolLink, SYMBOL_LINK);
    IoCreateSymbolicLink(&SymbolLink, &Devicename);
    DriverObject->Flags |= DO_BUFFERED_IO;

    DriverObject->MajorFunction[IRP_MJ_CREATE] = DEVICE_CREATE_Dispatch;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DEVICE_CONTROL_Dispatch;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = DEVICE_CLOSE_Dispatch;

    HookTerminateProcess();
    DbgPrint("驅動加載完畢!!!\n");

    return STATUS_SUCCESS;
}

NTSTATUS DEVICE_CONTROL_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{

    NTSTATUS status = STATUS_SUCCESS;
    PIO_STACK_LOCATION pIrqlStack;
    ULONG uIoControlCode;
    PVOID pIoBuffer;
    ULONG uInLength;
    ULONG uOutLength;
    ULONG uRead;

    //獲取IRP教據
    pIrqlStack = IoGetCurrentIrpStackLocation(pIrp);
    //獲取控制碼
    uIoControlCode = pIrqlStack->Parameters.DeviceIoControl.IoControlCode;

    pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;
    uInLength = pIrqlStack->Parameters.DeviceIoControl.InputBufferLength;
    uOutLength = pIrqlStack->Parameters.DeviceIoControl.OutputBufferLength;

    switch (uIoControlCode)
    {
        case Hook:
            RtlMoveMemory(&uRead, pIoBuffer, 4);
            if (PsLookupProcessByProcessId((HANDLE)uRead, &protectedProcess) == STATUS_SUCCESS)
            {
                DbgPrint("得到消息,目前 EPROCESS 地址為:0x%p", protectedProcess);
            }
            else
            {
                status = STATUS_INVALID_HANDLE;
            }           
            break;
        case UnHook:
            protectedProcess = NULL;           
            break;
        default:
            status = STATUS_INVALID_DEVICE_REQUEST;
            break;
    }

    //設置返回狀態,否則默認是失敗
    pIrp->IoStatus.Status = status;
    pIrp->IoStatus.Information = 0;    //返回給3環多少位元組數據,沒有填0
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

NTSTATUS DEVICE_CREATE_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{

    DbgPrint("Create Success!\n");

    //設置返回狀態,否則默認是失敗
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;    //返回給3環多少位元組數據,沒有填0
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

NTSTATUS DEVICE_CLOSE_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{

    DbgPrint("Close Success!\n");

    //設置返回狀態,否則默認是失敗
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;    //返回給3環多少位元組數據,沒有填0
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

🔒 點擊查看應用代碼 🔒
#include "stdafx.h"
#include <windows.h>
#include <winioctl.h>
#include <stdlib.h>

#define Hook CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define UnHook CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define SYMBOL_LINK_NAME L"\\\\.\\ProcessHook"

HANDLE g_Device;
int main(int argc, char* argv[])
{
    //獲取驅動鏈接對象句柄
    g_Device=CreateFileW(SYMBOL_LINK_NAME,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
    if (g_Device==INVALID_HANDLE_VALUE)
    {
        puts("訪問驅動符號鏈接失敗!");
        goto endproc;
    }

    DWORD pid;
    DWORD outBuffer;
    DWORD lbret;

    puts("請輸入需要保護的程序的 PID :");
    scanf("%d",&pid);
        
    if (DeviceIoControl(g_Device,Hook,&pid,sizeof(DWORD),&outBuffer,sizeof(DWORD),&lbret,NULL))
    {
        puts("保護命令正在發送,請測試……");
    }   

    CloseHandle(g_Device);
endproc:
    system("pause");
    return 0;
}

SSDT Hook 隱藏嘗試

  SSDT Hook在現在是比較爛大街的技術,很多殺軟是基於這個技術進行自保等技術,比如我們上面做的項目就是一個自保的實現。我們上面的項目很容易被PCHunter檢測到:

  既然有檢測,肯定有對抗。根據系統調用的流程有一些對抗ARKSSDT Hook隱藏思路,這裡就提供一些思路,其他具體代碼的實現需要自行完成。
  據他人分析,PCHunter是通過重載內核文件使用SSDT表一個一個比對完成的,什麼是內核重載將會在進程線程篇進行講解,但其實不是,沒那麼簡單,下面介紹的方法可以繞過內核重載檢查SSDT。簡單點說,內核重載就是把內核文件重新在內存展開,因為內核文件也遵守PE格式。我們從IDA看看SSDT表長啥樣子,由於篇幅,只摘錄一部分:

.data:0047BFA0 _KeServiceDescriptorTable dd 0          ; DATA XREF: KeAddSystemServiceTable(x,x,x,x,x)+11↓r
.data:0047BFA0                                         ; KeAddSystemServiceTable(x,x,x,x,x)+4D↓w ...
.data:0047BFA4 dword_47BFA4    dd 0                    ; DATA XREF: KeAddSystemServiceTable(x,x,x,x,x)+53↓w
.data:0047BFA4                                         ; KeRemoveSystemServiceTable(x)+47↓w ...
.data:0047BFA8 dword_47BFA8    dd 0                    ; DATA XREF: KeAddSystemServiceTable(x,x,x,x,x)+59↓w
.data:0047BFA8                                         ; KeRemoveSystemServiceTable(x)+4D↓w ...
.data:0047BFAC dword_47BFAC    dd 0                    ; DATA XREF: KeAddSystemServiceTable(x,x,x,x,x)+5F↓w
.data:0047BFAC                                         ; KeRemoveSystemServiceTable(x)+53↓w ...

  咱們逆向分析0環調用流程時,我們也做過思考題,如下所示:

APIService:                             ; CODE XREF: _KiBBTUnexpectedRange+18↑j
                                        ; _KiSystemService+6F↑j
                mov     edi, eax        ; eax = 3環傳來的服務號
                shr     edi, 8
                and     edi, 30h
                mov     ecx, edi        ; 正好一個表的大小就是0x10,如果是GUI相關,就加;反之不加。
                add     edi, [esi+_KTHREAD.ServiceTable] ; edi = ServiceTable
                mov     ebx, eax        ; eax = 3環傳來的服務號
                and     eax, 0FFFh      ; 去掉索引為12的位
                cmp     eax, [edi+8]    ; 得到的結果與ServiceLimit比較
                jnb     _KiBBTUnexpectedRange ; 如果超出,說明越界,跳走
                cmp     ecx, 10h
                jnz     short loc_46660C ; 判斷是否是調用 win32k.sys 的,不是的話跳走
                mov     ecx, ds:0FFDFF018h ; _DWORD
                xor     ebx, ebx

  我們可以看到,咱們調用內核真實的函數地址是從當前線程取出的,也就是說,我們替換當前線程的ServiceTablePCHunter就不會檢測出來。
  注意涉及GUI的就不是KeServiceDescriptorTable,而是KeServiceDescriptorTableShadow了。為了替換ServiceTable,我們首先申請一塊內存,把SSDTShadow拷貝一份到裏面,然後把該Hook的函數寫入進去即可。我們測試一下(有些知識可能有點超前,可以自行學習玩進程線程篇後再來看不懂的代碼):

🔒 點擊查看驅動代碼 🔒
#include <ntifs.h>
#include <wdm.h>
#include <ntddk.h>

#define TerminateProcessIndex 0x101
const int  ThreadListAndSerivceTableSpace = 0x1b0 - 0xe0;

UNICODE_STRING Devicename;
UNICODE_STRING SymbolLink;

#define DEVICE_NAME L"\\Device\\ProcessHook"
#define SYMBOL_LINK L"\\??\\ProcessHook"

//操作碼:0x0-0x7FF 被保留,0x800-0xFFF 可用
#define Hook CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define UnHook CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)

NTSTATUS DEVICE_CONTROL_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp);
NTSTATUS DEVICE_CREATE_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp);
NTSTATUS DEVICE_CLOSE_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp);

PEPROCESS protectedProcess = NULL;

struct SSDT_ITEM
{
    PULONG funcTable;
    ULONG count;
    ULONG limit;
    PUCHAR paramTable;
};

extern struct SSDT_ITEM* KeServiceDescriptorTable;

typedef  NTSTATUS(__stdcall* NtTerminateProcess)(HANDLE ProcessHandle, NTSTATUS ExitStatus);

NtTerminateProcess oldNtTerminateProcess = NULL;

struct SSDT_ITEM* mySSDT;

NTSTATUS __stdcall HookNtTerminateProcess(HANDLE ProcessHandle, NTSTATUS ExitStatus)
{
    if (protectedProcess != NULL)
    {
        PEPROCESS pro = IoGetCurrentProcess();
        PEPROCESS p;
        if (ObReferenceObjectByHandle(ProcessHandle, NULL, *PsProcessType, KernelMode, &p, NULL) == STATUS_SUCCESS)
        {
            if (p == protectedProcess && pro != protectedProcess)
            {
                return STATUS_ACCESS_DENIED;
            }
        }
    }
    return oldNtTerminateProcess(ProcessHandle, ExitStatus);
}

void HookTerminateProcess()
{
    mySSDT = ExAllocatePool(NonPagedPool, 0x40);
    if (mySSDT == NULL)
    {
        DbgPrint("構建SSDT失敗!");
        return;
    }
    memset(mySSDT, 0, 0x40);
    oldNtTerminateProcess = KeServiceDescriptorTable->funcTable[TerminateProcessIndex];
    RtlCopyMemory(mySSDT, (INT)KeServiceDescriptorTable - 0x40, 0x40);
    mySSDT->funcTable[TerminateProcessIndex] = HookNtTerminateProcess;
}

PDEVICE_OBJECT dobj;

void UnloadDriver(PDRIVER_OBJECT DriverObject)
{
    IoDeleteSymbolicLink(&SymbolLink);
    IoDeleteDevice(dobj);
    if (mySSDT != NULL)
    {
        ExFreePool(mySSDT);
    }
    DbgPrint("卸載成功!!!\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{

    DriverObject->DriverUnload = UnloadDriver;

    RtlInitUnicodeString(&Devicename, DEVICE_NAME);

    IoCreateDevice(DriverObject, 0, &Devicename, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &dobj);
    RtlInitUnicodeString(&SymbolLink, SYMBOL_LINK);
    IoCreateSymbolicLink(&SymbolLink, &Devicename);
    DriverObject->Flags |= DO_BUFFERED_IO;

    DriverObject->MajorFunction[IRP_MJ_CREATE] = DEVICE_CREATE_Dispatch;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DEVICE_CONTROL_Dispatch;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = DEVICE_CLOSE_Dispatch;

    HookTerminateProcess();
    DbgPrint("驅動加載完畢!!!\n");

    return STATUS_SUCCESS;
}

NTSTATUS DEVICE_CONTROL_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{

    NTSTATUS status = STATUS_SUCCESS;
    PIO_STACK_LOCATION pIrqlStack;
    ULONG uIoControlCode;
    PVOID pIoBuffer;
    ULONG uInLength;
    ULONG uOutLength;
    ULONG uRead;

    //獲取IRP教據
    pIrqlStack = IoGetCurrentIrpStackLocation(pIrp);
    //獲取控制碼
    uIoControlCode = pIrqlStack->Parameters.DeviceIoControl.IoControlCode;

    pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;
    uInLength = pIrqlStack->Parameters.DeviceIoControl.InputBufferLength;
    uOutLength = pIrqlStack->Parameters.DeviceIoControl.OutputBufferLength;

    switch (uIoControlCode)
    {
        case Hook:
            RtlMoveMemory(&uRead, pIoBuffer, 4);
            if (PsLookupProcessByProcessId((HANDLE)uRead, &protectedProcess) == STATUS_SUCCESS)
            {
                DbgPrint("得到消息,目前 EPROCESS 地址為:0x%p", protectedProcess);

                LIST_ENTRY* le = (INT)protectedProcess + 0x50;
                LIST_ENTRY* pe = le;

                while (1)
                {
                    pe = le->Blink;
                    if (pe == le)
                    {
                        break;
                    }
                    *(UINT32*)((UINT32)pe - ThreadListAndSerivceTableSpace) = mySSDT;
                }

                DbgPrint("處理完畢!");

            }
            else
            {
                status = STATUS_INVALID_HANDLE;
            }
            break;
        case UnHook:
            protectedProcess = NULL;

            LIST_ENTRY* le = (INT)protectedProcess + 0x50;
            LIST_ENTRY* pe = le;

            while (1)
            {
                pe = le->Blink;
                if (pe == le)
                {
                    break;
                }
                *(UINT32*)((UINT32)pe - ThreadListAndSerivceTableSpace) = KeServiceDescriptorTable;
            }

            break;
        default:
            status = STATUS_INVALID_DEVICE_REQUEST;
            break;
    }

    //設置返回狀態,否則默認是失敗
    pIrp->IoStatus.Status = status;
    pIrp->IoStatus.Information = 0;    //返回給3環多少位元組數據,沒有填0
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

NTSTATUS DEVICE_CREATE_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{

    DbgPrint("Create Success!\n");

    //設置返回狀態,否則默認是失敗
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;    //返回給3環多少位元組數據,沒有填0
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

NTSTATUS DEVICE_CLOSE_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{

    DbgPrint("Close Success!\n");

    //設置返回狀態,否則默認是失敗
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;    //返回給3環多少位元組數據,沒有填0
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

  應用層代碼同本篇小結項目。PCHunter還是能夠檢測到該種SSDT Hook。卸載該驅動模塊後,PCHunter檢測此方法會有後遺症,如下圖所示:

  其實我早已把我構造的假SSDT撤掉了。

GUI 線程轉化

  普通非GUI線程轉化為GUI線程的時候,會調用PsConvertToGuiThread函數實現,為了方便理解,直接看其偽代碼:

int __stdcall PsConvertToGuiThread()
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v0 = KeGetCurrentThread();
  v1 = v0;
  if ( !v0->PreviousMode )
    return 0xC000000D;
  if ( !PspW32ProcessCallout )
    return 0xC0000022;
  if ( v0->ServiceTable != &KeServiceDescriptorTable )
    return 0x4000001B;
  v7 = v0->ApcState.Process;
  if ( !v0->LargeStack )
  {
    v3 = MmCreateKernelStack(1, v0->InitialNode);
    if ( !v3 )
    {
      ms_exc.registration.TryLevel = 0;
      KeGetPcr()->NtTib.Self[1].Self = 8;
      ms_exc.registration.TryLevel = -1;
      return 0xC0000017;
    }
    NewIrql = KfRaiseIrql(1u);
    v4 = KeSwitchKernelStack(v3, v3 - 12288);
    KfLowerIrql(NewIrql);
    MmDeleteKernelStack(v4, 0);
  }
  if ( PPerfGlobalGroupMask && (*(PPerfGlobalGroupMask + 4) & 1) != 0 )
  {
    v5[6] = *&v1[1].DebugActive;
    v5[7] = *&v1[1].Iopl;
    v5[0] = v1->StackBase;
    v5[1] = v1->StackLimit;
    v5[2] = 0;
    v5[3] = 0;
    v5[4] = 0;
    v5[5] = 0;
    v6 = -1;
    PerfInfoLogBytes(1315, v5, 36);
  }
  result = PspW32ProcessCallout(v7, 1);
  if ( result >= 0 )
  {
    v1->ServiceTable = &KeServiceDescriptorTableShadow;
    result = PspW32ThreadCallout(v1, 0);
    if ( result < 0 )
      v1->ServiceTable = &KeServiceDescriptorTable;
  }
  return result;
}

  大概流程就是創建更大的線程堆棧,換掉並刪除原來的棧,經歷過一定流程的時候,再換ServiceTable,而這個特徵就是判斷是否為GUI線程的最明顯的特徵。

下一篇

  羽夏看Win系統內核——進程線程篇