1 驱动

1.1 内核编程基础

简单的驱动程序

读取GDT并打印:

#include <ntddk.h>    

NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject)
{
    return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    DbgPrint("Hello Driver!\n");
    DriverObject->DriverUnload = UnloadDriver;

    char gdt[6];
    _asm {
        sgdt gdt;
    }

    USHORT limit = *(USHORT*)gdt;
    UINT32 base = *(UINT32*)&gdt[2];
    UINT32* mem = (UINT32*)ExAllocatePoolWithTag(NonPagedPool, limit, NULL);
    if (!mem) {
        DbgPrint("Allocate Failed!\n");
    }
    else {
        RtlMoveMemory(mem, base, limit);
        for (int i = 0; i < limit / 4; i+=2) {
            DbgPrint("GDT[%d]:\t %0.8x`%0.8x\n", i / 2, mem[i + 1], mem[i]);
        }
    }
    ExFreePool(mem);
    return STATUS_SUCCESS;
}

DRIVER_OBJECT

DRIVER_OBJECT结构体中存储当前驱动的所有信息:
3-3

  • DriverStart:驱动对象加载后的起始地址;
  • DriverSize:驱动对象加载后的大小;
  • DriverSection:当前驱动在LDR_DATA_TABLE_ENTRY链表中的起始地址;

LDR_DATA_TABLE_ENTRY

此结构体构成的双向链表存储所有加载的内核驱动信息:
3-4

IRQL

由Windows系统提供的一套中断执行优先级,数字越大优先级越高:

IRQL 说明
0 PASSIVE_LEVEL 最低级别,不屏蔽任何中断,用于用户模式的线程;
1 APC_LEVEL 屏蔽其他APC级别的中断,用于异步过程调用;
2 DISPATCH_LEVEL <=2的中断被屏蔽,不能访问分页内存,主要用于
进程切换;
3-26 Device IRQL 屏蔽几乎所有中断,用于外部设备;
27 PROFILE_LEVEL 性能剖析;
28 CLOCK_LEVEL 时钟中断;
29 IPL_LEVEL 处理器间中断;
30 POWER_LEVEL 电源中断;
31 HIGH_LEVEL 最高中断层;

1.2 实验1:隐藏内核模块

遍历内核模块:
3-5

#include <ntddk.h>    

NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject)
{
    return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    DbgPrint("Hello Driver!\n");
    DriverObject->DriverUnload = UnloadDriver;

    LIST_ENTRY* pEntry = DriverObject->DriverSection;
    LIST_ENTRY* pCurrentEntry = pEntry;
    while (1) {
        PUNICODE_STRING name = (PUNICODE_STRING)((UINT32)pEntry+0x2c);
        DbgPrint("DriverName : %wZ\n", name);
        pEntry = pEntry->Flink;
        if (pEntry == pCurrentEntry)
            break;
    }

    return STATUS_SUCCESS;
}

修改双向链表,去掉当前节点:
使用XueTr工具仍然可以检测到隐藏后的驱动:
3-6

#include <ntddk.h>    

LIST_ENTRY* pCurrentEntry;
LIST_ENTRY* pBeforeEntry;
LIST_ENTRY* pFrontEntry;

NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject)
{
    pBeforeEntry->Flink = pCurrentEntry;
    pFrontEntry->Blink = pCurrentEntry;
    return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    DbgPrint("Hello Driver!\n");
    DriverObject->DriverUnload = UnloadDriver;

    pCurrentEntry = DriverObject->DriverSection;
    pBeforeEntry = pCurrentEntry->Blink;
    pFrontEntry = pCurrentEntry->Flink;

    pBeforeEntry->Flink = pFrontEntry;
    pFrontEntry->Blink = pBeforeEntry;

    return STATUS_SUCCESS;
}

1.3 实验2:设备通信

在编写驱动时,创建设备和对应的符号链接,然后在Ring3通过符号链接即可链接对应的设备。
驱动代码:

#include <ntddk.h>    


// 定义设备名和驱动符号
#define DEVICE_NAME L"\\Device\\Demo1"
#define SYMBOLICLINK_NAME L"\\??\\Demo1"
// 定义 I/O 控制代码,只有 0x800-0xFFF 可用
#define OPER1 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)

// 卸载驱动
NTSTATUS UnloadDriver(PDRIVER_OBJECT pDriObj)
{
    UNICODE_STRING SymbolicLinkName = { 0 };
    DbgPrint("Unload Driver!\n");

    // 删除符号和设备
    RtlInitUnicodeString(&SymbolicLinkName, SYMBOLICLINK_NAME);
    IoDeleteSymbolicLink(&SymbolicLinkName);
    IoDeleteDevice(pDriObj->DeviceObject);

    return STATUS_SUCCESS;
}

// Ring3 创建设备对象时的处理函数
NTSTATUS IrpCreateProc(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
    DbgPrint("Open Device!\n");

    // 设置返回状态
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

// 关闭设备对象时的处理函数
NTSTATUS IrpCloseProc(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
    DbgPrint("Close Device!\n");

    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

// 处理设备通信
NTSTATUS IrpDeviceControlProc(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
    NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
    PIO_STACK_LOCATION pIrpStack;
    ULONG uIoControlCode;
    PVOID pIoBuffer;
    ULONG uInLength;
    ULONG uOutLength;
    ULONG uRead;
    ULONG uWrite=0xaabbccdd;
    // 获取堆栈地址
    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    // 获取I/O控制码
    uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
    // 获取缓冲区地址
    pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;
    // 接收的数据长度
    uInLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
    // 要发送的数据长度
    uOutLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
    //DbgPrint("The IOCTL is %x\n", uIoControlCode);
    if (uIoControlCode == OPER1) {
        DbgPrint("The IOCTL is %x\n", uIoControlCode);
        DbgPrint("IrpDeviceControlProc -> OPER1 接收字节数:%d \n", uInLength);
        DbgPrint("IrpDeviceControlProc -> OPER1 输出字节数:%d \n", uOutLength);
        // Read From Buffer
        RtlMoveMemory(&uRead, pIoBuffer, 4);
        DbgPrint("IrpDeviceControlProc -> OPER1 ... %x \n", uRead);
        // Write To Buffer
        RtlMoveMemory(pIoBuffer, &uWrite, 4);
        // Set Status
        pIrp->IoStatus.Information = 4;
        status = STATUS_SUCCESS;
    }

    pIrp->IoStatus.Status = status;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return status;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegistryPath)
{
    NTSTATUS status = 0;
    ULONG    uIndex = 0;
    PDEVICE_OBJECT pDeviceObj = NULL;
    UNICODE_STRING Devicename;
    UNICODE_STRING SymbolicLinkName;

    DbgPrint("Load Driver!\n");
    pDriver->DriverUnload = UnloadDriver;

    // 创建设备
    RtlInitUnicodeString(&Devicename, DEVICE_NAME);
    status = IoCreateDevice(pDriver, 0, &Devicename, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObj);
    if (status != STATUS_SUCCESS)
    {
        DbgPrint("Create Device Failed! \n");
        return status;
    }
    DbgPrint("Create Device Success! \n");
    // 设置数据交互方式
    pDeviceObj->Flags |= DO_BUFFERED_IO;
    // 创建符号链接
    RtlInitUnicodeString(&SymbolicLinkName, SYMBOLICLINK_NAME);
    status = IoCreateSymbolicLink(&SymbolicLinkName, &Devicename);
    if (status != STATUS_SUCCESS)
    {
        DbgPrint("Create Symbols Failed! \n");
        IoDeleteDevice(pDeviceObj);
        return status;
    }
    DbgPrint("Create Symbols Success! \n");
    // 设置处理函数
    pDriver->MajorFunction[IRP_MJ_CREATE] = IrpCreateProc;
    pDriver->MajorFunction[IRP_MJ_CLOSE] = IrpCloseProc;
    pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceControlProc;

    return STATUS_SUCCESS;
}

Ring3代码(如果不使用宽字符的话,要将VS属性设置为多字符集):

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <winioctl.h>

#define IN_BUFFER_MAXLENGTH  0x10	//输入缓存最大长度
#define OUT_BUFFER_MAXLENGTH 0x10	//输出缓存最大长度
#define OPER1 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define SYMBOLICLINK_NAME "\\\\.\\Demo1"

HANDLE g_hDevice; // 驱动句柄

// 创建设备对象
BOOL Open(PCHAR pLinkName)
{
 TCHAR szBuffer[10] = { 0 };
 //在3环获取驱动程序
 g_hDevice = CreateFile(pLinkName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
 DWORD err = GetLastError();
 sprintf(szBuffer, "%d\n", err);
 if (g_hDevice != INVALID_HANDLE_VALUE)
  return TRUE;
 else
  return FALSE;
}

// 设备通信函数
BOOL IoControl(DWORD dwIoCode, PVOID InBuff, DWORD InBuffLen, PVOID OutBuff, DWORD OutBuffLen)
{
 DWORD dw;
 //驱动句柄/操作码/输入缓冲区地址/输入缓冲区长度/输出缓冲区地址/输出缓冲区长度/返回长度/指向OVERLAPPED 此处为NULL
 DeviceIoControl(g_hDevice, dwIoCode, InBuff, InBuffLen, OutBuff, OutBuffLen, &dw, NULL);
 return TRUE;
}

int main()
{
 DWORD dwInBuffer = 0x11223344;
 DWORD szOutBuffer = 0;
 Open(SYMBOLICLINK_NAME);
 IoControl(OPER1, &dwInBuffer, IN_BUFFER_MAXLENGTH, &szOutBuffer, OUT_BUFFER_MAXLENGTH);
 printf("%p\n", szOutBuffer);
 //3. 关闭设备
 CloseHandle(g_hDevice);
 system("pause");
 return 0;
}

3-7

1.4 实验3:中断门实现Inline Hook

通过构造中断门提权后修改特定的API函数入口跳转到我们构造的Hook函数,执行完操作后恢复现场返回到API入口的下一行继续执行。
本实验以ntkrnlpa.exe中的KiFastEntry函数为例,使用Xuetr查看模块地址后在IDA中修改基址然后就可以进行分析了。
3-1
3-2
首先在内存中写入构造的Hook函数,这里使用GDT中的空闲表项地址,实现了一个API调用计数器的简单功能:

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>


char* g_hookAddr;
void Hook(void);

// 向Hook函数地址写入字节码
void _declspec(naked) IdtEntry(void)
{
 g_hookAddr = (char*)0x8003f120;
 for (int i = 0; i < 64; ++i) {
  *g_hookAddr = ((char*)Hook)[i];
  g_hookAddr++;
 }
 _asm {
  iretd
 }
}

void _declspec(naked) Hook(void)
{
 _asm {
  pushad
  pushfd

  mov eax, ds:[0x8003f180]
  inc eax
  mov ds : [0x8003f180], eax

  popfd
  popad

  mov ecx, 0x23
  push 0x30
  pop fs
  mov ds, cx
  mov es, cx
  mov	ecx, 0x8053E54D

  jmp ecx
 }
}


void go(void)
{
 _asm {
  int 0x20
 }
}


int main()
{
 if ((DWORD)IdtEntry != 0x401040) {
  printf("Wrong Address!\n");
  exit(-1);
 }
 go();
 system("pause");
 return 0;
}

然后是修改API函数入口,将其指向跳转到Hook函数的地址:

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

// 设置Hook
void _declspec(naked) IdtEntry(void)
{
 _asm {
  // 关闭写保护
  mov eax, cr0
  and eax, not 0x10000
  mov cr0, eax

  // 构造跳转
  mov al, 0xe9
  mov ds:[0x8053E540], al
  mov eax, 0xFFB00BDB
  mov ds:[0x8053E541], eax
  // 初始化计数器
  xor eax, eax
  mov ds:[0x8003f180], eax
  // 恢复写保护
  mov eax, cr0
  or eax, 0x10000
  mov cr0, eax
  iretd
 }
}

void go(void)
{
 _asm {
  int 0x20
 }
}

int main()
{
 if ((DWORD)IdtEntry != 0x401040) {
  printf("Wrong Address!\n");
  exit(-1);
 }
 go();
 system("pause");
 return 0;
}

最后写一个循环读取计数器的程序:

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

DWORD g_num = 0;
DWORD g_flag = 1;
// 设置Hook
void _declspec(naked) IdtEntry(void)
{
 _asm {
  mov eax, g_flag
  cmp eax, 1
  jne L
  xor eax, eax
  mov g_flag, 0
  mov ds:[0x8003f180], eax
  L:
  mov eax, ds : [0x8003f180]
  mov g_num, eax
  iretd
 }
}

void go(void)
{
 _asm {
  //mov g_flag, 1
  int 0x20
 }
}

int main()
{
 if ((DWORD)IdtEntry != 0x401040) {
  printf("Wrong Address!\n");
  exit(-1);
 }	
 while (1)
 {
  go();
  printf("%d\n", g_num);
  Sleep(1000);
 }
 system("pause");
 return 0;
}

2 句柄表

句柄表的作用是封装内核对象指针为句柄供3环使用,避免在3环下直接操作内核对象地址。

2.1 进程句柄表

句柄结构:
3-12

  • 1:高字节给SetHandleInformation函数使用,低字节保留;
  • 2:访问掩码,给OpenProcess函数使用;
  • 3&4:
    • 第0位标识是否允许关闭此句柄,默认为1;
    • 第1位标识此句柄是否可被继承,默认为1;
    • 第2位标识关闭对象时是否产生一个审计事件,默认为0;

实验4:定位句柄表

编写代码,使用OpenProcess获取句柄:

#include <stdio.h>
#include <windows.h>

int main()
{
 DWORD PID;
 HANDLE hPro = NULL;

 HWND hWnd = ::FindWindow(NULL, L"计算器");
 ::GetWindowThreadProcessId(hWnd, &PID);

 for (int i = 0; i < 10; i++)
 {
  hPro = ::OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, TRUE, PID);

  printf("句柄:%x\n", hPro);
 }

 getchar();
 return 0;
}

查看进程结构体:

kd> dt _EPROCESS 822d6658
nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER 0x01da8104`323d7d16
   +0x078 ExitTime         : _LARGE_INTEGER 0x0
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : 0x00000640 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x8055b158 - 0x822d79d8 ]
   +0x090 QuotaUsage       : [3] 0x578
   +0x09c QuotaPeak        : [3] 0x578
   +0x0a8 CommitCharge     : 0x5a
   +0x0ac PeakVirtualSize  : 0xd9b000
   +0x0b0 VirtualSize      : 0xd9b000
   +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0xf8bd2014 - 0x822d7a04 ]
   +0x0bc DebugPort        : (null) 
   +0x0c0 ExceptionPort    : 0xe16659f8 Void
   +0x0c4 ObjectTable      : 0xe1fdae30 _HANDLE_TABLE
   ...

定位ObjectTable:

kd> dt _HANDLE_TABLE e1fdae30
nt!_HANDLE_TABLE
   +0x000 TableCode        : 0xe110d000
   +0x004 QuotaProcess     : 0x822d6658 _EPROCESS
   +0x008 UniqueProcessId  : 0x00000640 Void
   +0x00c HandleTableLock  : [4] _EX_PUSH_LOCK
   +0x01c HandleTableList  : _LIST_ENTRY [ 0x8055c448 - 0xe21ed88c ]
   +0x024 HandleContentionEvent : _EX_PUSH_LOCK
   +0x028 DebugInfo        : (null) 
   +0x02c ExtraInfoPages   : 0n0
   +0x030 FirstFree        : 0x60
   +0x034 LastFree         : 0
   +0x038 NextHandleNeedingPool : 0x800
   +0x03c HandleCount      : 0n23
   +0x040 Flags            : 0
   +0x040 StrictFIFO       : 0y0

根据OpenProcess返回的句柄号定位对应的句柄:

kd> dq e110d000 + 0x38 / 4 * 8
ReadVirtual: e110d080 not properly sign extended
e110d070  0000003a`822d793b 0000003a`822d793b
e110d080  0000003a`822d793b 0000003a`822d793b
e110d090  0000003a`822d793b 0000003a`822d793b
e110d0a0  0000003a`822d793b 0000003a`822d793b
e110d0b0  0000003a`822d793b 0000003a`822d793b
e110d0c0  00000064`00000000 00000068`00000000
e110d0d0  0000006c`00000000 00000070`00000000
e110d0e0  00000074`00000000 00000078`00000000

根据句柄结构,计算出内核对象的地址为822d7938,此地址是一个0x18字节的_OBJECT_HEADER结构体,后面紧跟着对应进程的EPROCESS结构体:

kd> dt _OBJECT_HEADER 822d7938
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n26
   +0x004 HandleCount      : 0n12
   +0x004 NextToFree       : 0x0000000c Void
   +0x008 Type             : 0x825b9e70 _OBJECT_TYPE
   +0x00c NameInfoOffset   : 0 ''
   +0x00d HandleInfoOffset : 0 ''
   +0x00e QuotaInfoOffset  : 0 ''
   +0x00f Flags            : 0x20 ' '
   +0x010 ObjectCreateInfo : 0x823117c8 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x823117c8 Void
   +0x014 SecurityDescriptor : 0xe1f9e4e6 Void
   +0x018 Body             : _QUAD

2.2 全局句柄表

全局句柄表中存储的是每一个线程和进程对应的句柄。
如果全局句柄表中的句柄总数多于512,操作系统会采取多级句柄表的形式,并使用HANDLE_TABLE结构体中的TableCode低字节标识,为0时标识一级句柄表,为1时标识二级句柄表,以此类推。

实验5:定位全局句柄表

获取全局句柄表地址:

kd> dd PspCidTable
8055b260  e1000898 00000002 00000000 00000000
8055b270  00000000 00000000 00000000 00000000
8055b280  00000000 00000000 00000000 00000000
8055b290  00000000 00000000 00000000 00000000
8055b2a0  00000000 00000000 00000000 00000000
8055b2b0  00000000 00000000 00000000 00000000
8055b2c0  00000000 00000000 00000000 00000000
8055b2d0  00000000 00000000 00000000 00000000

查看句柄表结构体:

kd> dt _HANDLE_TABLE e1000898
nt!_HANDLE_TABLE
   +0x000 TableCode        : 0xe1003000
   +0x004 QuotaProcess     : (null) 
   +0x008 UniqueProcessId  : (null) 
   +0x00c HandleTableLock  : [4] _EX_PUSH_LOCK
   +0x01c HandleTableList  : _LIST_ENTRY [ 0xe10008b4 - 0xe10008b4 ]
   +0x024 HandleContentionEvent : _EX_PUSH_LOCK
   +0x028 DebugInfo        : (null) 
   +0x02c ExtraInfoPages   : 0n0
   +0x030 FirstFree        : 0x42c
   +0x034 LastFree         : 0x414
   +0x038 NextHandleNeedingPool : 0x800
   +0x03c HandleCount      : 0n333
   +0x040 Flags            : 1
   +0x040 StrictFIFO       : 0y1

此时为1级句柄表,直接获取句柄即可,偏移量 = PID / 4 * 8:

kd> dq e1003000 + e60
ReadVirtual: e1003e60 not properly sign extended
e1003e60  00000000`8208d489 00000000`8233ada9
e1003e70  00000000`8241a021 00000000`81fa8b59
e1003e80  00000000`82336021 00000000`823365b9
e1003e90  00000000`8240abd9 00000000`8240a941
e1003ea0  00000000`81fa3021 00000000`81fa3da9
e1003eb0  00000000`81fa3b31 00000000`81fa38b9
e1003ec0  00000000`81fa3609 00000000`8208aa91
e1003ed0  00000000`82409021 00000450`00000000

根据句柄结构可以定位到地址8208d488,此时的地址就是对应的进程结构体:

kd> dt _EPROCESS 8208d488
nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER 0x01da8108`407f49fa
   +0x078 ExitTime         : _LARGE_INTEGER 0x0
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : 0x00000730 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x8251db18 - 0x821e8680 ]
...

3 系统调用

3.1 3环调用

分析一下kernel32.dll中的导出函数ReadProcessMemory函数,反编译伪码如下。可以看到,在此函数中又调用了导入函数(来自ntdll.dll)中的NtReadVirtualMemory函数,并没有更多的操作。
3-8
继续分析ntdll.dll中的导出函数NtReadVirtualMemory,只有四行汇编代码,给EAX寄存器赋值后就call了一个特定地址(7FFE0300h)的值,而在这个地址上的就是3环进0环所调用的API函数地址,EAX中的值标识了调用哪个0环API函数。
3-9

模拟3环调用

因此我们可以在3环直接调用到0环的API,而不使用任何3环API函数。

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

DWORD g_temp = 0x12345678;

void MyReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress, 
 LPVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesRead);

int main()
{
 DWORD dwBuffer;
 HANDLE hProcess = GetCurrentProcess();
 MyReadProcessMemory(hProcess, &g_temp, &dwBuffer, 4, 0);
 printf("The Buffer is %p\n", dwBuffer);
 system("pause");
 return 0;
}

void MyReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress,
 LPVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesRead)
{
 _asm {
  lea  eax, [ebp + 0x14]
  push eax; ReturnLength
  push[ebp + 0x14]; BufferLength
  push[ebp + 0x10]; Buffer
  push[ebp + 0x0C]; BaseAddress
  push[ebp + 0x08]; ProcessHandle

  mov  eax, 0BAh
  mov  edx, esp
  int  2eh

  add  esp, 20; 平栈 
 }
}

使用SystemCall

void __declspec(naked) __stdcall MyReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress,
 LPVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesRead)
{
 _asm {
  mov  eax, 0BAh
  mov  edx, 7FFE0300h
  call dword ptr[edx]
  retn 14h
 }
}

或者直接调用sysenter

void MyReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress,
 LPVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesRead)
{
 _asm {
  lea  eax, [ebp + 0x14]
  push eax; ReturnLength
  push[ebp + 0x14]; BufferLength
  push[ebp + 0x10]; Buffer
  push[ebp + 0x0C]; BaseAddress
  push[ebp + 0x08]; ProcessHandle

  mov eax, 0BAh
  sub esp, 4		// 模拟call ZwReadVirtualMemory 
  push RETADDR	// 模拟call KiFastSystemCall
  mov edx, esp
  _emit 0x0F		// sysenter
  _emit 0x34
  RETADDR:
  add esp, 18h
 }
}

KUSER_SHARED_DATA

0环和3环分别由虚拟地址0xFFDF0000和0x7FFE0300映射到此结构体,3环为只读权限,0环可写。其中偏移为0x300的字段指向的是3环进行系统调用时的函数。

kd> dt _KUSER_SHARED_DATA
nt!_KUSER_SHARED_DATA
   +0x000 TickCountLow     : Uint4B
   +0x004 TickCountMultiplier : Uint4B
   +0x008 InterruptTime    : _KSYSTEM_TIME
   +0x014 SystemTime       : _KSYSTEM_TIME
   +0x020 TimeZoneBias     : _KSYSTEM_TIME
   +0x02c ImageNumberLow   : Uint2B
   +0x02e ImageNumberHigh  : Uint2B
   +0x030 NtSystemRoot     : [260] Uint2B
   +0x238 MaxStackTraceDepth : Uint4B
   +0x23c CryptoExponent   : Uint4B
   +0x240 TimeZoneId       : Uint4B
   +0x244 Reserved2        : [8] Uint4B
   +0x264 NtProductType    : _NT_PRODUCT_TYPE
   +0x268 ProductTypeIsValid : UChar
   +0x26c NtMajorVersion   : Uint4B
   +0x270 NtMinorVersion   : Uint4B
   +0x274 ProcessorFeatures : [64] UChar
   +0x2b4 Reserved1        : Uint4B
   +0x2b8 Reserved3        : Uint4B
   +0x2bc TimeSlip         : Uint4B
   +0x2c0 AlternativeArchitecture : _ALTERNATIVE_ARCHITECTURE_TYPE
   +0x2c8 SystemExpirationDate : _LARGE_INTEGER
   +0x2d0 SuiteMask        : Uint4B
   +0x2d4 KdDebuggerEnabled : UChar
   +0x2d5 NXSupportPolicy  : UChar
   +0x2d8 ActiveConsoleId  : Uint4B
   +0x2dc DismountCount    : Uint4B
   +0x2e0 ComPlusPackage   : Uint4B
   +0x2e4 LastSystemRITEventTickCount : Uint4B
   +0x2e8 NumberOfPhysicalPages : Uint4B
   +0x2ec SafeBootMode     : UChar
   +0x2f0 TraceLogging     : Uint4B
   +0x2f8 TestRetInstruction : Uint8B
   +0x300 SystemCall       : Uint4B
   +0x304 SystemCallReturn : Uint4B
   +0x308 SystemCallPad    : [3] Uint8B
   +0x320 TickCount        : _KSYSTEM_TIME
   +0x320 TickCountQuad    : Uint8B
   +0x330 Cookie           : Uint4B

3环进0环

KiIntSystemCall:如果通过中断门进0环,固定的中断号为0x2E,CS、EIP的值由中断门描述符确定,ESP、SS的值由TSS寄存器确定。进入0环后执行的函数为NT!KiSystemService
KiIntSystemCall:如果是通过sysenter指令进入0环,则CS、ESP、EIP全部由MSR寄存器提供,SS固定为CS+8。进入0环后执行的函数是NT!KiFastCallEntry

3.2 保护现场

栈帧

用于CPU进入0环后进行保护现场。
3-10

KPCR

保存和CPU相关的信息。

kd> dt _KPCR
nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x01c SelfPcr          : Ptr32 _KPCR
   +0x020 Prcb             : Ptr32 _KPRCB
   +0x024 Irql             : UChar
   +0x028 IRR              : Uint4B
   +0x02c IrrActive        : Uint4B
   +0x030 IDR              : Uint4B
   +0x034 KdVersionBlock   : Ptr32 Void
   +0x038 IDT              : Ptr32 _KIDTENTRY
   +0x03c GDT              : Ptr32 _KGDTENTRY
   +0x040 TSS              : Ptr32 _KTSS
   +0x044 MajorVersion     : Uint2B
   +0x046 MinorVersion     : Uint2B
   +0x048 SetMember        : Uint4B
   +0x04c StallScaleFactor : Uint4B
   +0x050 DebugActive      : UChar
   +0x051 Number           : UChar
   +0x052 Spare0           : UChar
   +0x053 SecondLevelCacheAssociativity : UChar
   +0x054 VdmAlert         : Uint4B
   +0x058 KernelReserved   : [14] Uint4B
   +0x090 SecondLevelCacheSize : Uint4B
   +0x094 HalReserved      : [16] Uint4B
   +0x0d4 InterruptMode    : Uint4B
   +0x0d8 Spare1           : UChar
   +0x0dc KernelReserved2  : [17] Uint4B
   +0x120 PrcbData         : _KPRCB

最后一个字段是KPCR的扩展,可以通过此结构体定位到KPCR:

kd> dd KiProcessorBlock
80553e40  ffdff120 00000000 00000000 00000000
80553e50  00000000 00000000 00000000 00000000
80553e60  00000000 00000000 00000000 00000000
80553e70  00000000 00000000 00000000 00000000
80553e80  00000000 00000000 00000000 00000000
80553e90  00000000 00000000 00000000 00000000
80553ea0  00000000 00000000 00000000 00000000
80553eb0  00000000 00000000 00000000 00000000

KPRCB

kd> dt _KPRCB
nt!_KPRCB
   +0x000 MinorVersion     : Uint2B
   +0x002 MajorVersion     : Uint2B
   +0x004 CurrentThread    : Ptr32 _KTHREAD
   +0x008 NextThread       : Ptr32 _KTHREAD
   +0x00c IdleThread       : Ptr32 _KTHREAD
   +0x010 Number           : Char
   +0x011 Reserved         : Char
   +0x012 BuildType        : Uint2B
   +0x014 SetMember        : Uint4B
   +0x018 CpuType          : Char
   +0x019 CpuID            : Char
   +0x01a CpuStep          : Uint2B
   +0x01c ProcessorState   : _KPROCESSOR_STATE
   +0x33c KernelReserved   : [16] Uint4B
   +0x37c HalReserved      : [16] Uint4B
   +0x3bc PrcbPad0         : [92] UChar
   +0x418 LockQueue        : [16] _KSPIN_LOCK_QUEUE
   +0x498 PrcbPad1         : [8] UChar
   +0x4a0 NpxThread        : Ptr32 _KTHREAD
   +0x4a4 InterruptCount   : Uint4B
   +0x4a8 KernelTime       : Uint4B
   +0x4ac UserTime         : Uint4B
   +0x4b0 DpcTime          : Uint4B
   +0x4b4 DebugDpcTime     : Uint4B
   +0x4b8 InterruptTime    : Uint4B
   +0x4bc AdjustDpcThreshold : Uint4B
   +0x4c0 PageColor        : Uint4B
   +0x4c4 SkipTick         : Uint4B
   +0x4c8 MultiThreadSetBusy : UChar
   +0x4c9 Spare2           : [3] UChar
   +0x4cc ParentNode       : Ptr32 _KNODE
   +0x4d0 MultiThreadProcessorSet : Uint4B
   +0x4d4 MultiThreadSetMaster : Ptr32 _KPRCB
   +0x4d8 ThreadStartCount : [2] Uint4B
   +0x4e0 CcFastReadNoWait : Uint4B
   +0x4e4 CcFastReadWait   : Uint4B
   +0x4e8 CcFastReadNotPossible : Uint4B
   +0x4ec CcCopyReadNoWait : Uint4B
   +0x4f0 CcCopyReadWait   : Uint4B
   +0x4f4 CcCopyReadNoWaitMiss : Uint4B
   +0x4f8 KeAlignmentFixupCount : Uint4B
   +0x4fc KeContextSwitches : Uint4B
   +0x500 KeDcacheFlushCount : Uint4B
   +0x504 KeExceptionDispatchCount : Uint4B
   +0x508 KeFirstLevelTbFills : Uint4B
   +0x50c KeFloatingEmulationCount : Uint4B
   +0x510 KeIcacheFlushCount : Uint4B
   +0x514 KeSecondLevelTbFills : Uint4B
   +0x518 KeSystemCalls    : Uint4B
   +0x51c SpareCounter0    : [1] Uint4B
   +0x520 PPLookasideList  : [16] _PP_LOOKASIDE_LIST
   +0x5a0 PPNPagedLookasideList : [32] _PP_LOOKASIDE_LIST
   +0x6a0 PPPagedLookasideList : [32] _PP_LOOKASIDE_LIST
   +0x7a0 PacketBarrier    : Uint4B
   +0x7a4 ReverseStall     : Uint4B
   +0x7a8 IpiFrame         : Ptr32 Void
   +0x7ac PrcbPad2         : [52] UChar
   +0x7e0 CurrentPacket    : [3] Ptr32 Void
   +0x7ec TargetSet        : Uint4B
   +0x7f0 WorkerRoutine    : Ptr32     void 
   +0x7f4 IpiFrozen        : Uint4B
   +0x7f8 PrcbPad3         : [40] UChar
   +0x820 RequestSummary   : Uint4B
   +0x824 SignalDone       : Ptr32 _KPRCB
   +0x828 PrcbPad4         : [56] UChar
   +0x860 DpcListHead      : _LIST_ENTRY
   +0x868 DpcStack         : Ptr32 Void
   +0x86c DpcCount         : Uint4B
   +0x870 DpcQueueDepth    : Uint4B
   +0x874 DpcRoutineActive : Uint4B
   +0x878 DpcInterruptRequested : Uint4B
   +0x87c DpcLastCount     : Uint4B
   +0x880 DpcRequestRate   : Uint4B
   +0x884 MaximumDpcQueueDepth : Uint4B
   +0x888 MinimumDpcRate   : Uint4B
   +0x88c QuantumEnd       : Uint4B
   +0x890 PrcbPad5         : [16] UChar
   +0x8a0 DpcLock          : Uint4B
   +0x8a4 PrcbPad6         : [28] UChar
   +0x8c0 CallDpc          : _KDPC
   +0x8e0 ChainedInterruptList : Ptr32 Void
   +0x8e4 LookasideIrpFloat : Int4B
   +0x8e8 SpareFields0     : [6] Uint4B
   +0x900 VendorString     : [13] UChar
   +0x90d InitialApicId    : UChar
   +0x90e LogicalProcessorsPerPhysicalProcessor : UChar
   +0x910 MHz              : Uint4B
   +0x914 FeatureBits      : Uint4B
   +0x918 UpdateSignature  : _LARGE_INTEGER
   +0x920 NpxSaveArea      : _FX_SAVE_AREA
   +0xb30 PowerState       : _PROCESSOR_POWER_STATE

ETHREAD

存储线程相关信息:

kd> dt _ETHREAD
nt!_ETHREAD
   +0x000 Tcb              : _KTHREAD
   +0x1c0 CreateTime       : _LARGE_INTEGER
   +0x1c0 NestedFaultCount : Pos 0, 2 Bits
   +0x1c0 ApcNeeded        : Pos 2, 1 Bit
   +0x1c8 ExitTime         : _LARGE_INTEGER
   +0x1c8 LpcReplyChain    : _LIST_ENTRY
   +0x1c8 KeyedWaitChain   : _LIST_ENTRY
   +0x1d0 ExitStatus       : Int4B
   +0x1d0 OfsChain         : Ptr32 Void
   +0x1d4 PostBlockList    : _LIST_ENTRY
   +0x1dc TerminationPort  : Ptr32 _TERMINATION_PORT
   +0x1dc ReaperLink       : Ptr32 _ETHREAD
   +0x1dc KeyedWaitValue   : Ptr32 Void
   +0x1e0 ActiveTimerListLock : Uint4B
   +0x1e4 ActiveTimerListHead : _LIST_ENTRY
   +0x1ec Cid              : _CLIENT_ID
   +0x1f4 LpcReplySemaphore : _KSEMAPHORE
   +0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
   +0x208 LpcReplyMessage  : Ptr32 Void
   +0x208 LpcWaitingOnPort : Ptr32 Void
   +0x20c ImpersonationInfo : Ptr32 _PS_IMPERSONATION_INFORMATION
   +0x210 IrpList          : _LIST_ENTRY
   +0x218 TopLevelIrp      : Uint4B
   +0x21c DeviceToVerify   : Ptr32 _DEVICE_OBJECT
   +0x220 ThreadsProcess   : Ptr32 _EPROCESS
   +0x224 StartAddress     : Ptr32 Void
   +0x228 Win32StartAddress : Ptr32 Void
   +0x228 LpcReceivedMessageId : Uint4B
   +0x22c ThreadListEntry  : _LIST_ENTRY
   +0x234 RundownProtect   : _EX_RUNDOWN_REF
   +0x238 ThreadLock       : _EX_PUSH_LOCK
   +0x23c LpcReplyMessageId : Uint4B
   +0x240 ReadClusterSize  : Uint4B
   +0x244 GrantedAccess    : Uint4B
   +0x248 CrossThreadFlags : Uint4B
   +0x248 Terminated       : Pos 0, 1 Bit
   +0x248 DeadThread       : Pos 1, 1 Bit
   +0x248 HideFromDebugger : Pos 2, 1 Bit
   +0x248 ActiveImpersonationInfo : Pos 3, 1 Bit
   +0x248 SystemThread     : Pos 4, 1 Bit
   +0x248 HardErrorsAreDisabled : Pos 5, 1 Bit
   +0x248 BreakOnTermination : Pos 6, 1 Bit
   +0x248 SkipCreationMsg  : Pos 7, 1 Bit
   +0x248 SkipTerminationMsg : Pos 8, 1 Bit
   +0x24c SameThreadPassiveFlags : Uint4B
   +0x24c ActiveExWorker   : Pos 0, 1 Bit
   +0x24c ExWorkerCanWaitUser : Pos 1, 1 Bit
   +0x24c MemoryMaker      : Pos 2, 1 Bit
   +0x250 SameThreadApcFlags : Uint4B
   +0x250 LpcReceivedMsgIdValid : Pos 0, 1 Bit
   +0x250 LpcExitThreadCalled : Pos 1, 1 Bit
   +0x250 AddressSpaceOwner : Pos 2, 1 Bit
   +0x254 ForwardClusterOnly : UChar
   +0x255 DisablePageFaultClustering : UChar

KTHREAD

kd> dt _KTHREAD
nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListHead   : _LIST_ENTRY
   +0x018 InitialStack     : Ptr32 Void
   +0x01c StackLimit       : Ptr32 Void
   +0x020 Teb              : Ptr32 Void
   +0x024 TlsArray         : Ptr32 Void
   +0x028 KernelStack      : Ptr32 Void
   +0x02c DebugActive      : UChar
   +0x02d State            : UChar
   +0x02e Alerted          : [2] UChar
   +0x030 Iopl             : UChar
   +0x031 NpxState         : UChar
   +0x032 Saturation       : Char
   +0x033 Priority         : Char
   +0x034 ApcState         : _KAPC_STATE
   +0x04c ContextSwitches  : Uint4B
   +0x050 IdleSwapBlock    : UChar
   +0x051 Spare0           : [3] UChar
   +0x054 WaitStatus       : Int4B
   +0x058 WaitIrql         : UChar
   +0x059 WaitMode         : Char
   +0x05a WaitNext         : UChar
   +0x05b WaitReason       : UChar
   +0x05c WaitBlockList    : Ptr32 _KWAIT_BLOCK
   +0x060 WaitListEntry    : _LIST_ENTRY
   +0x060 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x068 WaitTime         : Uint4B
   +0x06c BasePriority     : Char
   +0x06d DecrementCount   : UChar
   +0x06e PriorityDecrement : Char
   +0x06f Quantum          : Char
   +0x070 WaitBlock        : [4] _KWAIT_BLOCK
   +0x0d0 LegoData         : Ptr32 Void
   +0x0d4 KernelApcDisable : Uint4B
   +0x0d8 UserAffinity     : Uint4B
   +0x0dc SystemAffinityActive : UChar
   +0x0dd PowerState       : UChar
   +0x0de NpxIrql          : UChar
   +0x0df InitialNode      : UChar
   +0x0e0 ServiceTable     : Ptr32 Void
   +0x0e4 Queue            : Ptr32 _KQUEUE
   +0x0e8 ApcQueueLock     : Uint4B
   +0x0f0 Timer            : _KTIMER
   +0x118 QueueListEntry   : _LIST_ENTRY
   +0x120 SoftAffinity     : Uint4B
   +0x124 Affinity         : Uint4B
   +0x128 Preempted        : UChar
   +0x129 ProcessReadyQueue : UChar
   +0x12a KernelStackResident : UChar
   +0x12b NextProcessor    : UChar
   +0x12c CallbackStack    : Ptr32 Void
   +0x130 Win32Thread      : Ptr32 Void
   +0x134 TrapFrame        : Ptr32 _KTRAP_FRAME
   +0x138 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
   +0x140 PreviousMode     : Char
   +0x141 EnableStackSwap  : UChar
   +0x142 LargeStack       : UChar
   +0x143 ResourceIndex    : UChar
   +0x144 KernelTime       : Uint4B
   +0x148 UserTime         : Uint4B
   +0x14c SavedApcState    : _KAPC_STATE
   +0x164 Alertable        : UChar
   +0x165 ApcStateIndex    : UChar
   +0x166 ApcQueueable     : UChar
   +0x167 AutoAlignment    : UChar
   +0x168 StackBase        : Ptr32 Void
   +0x16c SuspendApc       : _KAPC
   +0x19c SuspendSemaphore : _KSEMAPHORE
   +0x1b0 ThreadListEntry  : _LIST_ENTRY
   +0x1b8 FreezeCount      : Char
   +0x1b9 SuspendCount     : Char
   +0x1ba IdealProcessor   : UChar
   +0x1bb DisableBoost     : UChar

SST

Windows中的系统服务表(System Service Table)有两个,第一个是内核中的核心函数导出表,第二个是GUI相关的函数非导出表:
3-11

SSDT

SSDT中一共包含4个STT,只有前两个是有效的。

3.3 0环调用

KiSystemService

中断门进0环后调用:

push    0
push    ebp             ; 从Trap Frame+0x64开始,依次压入:
                        ; Errcode, EBP, EBX, ESI, EDI, 标志寄存器
push    ebx
push    esi
push    edi
push    fs
mov     ebx, 30h ; '0'  ; 更改fs,使其指向KPCR
mov     fs, bx
assume fs:nothing
push    dword ptr ds:0FFDFF000h ; 压入KPCR中的第一个字段作为ExceptionList
mov     dword ptr ds:0FFDFF000h, 0FFFFFFFFh ; 将 ExceptionList 置为 -1
mov     esi, ds:0FFDFF124h ; 获得_KPRCB中的CurrentThread字段
push    dword ptr [esi+140h] ; 压入_KTHREAD中的PreviousMode字段,0环调用为0,3环为1
sub     esp, 48h        ; 抬栈,ESP指向栈帧开头
mov     ebx, [esp+68h+arg_0] ; 获取栈帧中保存的CS
and     ebx, 1          ; 判断是否0环权限
mov     [esi+140h], bl  ; 结果放入当前进程的PreviousMode中
mov     ebp, esp
mov     ebx, [esi+134h] ; 获取当前进程的栈帧
mov     [ebp+3Ch], ebx  ; 将栈帧中的EDX的值赋予当前进程的栈帧地址
mov     [esi+134h], ebp ; 将当前进程的栈帧替换为上面构造的新栈帧
cld
mov     ebx, [ebp+60h]  ; 获取3环EBP
mov     edi, [ebp+68h]  ; 获取3环EIP
mov     [ebp+0Ch], edx  ; 将 3环 的参数列表存入到 DbgArgPointer
mov     dword ptr [ebp+8], 0BADB0D00h
mov     [ebp+0], ebx    ; 赋值DbgEBP,DbgEIP
mov     [ebp+4], edi
test    byte ptr [esi+2Ch], 0FFh ; 检测当前进程的DebugActive
jnz     Dr_kss_a        ; 如果被调试,则跳转
loc_8053E4EF:
sti
jmp     loc_8053E5CD    ; 跳转继续执行

KiFastCallEntry

sysenter进0环后调用:

mov     ecx, 23h ; '#'
push    30h ; '0'       ; 切换fs
pop     fs
mov     ds, ecx         ; 加载ds和es
mov     es, ecx
mov     ecx, ds:0FFDFF040h ; 获取KPCR中的TSS
mov     esp, [ecx+4]    ; 获取0环ESP
push    23h ; '#'       ; 依次压入HardwareSS,HardwareESP,EFlags
push    edx
pushf
loc_8053E55A:           ; 更新EFlags
push    2
add     edx, 8          ; 获取到真正的函数参数地址
popf
or      [esp+0Ch+var_B], 2 ; 设置栈帧中的EFlags?
push    1Bh             ; 依次压入CS, EIP, ...
push    dword ptr ds:0FFDF0304h
push    0
push    ebp
push    ebx
push    esi
push    edi
mov     ebx, ds:0FFDFF01Ch ; 获取KPCR
push    3Bh ; ';'       ; 压入FS
mov     esi, [ebx+124h] ; 获取CurrentThread
push    dword ptr [ebx] ; 压入ExceptionList
mov     dword ptr [ebx], 0FFFFFFFFh ; 赋值ExceptionList为-1
mov     ebp, [esi+18h]  ; 获取当前进程的栈帧
push    1
sub     esp, 48h        ; 抬栈,指向新的栈帧开头
sub     ebp, 29Ch
mov     byte ptr [esi+140h], 1 ; 赋值当前进程中的PreviousMode
cmp     ebp, esp        ; 判断是否为VX86线程
jnz     short loc_8053E53C
loc_8053E53C:
jmp     short loc_8053E519
; END OF FUNCTION CHUNK FOR _KiFastCallEntry
and     dword ptr [ebp+2Ch], 0 ; DR7
test    byte ptr [esi+2Ch], 0FFh ; DebugActive
mov     [esi+134h], ebp ; 替换栈帧
jnz     Dr_FastCallDrSave
loc_8053E5B6:           ; Debug相关
mov     ebx, [ebp+60h]
mov     edi, [ebp+68h]
mov     [ebp+0Ch], edx
mov     dword ptr [ebp+8], 0BADB0D00h
mov     [ebp+0], ebx
mov     [ebp+4], edi
sti
loc_8053E5CD:           ; KiFastCallEntry和KiSystemService的共同调用部分
mov     edi, eax        ; 获取3环传入的服务号
shr     edi, 8
and     edi, 30h
mov     ecx, edi
add     edi, [esi+0E0h] ; 加上SST的基址
mov     ebx, eax
and     eax, 0FFFh      ; 判断是否越界
cmp     eax, [edi+8]
jnb     _KiBBTUnexpectedRange
cmp     ecx, 10h        ; 判断是否是第2个SST中的函数
jnz     short loc_8053E60C 
loc_8053E60C:           ; KeSystemCalls 自增 1
inc     dword ptr ds:0FFDFF638h
mov     esi, edx        ; 获取参数地址
mov     ebx, [edi+0Ch]  ; 获取SSDT参数表地址
xor     ecx, ecx
mov     cl, [eax+ebx]   ; 获取参数长度
mov     edi, [edi]      ; 获取函数地址表
mov     ebx, [edi+eax*4] ; 获取对应的函数
sub     esp, ecx        ; 抬栈
shr     ecx, 2
mov     edi, esp
cmp     esi, ds:_MmUserProbeAddress ; 判断拷贝的源地址是否超出用户态能读取的宽度
jnb     loc_8053E7DC
loc_8053E634:           ; 将参数从3环拷贝到0环
rep movsd
call    ebx             ; 函数调用!
loc_8053E638:
mov     esp, ebp
loc_8053E63A:
mov     ecx, ds:0FFDFF124h
mov     edx, [ebp+3Ch]
mov     [ecx+134h], edx

3.4 实验6:驱动实现 SSDT Hook

#include <ntddk.h>    
#include <ntstatus.h>

ULONG OriginNtOpenProcess;

typedef NTSTATUS(*NTOPENPROCESS)(
    PHANDLE ProcessHandle,
    ACCESS_MASK DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    PCLIENT_ID ClientId);

// SST
typedef struct _KSYSTEM_SERVICE_TABLE
{
    PULONG  ServiceTableBase;			// 服务函数地址表基址  
    PULONG  ServiceCounterTableBase;		// SSDT函数被调用的次数
    ULONG   NumberOfService;			// 服务函数的个数  
    PULONG   ParamTableBase;			// 服务函数参数表基址   
} KSYSTEM_SERVICE_TABLE, * PKSYSTEM_SERVICE_TABLE;


// SSDT
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
    KSYSTEM_SERVICE_TABLE   ntoskrnl;		// ntoskrnl.exe 的服务函数  
    KSYSTEM_SERVICE_TABLE   win32k;			// win32k.sys 的服务函数(GDI32.dll/User32.dll 的内核支持)  
    KSYSTEM_SERVICE_TABLE   notUsed1;
    KSYSTEM_SERVICE_TABLE   notUsed2;
}KSERVICE_TABLE_DESCRIPTOR, * PKSERVICE_TABLE_DESCRIPTOR;

extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable; // SSDT

// 开启写保护
VOID PageProtectOn(VOID)
{
    __asm {
        mov  eax, cr0
        or eax, 10000h
        mov  cr0, eax
        sti
    }
}

// 关闭写保护
VOID PageProtectOff(VOID)
{
    __asm {
        cli
        mov  eax, cr0
        and eax, not 10000h
        mov  cr0, eax
    }
}

// Hook函数
NTSTATUS Hook(
    PHANDLE ProcessHandle,
    ACCESS_MASK DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    PCLIENT_ID ClientId
) {
    DbgPrint("Hooked!\n");
    return ((NTOPENPROCESS)OriginNtOpenProcess)(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
}

// 挂钩
NTSTATUS SetHook(VOID)
{
    PageProtectOff();
    OriginNtOpenProcess = KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7A];
    KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7A] = (ULONG)Hook;
    PageProtectOn();
    DbgPrint("NtOpenProcess Has Been Hooked!\n");
    return STATUS_SUCCESS;
}

// 脱钩
NTSTATUS UnsetHook(VOID)
{
    PageProtectOff();
    KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7A] = OriginNtOpenProcess;
    PageProtectOn();
    DbgPrint("NtOpenProcess Has Been Unhooked!\n");
    return STATUS_SUCCESS;
}

// 卸载驱动
NTSTATUS UnloadDriver(PDRIVER_OBJECT pDriObj)
{
    UnsetHook();
    DbgPrint("Unload Driver!\n");
    return STATUS_SUCCESS;
}


NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegistryPath)
{
    DbgPrint("Load Driver!\n");
    pDriver->DriverUnload = UnloadDriver;

    SetHook();

    return STATUS_SUCCESS;
}

4 参考