本文编写于242 天前,最后修改于242 天前,其中某些信息可能已经过时。

Meltdown&Spectre补丁

毫无疑问,这个补丁是微软用于修复Intel的CPU漏洞的补丁。
安装了这个补丁之后,最明显的变化可以看到msr地址0xC0000082所指向的函数不再是KiSystemCall64了,而是一个影子函数。
这给搜索ShadowSSDT(或SSDT)带来了困难,如果继续使用读取msr得到的地址作为搜索的起始位置,那么将无法搜索到ShadowSSDT(或SSDT)所在的位置。

幸运的是,其实我们并不需要做太多的修改,还是可以正常拿到ShadowSSDT(或SSDT)的地址的呢。


代码实现

咱们先来看代码吧:

ULONG64 GetKiSystemServiceUser()
{
    PUCHAR EndSearchAddress;
    //0xc0000082 现在变成了 KiSystemCall64Shadow
    ULONGLONG KiSystemServiceUser = 0;
    ULONGLONG templong = 0xffffffffffffffff;
    PUCHAR i;
    PUCHAR pKiSystemCall64 = (PUCHAR)__readmsr(0xc0000082);
    EndSearchAddress = pKiSystemCall64 + 0x500;

    for (i = pKiSystemCall64; i < EndSearchAddress + 0xff; i++)
    {
        if (MmIsAddressValid(i) && MmIsAddressValid(i + 5))
        {
            if (*(PUCHAR)i == 0xe9 && *(PUCHAR)(i + 5) == 0xc3)
            {
                //fffff803`23733383 e9631ae9ff      jmp     nt!KiSystemServiceUser(fffff803`235c4deb)
                //fffff803`23733388 c3              ret
                RtlCopyMemory(&templong, (PUCHAR)(i + 1), 4);
                KiSystemServiceUser = templong + 5 + (ULONGLONG)i; // KiSystemServiceUser
                return KiSystemServiceUser;
            }
        }
    }
    return 0;
}

ULONG64 g_shadowSSDT = NULL;
ULONG64 GetKeServiceDescriptorTableShadow64()
{
    if (g_shadowSSDT != NULL)
        return g_shadowSSDT;

    PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082);
    PUCHAR EndSearchAddress = StartSearchAddress + 0x500;
    PUCHAR i = NULL;
    UCHAR b1 = 0, b2 = 0, b3 = 0;
    ULONG templong = 0;
    ULONG64 addr = 0;
    for (i = StartSearchAddress; i<EndSearchAddress; i++)
    {
        if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2))
        {
            b1 = *i;
            b2 = *(i + 1);
            b3 = *(i + 2);
            if (b1 == 0x4c && b2 == 0x8d && b3 == 0x1d) //4c8d1d
            {
                memcpy(&templong, i + 3, 4);
                addr = (ULONG64)templong + (ULONG64)i + 7;
                g_shadowSSDT = addr;
                return addr;
            }
        }
    }

    // 没搜到, 此时一定可以确定是因为安装了CPU漏洞补丁, KiSystemCall64变成了Shadow
    // 这时候需要从KiSystemServiceUser开始继续搜

    StartSearchAddress = (PUCHAR)GetKiSystemServiceUser();
    if (StartSearchAddress == 0)
    {
        return 0;
    }

    EndSearchAddress = StartSearchAddress + 0x500;
    i = NULL;
    b1 = 0, b2 = 0, b3 = 0;
    templong = 0;
    addr = 0;
    for (i = StartSearchAddress; i<EndSearchAddress; i++)
    {
        if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2))
        {
            b1 = *i;
            b2 = *(i + 1);
            b3 = *(i + 2);
            if (b1 == 0x4c && b2 == 0x8d && b3 == 0x1d) //4c8d1d
            {
                memcpy(&templong, i + 3, 4);
                addr = (ULONG64)templong + (ULONG64)i + 7;
                g_shadowSSDT = addr;
                return addr;
            }
        }
    }

    return 0;
}

原理说明

其实这个代码非常好理解,我们先用常规的代码搜索一遍ShadowSSDT。如果没有搜到,那么可以说明系统安装了CPU漏洞补丁。
这个时候我们从0xc0000082的位置搜索KiSystemServiceUser函数,之后从KiSystemServiceUser函数的地址开始搜ShadowSSDT,就可以拿到ShadowSSDT的地址了。

其实原理也很好理解的呢……
首先,搜索ShadowSSDT或SSDT,因为保存这个地址的汇编代码总是在KiSystemServiceUser函数的下方,因此,其实本身就是可以从KiSystemServiceUser开始搜索的。
但是由于直接拿0xc0000082这个msr,可以拿到KiSystemCall64函数的地址,而且KiSystemCall64函数就在KiSystemServiceUser的上方,因此直接拿到0xc0000082的msr所指向的地址,并作为起始开始搜索ShadowSSDT(或SSDT),操作起来更为容易且简单。
但是现在msr不再指向KiSystemCall64,因此之前的方法不好用了。不过我们可以稍加变通:从这个位置开始搜索KiSystemServiceUser函数的所在位置,然后再从KiSystemServiceUser开始搜索,不就实现了和之前一样的效果了嘛……?

于是就这么简单啦w