1、
自写驱动保护XX进程(HOOK SSDT)
A、构建自己的内核函数(用来替换对应的内核函数)
C、Hook和UnHook函数构建
D、修改EXE和SYS对应源代码实现所谓保护
E、测试效果
【135】以28课的代码为例 新建一个hook.h单元
【200】实际上选用 第29/30课 的代码也是可以的
【380】hook.h 里面的代码:
“
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif#include <NTDDK.h> //这里包含需要用C方式编译的头文件
#ifdef __cplusplus
}
#endif//#include <windef.h>
bool ssdthook_flag=false;
ULONG RealNtOpenAddress;
HANDLE MyPID;// A、构建自己的内核函数(用来替换对应的内核函数)
// 定义一下NtOpenProcess的原型
extern "C" typedef NTSTATUS __stdcall NTOPENPROCESS
(
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK AccessMask,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId
);
NTOPENPROCESS * RealNtOpenProcess;PEPROCESS EP;
// 自定义的NtOpenProcess函数
#pragma PAGECODE
extern "C" NTSTATUS __stdcall MyNtOpenProcess(
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId )
{
NTSTATUS rc;
HANDLE PID; KdPrint(("++++++++++++Entry MyNtOpenProcess int ++++++++++++++\n"));
rc = (NTSTATUS)RealNtOpenProcess( ProcessHandle, DesiredAccess, ObjectAttributes, ClientId );
if( (ClientId != NULL) )
{
PID = ClientId->UniqueProcess;
KdPrint(( "------------------------- PID=%d--------------\n",(int*)PID )); // 如果是被保护的PID,则拒绝访问,并将句柄设置为空
if(PID == MyPID)
{
KdPrint(("被保护进程 MyPID=%d \n",(int)MyPID));
//调试输出 类似C语言的 Printf ProcessHandle = NULL; //这个是关键
rc = STATUS_ACCESS_DENIED; //这个返回值
//PsLookupProcessByProcessId((ULONG)PID,&EP);
EP=PsGetCurrentProcess();
KdPrint((" ACESS Process Name --:%s-- \n",(PTSTR)((ULONG)EP+0x174)));
}
}
return rc;
}
//HOOK 函数构建
#pragma PAGECODE
VOID Hook()
{
ssdthook_flag=true;//设置被HOOK标志
KdPrint(("++++HOOK START ++++-\n"));
LONG *SSDT_Adr,SSDT_NtOpenProcess_Cur_Addr,t_addr; KdPrint(("驱动成功被加载中.............................\n"));
//读取SSDT表中索引值为0x7A的函数
//poi(poi(KeServiceDescriptorTable)+0x7a*4)
t_addr=(LONG)KeServiceDescriptorTable->ServiceTableBase;
SSDT_Adr=(PLONG)(t_addr+0x7A*4);
SSDT_NtOpenProcess_Cur_Addr=*SSDT_Adr;
RealNtOpenAddress = *SSDT_Adr;
RealNtOpenProcess = ( NTOPENPROCESS *)RealNtOpenAddress; KdPrint(( "真实的NtOpenProcess地址: %x\n",(int) RealNtOpenAddress ));
KdPrint((" 伪造NTOpenProcess地址: %x\n", (int)MyNtOpenProcess )); __asm //去掉页面保护
{
cli
mov eax,cr0
and eax,not 10000h //and eax,0FFFEFFFFh
mov cr0,eax
} *SSDT_Adr= (ULONG)MyNtOpenProcess; // 【1990】SSDT HOOK的关键
__asm
{
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
}
return;
}
//UnHook函数构建
//////////////////////////////////////////////////////
#pragma PAGECODEVOID UnHook()
{
ULONG Old_ssdt;
Old_ssdt = (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;
if (ssdthook_flag)
{
ssdthook_flag=false;
__asm
{
cli
mov eax, cr0
and eax, not 10000h
mov cr0, eax
} // 还原SSDT
*((ULONG*)Old_ssdt) = (ULONG)RealNtOpenAddress; __asm
{
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
}
KdPrint(("UnHook还原SSDT OK \n"));
}
return;
}”
【500】自定义的NtOpenProcess函数 (函数MyNtOpenProcess) 的参数原型,可以参照ZwOpenProcess。∵这个函数才是导出的,可以通过【570】MSDN能够查到它的参数的原型
ZC: 命名 hook的是内核的NtOpenProcess,参数原型却是参照的ZwOpenProcess...这个如何理解的通...
ZC: 如何 看一个函数 是导出的 还是 不是导出的?看MSDN上能否查到该函数??看是否是 ntdll.dll等相关dll的导出函数?
ZC: 之前的课程里面有 跟踪 OpenProcess的过程,忘了 ZwOpenProcess 和 NtOpenProcess 是哪个在上层那个在底层了,貌似记得 应用层和内核 都有一个函数叫NtOpenProcess...比较乱,需要回顾前面的内容...
【655】假设 现在我们的hook已经完成了 SSDT里面的 NtOpenProcess的函数地址 指向了 我们自己的MyNtOpenProcess,那么就会对传入的参数进行过滤处理
【745】首先,调用真正的函数NtOpenProcess,得到真实的返回的值(函数返回值 和 第1个OUT参数ProcessHandle 比较关键)
【755】视频作者的理解:(1)、ProcessHandle返回的是进程的句柄,(2)、函数返回值 返回的应该是 可访问的权限
【910】比较 CLIENT_ID.UniqueProcess 和 我们要保护的进程的PID
【1170】改变它的返回值。这个返回值 STATUS_ACCESS_DENIED 表示 拒绝访问(不允许访问它)
ZC: 这里的“PsLookupProcessByProcessId((ULONG)PID,&EP);”,这个函数的作用是啥?看代码的上下内容,猜测是 通过 进程PID获取EPROCESS结构的内容
【1395】【1440】“PsGetCurrentProcess();” 用于取得 当前进程的 进程结构。该结构的 0x174偏移处 是 进程名
【1505】打印出 调试信息:调用NtOpenProcess打开被我们保护的PID的进程的 进程名。(即 打印出调用NtOpenProcess的进程的进程名,且 NtOpenProcess打开的是我们保护的进程)
【1595】具体看一下,具体 hook里面是怎么样实现的
【1670】ZC: 注意这里的函数指针的用法
【1715】typedef
【1785】NTOPENPROCESS 是一个函数结构。“NTOPENPROCESS *RealNtOpenProcess;”才是具体定义了函数指针
ZC: 这里并非是 去修改 NtOpenProcess里面的首条汇编指令 使之jmp到MyNtOpenProcess去执行。SSDT只是一张存放函数地址的表,要调用某函数的时候 只要取得某函数的地址然后push*?-->call就行了,因此 我们改SSDT表里面的对应元素使之变成我们的函数地址即可,无需对原始的NtOpenProcess做任何改动。
【1950】替换 SSDT中NtOpenProcess对应的表项
【2065】ZC: 内联hook(inline hook) 是指 在函数内部修改汇编代码 使之跳转到我们的函数执行 的hook?【2020】
【2220】这种hook(这里的SSDT的hook),有点像IAT的hook
【2310】unhook 就更简单了
【2370】这里特意用了另外一种方式(ZC: 不是一个意思么...)
【2655】修改ctl_code.h 头文件
【2680】按照 第30课 的代码,加了 hook_code 和 unhook_code
“
#ifndef CTL_CODE
#pragma message("\n \n-----------EXE模式 Include winioctl.h ")
#include<winioctl.h> //CTL_CODE ntddk.h wdm.h
#else
#pragma message("-\n \n---------SYS模式NO Include winioctl.h ")
#endif#define add_code CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER,FILE_ANY_ACCESS)
#define sub_code CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_NEITHER,FILE_ANY_ACCESS)
#define hook_code CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER,FILE_ANY_ACCESS)
#define unhook_code CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_NEITHER,FILE_ANY_ACCESS)”
ZC: 见【5965】处,这里是用的第28课的代码,应该是 METHOD_BUFFERED,非METHOD_NEITHER
【2760】统一 exe和sys 两边的 ctl_code.h 头文件
【2830】修改SYS部分源代码
【2850】添加 hook_code的处理
【2923】添加 unhook_code的处理
【3025】如果 没有手动的“UnHook();”(即 unhook_code)的话,需要在 卸载例程中调用“UnHook();”
【3400】函数Hook() 中有一个 标识 ssdthook_flag,函数 UnHook() 会判断该标识
【3610】MyPID(全局变量)的获取
【3850】输出缓冲区,返回 int型值为1,表示 操作成功(ZC: 这样做?能不能通过 pIrp->IoStatus.Status 的值 来指明操作是否成功?)
#include "hook.h"
1 case hook_code:
2 { //从buffer获取MyPid
3 //获取缓冲区数据 a,b
4 int * InputBuffer = (int*)pIrp->AssociatedIrp.SystemBuffer;
5 _asm
6 {
7 mov eax,InputBuffer
8 mov ebx,[eax]
9 mov MyPID,ebx
10
11 }
12 int* OutputBuffer = (int*)pIrp->AssociatedIrp.SystemBuffer;
13 _asm
14 {
15 mov eax,1
16 mov ebx,OutputBuffer
17 mov [ebx],eax //
18
19 }
20 info = 4;
21 Hook();
22 break;
23 }
24 case unhook_code:
25 { UnHook();
26 break;
27 }
1 #pragma PAGECODE
2 VOID DDK_Unload (IN PDRIVER_OBJECT pDriverObject)
3 {
4 PDEVICE_OBJECT pDev;//用来取得要删除设备对象
5 UNICODE_STRING symLinkName; //
6 UnHook();
7 if (ishook)
8 {//unhook
9
10
11 __asm //去掉页面保护
12 {
13 cli
14 mov eax,cr0
15 and eax,not 10000h //and eax,0FFFEFFFFh
16 mov cr0,eax
17
18 }
19
20
21 pcur->E9= oldCode.E9;//1字节
22 pcur->JMPADDR= oldCode.JMPADDR;//4字节
23 __asm //恢复页保护
24 {
25 mov eax,cr0
26 or eax,10000h //or eax,not 0FFFEFFFFh
27 mov cr0,eax
28 sti
29 }
30 } //end unhook
31 pDev=pDriverObject->DeviceObject;
32 IoDeleteDevice(pDev); //删除设备
33
34 //取符号链接名字
35 RtlInitUnicodeString(&symLinkName,L"\\??\\My_DriverLinkName");
36 //删除符号链接
37 IoDeleteSymbolicLink(&symLinkName);
38 KdPrint(("驱动成功被卸载...OK-----------")); //sprintf,printf
39 //取得要删除设备对象
40 //删掉所有设备
41 DbgPrint("卸载成功");
42 }
【4130】修改EXE部分源代码
【4210】函数“int Hook(...)”
【4303】函数“int UnHook(...)”
【4650】修改 main 中的代码
int Hook(HANDLE hDevice, ULONG pid) //pid 需要被保护进程ID
{
int port[2];
int bufret;
ULONG dwWrite;
port[0]=pid;
DeviceIoControl(hDevice, hook_code , &port, 8, &bufret, 4, &dwWrite, NULL);
return bufret;
}
int UnHook(HANDLE hDevice)
{
int port[2];
int bufret;
ULONG dwWrite;
DeviceIoControl(hDevice, unhook_code , &port, 8, &bufret, 4, &dwWrite, NULL);
return bufret;
}
int main(int argc, char* argv[])
{
//add
//CreateFile 打开设备 获取hDevice
HANDLE hDevice =
CreateFile("\\\\.\\My_DriverLinkName", //\\??\\My_DriverLinkName
GENERIC_READ | GENERIC_WRITE,
0, // share mode none
NULL, // no security
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL ); // no template
printf("start\n");
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("获取驱动句柄失败: %s with Win32 error code: %d\n","MyDriver", GetLastError() );
getchar();
return -1;
}
ULONG pid;
printf("请键入需要被保护的进程PID=");
scanf("%d",&pid);
Hook(hDevice,pid);
int a=55;
int b=33;
int r=add(hDevice,a,b);
printf("\n %d+%d=%d \n",a,b,r);
getchar();
return 0;
}
【4950】把 exe和sys 放到虚拟机中 测试
【5105】输入PID之后蓝屏
【5285】重启 虚拟机,运行WinDBG
【5430】看到 断下的地方了
【5455】WinDBG命令"r"
【5460】WinDBG命令"reg" “Bad register error in 'reg'”
【5495】WinDBG命令"r eax"
【5515】eax值为0
【5965】这里是用的第28课的代码,应该是 METHOD_BUFFERED,非METHOD_NEITHER(ZC: 从 输入输出缓冲区的获取方式,也应该看出来是 METHOD_BUFFERED,但是我没有注意到这一点...)
【6145】重启虚拟机,重新生成 exe/sys,重新测试
【6420】ZC:输入 PID,回车之后,exe就直接退出了...他也不管这个现象,继续使用CE来附加记事本进程。想起来了,这个exe就是 执行完直接退出的,不会停留...
【6500】CE报错“打开进程错误”
【6570】这种驱动保护,只是比较初级的驱动保护,效果不是很明显。比如 它就不能对抗OD(OD的StrongOD的内核模式,第31课的【830】处),【6645】OD-->文件-->附加 还是看不到记事本的进程,打开了"内核模式",它要加载一个驱动,重新启动OD一次,此时 OD-->文件-->附加 就能附加 记事本的进程 了。
【6680】StrongOD也是对NtOpenProcess之类的内核函数 进行了深层的模拟,它自己模拟了相应的函数,或者说 它恢复了SSDT
【6750】用工具来看一下 【6760】打开 KernelDetective
【6805】看到 这里有一个修改,具体是什么样的修改 它没有给出来,这个是SSDT表的修改,这个红色的 是我们自己修改的
ZC: 如何确定这个是我们的驱动修改的?
【6900】我们推出我们的驱动 再来看一下,看 函数UnHook() 是否写进去了
【6980】看到 SSDT确实写回去了
【7105】再次测试 hook,看到 KernelDetective中"当前地址"被改为"0xBAD6E000"
【7212】解除 我们的Hook的方案: (不是将我们的MyNtOpenProcess的汇编首指令改为JMP,而是) 找到 NtOpenProcess之后的je判断 直接改为
【7235】“je short BAD6E09B”-->右击--> "汇编+写入"
【7310】我们让这个跳转永远不实现,我们可以把它nop掉(ZC: 貌似烦了吧?应该是让它永远实现)
【7355】或者我们让“je short BAD6E09B”上面的比较的两个值,永远不相等(“cmp dword ptr [ebp+14],0”)
ZC: 这里我想到,可以直接修改 零标识位 吗?后来又想到: (1)、不知道是否允许改 零标识位,(2)、就算允许改 零标识位,那么也必须在cmp和je中间增加语句,这个貌似比较麻烦(需要跳到别的地方,改好 零标识位,再跳回来...)
【7378】“je short BAD6E09B”-->右击--> 二进制 --> 用NOP填充
【7435】测试,输入PID之后,直接蓝屏了,可能改的不对,这里不再讨论了
ZC: 这里的蓝屏,是∵我们改错了的缘故吗?我只能确定的是,这样改了之后,所有的需要通过NtOpenProcess的调用都会失败...
2、