原文链接: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. 删除 windows 下 node_modules 过深的目录

    本文同步自我的个人博客:http://www.52cik.com/2015/11/13/node-modules-del.html 说到 node 的模块,确实既好用又蛋疼.相信无数人吐槽 node_ ...

  2. c# r3 inline hook

    前言 老婆喜欢在QQ游戏玩拖拉机,且安装了一个记牌器小软件,打开的时候弹出几个IE页面加载很多广告,于是叫我去掉广告.想想可以用OD进行nop填充,也可以写api hook替换shellexecute ...

  3. 10.C#匿名函数的变量捕获(五章5.5)

    首先感谢园友的指定,后续的文章一定会多码多想,出来的文章才有说服力.那今天接上篇我们来聊一聊匿名函数,对于匿名函数,我们知道使用delegate关键字,那我们来需要知道匿名函数在变量是的处理方式,先说 ...

  4. Java死锁的例子

    死锁 死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放.由于线程被无限期地阻塞,因此程序不可能正常终止. 导致死锁的根源在于不适当地运用“synchronized”关 ...

  5. HDU 3401 Trade dp+单调队列优化

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=3401 Trade Time Limit: 2000/1000 MS (Java/Others)Mem ...

  6. HTML DOM 基础

    $. HTML DOM 定义了访问和操作 HTML 文档的标准方法.  DOM 是 W3C(万维网联盟)的标准. $. DOM树. $. W3C 文档对象模型 (DOM) 是中立于平台和语言的接口,它 ...

  7. Cocos2d-X3.0 刨根问底(七)----- 事件机制Event源码分析

    这一章,我们来分析Cocos2d-x 事件机制相关的源码, 根据Cocos2d-x的工程目录,我们可以找到所有关于事件的源码都存在放在下图所示的目录中. 从这个event_dispatcher目录中的 ...

  8. POJ-2352 Stars 树状数组

    Stars Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 39186 Accepted: 17027 Description A ...

  9. android选择时间攻略

    安卓开发过程中难免会碰到需要选择日期时间的情况,由于大部分android初级教程都没教怎么选择时间,初学者碰到这种难免会有些不知所措,难道要让用户自己输入日期时间?先不说用户体验不好,处理用户输入各式 ...

  10. MySQL 中 where id in (1,2,3,4,...) 的效率问题讨论

     MySQL ACMAIN_CHM06-26 16:36 等级 84次回复 [求证&散分]MySQL 中 where id in (1,2,3,4,...) 的效率问题讨论 庆祝本月大版得 ...