【系统底层】系统调用(R3API调用过程详解)

WindowsAPI

  • API(Application Programming Interface),我们调用时只需提供正确的参数以及接收返回值就可以判断API执行是否成功或者通过GetLastError获得错误原因.

  • 大部分API在R3都是处理各种校验,真正执行功能都是在R0(并不是所有的API都是在R0处理).

  • 系统中几个核心DLL(Kernel32.dll,User32.dll,GDI32.dll,Ntdll.dll(大多数API通过此DLL进入内核)).

  • 通过API ReadProcessMemory / OpenProcess 分析函数从R3进入R0过程,进入R0如何处理原有寄存器数据,传递参数,找到对应内核函数并调用,以及从R0返回R3过程.

  • 前置知识点(汇编,C,Win32,段页机制,段描述符,中断门,).

  • 涉及知识点(_KUSER_SHARED_DATA,_KTRAP_FRAME,_KPCR,_KPRCB,_KTHREAD,KiFastSystemCall→KiFastCallEntry,KiIntSystemCall→KiSystemService,SSDT)下文详解.

  • 代码示例(重写R3API,SSDTHOOK,内核重载).


R3API调用分析

代码示例:

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

int main()
{
  //随便选择一个进程
  HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 2252);

  //0x400000大部分情况下为ImageBase
  DWORD dwData = 0;
  ReadProcessMemory(hProcess, (PVOID)0x400000, &dwData, 4, NULL);

  return 0;
}

1).将编译好的文件拖入DBG / OD 分析(定位MAIN函数找到API调用位置)

 

2).OpenProcess执行流程分析

OpenProcess执行流程:进程模块内CALLAPI(OpenProcess) -> kernel32.dll(OpenProcess) -> kernelBase.dll(OpenProcess) -> ntdll.dll(ZwOpenProcess) -> ntdll.dll中执行会进入R0后文详解.

3).ReadProcessMemory执行流程分析

ReadProcessMemory执行流程:进程模块内CALLAPI(ReadProcessMemory) -> kernel32.dll(ReadProcessMemory) -> kernelBase.dll(ReadProcessMemory) -> ntdll.dll(ZwReadVirtualMemory) -> ntdll.dll中执行会进入R0后文详解.

R3API功能实现分析

1).ReadProcessMemory分析(R3功能实现分析)

1).通过IDA导入KernelBase.dll,查询ReadProcessMemory函数,如下图:

分析得出ReadProcessMemory函数并未做任何处理而是调用ntdll.dll中NtReadVirtualMemory

2).通过IDA导入Ntdll.dll,查询NtReadVirtualMemory函数,如下图:

后文详解此处...

2).OpenProcess分析(R3功能实现分析)

1).通过IDA导入KernelBase.dll,查询OpenProcess函数,如下图:

分析得出OpenProcess函数并未做任何功能实现,而是在原有参数基础上填充内核需要结构体信息后调用NtOpenProcess

2).通过IDA导入Ntdll.dll,查询NtOpenProcess函数,如下图:

这两个函数最终都执行到ntdll.dll中并且除了eax值不相同其余都一样.

edx = 7FFE0300h

call [edx]

这里只需要分析edx指向地址7FFE0300h中的值即可.

这里我们需要了解一个结构体_KUSER_SHARED_DATA

_KUSER_SHARED_DATA

1)._KUSER_SHARED_DATA

  • 用户层和内核层分别定义了一个_KUSER_SHARED_DATA结构体,用于在用户层和内核层共享数据,其大小为4KB(测试环境Win7 x86 这块结构系统默认用了0x5ff,意味着结构体 + 0x600 ~ 0xFFF可以构建自己的共享数据).

  • 页的知识可以知道共享数据是用户层和内核层_KUSER_SHARED_DATA结构体对应线性地址指向同一个物理页,但在用户层中这块内存是只读的,内核层中是可读可写的.

  • 用户层和内核层使用固定的地址映射_KUSER_SHARED_DATA结构体,地址如下表所示:

内核起始地址内核结束地址用户起始地址用户结束地址
x860xFFDF00000xFFDF0FFF0x7FFE00000x7FFE0FFF
x640xFFFFF78000000000|0xFFFFF78000000FFF0x7FFE00000x7FFE0FFF
  • _KUSER_SHARED_DATA共享论证.

测试环境Win7 x86

1).Windbg输入指令 !process 0 0 找一个进程附加

2).Windbg输入指令 .process /i xxxxxxxx

此时Windbg处于Dbgview进程空间中.

3).Windbg输入指令 !pte 用户层以及内核层_KUSER_SHARED_DATA结构体对应线性地址

4).修改用户层结构数据查看内核层对应数据

2)._KUSER_SHARED_DATA.SystemCall


(Windbg输入指令 dt _KUSER_SHARED_DATA)
nt!_KUSER_SHARED_DATA
   +0x300 SystemCall       : Uint4B //系统调用
   +0x304 SystemCallReturn : Uint4B //调用返回

R3API如果通过 MOV EDX, 7FFE0300h; CALL DWORD PTR [edx];方式进R0,实际上相当于调用_KUSER_SHARED_DATA.SystemCall中的存储的值.

_KUSER_SHARED_DATA.SystemCall中存储的值决定了函数通过什么方式进R0.(操作系统通过检查当前CPU是否支持快速调用来填充这个值,支持函数地址为KiFastSystemCall快速调用,不支持函数地址为KiIntSystemCall中断调用).

CPU是否支持快速调用?

当EAX = 1 执行CPUID指令 如果EDX第11位(SEP) = 1 说明支持快速调用,否则为中断调用,即_KUSER_SHARED_DATA.SystemCall中存储的值.

至此已经了解到R3进入R0两种方式,接下来分析中断调用,快速调用如何进入R0.

代码示例:


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

int main()
{
  DWORD dwEAX = 0;
  DWORD dwECX = 0;
  DWORD dwEDX = 0;

  __asm
  {
    xor eax, eax
    mov eax, 1
    CPUID

    mov dwEAX, eax
    mov dwECX, eax
    mov dwEDX, edx
  }

  printf("EAX 0x%08x \n",dwEAX);
  printf("ECX 0x%08x \n",dwECX);
  printf("EDX 0x%08x \n",dwEDX);

  printf("EDX 11(BIT) [%d] \n", (dwEDX & 0x800) >> 11);

  system("pause");
  return 0;
}

intel白皮书介绍如下:

系统调用

1).中断调用KiIntSystemCall

固定中断号为: 2Eh 通过解析如下图:

2).快速调用KiFastSystemCall

如果CPU支持sysenter(快速调用)指令,操作系统会提前将CS/ESP/EIP的值存储在MSR寄存器中,sysenter指令执行时,CPU会将MSR寄存器中的值写入相关寄存器(没有查询内存过程速度更快).

MSR地址
IA32_SYSENTER_CS174H
IA32_SYSENTER_ESP175H
IA32_SYSENTER_EIP176H

intel白皮书对SYSENTER介绍如下:

代码示例(特权指令需要在R0下运行)


#include <ntifs.h>

NTSTATUS DriverUnload(PDRIVER_OBJECT pDriver)
{
  DbgPrint("Driver Exit \r\n");
}

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

  DbgBreakPoint();

  ULONGLONG uData = 0;

  __asm
  {
    mov ecx, 0x174          //相当于msr寄存器index
    rdmsr
    mov dword ptr [uData], eax    //eax存储数据低32位
    mov dword ptr [uData + 4],edx  //edx存储数据高32位
  }

  DbgPrint("MSR[174] -> [0x%llx] \r\n", uData);

  return STATUS_SUCCESS;
}

3).中断调用快速调用区别如下

快速调用中断调用
R3执行APIKiFastSystemCallKiIntSystemCall
8bd4            mov     edx,esp //三环栈顶 系统调用号在EAX <br>0f34             sysenter8d542408        lea     edx,[esp+8] //参数指针 系统调用号在EAX<br>cd2e                int     2Eh<br>c3                    ret
提权方式(段的机制R3进入R0相当于CPL发生改变 )如果CPU支持sysenter指令,操作系统会提前将CS/ESP/EIP的值存储在MSR寄存器中,sysenter指令执行时,CPU会将MSR寄存器中的值写入相关寄存器(没有查询内存过程速度更快).将特权级切换到R0,如果EFLAG.VM被置位,则清除该标志位.int     2Eh对应段描述符为83e4ee00-00083fee中断门描述符,其中加载代码段选择子为0x0008 对应段描述符为00cf9(1001)b00-0000ffff DPL = 0执行成功后CPL = 0.且因权限切换会向堆栈压入SS,ESP,EFLAG,CS,EIP.
提权方式(段的机制R3进入R0相当于CPL发生改变 )查询MSR寄存器.CS = rdmsr 174.SS = CS + 8(数值上).ESP = rdmsr 175.EIP = rdmsr 176.ESP,SS由TSS提供.CS由中断门描述符中低4字节高16位提供.EIP由中断门描述符中高4字节高16位与低4字节低16位组成.
进入R0执行APIKiFastCallEntry(Windbg输入 rdmsr 176获取)KiSystemService(Windbg输入!IDT 2E获取)

至此已经知道R3API在ntdll.dll中进入R0的两种方法.

在分析对应内核函数前需要了解两个结构体_KTRAP_FRAME,_KPCR.

_KTRAP_FRAME

Windbg输入dt _KTRAP_FRAME指令:

_KTRAP_FRAME结构如下:


nt!_KTRAP_FRAME //类似于R3 -> CONTEXT
   +0x000 DbgEbp           : Uint4B
   +0x004 DbgEip           : Uint4B
   +0x008 DbgArgMark       : Uint4B
   +0x00c DbgArgPointer    : Uint4B
   +0x010 TempSegCs        : Uint2B
   +0x012 Logging          : UChar
   +0x013 Reserved         : UChar
   +0x014 TempEsp          : Uint4B
   +0x018 Dr0              : Uint4B
   +0x01c Dr1              : Uint4B
   +0x020 Dr2              : Uint4B
   +0x024 Dr3              : Uint4B
   +0x028 Dr6              : Uint4B
   +0x02c Dr7              : Uint4B
   +0x030 SegGs            : Uint4B
   +0x034 SegEs            : Uint4B
   +0x038 SegDs            : Uint4B
   +0x03c Edx              : Uint4B
   +0x040 Ecx              : Uint4B
   +0x044 Eax              : Uint4B
   +0x048 PreviousPreviousMode : Uint4B
   +0x04c ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x050 SegFs            : Uint4B
   +0x054 Edi              : Uint4B
   +0x058 Esi              : Uint4B
   +0x05c Ebx              : Uint4B
   +0x060 Ebp              : Uint4B
   +0x064 ErrCode          : Uint4B //如果是发生错误导致其他中断触发时,这里会有ErrCode,中断调用进内核函数KiSystemService这里push 0.
   +0x068 Eip              : Uint4B
   +0x06c SegCs            : Uint4B
   +0x070 EFlags           : Uint4B
   +0x074 HardwareEsp      : Uint4B
   +0x078 HardwareSegSs    : Uint4B
   +0x07c V86Es            : Uint4B //0x07c ~ 0x088位置为虚拟8086模式下使用,函数进入R0时栈顶默认指向_KTRAP_FRAME.V86Es
   +0x080 V86Ds            : Uint4B
   +0x084 V86Fs            : Uint4B
   +0x088 V86Gs            : Uint4B

0x07c ~ 0x088位置为虚拟8086模式下使用.

中断调用进入R0时栈顶(ESP由TSS提供(每个线程进入R0时ESP都由TSS.ESP0提供,以及TSS里存储的ESP0一直是当前线程进入R0时对应ESP0,线程切换时会更新TSS里存储的ESP0))默认指向_KTRAP_FRAME.V86Es,中断门执行权限发生切换时会向堆栈压入SS,ESP,EFLAG,CS,RETADDR(EIP),由此得知当执行函数KiIntSystemCall进入R0函数KiSystemService时此时ESP指向_KTRAP_FRAME.Eip.(下文分析).

快速调用进入R0时堆栈是由MSR[175]提供的,KiFastCallEntry函数执行时首先会修改FS指向_KPCR结构,通过_KPCR -> _TSS定位到当前线程ESP0,并切换新的堆栈.此时ESP指向_KTRAP_FRAME.V86Ds.(下文分析).

_KPCR

一个核一个_KPCR(Processor Control Region CPU控制块)记录当前CPU核对应各种状态以及上下文环境.


查看CPU数量

kd> dd KeNumberProcessors               
83fb796c  00000001                 //一个核心           

查看KPCR 

kd> dd KiProcessorBlock          //几个核对应几个KPCR 
83fb78c0  83f78d20 00000000      //减去120(kpcr的大小)                

kd> dt _KPCR 83f78d20-120        //就是kpcr的地址   

nt!_KPCR
   +0x000 NtTib            : _NT_TIB

       nt!_NT_TIB
       +0x000 ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
       +0x004 StackBase        : Ptr32 Void
       +0x008 StackLimit       : Ptr32 Void
       +0x00c SubSystemTib     : Ptr32 Void
       +0x010 FiberData        : Ptr32 Void
       +0x010 Version          : Uint4B
       +0x014 ArbitraryUserPointer : Ptr32 Void
       +0x018 Self             : Ptr32 _NT_TIB   //结构体指针 指向自己

   +0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Used_StackBase   : Ptr32 Void
   +0x008 Spare2           : Ptr32 Void
   +0x00c TssCopy          : Ptr32 Void
   +0x010 ContextSwitches  : Uint4B
   +0x014 SetMemberCopy    : Uint4B
   +0x018 Used_Self        : Ptr32 Void
   +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 SpareUnused      : 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

KiSystemService(函数分析)

KiIntSystemCall(R3) -> KiSystemService(R0)

设置环境

KiSystemService设置环境后跳转KiFastCallEntry(详情见下文)

KiFastCallEntry(函数分析)

KiFastSystemCall(R3) -> KiFastCallEntry(R0)

设置环境

 

寻找内核函数地址,拷贝参数

此时需要了解一个结构SystemServiceTable
结构如下:


定位SystemServiceTable
_KTHREAD -> ServiceTable 

系统服务表有两张:
1.ntoskrnl.exe导出的常用系统服务.
2.Win32k.sys导出的与图形显示和用户界面相关的系统服务(只有GDI相关线程访问对应系统服务表才会有值).

系统服务表结构如下:

typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
  KSYSTEM_SERVICE_TABLE ntoskrnl;    // 内核函数
  KSYSTEM_SERVICE_TABLE win32k;      // win32k.sys 函数
  KSYSTEM_SERVICE_TABLE unUsed1;     // 未使用
  KSYSTEM_SERVICE_TABLE unUsed2;     // 未使用
} KSERVICE_TABLE_DESCRIPTOR, * PKSERVICE_TABLE_DESCRIPTOR;

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

ServiceTable指向函数地址表每个成员大小为4字节,存储函数地址.
ServiceLimit存储函数地址表的成员个数.
ArgumentTable 函数参数表每个成员大小为1字节,存储函数参数个数(存储值 / 4 = 参数个数).

在快速调用和中断调用R3函数执行时,EAX存储了系统服务号.

通过第12位确定是哪张表.
通过低12位确定在函数地址表中的索引值以及函数参数表的索引值.

System Services Descriptor Table系统服务描述符表,为导出结构KeServiceDescriptorTable(代码中只需声明即可直接使用).

 

查找ReadProcessMemory(测试环境系统服务号为115h)对应内核函数地址以及参数

Windbg查看SSDT

dd KeServiceDescriptorTable

 

继续函数分析

 

至此完成了初始化内核环境以及参数拷贝,函数调用.

函数返回

涉及到APC,此部分会更新到APC处,大致流程为执行完毕后首先判断当前IRQL等级(不为0跳转处理蓝屏),然后判断是否为虚拟8086模式,接着判断有没有APC需要处理等.最后通过iretd返回.

涉及到的结构体如下图:

Win7 x86系统调用全过程...

重写R3API

  • WindowsAPI分析中可以判断出大部分API在R3都未做真正功能实现,只是完成一些内核所需结构数据填充,数据校验等等.

  • 重写R3API,需对接快速调用/中短调用堆栈要求,以及对应内核函数所需数据就可以实现(可避免恶意挂钩,R3层的API监控等).

1).快速调用方式重写

通过WindowsAPI分析,可以得知除了R3API业务实现内还需要注意EDX进入内核前指向栈顶,EAX存储系统服务号.


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

BOOL MyReadMemory(
    HANDLE  hProcess,
    LPCVOID lpBaseAddress,
    LPVOID  lpBuffer,
    SIZE_T  nSize,
    SIZE_T* lpNumberOfBytesRead
)
{
    BOOL bRet = 0;

    __asm
    {
        //Kernelbase.dll -> ReadProcessMemory 
        lea eax, nSize
        push eax
        push nSize
        push lpBuffer
        push lpBaseAddress
        push hProcess

        //ntdll.dll -> NtReadVirtualMemory 
        //模拟call 栈顶-4
        sub esp, 4

        //系统服务号
        mov eax, 0x115

        //KiFastSystemCall 
        //模拟call 堆栈保存返回地址
        PUSH RETADDR 

        //快速调用
        mov edx, esp

        //sysenter对应硬编码
        _emit 0x0F;
        _emit 0x34;

    RETADDR:

        add esp, 0x18

        mov bRet, eax
    }

    return bRet;
}

int main()
{

    DWORD dwData = 0;
    HANDLE handle = 0;

    handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 2252);
    MyReadMemory(handle, (LPVOID)0x400000, &dwData, 4, NULL);
    printf("dwData [0x%08x] \n", dwData);

    system("pause");
    return 0;
}

2).中断调用方式重写


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

BOOL MyReadMemory(
    HANDLE  hProcess,
    LPCVOID lpBaseAddress,
    LPVOID  lpBuffer,
    SIZE_T  nSize,
    SIZE_T* lpNumberOfBytesRead
)
{
    BOOL bRet = 0;

    __asm
    {
        //系统服务号
        mov eax, 0x115

        //首参数指针
        lea edx, hProcess

        //中断调用固定号
        int 0x2E
    }

    return bRet;
}

int main()
{
    DWORD dwData = 0;
    HANDLE handle = 0;

    handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 2252);
    MyReadMemory(handle, (LPVOID)0x400000, &dwData, 4, NULL);
    printf("dwData [0x%08x] \n", dwData);

    system("pause");
    return 0;
}

3).动态重写

  • 上述两种方式中系统服务号都是写死的不方便项目中使用,下述代码演示动态获取系统服务号并调用函数.


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

typedef NTSTATUS(WINAPI* ZwOpenProcessProc)(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, CLIENT_ID* ClientId);

int main()
{
    //获取ZwOpenProcess函数地址
    HMODULE hModule = LoadLibraryA("ntdll.dll");
    PUCHAR pFunAddr = (PUCHAR)GetProcAddress(hModule, "ZwOpenProcess");
    printf("Funaddr -> [0x%08x] \r\n", pFunAddr);

    //获取ZwOpenProcess函数长度
    ULONG uSize = 0;
    for (int i = 0; i < 100; i++)
    {
        //C2 == ret 
        if (pFunAddr[i] == 0xc2)
        {
            uSize = i + 2;
            break;
        }
    }
    printf("FunLength -> [0x%08x] \r\n", uSize);

    //函数指针申请内存
    ZwOpenProcessProc func = (ZwOpenProcessProc)VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    //拷贝默认函数数据
    memcpy(func, pFunAddr, uSize);

    //分析得知需要打开进程需要填充CLIENT_ID.UniqueProcess
    CLIENT_ID Client = { 0 };
    Client.UniqueProcess = (HANDLE)2252;

    //分析得知需要填充POBJECT_ATTRIBUTES.Length
    OBJECT_ATTRIBUTES Obj_Attr = { 0 };
    Obj_Attr.Length = sizeof(OBJECT_ATTRIBUTES);

    HANDLE hProcess = NULL;
    NTSTATUS ntstatus = func(&hProcess, PROCESS_ALL_ACCESS, &Obj_Attr, &Client);

    printf("Ret -> [%x]  hProcess -> [%x] \r\n", ntstatus, hProcess);
    system("Pause");

    return 0;
}

SSDT_HOOK

测试环境Win7 x86

  • 重新加载一份按照PE格式拉伸后的内核文件到内存(避免当前内核已经被挂钩).

  • 通过导出表获取HOOK函数系统服务号.

  • 利用导出KeServiceDescriptorTable结构定位系统服务表实现替换函数(类似IAT_HOOK).


#include <ntifs.h>
#include <ntimage.h>
#include <ntstrsafe.h>

//获取系统目录
PWCHAR GetSystemFullPath();

//内核文件按照PE拉伸后格式映射到内存
PUCHAR FileMaping(PWCHAR SystemPath);

//释放文件映射
VOID UnFileMaping(PVOID mapBase);

//通过函数名查找导出函数
ULONG64 GetFuntionAddressByExportTableName(PUCHAR ImageBuffer, PUCHAR FunctionName);

//导出未文档化函数
NTSTATUS MmCreateSection(
        __deref_out PVOID* SectionObject,
        __in ACCESS_MASK DesiredAccess,
        __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
        __in PLARGE_INTEGER InputMaximumSize,
        __in ULONG SectionPageProtection,
        __in ULONG AllocationAttributes,
        __in_opt HANDLE FileHandle,
        __in_opt PFILE_OBJECT FileObject
);

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

typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
        KSYSTEM_SERVICE_TABLE ntoskrnl;                // 内核函数
        KSYSTEM_SERVICE_TABLE win32k;                // win32k.sys 函数
        KSYSTEM_SERVICE_TABLE unUsed1;
        KSYSTEM_SERVICE_TABLE unUsed2;
} KSERVICE_TABLE_DESCRIPTOR, * PKSERVICE_TABLE_DESCRIPTOR;

extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;

PUCHAR G_MapNtdll = NULL;

//拷贝SSDT表
BOOLEAN SSDT_Init();

//释放SSDT表
VOID SSDT_Destroy();

//获取函数系统服务号
ULONG SSDT_GetFunIndex(PUCHAR szFunctionName);

//SSDTHOOK
ULONG_PTR SSDT_Hook(PUCHAR szFunctionName, ULONG_PTR FunctionAddr);

//关闭写保护以及中断
ULONG wpOff()
{
        ULONG cr0 = __readcr0();
        _disable();
        __writecr0(cr0 & (~0x10000));
        return cr0;
}

//恢复CR0默认数据
VOID wpOn(ULONG value)
{
        __writecr0(value);
        _enable();
}

//恢复HOOK时用到
ULONG G_OldFunAddr = NULL;

//函数指针
typedef NTSTATUS(NTAPI* OpenProcessProc)(_Out_ PHANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PCLIENT_ID ClientId);

//替换函数
NTSTATUS NTAPI MyOpenProcess(_Out_ PHANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PCLIENT_ID ClientId)
{
        PUCHAR pEprocess = (PUCHAR)IoGetCurrentProcess();
        DbgPrint("进程ID: [%d] 调用OpenProcess \r\n", *(PULONG)(pEprocess + 0xb4));

        //TODO:
        //获取参数,监控,修改返回值....

        return ((OpenProcessProc)G_OldFunAddr)(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
}

NTSTATUS DriverUnload(PDRIVER_OBJECT pDriver)
{
        DbgPrint("Driver Exit \r\n");

        //恢复钩子
        if (G_OldFunAddr)
        {
                SSDT_Hook("ZwOpenProcess", G_OldFunAddr);
        }

        //释放后延迟避免有进程还在执行我们函数释放导致蓝屏
        SSDT_Destroy();

        //延时
        LARGE_INTEGER inTime = { 0 };
        inTime.QuadPart = -10000 * 3000;
        KeDelayExecutionThread(KernelMode, FALSE, &inTime);

}

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

        if (SSDT_Init())
        {
                G_OldFunAddr = SSDT_Hook("ZwOpenProcess", MyOpenProcess);
        }

        return STATUS_SUCCESS;
}

PWCHAR GetSystemFullPath()
{
        //申请路径缓冲区
        PWCHAR SystemPath = ExAllocatePool(PagedPool, PAGE_SIZE);
        if (!SystemPath)
        {
                return NULL;
        }
        memset(SystemPath, 0, PAGE_SIZE);

        //初始化路径
        RtlStringCbPrintfW(SystemPath, PAGE_SIZE, L"\\??\\%s\\System32\\ntdll.dll", SharedUserData->NtSystemRoot);
        DbgPrint("SystemPath -> [%ws] \r\n", SystemPath);

        return SystemPath;
}

PUCHAR FileMaping(PWCHAR SystemPath)
{
        //Initialize UnicodeString
        UNICODE_STRING FileName = { 0 };
        RtlInitUnicodeString(&FileName, SystemPath);

        //Initialize ObjectAttribute
        OBJECT_ATTRIBUTES objectFile = { 0 };
        InitializeObjectAttributes(&objectFile, &FileName, OBJ_CASE_INSENSITIVE, NULL, NULL);

        //CreateFile
        HANDLE hFile = NULL;
        IO_STACK_LOCATION iostacklocation = { 0 };
        NTSTATUS ntstatus = ZwCreateFile(&hFile, GENERIC_READ, &objectFile, &iostacklocation, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, NULL);
        if (!NT_SUCCESS(ntstatus))
        {
                DbgPrint("FileMaping ZwCreateFile Filed \r\n");
                return NULL;
        }

        //Create Section
        OBJECT_ATTRIBUTES objectSection = { 0 };
        InitializeObjectAttributes(&objectSection, NULL, OBJ_CASE_INSENSITIVE, NULL, NULL);

        PVOID pSection = NULL;
        LARGE_INTEGER InputMaximumSize = { 0 };
        ntstatus = MmCreateSection(&pSection, SECTION_ALL_ACCESS, &objectSection, &InputMaximumSize, PAGE_EXECUTE_READWRITE, 0x1000000, hFile, NULL);
        if (!NT_SUCCESS(ntstatus))
        {
                DbgPrint("FileMaping MmCreateSection Filed \r\n");
                ZwClose(hFile);
                return NULL;
        }

        PVOID pMapBase = NULL;
        SIZE_T ViewSize = 0;
        ntstatus = MmMapViewInSystemSpace(pSection, &pMapBase, &ViewSize);
        ObDereferenceObject(pSection);
        ZwClose(hFile);

        if (NT_SUCCESS(ntstatus))
        {
                return pMapBase;
        }

        return NULL;
}

VOID UnFileMaping(PVOID pImage)
{
        if (pImage)
        {
                MmUnmapViewInSystemSpace(pImage);
        }
}

ULONG64 GetFuntionAddressByExportTableName(PUCHAR ImageBuffer, PUCHAR FunctionName)
{
        //Headers
        PIMAGE_DOS_HEADER    pDos = (PIMAGE_DOS_HEADER)ImageBuffer;
        if (*(PUSHORT)pDos != IMAGE_DOS_SIGNATURE)
        {
                DbgPrint("Not PeFile \r\n");
                return NULL;
        }

        PIMAGE_NT_HEADERS    pNts = (PIMAGE_NT_HEADERS)(ImageBuffer + pDos->e_lfanew);
        if (*(PULONG)pNts != IMAGE_NT_SIGNATURE)
        {
                DbgPrint("Not PeFile \r\n");
                return NULL;
        }

        PIMAGE_FILE_HEADER      pFil = (PIMAGE_FILE_HEADER)((ULONG)pNts + 0x4);
        PIMAGE_OPTIONAL_HEADER  pOpt = (PIMAGE_OPTIONAL_HEADER)((ULONG)pFil + IMAGE_SIZEOF_FILE_HEADER);
        PIMAGE_EXPORT_DIRECTORY pExp = (PIMAGE_EXPORT_DIRECTORY)(ImageBuffer + pOpt->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

        //遍历导出表
        ULONG64 FunctionAddr = NULL;
        for (int i = 0; i < pExp->NumberOfNames; i++)
        {
                PULONG  pAddressOfFuntion        = ImageBuffer + pExp->AddressOfFunctions;
                PULONG  pAddressOfNames                = ImageBuffer + pExp->AddressOfNames;
                PUSHORT pAddressOfOrd                = ImageBuffer + pExp->AddressOfNameOrdinals;
                PUCHAR  CurrentFunctionName = ImageBuffer + pAddressOfNames[i];

                ULONG uIndex = -1;

                if (strcmp(CurrentFunctionName, FunctionName) == 0)
                {
                        uIndex = pAddressOfOrd[i];
                }

                if (uIndex != -1)
                {
                        FunctionAddr = ImageBuffer + pAddressOfFuntion[uIndex];
                        break;
                }

        }

        if (FunctionAddr)
        {
                DbgPrint("FindFunctionAddress FunName[%s] Addr[%p] \r\n", FunctionName, FunctionAddr);
        }
        else
        {
                DbgPrint("FindFunctionAddress Error FunName[%s] \r\n", FunctionName);
        }

        return FunctionAddr;
}

BOOLEAN SSDT_Init()
{
        if (G_MapNtdll)
        {
                return TRUE;
        }

        PWCHAR szPath = GetSystemFullPath();
        if (szPath == NULL)
        {
                return FALSE;
        }

        G_MapNtdll = FileMaping(szPath);
        if (G_MapNtdll == NULL)
        {
                ExFreePool(szPath);
                return FALSE;
        }

        ExFreePool(szPath);
        return TRUE;
}

VOID SSDT_Destroy()
{
        if (G_MapNtdll)
        {
                UnFileMaping(G_MapNtdll);
                G_MapNtdll = NULL;
        }
}

ULONG SSDT_GetFunIndex(PUCHAR szFunctionName)
{
        //获取函数地址
        PUCHAR pFunAddr = (PUCHAR)GetFuntionAddressByExportTableName(G_MapNtdll, szFunctionName);
        if (pFunAddr == NULL)
        {
                return -1;
        }

        //获取函数系统服务号
        return *(PULONG)(pFunAddr + 1);
}

ULONG_PTR SSDT_Hook(PUCHAR szFunctionName, ULONG_PTR FunctionAddr)
{
        ULONG uIndex = SSDT_GetFunIndex(szFunctionName);
        if (uIndex == -1)
        {
                return NULL;
        }

        //备份旧的地址
        ULONG OldFuncAddr = KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[uIndex];

        //替换SSDT表中函数
        ULONG cr0 = wpOff();
        KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[uIndex] = FunctionAddr;
        wpOn(cr0);

        return OldFuncAddr;
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值