爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      X86驱动:恢复SSDT内核钩子

      首页 知识中心 其他 文章详情页

      X86驱动:恢复SSDT内核钩子

      2023-07-20 06:05:57 阅读次数:421

      地址

      SSDT 中文名称为系统服务描述符表,该表的作用是将Ring3应用层与Ring0内核层,两者的API函数连接起来,起到承上启下的作用,SSDT并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基址、服务函数个数等,SSDT 通过修改此表的函数地址可以对常用 Windows 函数进行内核级的Hook,从而实现对一些核心的系统动作进行过滤、监控的目的。

      通过前面的学习我们已经可以编写一个驱动程序并挂钩到指定的内核函数上了,接下来我们将一步步的通过编写驱动程序,手动的来解除 NtOpenProcess 函数的驱动保护,以此来模拟如何一步步干掉游戏保护。

      一般情况下当游戏启动的时候都会加载保护,而这种保护通常都是通过在SSDT层挂钩来实现的,而一旦函数被挂钩那么通过前面的读取方式就无法读取到函数的原始地址了,如下图是一个被Hook过的函数,可以看到函数的当前地址与原始地址已经发生了明显的变化。

      X86驱动:恢复SSDT内核钩子

      那么如何获取到原始函数地址呢?很简单只需要使用系统提供给我们的 MmGetSystemRoutineAddress 函数即可获取到原始函数的地址,最终测试代码如下:

      #include <ntddk.h>
      extern "C" LONG KerServiceDescriptorTable;
      
      ULONG Get_SSDTAddr(){
      	UNICODE_STRING NtOpen;     // 存放函数的Unicode字符串
      	ULONG SSDT_Addr;               // 用于存放原始的SSDT地址
      
      	// 将NtOpenProcess字符串以Uncode格式写入到NtOpen变量中
      	RtlInitUnicodeString(&NtOpen, L"NtOpenProcess");
      	// 获取系统程序地址,取得NtOpenProcess的原始地址
      	SSDT_Addr = (ULONG)MmGetSystemRoutineAddress(&NtOpen);
      	DbgPrint("原始函数的地址是: %x\n", SSDT_Addr);
      	return SSDT_Addr;
      }
      
      VOID UnDriver(PDRIVER_OBJECT driver)
      {
      	DbgPrint(("驱动卸载成功 ! \n"));
      }
      
      NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
      {
      	Get_SSDTAddr();
      	DriverObject->DriverUnload = UnDriver;
      	return STATUS_SUCCESS;
      }
      

      编译这段驱动代码,然后回到虚拟机并加载这段驱动,手动验证一下观察:

      X86驱动:恢复SSDT内核钩子

      上方的驱动代码也可以改用汇编来实现,其效果是相同的,贴出汇编代码的实现流程,这里就不演示了。

      #include <ntddk.h>
      extern "C" LONG KerServiceDescriptorTable;
      
      ULONG Get_SSDTAddr(){
      	UNICODE_STRING NtOpen;         // 存放函数的Unicode字符串
      	ULONG SSDT_Addr;                     // 用于存放原始的SSDT地址
      
      	// 将NtOpenProcess字符串以Uncode格式写入到NtProcess变量中
      	RtlInitUnicodeString(&NtOpen, L"NtOpenProcess");
      	__asm
      	{
      		lea eax, NtOpen             // 将初始化的NtOpenProcess地址给EAX
      		push eax                    // EAX压入堆栈 等待调用
      		call DWORD ptr DS:[MmGetSystemRoutineAddress]
      		mov SSDT_Addr,eax	        // 将结果赋值给变量
      	}
      	DbgPrint("原始函数的地址是: %x\n", SSDT_Addr);
      	return SSDT_Addr;
      }
      
      VOID UnDriver(PDRIVER_OBJECT driver)
      {
      	DbgPrint(("驱动卸载成功 ! \n"));
      }
      
      NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
      {
      	Get_SSDTAddr();
      	DriverObject->DriverUnload = UnDriver;
      	return STATUS_SUCCESS;
      }
      

      通过偏移二次读取: 上面的代码运行后只能获取到一部分函数的原始地址,有些函数的地址是无法获取到的,比如我们想要获取 NtReadVirtualMemory 这个内核函数的地址时,上方的代码就会显示获取失败,如下获取结果始终显示为0。

      X86驱动:恢复SSDT内核钩子

      既然无法获取到当前函数的地址,那么我们可以尝试获取NtReadVirtualMemory函数的前一个函数的内存地址,并通过相加偏移的方式来获取该函数的地址,首先我们通过Xuetr 查询到 NtReadVirtualMemory 函数的当前地址,然后通过 WinDBG 调试器找到其对应的前一个函数的偏移。

      lkd> u 83e7f82c
      
      nt!MmCopyVirtualMemory+0x50a:
      83e7f82c 6a18            push    18h
      83e7f82e 68285ac783      push    offset nt!NtBuildGUID+0xc9a4 (83c75a28)
      83e7f833 e870e3e1ff      call    nt!strchr+0x118 (83c9dba8)
      83e7f838 648b3d24010000  mov     edi,dword ptr fs:[124h]
      83e7f83f 8a873a010000    mov     al,byte ptr [edi+13Ah]
      83e7f845 8845e4          mov     byte ptr [ebp-1Ch],al
      83e7f848 8b7514          mov     esi,dword ptr [ebp+14h]
      83e7f84b 84c0            test    al,al
      

      X86驱动:恢复SSDT内核钩子

      查询结果中可以发现上一个函数的是 MmCopyVirtualMemory 而相对应的偏移地址是 0x50a,接着直接改进上方的程序,即可实现查询,代码如下:

      #include <ntddk.h>
      
      
      extern "C" LONG KerServiceDescriptorTable;
      
      ULONG Get_SSDTAddr(){
      	UNICODE_STRING NtOpen;
      	ULONG SSDT_Addr;
      
      	RtlInitUnicodeString(&NtOpen, L"MmCopyVirtualMemory");
      	SSDT_Addr = (ULONG)MmGetSystemRoutineAddress(&NtOpen);
      
      	__asm
      	{
      		push eax
      		mov eax,SSDT_Addr
      		add eax,50ah
      		mov SSDT_Addr,eax
      	}
      	DbgPrint("原始函数的地址是: %x\n", SSDT_Addr);
      	return SSDT_Addr;
      }
      
      VOID UnDriver(PDRIVER_OBJECT driver)
      {
      	KdPrint(("Uninstall Driver Is OK \n"));
      }
      
      NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
      {
      	Get_SSDTAddr();
      
      	DriverObject->DriverUnload = UnDriver;
      	return STATUS_SUCCESS;
      }
      

      X86驱动:恢复SSDT内核钩子


      判断函数是否被Hook: 上方的代码中,我们可以通过使用MmGetSystemRoutineAddress函数来获取到函数的原始地址,而在第一部分我们又通过汇编的方式得到了函数的当前地址,通过使用当前地址与原始地址做比较即可判断出函数是否被Hook。

      #include <ntddk.h>
      #include <windef.h>  //包含windef.h文件byte字节才能使用
      extern "C" LONG KerServiceDescriptorTable;
      extern "C" LONG KeServiceDescriptorTable;
      
      typedef struct _JMPDATE
      {
      	BYTE E9;       // 定义一个字节的e9成员名用来存放一字节数据
      	ULONG JMPADDR; // 定义JMPADDR成员名用来存放4字节跳转地址数据
      }JMPDATE;
      
      ULONG Get_Origin_SSDTAddr(){                 // 获取到NTOpenProcess的原始地址
      	UNICODE_STRING NtOpen;
      	ULONG SSDT_Addr;
      
      	RtlInitUnicodeString(&NtOpen, L"NtOpenProcess");
      	SSDT_Addr = (ULONG)MmGetSystemRoutineAddress(&NtOpen);
      	return SSDT_Addr;
      }
      ULONG Get_Now_SSDTAddr(){                     // 获取到NTOpenProcess的当前地址
      	ULONG SSDT_Addr;
      	__asm{
      			push ebx
      			push eax
      			mov ebx, KeServiceDescriptorTable    // 系统描述符号表的地址
      			mov ebx, [ebx]                       // 取服务表基址给EBX
      			mov eax, 0xBE                        // NtOpenProcess=转成十六进制等于BE
      			imul eax, eax, 4                      // eax=eax*4 -> 7a*4=1e8
      			add ebx, eax                         // eax=1e8与服务表基址EBX相加
      			mov ebx, [ebx]                       // 取ebx里面的内容给EBX
      			mov SSDT_Addr, ebx                   // 将得到的基址给变量
      			pop eax
      			pop ebx
      	}
      	return SSDT_Addr;
      }
      VOID UnDriver(PDRIVER_OBJECT driver)
      {
      	KdPrint(("驱动卸载成功 !\n"));
      }
      
      NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
      {
      	ULONG Get_Origin_SSDT, Get_Now_SSDT;
      	JMPDATE JmpDate;
      
      	Get_Now_SSDT = Get_Now_SSDTAddr();         // 获取NTOpenProcess的当前地址
      	Get_Origin_SSDT = Get_Origin_SSDTAddr();   // 获取原始的NTOpenProcess的地址
      	if (Get_Now_SSDT != Get_Origin_SSDT)
      	{
      		DbgPrint("该函数已经被Hook了! \n");
      		JmpDate.E9 = 0xe9;                                     // 0xe9 机器码是 jmp指令
      		JmpDate.JMPADDR = Get_Origin_SSDT - Get_Now_SSDT - 5; // 原始地址-当前地址-5 = 需要跳转的机器码数据
      		DbgPrint("写入了JMP数据=%x \n", JmpDate.JMPADDR);
      	}else
      	{
      		DbgPrint("该函数没有被Hook ! \n");
      	}
      	DriverObject->DriverUnload = UnDriver;
      	return STATUS_SUCCESS;
      }
      

      X86驱动:恢复SSDT内核钩子

      恢复被Hook过的函数: 接下来我们通过编写驱动程序的方式恢复 NtOpenProcess内核函数所Hook的地址,恢复Hook的原理非常的简单,只需要在函数头部添加一条Jmp xxxx并将其跳转到原始函数地址上面去即可恢复挂钩。

      在下方的代码中需要注意一条计算公式 JmpDate.JMPADDR = Get_Origin_SSDT - Get_Now_SSDT - 5; 如下使用 12345678 - 00401000 - 5 即可得到 E9机器码后面的跳转地址 7346F411 这也是计算代码的核心。

      00401000 >  - E9 7346F411   jmp     12345678
      

      有了计算的公式,我们在前面的代码的基础上继续改进一下即可,最终代码如下:

      #include <ntddk.h>
      #include <windef.h>  //包含windef.h文件
      extern "C" LONG KerServiceDescriptorTable;
      extern "C" LONG KeServiceDescriptorTable;
      
      #pragma pack(1)         // 使下面的结构以一字节方式对齐,而不是默认的4字节对齐
      typedef struct _JMPDATE
      {
      	BYTE E9;            // 定义一个字节的e9成员名用来存放一字节数据
      	ULONG JMPADDR;      // 定义JMPADDR成员名用来存放4字节跳转地址数据
      }JMPDATE, *PJMPDATE;
      #pragma pack()
      
      JMPDATE Origin_Data;       // 存放原始跳转数据
      PJMPDATE pNow_Data;        // 存放当前跳转数据
      
      ULONG Get_Origin_SSDTAddr(){                 // 获取到NTOpenProcess的原始地址
      	UNICODE_STRING NtOpen;
      	ULONG SSDT_Addr;
      
      	RtlInitUnicodeString(&NtOpen, L"NtOpenProcess");
      	SSDT_Addr = (ULONG)MmGetSystemRoutineAddress(&NtOpen);
      	return SSDT_Addr;
      }
      ULONG Get_Now_SSDTAddr(){                     // 获取到NTOpenProcess的当前地址
      	ULONG SSDT_Addr;
      	__asm{
      		push ebx
      			push eax
      			mov ebx, KeServiceDescriptorTable    // 系统描述符号表的地址
      			mov ebx, [ebx]                       // 取服务表基址给EBX
      			mov eax, 0xBE                        // NtOpenProcess=转成十六进制等于BE
      			imul eax, eax, 4                      // eax=eax*4 -> 7a*4=1e8
      			add ebx, eax                         // eax=1e8与服务表基址EBX相加
      			mov ebx, [ebx]                       // 取ebx里面的内容给EBX
      			mov SSDT_Addr, ebx                   // 将得到的基址给变量
      			pop eax
      			pop ebx
      	}
      	return SSDT_Addr;
      }
      VOID UnDriver(PDRIVER_OBJECT driver)
      {
      
      	__asm   //去掉内核页面保护
      	{
      		cli
      		mov eax, cr0
      		and eax, not 10000h
      		mov cr0, eax
      
      	}
      	// 恢复原始地址
      	pNow_Data->E9 = Origin_Data.E9;
      	pNow_Data->JMPADDR = Origin_Data.JMPADDR;
      
      	__asm   //恢复内核页面保护
      	{
      		mov eax, cr0
      		or  eax, 10000h
      		mov cr0, eax
      		sti
      	}
      	KdPrint(("驱动卸载成功 !\n"));
      }
      
      NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
      {
      	ULONG Get_Origin_SSDT, Get_Now_SSDT;
      	JMPDATE JmpDate;
      
      	Get_Now_SSDT = Get_Now_SSDTAddr();         // 获取NTOpenProcess的当前地址
      	Get_Origin_SSDT = Get_Origin_SSDTAddr();   // 获取原始的NTOpenProcess的地址
      	if (Get_Now_SSDT != Get_Origin_SSDT)
      	{
      		DbgPrint("该函数已经被Hook了! \n");
      
      		pNow_Data = (PJMPDATE)(Get_Now_SSDT);     // 初始化获取NtOpenProcess当前地址的PJMPDATE结构指针保存在 pNow_Data
      		Origin_Data.E9 = pNow_Data->E9;           // 将pNow_Data中E9里的内容给Origin_Data中的E9,1字节
      		Origin_Data.JMPADDR = pNow_Data->JMPADDR; // NtOpenProcess当前地址后4字节,保存在Origin_Data.JMPADDR里面
      		// 取出当前的地址,然后保存到JmpData结构中
      		JmpDate.E9 = 0xe9;                                     // 0xe9 机器码是 jmp指令
      		JmpDate.JMPADDR = Get_Origin_SSDT - Get_Now_SSDT - 5; // 原始地址-当前地址-5 = 需要跳转的机器码数据
      		DbgPrint("写入JMP的数据 = %x \n", JmpDate.JMPADDR);
      		
      		__asm   //去掉内核页面保护
      		{
      			cli
      			mov eax, cr0
      			and eax, not 10000h
      			mov cr0, eax
      		}
      		pNow_Data->E9 = JmpDate.E9;              // jmp 1字节数据写入
      		pNow_Data->JMPADDR = JmpDate.JMPADDR;    // 写入跳转到目标地址
      
      		__asm   //恢复内核页面保护
      		{
      			mov eax, cr0
      			or  eax, 10000h
      			mov cr0, eax
      			sti
      		}
      
      	}
      	DriverObject->DriverUnload = UnDriver;
      	return STATUS_SUCCESS;
      }
      

      附上汇编版本的Hook恢复代码,如下,自行替换此处不做测试了。

      // 挂钩代码汇编版本,替换到上方完整代码指定字段即可,此处不做演示。
      __asm{........}       //去掉内核页面保护
      		__asm
      		{
      			//保存写入前的数据,用于驱动卸载恢复。     
      			mov ebx, Get_Now_SSDT             // 将当前的地址给EBX
      				lea ecx, Origin_Data      // 将原始地址的地址给ECX
      				mov al, byte ptr[ebx]    // 取EBX的第一个字节给AL
      				mov byte ptr[ecx], al    // AL内容以字节方式写入ECX=原始地址里
      				mov eax, [ebx + 1]       // EBX=当前的地址+1的4字节内容给EAX
      				mov[ecx + 1], eax        // EAX的内容写入ECX=原始地址+1偏移处
      
      				//写入数据
      				mov ebx, Get_Now_SSDT   // 将当前的地址给EBX
      				lea  ecx, JmpDate               // 将JmpDate结构的地址给ECX  
      				mov al, byte ptr[ecx]         // 取出JmpDate结构地址的第一个字节给AL
      				mov byte ptr[ebx], al        // al=JmpDate.E9 ,将数据写入到EBX=当前的地址。
      				mov eax, [ecx + 1]            // [ECX+1]=JmpDate.JMPADDR ,将数据写入到EAX里保存
      				mov[ebx + 1], eax            // 将EAX的数据写入到EBX+1=dangqian的地址+1偏移处。
      		}
      __asm{........}             //恢复内核页面保护
      
      
      // 恢复代码汇编版本,替换到上方完整代码指定字段即可,此处不做演示。
      __asm{........}       //去掉内核页面保护
      	__asm
      	{
      		mov ebx, pNow_Data        // 将当前结构赋值到ebx中
      		lea ecx, Origin_Data      // 将原始结构的地址让ECX保存,等候使用
      		mov al, byte ptr[ecx]     // 取出原始结构的地址中第一个字节的数据给AL
      		mov byte ptr[ebx], al     // 将AL的数据以一个字节的方式写入ebx=当前的地址。
      		mov eax, [ecx + 1]        // 取出原始结构的地址+1偏移处开始的4字节数据给EAX。
      		mov[ebx + 1], eax         // EAX的数据以4字节的方式写入到当前的地址+1偏移处。
      	}
      __asm{........}             //恢复内核页面保护
      

      将代码编译,并拖入虚拟机加载驱动,Hook之前如图一所示,Hook之后如图二,发现程序已经跳转到了原始的代码上了,Hook被解除啦。

      X86驱动:恢复SSDT内核钩子

      X86驱动:恢复SSDT内核钩子

      在任意位置写入恢复代码: 上方的代码片段虽然可以恢复浅层的Hook,但如果保护驱动Hook的较深的话需上面的代码将无法恢复,我们需要使用如下代码.

      X86驱动:恢复SSDT内核钩子

      NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
      {
      	BYTE Jmp_OEP[5] = { 0xEB,0x28,0xDC,0xC8,0x83};    // jmp 83C8DC28 硬编码
      	BYTE *NtOpen = (BYTE*)0x83C8DC28;                // 此处为了方便演示直接写地址
      
      	__asm   //去掉内核页面保护。
      	{
      		cli
      		mov eax, cr0
      		and eax, not 10000h
      		mov cr0, eax
      	}
      	// 拷贝内存,在NtOpen的地址基础上加3,填充为 jmp 指令填充4字节
      	RtlCopyMemory(NtOpen+3, Jmp_OEP, 5);
      
      	__asm   //恢复内核页面保护
      	{
      		mov eax, cr0
      		or  eax, 10000h
      		mov cr0, eax
      		sti
      	}
      	DriverObject->DriverUnload = UnDriver;
      	return STATUS_SUCCESS;
      }
      

      编译生成好代码以后,拖入虚拟机并加载驱动,观察内存变化,发现已经写入地址成功,我们可以使用该方法写入任意位置,注意堆栈平衡,否则会直接蓝屏。

      X86驱动:恢复SSDT内核钩子

      给系统函数添加额外功能: 通过使用Jmp跳转指令,我们可以给相应的系统函数添加新功能,以NtOpenProcess为例

      X86驱动:恢复SSDT内核钩子

      核心汇编伪代码如下,这里并没有写全,可以自行完善:

      #include <ntddk.h>
      #include <windef.h>
      
      VOID UnDriver(PDRIVER_OBJECT driver)
      {
      	KdPrint(("Uninstall Driver Is OK \n"));
      }
      
      BYTE *RetAddr = NULL;      // 保存函数的返回地址
      BYTE *MyHook = NULL;       // Hook要修改的地址
      
      // 对于jmp类型hook 如果没有使用_declspec(naked)修饰,会破破坏我们的堆栈平衡
      // 对于call类型的hook,如果使用_declspec(naked)修饰的话,要注意自己恢复堆栈平衡
      __declspec(naked) VOID inline_NtOpenProcess()
      {
      	__asm
      	{
      		mov ecx, dword ptr[ebp + 14]     // 这两条原始代码必须写在这里
      		mov edx, dword ptr[ebp + 10]
      		mov eax, RetAddr
      		jmp eax
      	}
      
      }
      
      NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
      {
      	BYTE *NtOpenProcess = (BYTE*)0x83C159DC;        // 函数的基址: 为方便演示这里直接填上了
      	BYTE Jmp_Addr[6] = { 0xE9, 0, 0, 0, 0,0x90};    // JMP跳转字节码 0x0=等待添加跳转地址 0x90 为填充字节(凑够6字节)
      
      	__asm
      	{
      		push eax
      		mov eax, NtOpenProcess          // 取出NtOpenProcess函数的基地址,原始地址
      		add eax, 0x13                   // 我们要写入数据的地方 (写入数据的位置)-(NtOpenProcess起始位置) = 相对偏移
      		mov MyHook, eax                 // 相加后获取到需要Hook的位置
      		add eax, 0x06                   // 获取到函数的返回地址,相加后得到,0x6h就是指令的间隔
      		mov RetAddr, eax                // 获取返回的地址
      		pop eax
      	}
      
      	// Jmp_Addr+1=指向E9后面的字节  // inline_NtOpenProcess - MyHook + 5 得到需要跳转到的位置
      	*(ULONG *)(Jmp_Addr + 1) = (ULONG)inline_NtOpenProcess - ((ULONG)MyHook + 5);
      
      	CloseProtect(); //去掉内核页面保护
      	RtlCopyMemory(MyHook, Jmp_Addr, 6);  //将Jmp_Addr的6字节写入到 MyHook要HOOK的位置
      	StartProtect(); //恢复内核页面保护
      
      	DriverObject->DriverUnload = UnDriver;
      	return STATUS_SUCCESS;
      }
      

      Hook以后截图如下,可以看到我们能够在自己的函数中为NtOpenProcess函数增加额外的功能,也可以利用该方法过掉某些游戏的驱动保护,点到为止不说了。

      X86驱动:恢复SSDT内核钩子

      X86驱动:恢复SSDT内核钩子


      拓展:还原 Shadow SSDT 中被Hook的函数

      Shadow SSDT的全称是 Shadow System Services Descriptor Table 影子系统服务描述符表,该表中存放的是一些与系统图形回调队列以及键盘鼠标事件相关的信息。

      ServiceDescriptor中只有指向KiServiceTable的SST,是ServiceDescriptorTable是被系统所导出的表结构,而ServiceDescriptorTableShadow是未导出的,但我们依然可以通过相加偏移的方式得到其当前地址。

      在网络游戏中通常会Hook挂钩 NtUserSendInput 这个内核函数,从而实现拦截用户使用能够模拟合成鼠标键盘事件操作的软件脚本精灵,那么该怎末过保护?来直接上车。

      通过WinDBG附加内核调试,然后输入以下命令,记得加载符号链接。

      lkd> dd KeServiceDescriptorTable                   // 获取到SSDT表基址
      8055d700  80505570 00000000 0000011c 805059e4
      8055d710  00000000 00000000 00000000 00000000
      
      lkd> dd KeServiceDescriptorTableShadow              // 获取到ShadowSSDT的基址
      8055d6c0  80505570 00000000 0000011c 805059e4
      8055d6d0  bf9a1500 00000000 0000029b bf9a2210
      

      KeServiceDescriptorTable - KeServiceDescriptorTableShadow 相减得到SSDT相对SSSDT的偏移地址此处的便宜地址是0x40,然后直接 dd poi(KeServiceDescriptorTable-0x40)此处的poi命令为取出后面的内存地址。

      lkd> dd KeServiceDescriptorTable - KeServiceDescriptorTableShadow
      
      00000040  ???????? ???????? ???????? ????????
      00000050  ???????? ???????? ???????? ????????
      
      lkd> dd poi(KeServiceDescriptorTable-0x40)
      80505570  805a5664 805f23ea 805f5c20 805f241c
      80505580  805f5c5a 805f2452 805f5c9e 805f5ce2
      
      lkd> u 805a5664                                 // 得到SSDT表中第一个函数的地址
      nt!NtAcceptConnectPort:                       
      805a5664 689c000000      push    9Ch
      805a5669 6850ab4d80      push    offset nt!_real+0x118 (804dab50)
      805a566e e8cd76f9ff      call    nt!_SEH_prolog (8053cd40)
      
      lkd> Dd poi(KeServiceDescriptorTable-0x40+0x10)       // +0x10得到SSSDT表地址中第一个NtGdiAbortDoc
      
      bf9a1500  bf93b025 bf94c876 bf88e421 bf9442da
      bf9a1510  bf94df11 bf93b2b9 bf93b35e bf839eba
      

      上方结果显示 bf93b025 是第一个函数NtGdiAbortDoc的地址,加上 NtUserSendInput 的序号十进制的529转为十六进制是0x211,然后乘以4字节即可获取到 NtUserSendInput 函数的基址,这里由于电脑管家Hook了所以显示的地址是a1ea5e9e 如果管家关闭的话这里就是了。

      lkd> dd poi[KeServiceDescriptorTable-0x40+0x10]+0x211*4
      bf9a1d44  a1ea5e9e bf86b7d8 bf82938b bf914622
      bf9a1d54  bf80e6cb bf8921d4 bf914ae8 bf915076
      

      既然流程都已经清楚了,还原就很简单了,附上汇编代码。

      extern "C" LONG KeServiceDescriptorTable;
      
      NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
      {
      	ULONG Shadow_Address;                       // 存储ShadowSSDT地址
      	ULONG NtUserSendInput;                      // 保存NtUserSendInput当前地址
      	__asm
      	{
      			push eax
      			push ebx
      			push ecx
      			mov eax, KeServiceDescriptorTable    // 系统服务描述表地址给EAX
      			sub eax, 0x40                        // EAX-0x40
      			add eax, 0x10                        // eax+0x10
      			mov eax, [eax]                       // 取[EAX] eax里面的数据给EAX
      			mov Shadow_Address, eax              // 将取出的数据给变量Shadow_Address
      			mov ecx, eax                         // 取出的数据给了ECX
      			mov eax, 0x211                       // 0x211给EAX,NtUserSendInput的序号
      			imul eax, eax, 4                     // EAX*4
      			add ecx, eax                         // ecx+eax
      			mov ebx, [ecx]                       // 取ecx里面的数据给EBX
      			mov NtUserSendInput, ebx             // EBX给局部变量NtUserSendInput
      			pop ecx
      			pop ebx
      			pop eax
      	}
      	DbgPrint("KeServiceDescriptorTable地址为:%x", Shadow_Address);
      	DbgPrint("NtUserSendInput地址为:%x", NtUserSendInput);
      
      	driver->DriverUnload = DriverUnload;
      	return STATUS_SUCCESS;
      }
      

      恢复代码如下,只附上关键代码吧,其他的和上方基本一致。

      	ULONG NtUserSendInput_Now;              // 局部变量存放当前地址
      	ULONG NtUserSendInput_Ord = 0xFFFFFFFF; // 此处的地址可以用Xuetr直接获取到起源地址
      
      		__asm   //去掉内核页面保护。
      		{
      			cli
      				mov eax, cr0
      				and eax, not 10000h
      				mov cr0, eax
      		}
      		__asm
      		{
      				push eax
      				push ebx
      				push ecx
      				push edx
      				mov eax, KeServiceDescriptorTable
      				sub eax, 0x40
      				add eax, 0x10
      				mov eax, [eax]
      				mov ecx, eax
      				mov eax, 0x211
      				imul eax, eax, 4
      				add ecx, eax
      				mov edx, NtUserSendInput_Ord   // 将函数的原始地址给EDX
      				mov[ecx], edx                  // EDX写入到ECX里
      				mov ebx, [ecx]                 // 取出NtUserSendInput的地址
      				mov NtUserSendInput_Now, ebx   // 取出NtUserSendInput的起源地址
      				pop edx
      				pop ecx
      				pop ebx
      				pop eax
      		}
      
      		__asm   //恢复内核页面保护
      		{
      			mov eax, cr0
      				or  eax, 10000h
      				mov cr0, eax
      				sti
      		}
      	}
      
      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://my.oschina.net/lyshark/blog/5550664,作者:LyShark,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:(20):event()

      下一篇:Ansible PlayBook语法

      相关文章

      2025-05-19 09:04:44

      地址与指针

      地址与指针

      2025-05-19 09:04:44
      char , 地址 , 指向
      2025-05-16 09:15:24

      WCF 配置文件分解

      WCF 配置文件分解

      2025-05-16 09:15:24
      localhost , WCF , 地址 , 服务 , 配置
      2025-05-14 10:33:31

      计算机小白的成长历程——数组(1)

      计算机小白的成长历程——数组(1)

      2025-05-14 10:33:31
      strlen , 个数 , 元素 , 内存 , 十六进制 , 地址 , 数组
      2025-05-14 10:33:25

      超级好用的C++实用库之网络

      在网络相关的项目中,我们经常需要去获取和设置设备的IP地址、子网掩码、网关地址、MAC地址等信息。这些信息一般与操作系统相关,在Windows系统和Linux系统上调用的接口是不一样的。

      2025-05-14 10:33:25
      Linux , 参数 , 地址 , 接口 , 网卡 , 返回值
      2025-05-14 09:51:21

      python向IP地址发送字符串

      在Python中,向IP地址发送字符串通常意味着你需要通过某种协议来实现通信。最常见的协议包括TCP和UDP。这里,我将分别给出使用TCP和UDP协议向指定IP地址发送字符串的示例代码。

      2025-05-14 09:51:21
      TCP , UDP , 协议 , 地址 , 示例 , 端口
      2025-05-13 09:53:13

      计算机萌新的成长历程18——指针

      计算机要存储数据的话有以下几种途径,按访问速度由快到慢来排列分别是:寄存器>高速缓存>内存>硬盘。它们的存储空间大小是依次增大的,寄存器的存储空间大小最小,硬盘存储空间大小最大。

      2025-05-13 09:53:13
      内存 , 变量 , 地址 , 寄存器 , 指针
      2025-05-12 08:45:17

      复原 IP 地址

      有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

      2025-05-12 08:45:17
      IP , 地址 , 示例
      2025-05-07 09:09:52

      C语言:深入理解指针(2)

      C语言:深入理解指针(2)                                                        

      2025-05-07 09:09:52
      amp , arr , sizeof , 元素 , 地址 , 指针 , 数组
      2025-05-07 09:09:52

      C语言:指针典型例题剖析

      C语言:指针典型例题剖析

      2025-05-07 09:09:52
      sizeof , strlen , 地址 , 字符 , 指针 , 数组 , 数组名
      2025-05-06 09:20:29

      【网络】什么是IP地址?Internet Protocol Address

      IP地址(Internet Protocol Address,互联网协议地址) 是分配给每台连接到网络上的设备的唯一标识符。IP地址的作用是标识设备在网络中的位置,使得数据可以正确地发送和接收。它就像是每台设备在网络中的“地址”,是网络通信中不可或缺的基础。

      2025-05-06 09:20:29
      IP , 地址 , 设备 , 路由器
      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5236321

      查看更多

      最新文章

      地址与指针

      2025-05-19 09:04:44

      复原 IP 地址

      2025-05-12 08:45:17

      【网络工程】IP地址基础01

      2025-05-06 09:20:29

      【网络】什么是IP地址?Internet Protocol Address

      2025-05-06 09:20:29

      【网络】网络层协议ARP和IP协议转发流程

      2025-04-22 09:40:08

      Android studio 网络代理

      2025-03-11 09:34:56

      查看更多

      热门文章

      网络基础概念(IP、MAC、网关、子网掩码)

      2023-06-25 07:03:11

      以太网安全_第九章

      2023-06-25 07:00:33

      CE修改器入门:查找多级指针

      2023-07-20 06:03:18

      【Linux】翻山越岭——进程地址空间

      2023-07-26 08:09:55

      IPv4的地址真的用光了吗

      2023-04-24 11:25:06

      【Linux】进程地址空间

      2023-03-21 03:15:34

      查看更多

      热门标签

      linux java python javascript 数组 前端 docker Linux vue 函数 shell git 节点 容器 示例
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      查看更多

      随机文章

      vue-router路由

      【收藏】使用网络地址转换 增强网络安全

      Linux平台下的进程地址空间

      地址与指针

      LyScript 实现Hook隐藏调试器

      指针进阶再启航,多级开篇韵更长

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号