枚举与删除线程回调

进程回调可以监视进程的创建和退出,这个在前面的章节已经总结过了。某些游戏保护的驱动喜欢用这个函数来监视有没有黑名单中的程序运行,如果运行则阻止运行或者把游戏退出。而线程回调则通常用来监控远程线程的建立,如果发现有远程线程注入到了游戏进程里,则马上把游戏退出。现在来详细讲解如何绕过这个两个监控。

我们注册的进程回调,会存储在一个名为 PspCreateProcessNotifyRoutine 的数组里。

PspCreateProcessNotifyRoutine 可以理解成一个 PVOID 数组,它记录了系统里所有进程回调的地址。这个数组最大长度是 64*sizeof(PVOID)。所以枚举进程回调的思路如下:找到这个数组的地址,然后解密数组的数据,得到所有回调的地址(这个数组记录的数据并不是回调的 地 址 , 而 是 经 过 加 密 地 址 , 需 要 解 密 才 行 )。 枚 举 线 程 回 调 同 理 , 要 找 到PspCreateThreadNotifyRoutine 的地址(这个数组最大长度也是 64*sizeof(PVOID)),然后解密数据,并把解密后的地址打印出来。

至于怎么处理这些回调就简单了。可以使用标准函数(PsSetCreateProcessNotifyRoutine、PsRemoveCreateThreadNotifyRoutine)将其摘掉,也可以直接在回调函数首地址写入 RET 把回调函数废掉。

首先要获得 PspCreateProcessNotifyRoutine 的地址。PspCreateProcessNotifyRoutine 在PspSetCreateProcessNotifyRoutine 函数里出现了。而 PspSetCreateProcessNotifyRoutine 则在PsSetCreateProcessNotifyRoutine 中被调用(注意前一个是 PspXXX,后一个是 PsXXX)。找到PspSetCreateProcessNotifyRoutine 之后,再匹配特征码:

于是我们根据特征码写出了以下代码(仅在 WIN7X64 上有效,WIN8、8.1 需要自己重新

定义特征码):

ULONG64 FindPspCreateProcessNotifyRoutine()
{
LONG OffsetAddr=0;
ULONG64 i=0,pCheckArea=0;
UNICODE_STRING unstrFunc;
//获得PsSetCreateProcessNotifyRoutine的地址
RtlInitUnicodeString(&unstrFunc, L"PsSetCreateProcessNotifyRoutine");
pCheckArea = (ULONG64)MmGetSystemRoutineAddress (&unstrFunc);
//获得PspSetCreateProcessNotifyRoutine的地址
memcpy(&OffsetAddr,(PUCHAR)pCheckArea+4,4);
pCheckArea=(pCheckArea+3)+5+OffsetAddr;
DbgPrint("PspSetCreateProcessNotifyRoutine: %llx",pCheckArea);
//获得PspCreateProcessNotifyRoutine的地址
for(i=pCheckArea;i<pCheckArea+0xff;i++)
{
if(*(PUCHAR)i==0x4c && *(PUCHAR)(i+1)==0x8d && *(PUCHAR)(i+2)==0x35) //lea r14,xxxx
{
LONG OffsetAddr=0;
memcpy(&OffsetAddr,(PUCHAR)(i+3),4);
return OffsetAddr+7+i;
}
}
return 0;
}

找到了 PspCreateProcessNotifyRoutine,枚举操作就好办了。需要说明的是,在PspCreateProcessNotifyRoutine 里的数据竟然被加密了,需要把数组的值和 0xfffffffffffffff8进行“与”位运算才行:

找线程的同理:

PspCreateThreadNotifyRoutine ->

PsSetCreateThreadNotifyRoutine ->

PspCreateThreadNotifyRoutine

执行结果如下(干净win7系统没有线程回调,特意注册了两个):

void EnumCreateProcessNotify()
{
int i=0;
BOOLEAN b;
ULONG64 NotifyAddr=0,MagicPtr=0;
ULONG64 PspCreateProcessNotifyRoutine=FindPspCreateProcessNotifyRoutine();
DbgPrint("PspCreateProcessNotifyRoutine: %llx",PspCreateProcessNotifyRoutine);
if(!PspCreateProcessNotifyRoutine)
return;
for(i=0;i<64;i++)
{
MagicPtr=PspCreateProcessNotifyRoutine+i*8;
NotifyAddr=*(PULONG64)(MagicPtr);
if(MmIsAddressValid((PVOID)NotifyAddr) && NotifyAddr!=0)
{
NotifyAddr=*(PULONG64)(NotifyAddr & 0xfffffffffffffff8);
DbgPrint("[CreateProcess]%llx",NotifyAddr);
}
}
}

代码整理: 进程

ULONG64 FindPspCreateProcessNotifyRoutine()
{
LONG OffsetAddr=0;
ULONG64 i=0,pCheckArea=0;
UNICODE_STRING unstrFunc;
//获得PsSetCreateProcessNotifyRoutine的地址
RtlInitUnicodeString(&unstrFunc, L"PsSetCreateProcessNotifyRoutine");
pCheckArea = (ULONG64)MmGetSystemRoutineAddress (&unstrFunc);
//获得PspSetCreateProcessNotifyRoutine的地址
memcpy(&OffsetAddr,(PUCHAR)pCheckArea+4,4);
pCheckArea=(pCheckArea+3)+5+OffsetAddr;
DbgPrint("PspSetCreateProcessNotifyRoutine: %llx",pCheckArea);
//获得PspCreateProcessNotifyRoutine的地址
for(i=pCheckArea;i<pCheckArea+0xff;i++)
{
if(*(PUCHAR)i==0x4c && *(PUCHAR)(i+1)==0x8d && *(PUCHAR)(i+2)==0x35) //lea r14,xxxx
{
LONG OffsetAddr=0;
memcpy(&OffsetAddr,(PUCHAR)(i+3),4);
return OffsetAddr+7+i;
}
}
return 0;
} void EnumCreateProcessNotify()
{
int i=0;
BOOLEAN b;
ULONG64 NotifyAddr=0,MagicPtr=0;
ULONG64 PspCreateProcessNotifyRoutine=FindPspCreateProcessNotifyRoutine();
DbgPrint("PspCreateProcessNotifyRoutine: %llx",PspCreateProcessNotifyRoutine);
if(!PspCreateProcessNotifyRoutine)
return;
for(i=0;i<64;i++)
{
MagicPtr=PspCreateProcessNotifyRoutine+i*8;
NotifyAddr=*(PULONG64)(MagicPtr);
if(MmIsAddressValid((PVOID)NotifyAddr) && NotifyAddr!=0)
{
NotifyAddr=*(PULONG64)(NotifyAddr & 0xfffffffffffffff8);
DbgPrint("[CreateProcess]%llx",NotifyAddr);
}
}
}

线程(包括创建线程回调测试代码):

void CreateThreadNotify1
(
IN HANDLE ProcessId,
IN HANDLE ThreadId,
IN BOOLEAN Create
)
{
DbgPrint("CreateThreadNotify1\n");
} void CreateThreadNotify2
(
IN HANDLE ProcessId,
IN HANDLE ThreadId,
IN BOOLEAN Create
)
{
DbgPrint("CreateThreadNotify2\n");
} void CreateThreadNotifyTest(BOOLEAN Remove)
{
if(!Remove)
{
PsSetCreateThreadNotifyRoutine(CreateThreadNotify1);
PsSetCreateThreadNotifyRoutine(CreateThreadNotify2);
}
else
{
PsRemoveCreateThreadNotifyRoutine(CreateThreadNotify1);
PsRemoveCreateThreadNotifyRoutine(CreateThreadNotify2);
}
} ULONG64 FindPspCreateThreadNotifyRoutine()
{
ULONG64 i=0,pCheckArea=0;
UNICODE_STRING unstrFunc;
RtlInitUnicodeString(&unstrFunc, L"PsSetCreateThreadNotifyRoutine");
pCheckArea = (ULONG64)MmGetSystemRoutineAddress (&unstrFunc);
DbgPrint("PsSetCreateThreadNotifyRoutine: %llx",pCheckArea);
for(i=pCheckArea;i<pCheckArea+0xff;i++)
{
if(*(PUCHAR)i==0x48 && *(PUCHAR)(i+1)==0x8d && *(PUCHAR)(i+2)==0x0d) //lea rcx,xxxx
{
LONG OffsetAddr=0;
memcpy(&OffsetAddr,(PUCHAR)(i+3),4);
return OffsetAddr+7+i;
}
}
return 0;
} void EnumCreateThreadNotify()
{
int i=0;
BOOLEAN b;
ULONG64 NotifyAddr=0,MagicPtr=0;
ULONG64 PspCreateThreadNotifyRoutine=FindPspCreateThreadNotifyRoutine();
DbgPrint("PspCreateThreadNotifyRoutine: %llx",PspCreateThreadNotifyRoutine);
if(!PspCreateThreadNotifyRoutine)
return;
for(i=0;i<64;i++)
{
MagicPtr=PspCreateThreadNotifyRoutine+i*8;
NotifyAddr=*(PULONG64)(MagicPtr);
if(MmIsAddressValid((PVOID)NotifyAddr) && NotifyAddr!=0)
{
NotifyAddr=*(PULONG64)(NotifyAddr & 0xfffffffffffffff8);
DbgPrint("[CreateThread]%llx",NotifyAddr); PsRemoveCreateThreadNotifyRoutine(NotifyAddr);
}
}
}

宋孖健,13

Win64 驱动内核编程-30.枚举与删除线程回调的更多相关文章

  1. Win64 驱动内核编程-33.枚举与删除对象回调

    转载:http://www.voidcn.com/article/p-wulgeluy-bao.html 枚举与删除对象回调 对象回调存储在对应对象结构体里,简单来说,就是存储在 ObjectType ...

  2. Win64 驱动内核编程-31.枚举与删除映像回调

    枚举与删除映像回调 映像回调可以拦截 RING3 和 RING0 的映像加载.某些游戏保护会用此来拦截黑名单中的驱动加载,比如 XUETR.WIN64AST 的驱动.同理,在反游戏保护的过程中,也可以 ...

  3. Win64 驱动内核编程-32.枚举与删除注册表回调

    枚举与删除注册表回调 注册表回调是一个监控注册表读写的回调,它的效果非常明显,一个回调能实现在SSDT 上 HOOK 十几个 API 的效果.部分游戏保护还会在注册表回调上做功夫,监控 service ...

  4. Win64 驱动内核编程-28.枚举消息钩子

    枚举消息钩子 简单粘贴点百度的解释,科普下消息钩子: 钩子是WINDOWS中消息处理机制的一个要点,通过安装各种钩子,应用程序能够设置相应的子例程来监视系统里的消息传递以及在这些消息到达目标窗口程序之 ...

  5. Win64 驱动内核编程-3.内核里使用内存

    内核里使用内存 内存使用,无非就是申请.复制.设置.释放.在 C 语言里,它们对应的函数是:malloc.memcpy.memset.free:在内核编程里,他们分别对应 ExAllocatePool ...

  6. Win64 驱动内核编程-8.内核里的其他常用

    内核里的其他常用 1.遍历链表.内核里有很多数据结构,但它们并不是孤立的,内核使用双向链表把它们像糖 葫芦一样给串了起来.所以遍历双向链表能获得很多重要的内核数据.举个简单的例子,驱 动对象 Driv ...

  7. Win64 驱动内核编程-7.内核里操作进程

    在内核里操作进程 在内核里操作进程,相信是很多对 WINDOWS 内核编程感兴趣的朋友第一个学习的知识点.但在这里,我要让大家失望了,在内核里操作进程没什么特别的,就标准方法而言,还是调用那几个和进程 ...

  8. Win64 驱动内核编程-2.基本框架(安装.通讯.HelloWorld)

    驱动安装,通讯,Hello World 开发驱动的简单流程是这样,开发驱动安装程序,开发驱动程序,然后安装程序(或者其他程序)通过通讯给驱动传命令,驱动接到之后进行解析并且执行,然后把执行结果返回. ...

  9. Win64 驱动内核编程-11.回调监控进线程句柄操作

    无HOOK监控进线程句柄操作 在 NT5 平台下,要监控进线程句柄的操作. 通常要挂钩三个API:NtOpenProcess.NtOpenThread.NtDuplicateObject.但是在 VI ...

随机推荐

  1. Go中定时器实现原理及源码解析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 本文使用的go的源码15.7,需要注意的是由于timer是1.14版本进行改版,但是1. ...

  2. python-链队列的实现

    7 class Node(object): 8 def __init__(self,data): 9 self.data = data 10 self.next = None 11 12 class ...

  3. pta 简单求和

    6-1 简单求和 (10 分)   本题要求实现一个函数,求给定的N个整数的和. 函数接口定义: int Sum ( int List[], int N ); 其中给定整数存放在数组List[]中,正 ...

  4. 微信小程序 | flex布局属性

    flex-direction 主轴方向 row: 横向 row-reverse: 横向倒序 column: 纵向 column-reverse: 纵向倒序; flex-wrap 元素排列换行 nowr ...

  5. MongoDB数据库的使用

    MongoDB是一个基于分布式 文件存储的NoSQL数据库,适合存储JSON风格文件的形式. 三元素:数据库.集合和文档. 文档:对应着关系数据库中的行,就是一个对象,由键值对构成,是json的扩展B ...

  6. PAT (Advanced Level) Practice 1006 Sign In and Sign Out (25 分) 凌宸1642

    PAT (Advanced Level) Practice 1006 Sign In and Sign Out (25 分) 凌宸1642 题目描述: At the beginning of ever ...

  7. CentOS7 搭建 K8S 环境

    前期准备 环境规划 K8S 与Docker兼容问题 k8s v1.18.0 => Docker v18.x k8s v1.19.0 => Docker v19.x 软件 版本 Linux操 ...

  8. rancher的ssl部署

    前言 因为我司有多套k8s环境,管理起来过于麻烦,需要一个统一的管理平台,又因为没有预留时间自己开发,经过选择后,使用rancher来进行多k8s环境的统一管理平台. 部署 1.在阿里云上申请免费的证 ...

  9. [hash-bfs]USACO 3.2 Magic Squares 魔板

    魔 板 魔板 魔板 题目描述 在成功地发明了魔方之后,拉比克先生发明了它的二维版本,称作魔板.这是一张有8个大小相同的格子的魔板: 1 2 3 4 8 7 6 5 我们知道魔板的每一个方格都有一种颜色 ...

  10. .NET 6 Preview 3 发布

    前言 2021/4/8 .NET 6 Preview 3 发布,这个版本的改进大多来自于底层,一起来看看都有什么新特性和改进吧. 库改进 新增值类型作为字典值时更快的处理方法 .NET 6 Previ ...