SSDT其实就是一张表。先dump出来瞧瞧
我之前说过,导出的是KeServiceDescrīptorTable。用WinDBG来dump。
Quote:
lkd> dd KeServiceDescrīptorTable
8055a680 804e36a8 00000000 0000011c 80513eb8
8055a690 00000000 00000000 00000000 00000000
8055a6a0 00000000 00000000 00000000 00000000
8055a6b0 00000000 00000000 00000000 00000000
8055a6c0 00002730 bf80c247 00000000 00000000
8055a6d0 fa86aa80 faad83c0 80def5e0 faad83e0
8055a6e0 00000000 00000000 00000000 00000000
8055a6f0 e8f01240 01c7c671 00000000 00000000
再看看KSDT是怎么被定义的:
Copy code
typedef struct _SERVICE_DEscrīptOR_TABLE
{
PVOID ServiceTableBase;
PULONG ServiceCounterTableBase;
ULONG NumberOfService;
ULONG ParamTableBase;
}SERVICE_DEscrīptOR_TABLE,*PSERVICE_DEscrīptOR_TABLE;
extern PSERVICE_DEscrīptOR_TABLE KeServiceDescrīptorTable;
第一个是指向表基址(表头)的一个指针,第二个是服务调用数的基值(也就是服务号的起始值),第三个是服务数量,第四个是参数表的基址(参数表中每1个字节代表一个参数长度,各个参数长度的顺序和服务调用的顺序的对应的。
//虽然SSDT指向的都是一些Native API,但是在这儿,他们更多的被称为系统服务调用。
其他三个先不用管,先看ServiceTableBase——因为在大多数情况下,我们修改SSDT之前我们都是已经知道要修改的服务调用的服务调用号(索引号),并且,我们所了解到的服务调用号总是不会超过服务总数,而参数表对于我们来说也应当是已知的。
再用WinDBG把ServuceTableBase dump出来。
Quote:
lkd> dd 804e36a8
804e36a8 80580302 80579b8c 8058b7ae 805907e4
804e36b8 805905fe 806377a0 80639931 8063997a
804e36c8 8057560b 806481cf 80636f5f 8058fb85
804e36d8 8062f0a4 8057be31 8058cc26 806261bd
804e36e8 805dcf20 80568f9d 805d9ac1 805a2bb0
804e36f8 804e3cb4 806481bb 805ca22c 804f0e28
804e3708 80569649 80567d49 8058fff3 8064e1c1
804e3718 8058f8f5 80581225 8064e42f 8058b800
正如你所看到的,在ServiceTableBase中,由多个ULONG型数值排列成了一个表——这也就是前面所说的SSDT,而其中的数值就是各个服务调用的地址,我们可以修改这个表中的地址来对这些服务调用进行重定向。
//注:并非所有的服务调用都会查这张表,比如在驱动中直接调用被导出的函数,这张表就会被忽略——换句话说,这张表只能用于对r3的进程所做的防护,而且,在2k,xp以及未打sp1的2k3系统下,这也不是绝对的。
通过分析服务地址的长度以及得到ServiceTableBase的方法,我们不难得出一个结论:
服务调用的函数地址在内存中存放的地址 = ServiceTableBase + 服务函数调用号 * 4 //sizeof(ULONG)=4
换句话说,我们可以修改【ServiceTableBase + 服务函数调用号 * 4】所指向的内存数据来使得表中的函数重定位到我们自己的函数中。
由于内存中的某些部分是有写保护的,所以我们得先置cr0寄存器去掉这些地方的内存保护(不然等着PAGE_FAULT吧……)
Copy code
VOID DisableWriteProtect( PULONG pOldAttr)
{
ULONG uAttr;
_asm
{
push eax;
mov eax, cr0;
mov uAttr, eax;
and eax, 0FFFEFFFFh; // CR0 16 BIT = 0
mov cr0, eax;
pop eax;
};
*pOldAttr = uAttr; //保存原有的 CRO 属性
}
VOID EnableWriteProtect( ULONG uOldAttr )
{
_asm
{
push eax;
mov eax, uOldAttr; //恢复原有 CR0 属性
我之前说过,导出的是KeServiceDescrīptorTable。用WinDBG来dump。
Quote:
lkd> dd KeServiceDescrīptorTable
8055a680 804e36a8 00000000 0000011c 80513eb8
8055a690 00000000 00000000 00000000 00000000
8055a6a0 00000000 00000000 00000000 00000000
8055a6b0 00000000 00000000 00000000 00000000
8055a6c0 00002730 bf80c247 00000000 00000000
8055a6d0 fa86aa80 faad83c0 80def5e0 faad83e0
8055a6e0 00000000 00000000 00000000 00000000
8055a6f0 e8f01240 01c7c671 00000000 00000000
再看看KSDT是怎么被定义的:
Copy code
typedef struct _SERVICE_DEscrīptOR_TABLE
{
PVOID ServiceTableBase;
PULONG ServiceCounterTableBase;
ULONG NumberOfService;
ULONG ParamTableBase;
}SERVICE_DEscrīptOR_TABLE,*PSERVICE_DEscrīptOR_TABLE;
extern PSERVICE_DEscrīptOR_TABLE KeServiceDescrīptorTable;
第一个是指向表基址(表头)的一个指针,第二个是服务调用数的基值(也就是服务号的起始值),第三个是服务数量,第四个是参数表的基址(参数表中每1个字节代表一个参数长度,各个参数长度的顺序和服务调用的顺序的对应的。
//虽然SSDT指向的都是一些Native API,但是在这儿,他们更多的被称为系统服务调用。
其他三个先不用管,先看ServiceTableBase——因为在大多数情况下,我们修改SSDT之前我们都是已经知道要修改的服务调用的服务调用号(索引号),并且,我们所了解到的服务调用号总是不会超过服务总数,而参数表对于我们来说也应当是已知的。
再用WinDBG把ServuceTableBase dump出来。
Quote:
lkd> dd 804e36a8
804e36a8 80580302 80579b8c 8058b7ae 805907e4
804e36b8 805905fe 806377a0 80639931 8063997a
804e36c8 8057560b 806481cf 80636f5f 8058fb85
804e36d8 8062f0a4 8057be31 8058cc26 806261bd
804e36e8 805dcf20 80568f9d 805d9ac1 805a2bb0
804e36f8 804e3cb4 806481bb 805ca22c 804f0e28
804e3708 80569649 80567d49 8058fff3 8064e1c1
804e3718 8058f8f5 80581225 8064e42f 8058b800
正如你所看到的,在ServiceTableBase中,由多个ULONG型数值排列成了一个表——这也就是前面所说的SSDT,而其中的数值就是各个服务调用的地址,我们可以修改这个表中的地址来对这些服务调用进行重定向。
//注:并非所有的服务调用都会查这张表,比如在驱动中直接调用被导出的函数,这张表就会被忽略——换句话说,这张表只能用于对r3的进程所做的防护,而且,在2k,xp以及未打sp1的2k3系统下,这也不是绝对的。
通过分析服务地址的长度以及得到ServiceTableBase的方法,我们不难得出一个结论:
服务调用的函数地址在内存中存放的地址 = ServiceTableBase + 服务函数调用号 * 4 //sizeof(ULONG)=4
换句话说,我们可以修改【ServiceTableBase + 服务函数调用号 * 4】所指向的内存数据来使得表中的函数重定位到我们自己的函数中。
由于内存中的某些部分是有写保护的,所以我们得先置cr0寄存器去掉这些地方的内存保护(不然等着PAGE_FAULT吧……)
Copy code
VOID DisableWriteProtect( PULONG pOldAttr)
{
ULONG uAttr;
_asm
{
push eax;
mov eax, cr0;
mov uAttr, eax;
and eax, 0FFFEFFFFh; // CR0 16 BIT = 0
mov cr0, eax;
pop eax;
};
*pOldAttr = uAttr; //保存原有的 CRO 属性
}
VOID EnableWriteProtect( ULONG uOldAttr )
{
_asm
{
push eax;
mov eax, uOldAttr; //恢复原有 CR0 属性