首页
论坛
专栏
课程

[原创]x64内核中的HOOK技术. 拦截进程,拦截线程,拦截模块(思路)

2018-2-5 23:39 10915

[原创]x64内核中的HOOK技术. 拦截进程,拦截线程,拦截模块(思路)

2018-2-5 23:39
10915

            x64内核中的HOOK技术. 拦截进程,拦截线程,拦截模块(思路)

一丶为什么讲解HOOK技术.

在32系统下, 例如我们要HOOK SSDT表,那么直接讲CR0的内存保护属性去掉. 直接讲表的地址修改即可.

但是在64位系统下,不可以这样操作了.

第一是因为 SSDT表加密了.  

第二是 SSDT表你就算解密了. 那么你的API的地址也会放的很远. SSDT表放不下4G以外的地址. 所以也不能放.

而现在.试想一下, 杀毒软件也要在内核中做点手脚. 如果不能HOOK了,它们该怎么发展那?

所以到了win7 (64位)系统下, 微软被玩怕了. 直接告诉你. 你想HOOK那个API,我帮你做. 你不用动手做. 而且我的还稳定.不会蓝屏.

所以x64下的HOOK都是很简单的

比如 xxx杀毒软件要监视进程的创建. 那么微软就说.我给你API调用,你提供回调. 当创建的时候我就通知你. 返回值也是你说了算.

这样我们就可以使用它的API了.  线程. 模块都是这样的.

二丶进程监控以及拦截HOOK

API:

  PsSetCreateProcessNotifyRoutineEx

函数原型

NTSTATUS
  PsSetCreateProcessNotifyRoutineEx(
    IN PCREATE_PROCESS_NOTIFY_ROUTINE_EX  NotifyRoutine,    //你提供的回调函数地址.
    IN BOOLEAN  Remove                        //是否删除回调. 如果为False则是注册回调,如果为TRUE则是删除回调.
    );

这个函数的作用就是 当进程创建的时候会通知你.

参数一回调函数原型

VOID
  CreateProcessNotifyEx(
    __inout PEPROCESS  Process,                //进程的EPROCESS会提供给你.
    __in HANDLE  ProcessId,                  //进程ID会提供给你.
    __in_opt PPS_CREATE_NOTIFY_INFO  CreateInfo      //进程信息的额外信息会提供给你. 注意参数是可操作的.也就是说可能为NULL
    );

进程额外信息结构体

typedef struct _PS_CREATE_NOTIFY_INFO {
  __in SIZE_T  Size;                    //大小.
  union {
    __in ULONG  Flags;
    struct {
      __in ULONG  FileOpenNameAvailable : 1;
      __in ULONG  Reserved : 31;
    };
  };
  __in HANDLE  ParentProcessId;            //父进程ID
  __in CLIENT_ID  CreatingThreadId;      
  __inout struct _FILE_OBJECT  *FileObject;     //文件对象
  __in PCUNICODE_STRING  ImageFileName;       //进程路径
  __in_opt PCUNICODE_STRING  CommandLine;      //命令行参数
  __inout NTSTATUS  CreationStatus;          //返回状态.
} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;

我们如果对返回状态进行操作. 那么如果你返回值给失败,那么进程就不会创建. 也就实现了我们阻止进程的创建了.

内核 代码实际操作 阻止calc计算器的创建.

initHook函数代码.

void InstallHook()
{  
  NTSTATUS status;                                  //定义NT状态                                                    
  status = PsSetCreateProcessNotifyRoutineEx(CreateProcessNotifyEx, FALSE);//注册回调
  if (NT_SUCCESS(status))                              //判断状态是否成功.
    dprintf("[Hello] PsSetCreateProcessNotifyRoutineEx CreateProcessNotifyEx=%pOK\r\n", CreateProcessNotifyEx);
  else
    dprintf("[Hello] PsSetCreateProcessNotifyRoutineEx status=%d\r\n", status);
}

回调函数代码.

VOID CreateProcessNotifyEx( __inout PEPROCESS  Process,
                           __in HANDLE  ProcessId,
                           __in_opt PPS_CREATE_NOTIFY_INFO  CreateInfo)
{

  UNICODE_STRING ustrCalc;                              //定义内核函数字符串
  RtlInitUnicodeString(&ustrCalc, L"\\??\\C:\\Windows\\system32\\calc.exe");//初始化计算器名字. 注意,这个要带上符号连接.  
  if (CreateInfo != NULL)                               //参数有可能为NULL
  {
    dprintf("[Hello] Create Porcess pid=%d ImageFileName=%wZ\r\n", 
            ProcessId, 
            CreateInfo->ImageFileName);

    if (RtlCompareUnicodeString(CreateInfo->ImageFileName, &ustrCalc, TRUE) == 0) //比较字符串.如果成功
    {
      //阻止创建
      CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL;                           //标志给失败,则计算器不能运行.
    }
  }
  else
    dprintf("[Hello] Exit Porcess pid=%d\r\n", ProcessId);
}

回调卸载函数.

PsSetCreateProcessNotifyRoutineEx(CreateProcessNotifyEx, TRUE);        //卸载回调

代码图片:


其中UnInstallHook则是调用卸载回调的函数.

驱动入口点调用initHook即可.

拦截图片:

编译好我们的驱动.去x64加载. 打开计算机则会被拦截.


原理:

  如果想知道原理,那么就要看Wrk源码.而在Wrk中,并没有这个API,这个API是64位才有的.但是在32下有一个,有一个不带EX版本的.我们可以参考一下.

 


通过源码可以发现.是操作数组.且这个数组里面是我们填写的回调.操作系统会依次调用回调.

跟随数组查看.


跟随查看,发现是个定长数组.里面只有八项.但是在32位,没人会使用这个API.所以8项正好,但是在64位系统下,数组变为了64

也就是说.我们可以通过找到数组首地址,进行HOOK里面的回调.那么我们的就会执行.当然我们也可以自己注册.

三丶线程监控以及拦截HOOK

线程拦截和进程拦截相似.但是在64位下,微软提供的HOOK函数没有这么强大.

并没有带有EX的.

所以我们只能用原来的.

线程监控API

  监控:    PsSetCreateThreadNotifyRoutine

  取消监控:  PsRemoveCreateThreadNotifyRoutine

监控原型:

NTSTATUS
  PsSetCreateThreadNotifyRoutine(
    IN PCREATE_THREAD_NOTIFY_ROUTINE  NotifyRoutine  //回调函数地址.当线程创建完毕,但还没运行的时候会调用你的回调.
    );

卸载函数原型:

NTSTATUS
  PsRemoveCreateThreadNotifyRoutine(
    IN PCREATE_THREAD_NOTIFY_ROUTINE  NotifyRoutine  //回调地址,你刚才注册的回调会取消.
    );

本质也是在数组里面注册函数地址.

这两个函数只能看.但是不能进行操作. 但是很多时候看看就已经可以了.

线程回调原型:

VOID
(*PCREATE_THREAD_NOTIFY_ROUTINE) (
    IN HANDLE  ProcessId,          //进程ID
    IN HANDLE  ThreadId,           //线程ID
    IN BOOLEAN  Create            
    );

可以看到,线程HOOK后,信息很少,就一个进程ID,一个线程ID. 还有一个创建标志.没有什么了.现在我们要进行HOOK

HOOK思路:

  1.通过进程ID,找到EPROCESS 对象

  2.通过线程ID,找到ETHREAD   对象

  3.通过EPROCESS对象.获得进程路径.

  4.通过进程路径.找到对应进程名.

  5..判断进程名是否相同.

    相同: 通过ETHREAD + 偏移,找到线程回调函数的地址. 并修改回调地址的内容为ret.

    不相同:  不相同退出.

在上面HOOK思路中,第五不很重要.ETHREAD + 偏移. 那么找到是那个成员?

那么我们要熟悉ETHREAD中那个成员重要.


通过分析ETHREAD,可以得出,在偏移410的位置,存放这ring3下线程回调函数的地址. 那么我们可以操作这个偏移,使其里面变为ret,变相了拦截了线程的创建.

实战代码:

  initHook函数

void InstallHook()
{
  NTSTATUS status;
  status = PsSetCreateThreadNotifyRoutine(CreateThreadNotify);      //注册线程回调
  if (NT_SUCCESS(status))
    dprintf("[Hello] PsSetCreateThreadNotifyRoutine CreateThreadNotify=%p\r\n", CreateProcessNotifyEx);
  else
    dprintf("[Hello] PsSetCreateThreadNotifyRoutine status=%d\r\n", status);
}

  回调函数中的代码

VOID CreateThreadNotify(
                                  IN HANDLE  ProcessId,
                                  IN HANDLE  ThreadId,
                                  IN BOOLEAN  Create)
{
  PEPROCESS Process = NULL;
  PETHREAD  Thread = NULL;
  UCHAR *pszImageName = NULL;
  NTSTATUS status;
  UCHAR *pWin32Address = NULL;

  status = PsLookupProcessByProcessId(ProcessId, &Process);  //1.通过进程ID,获取EPROCESS
  if (!NT_SUCCESS(status))
    return;

  status = PsLookupThreadByThreadId(ThreadId, &Thread);    //2.通过线程ID,获取ETHREAD

  pszImageName = PsGetProcessImageFileName(Process);      //3.通过EPROCESS获取进程名


  if (Create) 
  {
    dprintf("[Hello] Create Thread pid=%d tid=%d ImageName=%s\r\n", ProcessId, ThreadId, pszImageName);
    if (strstr(pszImageName, "calc") != NULL)          //4.判断进程名是否是计算器
    {
      //KdBreakPoint();

      //修改回调函数代码
      pWin32Address = *(UCHAR**)((UCHAR*)Thread + 0x410);   //5.是的话.找到回调函数地址.并改为C3
      //KeAttachProcess();
      if (MmIsAddressValid(pWin32Address))
      {
        KdBreakPoint();
        //*pWin32Address = 0xC3;                 //修改为C3,但是注意,你修改的是否内存保护属性需要去掉.但是在64位下,不允许使用内联汇编了.不过你可以写二进制进行更改.或者汇编生成obj,驱动来使用
                                        //这里直接手动更改为C3.
      }
      //KeUnstackDetachProcess();
    }
  }
  else
    dprintf("[Hello] Exit Thread pid=%d tid=%d\r\n", ProcessId, ThreadId);


  if( Process )
    ObDereferenceObject (Process);              //引用计数--
  if( Thread )
    ObDereferenceObject (Thread);

}

PS: 

上面通过进程ID获取EPROCESS等等的API,都是微软未公开的.但是导出了. 我们只需要声明一下即可.但是既然是未公开那么就要看微软怎么使用.
微软使用之后,会对其引用计数--,所以我们也要这样做.

 内存中都改为C3,则线程都不能创建了.

 四丶模块拦截,以及阻止思路.

模块拦截以及HOOK也是和上面一样,提供回调即可.

但莫模块是给的ImageBae,也就是模块基址. 所以我们只需要解析PE找到OEP,把OEP代码改成ret即可.

API:

监控:

  PsSetLoadImageNotifyRoutine

取消监控:

  PsRemoveLoadImageNotifyRoutine

函数原型:

NTSTATUS
  PsSetLoadImageNotifyRoutine(
    IN PLOAD_IMAGE_NOTIFY_ROUTINE  NotifyRoutine
    );

回调函数原型:

VOID
(*PLOAD_IMAGE_NOTIFY_ROUTINE) (
    IN PUNICODE_STRING  FullImageName,
    IN HANDLE  ProcessId, // where image is mapped
    IN PIMAGE_INFO  ImageInfo
    );


对于模块,并没有写实现代码.但是我觉着,思路很重要. 仿照前面两个例子.自己扩展一下思路.自己实现也可以.


快过年了.在这里先给大家拜个早年. 学习逆向方面的技术,第一是自己的兴趣.第二是自己的自学能力. 第三就是找一个可靠的师兄. 所谓,前人栽树,后人乘凉.

如果你有兴趣,也有自学能力, 但是还想找"师父"精进, 那么我推荐下自己的师承   武汉科锐x86逆向. 30期招聘了. 注意,我不是发"广告"  而是真心的推荐科锐.

来到科锐学习,已经一年了. 相比一年前的自己,确实学有所得. 谢谢.

了解科锐请点击:    科锐看雪帖子    科锐官网

如果发帖不对,请管理大大删帖. 诚心感谢.




[公告]LV6级以上的看雪会员可以免费获得《2019安全开发者峰会》门票一张!!

最后于 2019-2-1 17:09 被admin编辑 ,原因:
最新回复 (47)
张新琪 1 2018-2-5 23:44
2
0
大佬给点评论. 
张新琪 1 2018-2-5 23:52
3
0
那里写的不对,请告诉我.分享技术.是乐趣.  别人学到了.自己也在总结的过程中学到了.  谢谢大家.
KevinsBobo 4 2018-2-6 00:06
4
0
64位内核资料网上比较少,感谢楼主的分享。HOOK是很常见的对抗手法,0环的HOOK乃兵家必争之地。64位内核提供了HOOK的API但也有了局限性,第一个HOOK的就是老大啊。楼主有空带我们分析分析这些API的实现原理,找到保存HOOK的函数指针表的位置。
turtledove 2 2018-2-6 00:18
5
0
x64下Hook  SSDT可以通过KeBugCheckEx中转实现的
张新琪 1 2018-2-6 00:31
6
0
JieLiuHouZi x64下Hook SSDT可以通过KeBugCheckEx中转实现的[em_2]
在寄存器msr  中,在64位下,通过查看cpu  文档可以得知地址是在c0000082  中,使用汇编指令  rdmsr  c0000082  可以得出地址,对它u下就能得到  中专api  了,但是对他加密了
叁毛 1 2018-2-6 06:33
7
0
还是认真逆向下win  defender吧。
XiaoyDelog 2018-2-6 09:30
8
0
原理不难,难的是业务.
hzqst 3 2018-2-6 10:08
9
0
拦截线程和拦截模块呢?
FaEry 5 2018-2-6 10:11
10
0
我只看到了拦截进程创建,后面两个被吃了?
niuzuoquan 2018-2-6 10:32
11
0
mark
张新琪 1 2018-2-6 14:16
12
0
叁毛 还是认真逆向下win defender吧。
谢谢提醒.
张新琪 1 2018-2-6 14:17
13
0
FaEry 我只看到了拦截进程创建,后面两个被吃了?
后面一个被吃了,  前面两个没被吃    后面一个是思路.
张新琪 1 2018-2-6 14:17
14
0
hzqst 拦截线程和拦截模块呢?
有写,  模块没有写,模块提供了思路
轩辕之风 1 2018-2-6 15:22
15
0
好久没搞这些,依稀记得64位的SSDT表不是加密吧
张新琪 1 2018-2-6 15:25
16
0
轩辕之风 好久没搞这些,依稀记得64位的SSDT表不是加密吧
右边移动4位. 
轩辕之风 1 2018-2-6 16:39
17
0
张新琪 轩辕之风 好久没搞这些,依稀记得64位的SSDT表不是加密吧 右边移动4位. 
好像是这样,不过这也不是加密啊
Bho 2018-2-6 17:31
18
0
我就知道是科锐的
张新琪 1 2018-2-6 17:53
19
0
Bho [em_19]我就知道是科锐的
咋,
张新琪 1 2018-2-6 17:54
20
0
轩辕之风 好像是这样,不过这也不是加密啊[em_13]
是吧,谢谢哈.
sylingyy 2018-2-6 18:02
21
0
试一下你就会发现拦截模块修改OEP  并没有卵用!
panti 2018-2-7 07:39
22
0
感谢知识分享
Tennn 5 2018-2-7 10:12
23
0
回调这也算啊  ,,,, 
CmRegisterCallback
CmRegisterCallbackEx
ExRegisterCallback
ObRegisterCallbacks
PsSetCreateProcessNotifyRoutine
PsSetCreateProcessNotifyRoutineEx
PsSetCreateProcessNotifyRoutineEx2
PsSetCreateThreadNotifyRoutine
PsSetCreateThreadNotifyRoutineEx
PsSetLoadImageNotifyRoutine
ielts 2018-2-7 17:20
24
0
感谢知识分享
wem 2018-2-8 16:34
25
0
谢谢分享
苍穹之下 2018-2-8 21:11
26
0
谢谢分享。看不懂。但是感觉很厉害
fior 2018-2-14 11:06
27
0
可以通过改变CreateInfo的信息改变启动流程吗,还是说里面大部分参数只是只读的
狂神疯子 2018-2-14 18:22
28
0
谢谢分享。
冰雄 2018-2-14 20:15
29
0
x64照样可以hook  ssdt
wx_韩禹 2018-2-17 19:25
30
0
无法拦截nt层啊
CkDebug 2018-2-18 17:04
31
0
琪哥666,已经膜拜完毕..
流哥 2018-2-24 09:55
32
0
楼主可以去看看  m5home
moods 2018-2-24 14:36
33
0
xp下怎么拦截进程创建呢,没有ex函数
Archar 2018-2-24 16:17
34
0
不全啊
张新琪 1 2018-2-25 20:29
35
0
moods xp下怎么拦截进程创建呢,没有ex函数
HOOK
张新琪 1 2018-2-25 20:30
36
0
Archar [em_4]不全啊
弄了一部分.初学内核.
张新琪 1 2018-2-25 20:31
37
0
fior 可以通过改变CreateInfo的信息改变启动流程吗,还是说里面大部分参数只是只读的[em_31]
基本上就是给你控制了,  在创建还没创建玩,内核会调用这个.  所以由你进行操控.
MaMy 2018-2-26 14:16
38
0
拦截模块加载,修改  ret是真没用,,因为那个地方不是最早的..
依稀记得这个应该是无解的,只能X掉进程
心有猛虎 2018-3-15 15:52
39
0
感谢分享,学习。MARK。
BiaoGeKX 2018-3-28 21:01
40
0
既然提供了api,不用驱动可以直接用嘛
只想睡个好觉 2018-10-3 12:09
41
0
我在PsSetLoadImageNotifyRoutine中使用ZwReadFile会出错,是为什么呢??
张新琪 1 2018-10-4 17:00
42
0
只想睡个好觉 我在PsSetLoadImageNotifyRoutine中使用ZwReadFile会出错,是为什么呢??
没有深入内核了,你的问题无法回答.抱歉. 你看看是不是参数错误.或者别的问题.
熊伟Sys 2018-10-5 11:38
43
0
高端技术,值得学习!!
只想睡个好觉 2018-10-26 16:53
44
0
其实这个不是HOOK吧。只是微软提供的通知与回调的接口吧
fengyunabc 1 2018-10-26 17:11
45
0
感谢分享!
jackertom 2019-3-30 20:40
46
0
模块回调  oep写入ret局性很强
花弄影h 2019-4-1 08:43
47
0
mark
慢热型 15小时前
48
0
mark
游客
登录 | 注册 方可回帖
返回