天天看点

系统调用篇——总结与提升

系统调用篇之总结与提升,介绍 SSDT Hook 项目编写,隐藏 SSDT Hook 尝试和 GUI 线程的简单介绍。

写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。

你如果是从中间插过来看的,请仔细阅读 羽夏看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

,我们就必须具有写权限,修改自身模块我们可以想改就改没啥问题,但是要修改其他模块的,就得小心行事了,因为我们不能确保那块内存我们具有写的权限。一种方式我们可以修改物理页属性,这个我就不赘述了,提供下面的代码仅供参考:

void SetReadWrite(UINT32 HookFunAddr, BOOLEAN writeable)
{
    DWORD32 RCR4;

    _asm
    {
        _emit 0x0F;
        _emit 0x20;
        _emit 0xE0; //mov eax,cr4
        mov RCR4, eax;
    }

    if (RCR4 & 0x00000020)
    {
        // 2 - 9 - 9 - 12 分页
        DbgPrint("2-9-9-12分页");


        DWORD64* PDE = (DWORD64*)(0xC0600000 + ((HookFunAddr >> 18) & 0x3FF8));

        if (writeable) *PDE |= 2; else *PDE &= ~2;

        if (!(*PDE & 0x80)) //判断是否不为大页
        {
            DWORD64* PTE = (DWORD64*)(0xC0000000 + ((HookFunAddr >> 9) & 0x7FFFF8));
            if (writeable) *PDE |= 2; else *PDE &= ~2;
        }
        else
        {
            DbgPrint("大页");
        }
    }
    else
    {
        // 10 - 10 - 12 分页
        DbgPrint("10-10-12分页\n");

        DWORD32* PDE = (DWORD32*)(0XC0300000 + (HookFunAddr >> 20) & 0xFFC);
        if (writeable) *PDE |= 2; else *PDE &= ~2;

        if (!(*PDE & 0x80)) //判断是否不为大页
        {
            DWORD32* PTE = (DWORD32*)(0xC0000000 + (HookFunAddr >> 10) & 0x3FFFFC);
            if (writeable) *PDE |= 2; else *PDE &= ~2;
        }
        else
        {
            DbgPrint("大页");
        }
    }
}
           

  本项目通过修改

CR0

WP

位实现绕过只读,如果忘却,请自行复习 保护模式篇——中断与异常和控制寄存器 中的控制寄存器的

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();
}
           

  如果你使用修改物理页的方式代码的话,请不要这么写:

void HookTerminateProcess()
{
    SetReadWrite(KeServiceDescriptorTable->funcTable, TRUE);
    oldNtTerminateProcess = KeServiceDescriptorTable->funcTable[TerminateProcessIndex];
    KeServiceDescriptorTable->funcTable[TerminateProcessIndex] = HookNtTerminateProcess;
    SetReadWrite(KeServiceDescriptorTable->funcTable, FALSE);
}
           

  如果你使用上面的代码,在

2-9-9-12

分页下,加载驱动后虚拟机将会死机,

Windbg

无法下断点。我仔细观察该物理页的属性,发现它的值如下为

0x4001e3

,根据

PAE

分页,该物理页可读可写,且为大页,只能特权用户访问,也就是说,这个地方本来就是可写的,你把它改为只读,就会出错。在

10-10-12

分页下未经测试,应该效果是一样的。

  我们接下来看

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

检测到:

系统调用篇——总结与提升

  既然有检测,肯定有对抗。根据系统调用的流程有一些对抗

ARK

SSDT 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
           

  我们可以看到,咱们调用内核真实的函数地址是从当前线程取出的,也就是说,我们替换当前线程的

ServiceTable

PCHunter

就不会检测出来。

  注意涉及

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系统内核——进程线程篇

系统调用篇——总结与提升
系统调用篇——总结与提升

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可

本文来自博客园,作者:寂静的羽夏 ,一个热爱计算机技术的菜鸟

转载请注明原文链接:https://www.cnblogs.com/wingsummer/p/15831506.html

系统调用篇——总结与提升