首页
社区
课程
招聘
[原创]摘除MiniFilter回调的正确姿势?
发表于: 2023-12-7 18:15 8769

[原创]摘除MiniFilter回调的正确姿势?

2023-12-7 18:15
8769

前段时间因有需要我必须摘除一个MiniFilter的回调,了解的人都知道FltUnregisterFilter是移除MiniFilter的API,但是MSDN强调过这个API只能模块自身使用,于是当我尝试拿他去对我的目标驱动手时,代码进入FltUnregisterFilter后就一去不返了。

在看雪上拜读到这篇文章,文中定位了阻塞的原因:进入FltUnregisterFilter后,ExWaitForRundownProtectionRelease会检查filter对象的引用计数,按照MSDN的解释,如果不为0那么就会阻塞。作者的实验场景中此时引用计数为2,那么一定也就阻塞了。
作者再观察使用PCHunter来摘除MiniFilter的过程,发现在进入ExWaitForRundownProtectionRelease之前这个filter对象的引用计数已经变为了0,于是他猜测PCHunter使用APIExReleaseRundownProtection来减少了引用计数。因此他也在自己驱动中在调用FltUnregisterFilter之前添加执行了ExReleaseRundownProtection,然后成功了。

我使用作者这个方法在一个环境下测试成功了,但是换了一个环境后并没成功,原因是要摘除的filter的引用计数在 ExWaitForRundownProtectionRelease之前并不能到达0:
执行ExReleaseRundownProtection后进入FltUnregisterFilter之时引用计数值为0x7623:
图片描述
执行到ExWaitForRundownProtectionRelease之时这个引用计数值还剩0x6:
图片描述
接着进入ExWaitForRundownProtectionRelease就再次一去不返了。

所以我需要调多次ExReleaseRundownProtection吗?如果是多次那么到底应该调几次才是恰当的?我尝试寻找一个真正的具有通用性的解决办法:
我尝试连续调用ExReleaseRundownProtection,发现每次这个引用计数都会减少2:
图片描述
所以如果我循环“引用计数/2”次是不是就能清零了?如下:

这样在执行FltUnregisterFilter之前,引用计数清零了:
图片描述
但是继续执行后,依然阻塞了。
暂停下来看传入对象的引用计数:
图片描述
可见这个计数变得很大,高位为ffff,可以猜测是不是它在为0后经过了多次递减最终导致这么大的数字,现在就需要看看 FltUnregisterFilter中发生了什么事了,关键反编译代码如下:

经过简单调试就可以发现,从刚进入FltUnregisterFilter到断点FltpWaitForRundownProtectionReleaseInternal,filter对象的引用计数发生较大的减小,a处FltpFreeInstance函数和b处FltObjectDereference执行了修改操作。
看看具体引用计数是怎么被修改的,先看FltObjectDereference

可见InterlockedCompareExchange会将计数减2。
再看看FltpFreeInstance,由于里面调用栈较深,我这里通过访问断点来演示,在filter+8处下写断点很快就命中了,调用栈如下:

这里也是每次减计数2:
图片描述
从上面给的FltUnregisterFilter精简代码可以看到,a会被循环调用,再观察下这个for循环的结构很容易猜测到这是一个_LIST_ENTRY链接的链表,偏移位于208处。
现在大概知道FltUnregisterFilter怎么处理filter对象的引用计数了:首先它会遍历对象偏移0xD0位置的链表,对每个节点使用 FltpFreeInstance最终使用nt! ExfReleaseRundownProtection来释放这个节点和它对filter对象的引用计数,这一步完成后,此时要求filter的引用计数剩且仅剩2次,然后下面再调用FltObjectDereference减去最后两次引用,这样在进入FltpWaitForRundownProtectionReleaseInternal前引用计数就清零了。
所以该怎么来恰如其分地释放filter对象?现在的问题在于,不知道在前面a处循环后到达b时还剩多少次引用计数,如本节开头我演示的实验,剩的引用计数为6,那么可以在FltUnregisterFilter前使用三次ExReleaseRundownProtection即可。但是要是再换一个环境或者当这个环境发生变化时这个计数又会变为多少?那我能不能计算在节点循环时引用计数会减少多少,然后再加上后面FltpWaitForRundownProtectionReleaseInternal之前减少的2,两者之和与总引用计数值的差值就是我需要手动调用 ExReleaseRundownProtection来减少的计数? 或许可以,这意味着我需要逆向分析FltpFreeInstance内部实现,然后编写代码模拟其判断逻辑,才能精确计算出到底他使用了多少次引用计数,我想我没有兴趣做这件繁琐的事。这里有另一个办法:在执行 FltpWaitForRundownProtectionReleaseInternal前进行hook,调用ExReleaseRundownProtection减掉多余的引用计数值

Hook怎么来编写?先看下fltmgr!FltUnregisterFilter中调用FltpWaitForRundownProtectionReleaseInternal处的代码长什么样:
图片描述
这是在Windows 10 22H2的fltmgr.sys中的样子,可以拿上图 "1C0058627"到"1C005862D+1"部分作为特征定位位置, 然后从"1C005861F"写入hook代码,到"1C005862D"正好有14个字节的空间。由于x64下内联hook进行跳转至少需要两条指令,因此在没有特殊条件下,至少14个字节完成如下两条指令:

示例代码如下:

搜索hook点和编写hook代码部分在部分平台会有差异,自行微调。当设置这个hook后,就只需要在简单对filter对象执行一次 FltUnregisterFilter即可稳定地摘除它的回调。

这个方法在hook中减掉了多余的引用计数,但是这会不会有个问题,内核对象的引用都是有意义的,如果一个对象的引用不是由使用者在使用完毕后自行关闭,而是被其他模块在不告知的情况下关闭,那么使用者再次使用这个对象时就可能出现未知问题。所以如果注册MiniFilter的模块再次使用这个filter对象时是有可能出现BSOD的,因为我在摘除后一直没有遇到问题所以暂时还没研究这个问题。

这是一种劫持执行流的解决方案,而且如0x5点所述我还不确定绝不会出现问题,不过这里似乎还有另一个方法,就是直接编写代码模仿FltUnregisterFilter的行为,FltUnregisterFilter反编译后可以看到其实并不是很复杂,如果手动循环调用FltpFreeInstance后再在FltpWaitForRundownProtectionReleaseInternal之前根据引用计数决定释放次数,然后结合调试执行其余关键的操作。我想理论上是可行的,这种办法还能避开不同系统版本的适配性问题,但这也无法解决0x5点中提到的问题,所以或许也不是拆除MiniFilter的最正确姿势。

UINT64 count = *((UINT64*)pFilter + 1);
for (size_t i = 0; i < count /2; i++)
{
    ExReleaseRundownProtection(RunRefs);
}
FltUnregisterFilter(pFilter);
UINT64 count = *((UINT64*)pFilter + 1);
for (size_t i = 0; i < count /2; i++)
{
    ExReleaseRundownProtection(RunRefs);
}
FltUnregisterFilter(pFilter);
__int64 __fastcall FltUnregisterFilter(__int64 a1)
{
    _QWORD **v6;
    _RBX = a1;
    v6 = (_QWORD **)(_RBX + 208);
    for (_QWORD * i = *v6; i != v6; i = (_QWORD *)*i )                                          
    {
        if ( !(*(_DWORD *)(i - 14) & 1) && !(*(_DWORD *)(i - 5) & 4) )
        {
            v4 = FltObjectReference((__int64)(i - 14));        
            if ( v4 >= 0 )
            {
                ExReleaseResourceLite((PERESOURCE)(_RBX + 104));
                KeLeaveCriticalRegion();
                // Inside here the references reduced
                FltpFreeInstance((__int64)(i - 14), 2 * (*(_DWORD *)(_RBX + 88) & 1) + 2, v8); // a.
                goto LABEL_7;
            }
        }
    }
    ......
 
    FltObjectDereference(_RBX);                                     // b.
    FltpWaitForRundownProtectionReleaseInternal(_RBX + 8, 0);   // c. Here the block resides
    FltpTerminateActiveConnections(_RBX);
 
......
}
__int64 __fastcall FltUnregisterFilter(__int64 a1)
{
    _QWORD **v6;
    _RBX = a1;
    v6 = (_QWORD **)(_RBX + 208);
    for (_QWORD * i = *v6; i != v6; i = (_QWORD *)*i )                                          
    {
        if ( !(*(_DWORD *)(i - 14) & 1) && !(*(_DWORD *)(i - 5) & 4) )
        {
            v4 = FltObjectReference((__int64)(i - 14));        
            if ( v4 >= 0 )
            {
                ExReleaseResourceLite((PERESOURCE)(_RBX + 104));
                KeLeaveCriticalRegion();
                // Inside here the references reduced
                FltpFreeInstance((__int64)(i - 14), 2 * (*(_DWORD *)(_RBX + 88) & 1) + 2, v8); // a.
                goto LABEL_7;
            }
        }
    }
    ......
 
    FltObjectDereference(_RBX);                                     // b.
    FltpWaitForRundownProtectionReleaseInternal(_RBX + 8, 0);   // c. Here the block resides
    FltpTerminateActiveConnections(_RBX);
 
......
}
__int64 __fastcall FltObjectDereference(__int64 a1)
{
  return ExReleaseRundownProtection(a1 + 8);        //located in ntoskrnl.exe
}
 
int __fastcall ExfReleaseRundownProtection(volatile signed __int64 *_RCX)
{
  ……
  v3 = _InterlockedCompareExchange(_RCX, v1 - 2, v1);
  ……
   
  return v3;
}
__int64 __fastcall FltObjectDereference(__int64 a1)
{
  return ExReleaseRundownProtection(a1 + 8);        //located in ntoskrnl.exe
}
 
int __fastcall ExfReleaseRundownProtection(volatile signed __int64 *_RCX)
{
  ……
  v3 = _InterlockedCompareExchange(_RCX, v1 - 2, v1);
  ……
   
  return v3;
}
1: kd> ba w4 ffff9e07`a8346a68
1: kd> g
Breakpoint 1 hit
nt!ExfReleaseRundownProtection+0x1c:
fffff801`1be219dc 4c8bc0          mov     r8,rax
 
# Child-SP          RetAddr               Call Site
00 ffff8506`709795c0 fffff801`1ba58d29     nt!ExfReleaseRundownProtection+0x1c
01 ffff8506`709795f0 fffff801`1ba8a6b1     FLTMGR!DoReleaseContext+0xf9
02 ffff8506`70979630 fffff801`1ba9de47     FLTMGR!FltpDeleteContextList+0xc1
03 ffff8506`70979660 fffff801`1ba8e627     FLTMGR!FltpCleanupStreamListCtrlForInstanceRemoval+0xf1b7
04 ffff8506`709796b0 fffff801`1baa85aa     FLTMGR!FltpFreeInstance+0x1db
05 ffff8506`70979780 fffff801`451816a6     FLTMGR!FltUnregisterFilter+0x11a
06 ffff8506`70979840 fffff801`451813d6     MyDriver0!RemoveCsMinifilters+0x156
07 ffff8506`709798b0 fffff801`1c36dd1c     MyDriver0!DriverEntry+0x3d6
1: kd> ba w4 ffff9e07`a8346a68
1: kd> g
Breakpoint 1 hit
nt!ExfReleaseRundownProtection+0x1c:
fffff801`1be219dc 4c8bc0          mov     r8,rax
 
# Child-SP          RetAddr               Call Site
00 ffff8506`709795c0 fffff801`1ba58d29     nt!ExfReleaseRundownProtection+0x1c
01 ffff8506`709795f0 fffff801`1ba8a6b1     FLTMGR!DoReleaseContext+0xf9
02 ffff8506`70979630 fffff801`1ba9de47     FLTMGR!FltpDeleteContextList+0xc1
03 ffff8506`70979660 fffff801`1ba8e627     FLTMGR!FltpCleanupStreamListCtrlForInstanceRemoval+0xf1b7
04 ffff8506`709796b0 fffff801`1baa85aa     FLTMGR!FltpFreeInstance+0x1db
05 ffff8506`70979780 fffff801`451816a6     FLTMGR!FltUnregisterFilter+0x11a
06 ffff8506`70979840 fffff801`451813d6     MyDriver0!RemoveCsMinifilters+0x156
07 ffff8506`709798b0 fffff801`1c36dd1c     MyDriver0!DriverEntry+0x3d6
mov rax, shellcode_addr
jump rax
mov rax, shellcode_addr
jump rax
NTSTATUS HookFltUnregisterFilter()
{
    DbgPrint("Enter HookFltUnregisterFilter\n");
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    BYTE* phookaddr = 0;
 
    BYTE* pfun = (BYTE*)FltUnregisterFilter;
    pfun = pfun + 6 + *(LONG*)(pfun + 2);
    pfun = *(BYTE**)pfun;
     
    //searching for the codes behind "call   FltObjectDereference"
    //33 D2 48 8D 4B 08
    BYTE searchBytes[6] = { 0x33,0xD2, 0x48, 0x8D, 0x4B, 0x08 };
    for(int i = 0;i<0x400;i++)
    {
        if (memcmp(pfun + i, searchBytes, 6) == 0)
        {
            phookaddr = pfun + i-8;
            break;
        }
    }
 
    if (phookaddr == 0)
    {
        DbgPrint("HookFltUnregisterFilter cant find hoodaddr\n");
        return status;
    }
 
 
    /*
    mov     rcx, rbx                        //48 8b cb
    mov     rax,    0x0000000000000000      //48 b8 00 00 00 00 00 00 00 00
    call    rax                             //ff d0            
     
    mov     edx, 1                          //BA 01 00 00 00
    lea     rcx, [rbx+8]                    //48 8b 4b 08
     
    lock cmpxchg [rcx], rdx
    cmp     rax, 2                          //F0 48 0F B1 11 48 83 F8 02
    jnb     short loc_1402F84CC             // 73 offset
 
    xor     edx, edx                            //33 d2      must set rdx 0 due to the origin codes
    */
     
    BYTE hookcode[] = {
        0x48,0x8b,0xcb,
        0x48,0xb8,0x00,0x0,0x00,0x00,0x00,0x00,0x00,0x00,
        0xff,0xd0,
        0xba,0x01,0x00,0x00,0x00,
        0x48,0x8d,0x4b,0x08,
        0xf0,0x48,0x0f,0xb1,0x11,0x48,0x83,0xf8,0x02,
        0x73,0x00,
        0x33,0xd2
    };
     
    //mov rax, next_addr ; jmp rax
    BYTE    jumpcode[12] = { 0x48,0xB8,0xFF,0xFF,0xFF,0xFF,0xAA,0xAA,0xAA,0xAA,0xFF,0xE0 };        
    int jmpcodelen = 12;
    int jmpcode_uselen = 14;
    int hookcodelen = sizeof(hookcode);
 
    //1 backup
    g_hookpoint1.g_HookBak = (BYTE*)ExAllocatePool(NonPagedPool, 0x100);
    memcpy(g_hookpoint1.g_HookBak, phookaddr, jmpcode_uselen);
    g_hookpoint1.g_HookBakSize = jmpcode_uselen;
 
    //2 prepare hook buffer
    g_hookpoint1.g_HookAddr = phookaddr;
    g_hookpoint1.g_HookBuffer = (BYTE*)ExAllocatePool(NonPagedPool, 0x100);
    ///here i must calculate the abs addr of FltObjectDereference
    LONG offset = *(LONG*)((UINT64)phookaddr + 3 + 1);
    UINT64 nFltObjectDereferenceAddr = (UINT64)phookaddr + 3 + 5 + offset;
    *(UINT64*)(hookcode + 5) = nFltObjectDereferenceAddr;
    DbgPrint("nFltObjectDereferenceAddr:<0x%p>\n", nFltObjectDereferenceAddr);
    ///calculate the offset for loop
    BYTE bytejmp = 0 - hookcodelen + 2;
    hookcode[hookcodelen - 3] = bytejmp;
    memcpy(g_hookpoint1.g_HookBuffer, hookcode, hookcodelen);
    ///jump back
    *(UINT64*)(jumpcode + 2) = (UINT64)phookaddr + jmpcode_uselen;
    memcpy(g_hookpoint1.g_HookBuffer+ hookcodelen, jumpcode, jmpcodelen);
    g_hookpoint1.g_HookBufferSize = 0x100;
 
    //3 now set the hook
    *(UINT64*)(jumpcode + 2) = (UINT64)g_hookpoint1.g_HookBuffer;
    KIRQL tmpirql = WriteProtectOff();      //turn off write protect
    memcpy(phookaddr, jumpcode, jmpcodelen);
    WriteProtectOn(tmpirql);                //recover
 
    DbgPrint("Leave HookFltUnregisterFilter\n");
    return status;
}
NTSTATUS HookFltUnregisterFilter()
{
    DbgPrint("Enter HookFltUnregisterFilter\n");
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    BYTE* phookaddr = 0;
 
    BYTE* pfun = (BYTE*)FltUnregisterFilter;
    pfun = pfun + 6 + *(LONG*)(pfun + 2);

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2023-12-7 18:25 被adc又死了编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (2)
雪    币: 589
活跃值: (1143)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
太强了
2023-12-27 19:37
0
雪    币: 1795
活跃值: (3995)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2024-8-9 15:11
0
游客
登录 | 注册 方可回帖
返回
//