原文链接:http://www.blogfshare.com/ssdthook-hide-protect.html

原文作者:AloneMonkey

SSDT Hook实现简单的进程隐藏和保护

AloneMonkey 2014年7月21日 

有前面对SSDT的了解之后,如果还有不是很了解的同学,请参考SSDT详解及Win32 API到系统服务描述符表调用的完整过程。已经讲的很详细了。

一、SSDT HOOK原理

其实 SSDT Hook 的原理是很简单,从前篇的分析,我们可以知道在 SSDT 这个数组中呢,保存了系统服务的地址,比如对于 Ring0 下的 NtQuerySystemInformation 这个系统服务的地址,就保存在 KeServiceDescriptorTable[105h] 中,既然是 Hook 的话,我们就可以将这个 KeServiceDescriptorTable[105h] 下保存的服务地址替换掉,将我们自己的 Hook 处理函数的地址来替换掉原来的地址,这样当每次调用 KeServiceDescriptorTable[105h]时就会调用我们自己的这个 Hook 处理函数(MyHookNtQuerySystemInformation )了。

这样的话,每次系统调用 NtQuerySystemInformation 这个系统服务时,实质上调用的就是 MyHookNtQuerySystemInformation 了,而我们为了保证系统的稳定性(至少不让其崩溃),一般会在 MyHookNtQuerySystemInformation 中调用系统中原来的服务,也就是NtQuerySystemInformation。

这里借用网上一张图让大家有更加清晰的认识:

其实Hook的原理差不多都是这样的,替换当前函数地址,修改成自定义的函数地址,然后再调用回原来函数的地址。

二、常用的应用层获取进程的方法

①使用ToolHelp遍历获取到所有进程。

hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

②使用 PSAPI 下的 EnumProcesses 获取到所有进程的 PID,然后提升进程权限为 SE_DEBUG 权限,

再调用 OpenProcess 即可打开进程,从而获取到进程的基本信息。

③使用Ntdll.dll 中的未文档化的 API – NtQuerySystemInformation,而Windows 任务管理器就是通过这种方式来获取到所有的进程信息的。

所以隐藏进程最简单的办法就是Hook 掉 NtQuerySystemInformation 函数 。

至于进程保护的话,第一种则是该进程自行终止,第二种情况则是该进程被其他进程给杀掉,第一种情况基本上对于窗口应用程序来说,一般都是用 户点击了右上角的 x 按钮,然后产生 WM_CLOSE 消息,最后由窗口过程退出进程,这种情况下,我们应该是需要允许退出的,也就是进程是可以正常退出的。而第二种情况的话,就是进程被别的进程杀掉,比如在 任务管理器中就可以杀掉绝大部分的应用程序进程,而这里的进程保护就是要实现进程不能够被任务管理器或者其他的进程管理工具杀掉。在 Ring3 中,由一个进程结束其他进程,先会调用API OpenProcess这个我已经在前篇说的很详细了,然后再去调用Kernel32.dll 中的 TerminateProcess,如果追溯这个 TerminateProcess,可以发现,其调用了 Ntdll.dll 中的 NtTerminateProcess API,然后再追溯下去就可以到 ntoskrnl.exe 中的 ZwTerminateProcess 和系统服务NtTerminateProcess 了。所以这里我们选择Hook掉NtTerminateProcess 函数

三、代码说明

口说还不如来点实际的代码实际。

首先我们来定义SSDT需要用到的结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct _KSYSTEM_SERVICE_TABLE
{
    PULONG  ServiceTableBase;                   // SSDT (System Service Dispatch Table)的基地址
    PULONG  ServiceCounterTableBase;            // 用于 checked builds, 包含 SSDT 中每个服务被调用的次数
    ULONG   NumberOfService;                    // 服务函数的个数, NumberOfService * 4 就是整个地址表的大小
    ULONG   ParamTableBase;                     // SSPT(System Service Parameter Table)的基地址

} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;

typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
    KSYSTEM_SERVICE_TABLE   ntoskrnl;           // ntoskrnl.exe 的服务函数
    KSYSTEM_SERVICE_TABLE   win32k;             // win32k.sys 的服务函数(GDI32.dll/User32.dll 的内核支持)
    KSYSTEM_SERVICE_TABLE   notUsed1;
    KSYSTEM_SERVICE_TABLE   notUsed2;

} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;

//导出由 ntoskrnl.exe 所导出的 SSDT
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;

在 Hook 任意的系统服务之前,将 SSDT 中的所有系统服务的地址保存或者说是备份到 ULONG 数组中即可。而后在解除 Hook 时,我们就可以从这个 ULONG 数组中取出原有系统服务的地址。

1
2
3
4
5
6
//定义 SSDT(系统服务描述表) 中服务个数的最大数目
//这里定义为 1024 个,实际上在win7 32是 0x0191个
#define MAX_SYSTEM_SERVICE_NUMBER 1024
 
//用来保存 SSDT 中所有的旧的服务函数的地址
ULONG oldSysServiceAddr[MAX_SYSTEM_SERVICE_NUMBER];

我们还需要备份、安装、解除的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//=====================================================================================//
//Name: VOID BackupSysServicesTable()                                                  //
//                                                                                     //
//Descripion: 用来备份 SSDT 中原有服务的地址,因为我们在解除 Hook 时需要还原 SSDT 中原有地址     //
//                                                                                     //
//=====================================================================================//
VOID BackupSysServicesTable()
{
    ULONG i;

for(i = 0; (i < KeServiceDescriptorTable->ntoskrnl.NumberOfService) && (i < MAX_SYSTEM_SERVICE_NUMBER); i++)
    {
        oldSysServiceAddr[i] = KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[i];
    }
}

//=====================================================================================//
//Name: NTSTATUS InstallSysServiceHook()                                               //
//                                                                                     //
//Descripion: 实现 Hook 的安装,主要是在 SSDT 中用 newService 来替换掉 oldService              //
//                                                                                     //
//=====================================================================================//
NTSTATUS InstallSysServiceHook(ULONG oldService, ULONG newService)
{
    ULONG uOldAttr = 0;

EnableWriteProtect(&uOldAttr);

SYSCALL_FUNCTION(oldService) = newService;

DisableWriteProtect(uOldAttr);

return STATUS_SUCCESS;
}

//=====================================================================================//
//Name: NTSTATUS UnInstallSysServiceHook()                                             //
//                                                                                     //
//Descripion: 实现 Hook 的解除,主要是在 SSDT 中用备份下的服务地址来替换掉 oldService          //
//                                                                                     //
//=====================================================================================//
NTSTATUS UnInstallSysServiceHook(ULONG oldService)
{
    ULONG uOldAttr = 0;

EnableWriteProtect(&uOldAttr);

SYSCALL_FUNCTION(oldService) = oldSysServiceAddr[SYSCALL_INDEX(oldService)];

DisableWriteProtect(uOldAttr);

return STATUS_SUCCESS;
}

然后,SSDT 在内存中是具有只读属性保护的,如果你想修改 SSDT 中的内容,你必须先要解除只读属性,也就是要赋予 SSDT 所在的这块内存具有可写属性才行。而这个属性保存在寄存器CR0的第17位,index为16.如果为1表示只读,置为0才可写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//=====================================================================================//
//Name: VOID DisableWriteProtect()                                                     //
//                                                                                     //
//Descripion: 用来去掉内存的可写属性,从而实现内存只读                                         //
//                                                                                     //
//=====================================================================================//
VOID DisableWriteProtect(ULONG oldAttr)
{
    _asm
    {
            mov eax, oldAttr
            mov cr0, eax
            sti;
    }
}

//=====================================================================================//
//Name: VOID EnableWriteProtect()                                                      //
//                                                                                     //
//Descripion: 用来去掉内存的只读保护,从而实现可以写内存                                      //
//                                                                                     //
//=====================================================================================//
VOID EnableWriteProtect(PULONG pOldAttr)
{
    ULONG uAttr;

_asm
    {
        cli;
        mov  eax, cr0;
        mov  uAttr, eax;
        and  eax, 0FFFEFFFFh; // CR0 16 BIT = 0
        mov  cr0, eax;
    };

//保存原有的 CRO 属性
    *pOldAttr = uAttr;
}

注意到上面有两个很重要的宏,即 SYSCALL_FUNCTION 和 SYSCALL_INDEX 宏。

1
2
3
4
5
6
//根据 Zw_ServiceFunction 获取 Zw_ServiceFunction 在 SSDT 中所对应的服务的索引号
#define SYSCALL_INDEX(ServiceFunction) (*(PULONG)((PUCHAR)ServiceFunction + 1))

//根据 Zw_ServiceFunction 来获得服务在 SSDT 中的索引号,
//然后再通过该索引号来获取 Nt_ServiceFunction的地址
#define SYSCALL_FUNCTION(ServiceFunction) KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[SYSCALL_INDEX(ServiceFunction)]

上面的事情做完后, 实现进程隐藏只需要Hook NtQuerySystemInformation函数,并实现我们自定义的函数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
NTSTATUS HookNtQuerySystemInformation (
    __in SYSTEM_INFORMATION_CLASS SystemInformationClass,
    __out_bcount_opt(SystemInformationLength) PVOID SystemInformation,
    __in ULONG SystemInformationLength,
    __out_opt PULONG ReturnLength
    )
{
    NTSTATUS rtStatus;

pOldNtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)oldSysServiceAddr[SYSCALL_INDEX(ZwQuerySystemInformation)];

//执行原来的函数
    rtStatus = pOldNtQuerySystemInformation(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
    if(NT_SUCCESS(rtStatus))
    {
        //如果查询的是进程信息
        if(SystemProcessInformation == SystemInformationClass)
        {
            PSYSTEM_PROCESS_INFORMATION pPrevProcessInfo = NULL;
            PSYSTEM_PROCESS_INFORMATION pCurrProcessInfo = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;

while(pCurrProcessInfo != NULL)
            {
                //获取当前遍历的 SYSTEM_PROCESS_INFORMATION 节点的进程名称和进程 ID
                ULONG uPID = (ULONG)pCurrProcessInfo->UniqueProcessId;
                UNICODE_STRING strTmpProcessName = pCurrProcessInfo->ImageName;

//判断当前遍历的这个进程是否为需要隐藏的进程
                if(ValidateProcessNeedHide(uPID) != -1)
                {
                    if(pPrevProcessInfo)
                    {
                        if(pCurrProcessInfo->NextEntryOffset)
                        {
                            //将当前这个进程(即要隐藏的进程)从 SystemInformation 中摘除(更改链表偏移指针实现)
                            pPrevProcessInfo->NextEntryOffset += pCurrProcessInfo->NextEntryOffset;
                        }
                        else
                        {
                            //说明当前要隐藏的这个进程是进程链表中的最后一个
                            pPrevProcessInfo->NextEntryOffset = 0;
                        }
                    }
                    else
                    {
                        //第一个遍历到得进程就是需要隐藏的进程
                        if(pCurrProcessInfo->NextEntryOffset)
                        {
                            (PCHAR)SystemInformation += pCurrProcessInfo->NextEntryOffset;
                        }
                        else
                        {
                            SystemInformation = NULL;
                        }
                    }
                }

//遍历下一个 SYSTEM_PROCESS_INFORMATION 节点
                pPrevProcessInfo = pCurrProcessInfo;

//遍历结束
                if(pCurrProcessInfo->NextEntryOffset)
                {
                    pCurrProcessInfo = (PSYSTEM_PROCESS_INFORMATION)(((PCHAR)pCurrProcessInfo) + pCurrProcessInfo->NextEntryOffset);
                }
                else
                {
                    pCurrProcessInfo = NULL;
                }
            }
        }
    }
    return rtStatus;
}

上面我们看到是先调用原来的NtQuerySystemInformation得到返回结果,然后根据我们需要隐藏的进程id,遍历返回结果的进程链表,从链表中摘除该节点,然后在返回给应用层。

从下面的代码中,我们看到在安装 Hook 和解除 Hook 时参数传递进去的是
ZwQuerySystemInformation,这样很有可能会让很多朋友认为我们在 Ring0 下的 Hook 的是
ZwQuerySystemInformation,如果你这样认为的话,那就大错特错了,我们这里传入
ZwQuerySystemInformation ,是因为我们需要调用
SYS_INDEX(ZwQuerySystemInformation)来获得 NtQuerySystemInformation 在 SSDT
中的地址所在的索引号,然后我们根据这个索引号来 Hook
NtQuerySystemInformation,所以我们Hook的是NtQuerySystemInformation!!!

InstallSysServiceHook((ULONG)ZwQuerySystemInformation, (ULONG)HookNtQuerySystemInformation);

进程保护呢其实也是比较简单的,因为从上面一层的调用会传递一个进程句柄下来,而后我们可以根据这个进程句柄来获得进程的
EPROCESS 对象(进程位于执行体层得对象),通过这个 EPROCESS 对象,我们就可以获得这个请求被结束的进程的 PID,我们再判断这个
PID 是否是我们已经保护了的 PID,如果是的话,直接返回一个请求被拒绝即可,而如果这个 PID 未被保护,自然我们就交给原来的
NtTerminateProcess 处理即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
NTSTATUS HookNtTerminateProcess(
    __in_opt HANDLE ProcessHandle,
    __in NTSTATUS ExitStatus
    )
{
    ULONG uPID;
    NTSTATUS rtStatus;
    PCHAR pStrProcName;
    PEPROCESS pEProcess;
    ANSI_STRING strProcName;

//通过进程句柄来获得该进程所对应的 FileObject 对象,由于这里是进程对象,自然获得的是 EPROCESS 对象
    rtStatus = ObReferenceObjectByHandle(ProcessHandle, FILE_READ_DATA, NULL, KernelMode, &pEProcess, NULL);
    if(!NT_SUCCESS(rtStatus))
    {
        return rtStatus;
    }

//保存 SSDT 中原来的 NtTerminateProcess 地址
    pOldNtTerminateProcess = (NTTERMINATEPROCESS)oldSysServiceAddr[SYSCALL_INDEX(ZwTerminateProcess)];

//通过该函数可以获取到进程名称和进程 ID,该函数在内核中实质是导出的(在 WRK 中可以看到)
    //但是 ntddk.h 中并没有到处,所以需要自己声明才能使用
    uPID = (ULONG)PsGetProcessId(pEProcess);
    pStrProcName = (PCHAR)PsGetProcessImageFileName(pEProcess);

//通过进程名来初始化一个 ASCII 字符串
    RtlInitAnsiString(&strProcName, pStrProcName);

if(ValidateProcessNeedProtect(uPID) != -1)
    {
        //确保调用者进程能够结束(这里主要是指 taskmgr.exe)
        if(uPID != (ULONG)PsGetProcessId(PsGetCurrentProcess()))
        {
            //如果该进程是所保护的的进程的话,则返回权限不够的异常即可
            return STATUS_ACCESS_DENIED;
        }
    }

//对于非保护的进程可以直接调用原来 SSDT 中的 NtTerminateProcess 来结束进程
    rtStatus = pOldNtTerminateProcess(ProcessHandle, ExitStatus);

return rtStatus;
}

四、效果演示

隐藏进程的效果就不演示了,反正隐藏之后在任务管理器就找不到你隐藏的进程了。

如果进程被保护了的话,如果你从任务管理器结束该进程,则会返回:

本文链接:http://www.blogfshare.com/ssdthook-hide-protect.html

转载提示:除非注明,AloneMonkey的文章均为原创,转载请以链接形式注明作者和出处。谢谢合作!

SSDT Hook实现简单的进程隐藏和保护【转载】的更多相关文章

  1. 进程隐藏与进程保护(SSDT Hook 实现)(二)

    文章目录:                   1. 引子 – Demo 实现效果: 2. 进程隐藏与进程保护概念: 3. SSDT Hook 框架搭建: 4. Ring0 实现进程隐藏: 5. Ri ...

  2. linux进程隐藏手段及对抗方法

    1.命令替换 实现方法 替换系统中常见的进程查看工具(比如ps.top.lsof)的二进制程序 对抗方法 使用stat命令查看文件状态并且使用md5sum命令查看文件hash,从干净的系统上拷贝这些工 ...

  3. HOOK技术之SSDT hook(x86/x64)

    x86 SSDT Hook 32位下进行SSDT Hook比较简单,通过修改SSDT表中需要hook的系统服务为自己的函数,在自己的函数中进行过滤判断达到hook的目的. 获取KeServiceDes ...

  4. 进程隐藏与进程保护(SSDT Hook 实现)(三)

    文章目录: 1. 引子: 2. 获取当前系统下所有进程: 3. 服务管理(安装,启动,停止,卸载): 4. 应用程序和内核程序通信: 5. 小结: 1. 引子: 关于这个 SSDT Hook 实现进程 ...

  5. 进程隐藏与进程保护(SSDT Hook 实现)(一)

    读了这篇文章终于明白大致怎么回事了 文章目录:                   1. 引子 – Hook 技术: 2. SSDT 简介: 3. 应用层调用 Win32 API 的完整执行流程: 4 ...

  6. 通过SSDT HOOK实现进程保护和进程隐藏

    ---恢复内容开始--- 首先,我要说一件很重要的事,本人文采不好,如果哪里说的尴尬了,那你就尴尬着听吧...... SSDT HOOK最初貌似源于Rookit,但是Rookit之前有没有其他病毒使用 ...

  7. SSDT Hook实现内核级的进程保护

    目录 SSDT Hook效果图 SSDT简介 SSDT结构 SSDT HOOK原理 Hook前准备 如何获得SSDT中函数的地址呢 SSDT Hook流程 SSDT Hook实现进程保护 Ring3与 ...

  8. SSDT Hook结构

    目录 SSDT Hook效果图 SSDT简介 SSDT结构 SSDT HOOK原理 Hook前准备 如何获得SSDT中函数的地址呢 SSDT Hook流程 SSDT Hook实现进程保护 Ring3与 ...

  9. Windows2003 内核级进程隐藏、侦测技术

    论文关键字: 内核 拦截 活动进程链表 系统服务派遣表 线程调度链 驱动程序简介    论文摘要:信息对抗是目前计算机发展的一个重要的方向,为了更好的防御,必须去深入的了解敌人进攻的招式.信息对抗促使 ...

随机推荐

  1. Mac无法找到摄像头问题解决

    facetime显示“未检测到摄像头”之类的,重启后可能摄像头有工作正常了,摄像头不稳定 重置 NVRAM后恢复正常,据说机器卡的时候,此法也可以使用. https://support.apple.c ...

  2. Android学习第八弹之改变状态栏的颜色使其与APP风格一体化

    公众号:smart_android 作者:耿广龙|loonggg 点击"阅读原文",可查看更多内容和干货 导语:沉浸式状态栏,改变状态栏的颜色使之与APP风格一体化是不是感觉很漂亮 ...

  3. Git.Framework 框架随手记--ORM新增操作

    本篇主要记录具体如何新增数据,废话不多说,开始进入正文. 一. 生成工程结构 上一篇已经说到了如何生成工程结构,这里在累述一次. 1. 新建项目总体结构 使用VS新建项目结构,分层结构可以随意.我们使 ...

  4. [USACO2006][poj3182]The Grove(巧妙的BFS)

    题目:http://poj.org/problem?id=3182 题意:一个棋盘中间有一个联通块,给你一个起点让你从起点开始绕联通块外围一圈并回到起点,求最小步数. 分析: 首先根据数据的范围比较小 ...

  5. javascript继承(二)—创建对象的三种模式

    一.工厂模式 function createPerson(name,age){ var o = {}; o.name = name; o.age = age; o.sayHi = function() ...

  6. MongoDB 客户端 MongoVue

    直接上图片,图片是按顺序来的 软件下载地址(Windows下的MongoDB客户端MongoVUE 这是最后一个全功能的不收费的版本): http://pan.baidu.com/s/1skYIEq5

  7. PHP乱码问题,UTF-8(乱码)

    一.HTML页面转UTF-8编码问题 1.在head后,title前加入一行: <meta http-equiv='Content-Type' content='text/html; chars ...

  8. ajax使用post提交中文

    Ajax使用POST提交中文乱码问题 前段时间写JSP,使用AJAX以POST方式提交数据,如果是中文字符提交就会乱码,后来写ASP时用到AJAX以POST方式提交数据,中文一样是乱码.搜索一下相关资 ...

  9. javaee添加验证码

    package com.zh.code; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import j ...

  10. hdu3308 线段树 区间合并

    给n个数字 U表示第A个数改为B.A是从0开始. Q输出最大的递增序列个数. 考虑左边,右边,和最大的. #include<stdio.h> #define lson l,m,rt< ...