• 驱动开发:内核读取SSDT表基址


    在前面的章节《X86驱动:挂接SSDT内核钩子》我们通过代码的方式直接读取 KeServiceDescriptorTable 这个被导出的表结构从而可以直接读取到SSDT表的基址,而在Win64系统中 KeServiceDescriptorTable 这个表并没有被导出,所以我们必须手动搜索到它的地址。

    为了确保系统的安全性与稳定性,微软从 Windows Vista X64 开始对系统内核增加了一定的限制,其主要增加了两种保护措施,一是KPP (内核补丁保护),KPP是机制其利用了PG(PatchGuard)技术,PG技术在x64系统下加入了内核哨兵,用于检测系统内核是否被恶意篡改(打补丁),如果发现被打了补丁,则会导致关键结构损毁直接蓝屏,二是DSE (驱动强制签名),DSE技术则是拒绝加载不包含正确签名的驱动。

    1.这里我们可以通过MSR(特别模块寄存器),读取C0000082寄存器,从而得到KiSystemCall64的地址,在内核调试模式下直接输入 rdmsr c0000082 即可读取到该地址,反汇编可看到 nt!KiSystemCall64 函数。

    kd> rdmsr c0000082
    msr[c0000082] = fffff800`03c72ec0
    
    kd> u fffff800`03c72ec0
    nt!KiSystemCall64:
    fffff800`03c72ec0 0f01f8          swapgs
    fffff800`03c72ec3 654889242510000000 mov   qword ptr gs:[10h],rsp
    fffff800`03c72ecc 65488b2425a8010000 mov   rsp,qword ptr gs:[1A8h]
    fffff800`03c72ed5 6a2b            push    2Bh
    fffff800`03c72ed7 65ff342510000000 push    qword ptr gs:[10h]
    fffff800`03c72edf 4153            push    r11
    fffff800`03c72ee1 6a33            push    33h
    fffff800`03c72ee3 51              push    rcx
    

    2.接着我们从 KiSystemCall64 函数地址开始向下反汇编,可以看到最后 nt!KiSystemServiceRepeat 这个函数里面包含了 nt!KeServiceDescriptorTable (fffff80003eaa840) ,通过 03c72ff2 减去03c72ec0 即可得到SDT表结构与KiSystemCall64函数之间的偏移值 132 (306)

    kd> uf KiSystemCall64
    Flow analysis was incomplete, some code may be missing
    nt!KiSystemCall64:
    fffff800`03c72ec0 0f01f8          swapgs
    fffff800`03c72ec3 654889242510000000 mov   qword ptr gs:[10h],rsp
    fffff800`03c72ecc 65488b2425a8010000 mov   rsp,qword ptr gs:[1A8h]
    fffff800`03c72ed5 6a2b            push    2Bh
    fffff800`03c72ed7 65ff342510000000 push    qword ptr gs:[10h]
    fffff800`03c72edf 4153            push    r11
    fffff800`03c72ee1 6a33            push    33h
    fffff800`03c72ee3 51              push    rcx
    fffff800`03c72ee4 498bca          mov     rcx,r10
    
    nt!KiSystemServiceRepeat:
    fffff800`03c72ff2 4c8d1547782300  lea     r10,[nt!KeServiceDescriptorTable (fffff800`03eaa840)]
    fffff800`03c72ff9 4c8d1d80782300  lea     r11,[nt!KeServiceDescriptorTableShadow (fffff800`03eaa880)]
    fffff800`03c73000 f7830001000080000000 test dword ptr [rbx+100h],80h
    fffff800`03c7300a 4d0f45d3        cmovne  r10,r11
    fffff800`03c7300e 423b441710      cmp     eax,dword ptr [rdi+r10+10h]
    fffff800`03c73013 0f83e9020000    jae     nt!KiSystemServiceExit+0x1a7 (fffff800`03c73302)  Branch
    

    总结一下:我们通过读取C0000082寄存器,能够得到KiSystemCall64的地址,然后从KiSystemCall64的地址开始,往下搜索0x150字节左右(特征码4c8d15),就能得到KeServiceDescriptorTable的地址。

    #include <ntddk.h>
    #include <windef.h>
    #include <intrin.h>
    
    #pragma intrinsic(__readmsr)
    
    VOID UnDriver(PDRIVER_OBJECT driver)
    {
    	DbgPrint(("驱动程序卸载成功! \n"));
    }
    
    ULONGLONG Get_SSTD_Base()
    {
    	PUCHAR Base = (PUCHAR)__readmsr(0xC0000082);      // 读取C0000082寄存器
    	PUCHAR Address = Base + 0x150;                    // 相加偏移
    	PUCHAR i = NULL;
    	UCHAR b1 = 0, b2 = 0, b3 = 0;                     // 保存特征码
    	ULONG templong = 0;
    	ULONGLONG addr = 0;                               // 最后获取到的地址
    	for (i = Base; i<Address; i++)
    	{
    		if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2))
    		{
    			b1 = *i; b2 = *(i + 1); b3 = *(i + 2);
    			if (b1 == 0x4c && b2 == 0x8d && b3 == 0x15)   // 判断是否=4c8d15
    			{
    				memcpy(&templong, i + 3, 4);              // 在i+3位置拷贝,拷贝4字节
    				addr = (ULONGLONG)templong + (ULONGLONG)i + 7;
    				return addr;
    			}
    		}
    	}
    	return 0;
    }
    
    NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
    {
    	DbgPrint("SSTD Base= %11x", Get_SSTD_Base());
    	DriverObject->DriverUnload = UnDriver;
    	return STATUS_SUCCESS;
    }
    

    3.接着我们则需要获取到SSDT中某个函数的序号,这里以OpenProcess为例:

    0:000> u ntdll!NtOpenProcess
    ntdll!NtOpenProcess:
    77820700 b826000000      mov     eax,23h
    77820705 bac04f8377      mov     edx,offset ntdll!Wow64SystemServiceCall (77834fc0)
    7782070a ffd2            call    edx
    7782070c c21000          ret     10h
    7782070f 90              nop
    

    4.读取代码如下.

    #include <ntddk.h>
    #include <windef.h>
    #include <intrin.h>
    
    #pragma intrinsic(__readmsr)
    
    VOID UnDriver(PDRIVER_OBJECT driver)
    {
    	DbgPrint(("驱动程序卸载成功! \n"));
    }
    
    ULONGLONG Get_SSDT_Base()
    {
    	PUCHAR Base = (PUCHAR)__readmsr(0xC0000082);      // 读取C0000082寄存器
    	PUCHAR Address = Base + 0x150;                    // 相加偏移
    	PUCHAR i = NULL;
    	UCHAR b1 = 0, b2 = 0, b3 = 0;                     // 保存特征码
    	ULONG templong = 0;
    	ULONGLONG addr = 0;                               // 最后获取到的地址
    	for (i = Base; i<Address; i++)
    	{
    		if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2))
    		{
    			b1 = *i; b2 = *(i + 1); b3 = *(i + 2);
    			if (b1 == 0x4c && b2 == 0x8d && b3 == 0x15)   // 判断是否=4c8d15
    			{
    				memcpy(&templong, i + 3, 4);              // 在i+3位置拷贝,拷贝4字节
    				addr = (ULONGLONG)templong + (ULONGLONG)i + 7;
    				return addr;
    			}
    		}
    	}
    	return 0;
    }
    
    typedef struct _SYSTEM_SERVICE_TABLE{
    	PVOID  		ServiceTableBase;
    	PVOID  		ServiceCounterTableBase;
    	ULONGLONG  	NumberOfServices;
    	PVOID  		ParamTableBase;
    } SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
    
    ULONGLONG GetSSDTFunction(ULONGLONG Index)
    {
    	LONG dwTemp = 0;
    	ULONGLONG qwTemp = 0, stb = 0, ret = 0;
    	PSYSTEM_SERVICE_TABLE ssdt = (PSYSTEM_SERVICE_TABLE)Get_SSDT_Base();
    	stb = (ULONGLONG)(ssdt->ServiceTableBase);
    	qwTemp = stb + 4 * Index;
    	dwTemp = *(PLONG)qwTemp;
    	dwTemp = dwTemp >> 4;
    	ret = stb + (LONG64)dwTemp;
    	return ret;
    }
    
    NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
    {
    	DbgPrint("OpenProcess=%llx", GetSSDTFunction(0x23));
    	DriverObject->DriverUnload = UnDriver;
    	return STATUS_SUCCESS;
    }
    

    在64位环境下想要任意Hook系统函数是不可能的,因为64位中每个驱动程序都不在同一个4GB空间里,而4字节的整数只能表示4GB的范围,所以无论你怎么改,都不可能跨越这个内存空间,而微软也不希望你挂钩内核的一些函数,如果非要使用的话,微软提供了一些回调函数可以实现相应的挂钩效果。

  • 相关阅读:
    聊天类功能测试用例
    即时通讯软件针对通讯以及协议方面有哪些测试点?
    面试前期准备工作
    黑盒功能业务测试过程
    Web网站实现facebook登录
    Nginx配置SSL实现HTTPS访问
    jQuery判断当前页面是APP内打开还是浏览器打开
    jQuery实现点击图片简单放大效果
    Linux排查PHP-FPM进程过量常用命令
    PHP防止SQL注入攻击和XSS攻击
  • 原文地址:https://www.cnblogs.com/LyShark/p/11639533.html
Copyright © 2020-2023  润新知