首页 > 简单说说SSDT

简单说说SSDT

论技术,我还差得远,而且网上关于SSDT的文章也多不胜数。但是还是想自己写一下,因为我想试试我能不能用最简单的语言来描述SSDT——这个对一般来 人来说比较神秘的属于内核的地带。引用EVA说的一句话,“以为写个驱动就是内核,还远着了”——大概是这么个意思,记得不是很清楚。



关于SSDT,描述得最清楚的应该算《SSDT Hook的妙用-对抗ring0 inline hook》一文了,作者是堕落天才。这里引用一下他写的开头部分,略有个别字符的修改:



内核中有两个系统服务描述符表,一个是KeServiceDescriptorTable,由ntoskrnl.exe导出,一个是 KeServieDescriptorTableShadow,没有导出。这两者都是一个结构体,结构下面会给出。他们的区别 是,KeServiceDescriptorTable仅有 ntoskrnel一项,而KeServieDescriptorTableShadow则包含了ntoskrnel和win32k。一般的Native API的服务地址由KeServiceDescriptorTable分派,而gdi.dll和

user.dll的内核API调用服务地址,由 KeServieDescriptorTableShadow分派。还有要清楚一点的是win32k.sys只有在GUI线程中才加载,一般情况下是不加 载的。



他们的结构如下:

typedef struct _SYSTEM_SERVICE_TABLE

{

    PVOID ServiceTableBase;    
//这个指向系统服务函数地址表

    PULONG ServiceCounterTableBase;

    ULONG NumberOfService;     
//服务函数的个数,NumberOfService*4 就是整个地址表的大小

    ULONG ParamTableBase;

}SYSTEM_SERVICE_TABLE,
*PSYSTEM_SERVICE_TABLE;



typedef 
struct _SERVICE_DESCRIPTOR_TABLE

{

    SYSTEM_SERVICE_TABLE ntoskrnel;    
//ntoskrnl.exe的服务函数

    SYSTEM_SERVICE_TABLE win32k;    //win32k.sys的服务函数,(gdi.dll/user.dll的内核支持)

    SYSTEM_SERVICE_TABLE NotUsed1;

    SYSTEM_SERVICE_TABLE NotUsed2;

}SYSTEM_DESCRIPTOR_TABLE,
*PSYSTEM_DESCRIPTOR_TABLE; 

当系统需要使用一个本机API的时候,就会去查找SYSTEM_DESCRIPTOR_TABLE这个表,也就是由ntoskrnl.exe导出的 KeServiceDescriptorTable:

nt!RtlpBreakWithStatusInstruction:

80527fc8 cc              
int     3

kd
> dd KeServiceDescriptorTable

80553380  805021fc 00000000 0000011c 80502670

80553390  00000000 00000000 00000000 00000000

805533a0  
00000000 00000000 00000000 00000000

805533b0  
00000000 00000000 00000000 00000000

805533c0  
00002710 bf80c227 00000000 00000000

805533d0  f9e6da80 f963a9e0 816850f0 806e0f40

805533e0  
00000000 00000000 00000000 00000000

805533f0  97c5ac40 01c7abf5 
00000000 00000000 

可以看到,KeServiceDescriptorTable的地址是80553380。现在看看这个地址保存的是什么,因为这个地址的值就是 SYSTEM_SERVICE_TABLE的起始地址。好了,我们看到这个地址保存的是805021fc,那么也就是说,系统服务的地址表起始地址为 805021fc了。看看这个表是些什么鬼东西:

kd> dd 805021fc

805021fc  
80599746 805e6914 805ea15a 805e6946

8050220c  805ea194 805e697c 805ea1d8 805ea21c

8050221c  8060b880 8060c5d2 805e1cac 805e1904

8050222c  805ca928 805ca8d8 8060bea6 805ab334

8050223c  8060b4be 8059dbbc 805a5786 805cc406

8050224c  804ffed0 8060c5c4 8056be64 805353f2

8050225c  80604b90 805b19c0 805ea694 80619a56

8050226c  805eeb86 80599e34 80619caa 805996e6 
这个过程是这样的,最开始是SYSTEM_DESCRIPTOR_TABLE(80553380)保存了SYSTEM_SERVICE_TABLE的地址 (805021fc),SYSTEM_SERVICE_TABLE的地址(805021fc)又保存了很多地址,这个地址就是系统服务的地址了,类似 NtOpenProcess这样的ring0的函数地址。这样,系统就可以方便的找到每一个ring0函数去调用。



我们先看看第一个地址80599746是个什么函数,反汇编一下:

kd> u 80599746

nt
!NtAcceptConnectPort:

80599746 689c000000      push    9Ch

8059974b 6820a14d80      push    offset nt
!_real+0x128 (804da120)

80599750 e8abebf9ff      call    nt!_SEH_prolog (80538300)

80599755 64a124010000    mov     eax,dword ptr fs:[00000124h]

8059975b 8a8040010000    mov     al,
byte ptr [eax+140h]

80599761 884590          mov     byte ptr [ebp-70h],al

80599764 84c0            test    al,al

80599766 0f84b9010000    je      nt!NtAcceptConnectPort+0x1df (80599925

原来是NtAcceptConnectPort函数,第二个805e6914呢?我们也看一下,

kd> u 805e6914

nt
!NtAccessCheck:

805e6914 8bff            mov     edi,edi

805e6916 
55              push    ebp

805e6917 8bec            mov     ebp,esp

805e6919 33c0            xor     eax,eax

805e691b 
50              push    eax

805e691c ff7524          push    dword ptr [ebp
+24h]

805e691f ff7520          push    dword ptr [ebp+20h]

805e6922 ff751c          push    dword ptr [ebp
+1Ch] 
原来是NtAccessCheck函数。



这样我们可以清楚的看到,在这个起始地址为0x805021fc的表中,保存了各个ring0函数的地址。下面我来做个简单的比喻。



从前有一个很大的帮派,名字叫做Windows,功能很多并且很强大。因为这些各方面的能力由各个专人负责,他们一个人做一件事情。随着人员增 多,帮主发现联系起来越来越困了。有一天帮主要找竟然NtOpenProcess来调查一下他的一个手下是不是别的帮派派来的间谍,但是他发现 NtOpenProcess跑不见了。



于是军师就想出了一个好办法来解决这个问题:先建立一个封闭的密室,这个密室只有八袋长老以上的人才能进去。密室中间有一张纸条,上面写着一个地 址——温家堡,还有这个地址放着多少人的联系信息等内容。这个密室就是Ntdll.dll,这个纸条就是 SYSTEM_DESCRIPTOR_TABLE,上写的地址就是SYSTEM_SERVICE_TABLE,也就是温家堡了。这个温家堡是一个有很多大 房间的地方,每个房子有个房间号

,房间里面又放着一张纸条,上面写着各个手下的住所。比如说编号为7A的房间,里面放的是NtOpenProcess的家庭住址。



这样一来,帮主要找人就容易了。先去密室找到纸条,看看上面写的是温家堡还是白云城,那个地方有多少个人的联系信息等。如果是温家堡就跑到那里 去,看看要找谁,找NtOpenProcess就去7A房间。在这个房间里一看,啊,里面写着NtOpenProcess现在就住在密室的旁边……搞定。



这里就有一个新的问题,帮主假设这个里面写的东西都是正确的,没有被人改过。于是就有了别派的间谍发现了,偷偷溜进密室,然后根据纸条的内容,又 跑到温家堡。进到7A房间,神不知鬼不觉的把里面记录的NtOpenProcess的地址改成了自己的家。于是,帮主再找人,发现找到对头家里去了。这个 就是传说中的SSDT Hook了。



攻击者进入ring0之后,找到KeServiceDescriptorTable地址的值,即SYSTEM_SERVICE_TABLE的地址 (进入密室,找到纸条写的地址——温家堡)。然后改写SYSTEM_SERVICE_TABLE中一个特定函数的地址为自己定义的函数入口处,截获了系统 调用(来到温家堡,改掉7A房间里面写的住所,改成自己家)。一次HOOK就完成了。



下面我给一段简单的代码,演示怎么样让一个特定的PID不会被杀死。这段代码基本和《SSDT Hook的妙用-对抗ring0 inline hook》一文一样,我只是注释了一下而已,另外在MyNtOpenProcess处加了个判断是不是某个特定PID的功能。

ExpandedBlockStart.gif代码


/*

演示HOOK系统服务调用表中的NtOpenProcess函数,保护需要保护的进程被,防止被杀掉

*/



#include
<ntddk.h>



/*

KeServiceDescriptorTable仅有ntoskrnel一项,没有包含win32k,而且后面的两个字段都没有使用,所



以为了简便直接把SystemServiceDescriptorTable定义成SYSTEM_SERVICE_TABLE,免得访问多个结构体的



字段,麻烦。这里明白就行了。

*/

typedef 
struct _SystemServiceDescriptorTable

{

    PVOID    ServiceTableBase;

    PULONG    ServiceCounterTableBase;

    ULONG    NumberOfService;

    ULONG    ParamTableBase;

}SystemServiceDescriptorTable,
*PSystemServiceDescriptorTable;



// KeServiceDescriptorTable为ntoskrnl.exe导出

extern    PSystemServiceDescriptorTable    KeServiceDescriptorTable;



// 定义一下NtOpenProcess的原型,下面如果用汇编调用就不用定义了,但是我想尽量不用汇编

typedef    NTSTATUS    (__stdcall *NTOPENPROCESS)( OUT PHANDLE ProcessHandle,

                                                



IN ACCESS_MASK AccessMask,

                                                



IN POBJECT_ATTRIBUTES ObjectAttributes,

                                                



IN PCLIENT_ID ClientId

                                                



);



NTOPENPROCESS    RealNtOpenProcess;



// 定义函数原型

VOID Hook();

VOID Unhook();

VOID OnUnload(IN PDRIVER_OBJECT DriverObject);



// 真实的函数地址,我们会在自定义的函数中调用

ULONG    RealServiceAddress;



// 需要被驱动保护的进程ID

HANDLE    MyPID;



// 自定义的NtOpenProcess函数

NTSTATUS __stdcall MyNtOpenProcess( OUT    PHANDLE ProcessHandle,

                    IN    ACCESS_MASK DesiredAccess,

                    IN    POBJECT_ATTRIBUTES ObjectAttributes,

                    IN    PCLIENT_ID ClientId )

{

    NTSTATUS    rc;

    ULONG        PID;

    

    
//DbgPrint( "NtOpenProcess() called. " );

    

    rc 
= (NTSTATUS)(NTOPENPROCESS)RealNtOpenProcess( ProcessHandle, DesiredAccess,



ObjectAttributes, ClientId );

    

    
if( (ClientId != NULL) )

    {

        PID 
= (ULONG)ClientId->UniqueProcess;

        
//DbgPrint( "%d was opened,Handle is %d. ", PID, (ULONG)ProcessHandle );

        

        
// 如果进程PID是1520,直接返回权限不足,并将句柄设置为空

        if( PID == 1520 )

        {

            DbgPrint( 
"Some want to open pid 1520! " );

            

            ProcessHandle 
= NULL;

                        

            rc 
= STATUS_ACCESS_DENIED;

        }

    }

    

    
return rc;

}



// 驱动入口

NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath )

{

    DriverObject
->DriverUnload = OnUnload;



    Hook();

    

    
return STATUS_SUCCESS;

}



// 驱动卸载

VOID OnUnload(IN PDRIVER_OBJECT DriverObject)

{

    Unhook( );

}



//  此处修改SSDT中的NtOpenProcess服务地址

VOID Hook()

{

    ULONG            Address;

    

    
// 0x7A为Winxp+SP2下NtOpenProcess服务ID号

    
// Adress是个地址A,这个地址的数据还是一个地址B,这个地址B就是NtOpenProcess的地址了

    
// (ULONG)KeServiceDescriptorTable->ServiceTableBase就是温家堡的第一个房间

    
// Address是第7A个房间。

    Address = (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;



    
// 取得地址A的值,也就是NtOpenProcess服务的地址了,保存原来NtOpenProcess的地址以后恢



复用

    RealServiceAddress 
= *(ULONG*)Address;

    

    RealNtOpenProcess 
= (NTOPENPROCESS)RealServiceAddress;

    

    DbgPrint( 
"Address of Real NtOpenProcess: 0x%08X ", RealServiceAddress );



    DbgPrint(
" Address of MyNtOpenProcess: 0x%08X ", MyNtOpenProcess );



    
// 去掉内存保护

    __asm

    {

        cli

        mov    eax, cr0

        and    eax, not 10000h

        mov    cr0, eax

    }

    

    
// 修改SSDT中NtOpenProcess服务的地址

   *((ULONG*)Address) = (ULONG)MyNtOpenProcess;



    
// 恢复内存保护

    __asm

    {

        mov    eax, cr0

        or    eax, 10000h

        mov    cr0, eax

        sti

    }

}



//

VOID Unhook()

{

   ULONG   Address;

   Address 
= (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;



    __asm

    {

        cli

        mov    eax, cr0

        and    eax, not 10000h

        mov    cr0, eax

    }



    
// 还原SSDT

    *((ULONG*)Address) = (ULONG)RealServiceAddress;

    

    __asm

    {

        mov    eax, cr0

        or    eax, 10000h

        mov    cr0, eax

        sti

    }



    DbgPrint(
"Unhook");



 转载自:http://icylife.net/yunshu/show.php?id=435

 

 

 

 

 



 

 

更多相关:

  • 51 三菱PLC可读不可写Q:MT8102IQ和三菱Q系列PLC通讯,屏无法写入PLC,但是可以读取PLC的状态和数值?A:PLC程序中"允许RUN中写入"打钩,程序下载重启后解决。52 控制不了输入点Q:触摸屏做了三菱PLC的X点的元件,但是控制不了X输出?A:是的,PLC端X点无法通过触摸屏控制输出,屏上只能做X点的显示。53 M...

  • 传统方法(仅适用于普通项目):   1、在vscode中安装 Live Server 插件: 2、安装成功后,vscode右下角会有 Go Live 标识点击: 3、cmd ipconfig 查询自己电脑的ip地址: 4、复制地址替换端口前的地址(http://127.0.0.1:8080修改为http://192.168....

  • ngx_http_geo_module模块,默认情况下,nginx会加载,除非人为的 --without-http_geo_module。 这个模块提供了一个非常好用的geo指令,可以用它来创建变量,诞生其值依赖于客户端IP地址。 ngx_http_geo_module 模块官网地址 http://nginx.org/en...

  • uboot启动Linux内核过程分为4大步骤: 问题2: uboot阶段DDR的分区的问题 上述步骤2和步骤4中,有将uboot/kernel拷贝纸DDR的步骤,具体要拷贝到DDR的什么位置呢? 分清楚这两个概念: 链接地址:链接时指定的地址(指定方式为:Makefile中用-Ttext,或者链接脚本) 运行地址:程序实际运行...

  • 在Linux系统中,以32bit x86系统来说,进程的4GB内存空间(虚拟地址空间)被划分成为两个部分 ------用户空间和内核空间,大小分别为0-3G,3-4G。        用户进程通常情况下,只能访问用户空间的虚拟地址,不能访问到内核空间。          每个进程的用户空间存放用户的程序和代码(堆栈,数据区,代码区等)...

  • 一、代码结构概览1.核心部分configs:储存各种网络的yaml配置文件datasets:存放数据集的地方detectron2:运行代码的核心组件tools:提供了运行代码的入口以及一切可视化的代码文件。2.Tutorial部分demo:显而易见就是demodocs: 同样显而易见。。tests:提供了一些测试代码projects:...

  •     我刚刚接手这个项目的时候就被一系列不知所措的文件命名给深深的震惊了,那种振聋发聩不亚于听到赌王离世的消息。 首先请看,文件本来是用于处理“请假审批”,但是文件名居然叫做“teaApprove”,不要欺负我的初中英语不好,这个teaApprove我第一个感觉就是和“喝茶、茶叶”有关的业务,可是和我们这个项目八竿子打不着...

  • 这个问题简单,不做过多描述,如题所述,如果因为这个导致错误,请安装 npm install stylus-loader css-loader style-loader -D...

  • 使用这个宏TS_VERSION_MAOR来判断,这个宏定义在编译时生成在apidefs.h,它包含在ts/ts.h中,所以请在插件这包含...

  • linux valgrind Memcheck–内存检查工具 使用方法: 注意,这里要用debug版本,如果是release的运行文件,则用debug编译出来的可执行文件替换 输出到终端: valgrind --tool=memcheck --leak-check=full ./test.out 输出到文件: valgri...

  • Spec: TS36.211 - Table 5.7.1-2...

  • 阿里云介绍: 1. 下载安装包。作为阿里主要的数据传输工具Datax,阿里已经完全开源到github上面了。下载地址(https://github.com/alibaba/DataX)。 2. 安装环境: JDK(1.6以上,推荐1.6)Python(推荐Python2.6.X)Apache Maven 3.x (Compile D...

  • xmlns xml namespaces 参考 http://www.w3school.com.cn/tags/tag_prop_xmlns.asp http://www.w3school.com.cn/xml/xml_namespaces.asp

    这是一行
  • 1.创建数据库:create database database-name 2.删除数据库:delete database database-name 3.选择:select * from table where ... 4.插入:insert into table(field1,field2) values(value1,value...