其他
摘除MiniFilter回调的正确姿势
一
引言
MiniFilter
的回调,了解的人都知道FltUnregisterFilter
是移除MiniFilter
的API,但是MSDN强调过这个API只能模块自身使用,于是当我尝试拿他去对我的目标驱动手时,代码进入FltUnregisterFilter
后就一去不返了。二
一篇启发性文章
FltUnregisterFilter
后,ExWaitForRundownProtectionRelease
会检查filter对象的引用计数,按照MSDN的解释,如果不为0那么就会阻塞。作者的实验场景中此时引用计数为2,那么一定也就阻塞了。作者再观察使用
PCHunter
来摘除MiniFilter
的过程,发现在进入ExWaitForRundownProtectionRelease
之前这个filter对象的引用计数已经变为了0,于是他猜测PCHunter
使用APIExReleaseRundownProtection
来减少了引用计数。因此他也在自己驱动中在调用FltUnregisterFilter
之前添加执行了ExReleaseRundownProtection
,然后成功了。三
有没有问题
ExWaitForRundownProtectionRelease
之前并不能到达0:ExReleaseRundownProtection
后进入FltUnregisterFilter
之时引用计数值为0x7623:ExWaitForRundownProtectionRelease
之时这个引用计数值还剩0x6:ExWaitForRundownProtectionRelease
就再次一去不返了。四
寻找一个真正的解决方法
ExReleaseRundownProtection
吗?如果是多次那么到底应该调几次才是恰当的?我尝试寻找一个真正的具有通用性的解决办法:我尝试连续调用
ExReleaseRundownProtection
,发现每次这个引用计数都会减少2:for (size_t i = 0; i < count /2; i++)
{
ExReleaseRundownProtection(RunRefs);
}
FltUnregisterFilter(pFilter);
FltUnregisterFilter
之前,引用计数清零了:暂停下来看传入对象的引用计数:
FltUnregisterFilter
中发生了什么事了,关键反编译代码如下:{
_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);
......
}
FltUnregisterFilter
到断点FltpWaitForRundownProtectionReleaseInternal
,filter对象的引用计数发生较大的减小,a处FltpFreeInstance
函数和b处FltObjectDereference
执行了修改操作。看看具体引用计数是怎么被修改的,先看
FltObjectDereference
:{
return ExReleaseRundownProtection(a1 + 8); //located in ntoskrnl.exe
}
int __fastcall ExfReleaseRundownProtection(volatile signed __int64 *_RCX)
{
……
v3 = _InterlockedCompareExchange(_RCX, v1 - 2, v1);
……
return v3;
}
InterlockedCompareExchange
会将计数减2。再看看
FltpFreeInstance
,由于里面调用栈较深,我这里通过访问断点来演示,在filter+8处下写断点很快就命中了,调用栈如下: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
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
减掉多余的引用计数值。五
实现
fltmgr!FltUnregisterFilter
中调用FltpWaitForRundownProtectionReleaseInternal
处的代码长什么样:fltmgr.sys
中的样子,可以拿上图 "1C0058627"到"1C005862D+1"部分作为特征定位位置, 然后从"1C005861F"写入hook代码,到"1C005862D"正好有14个字节的空间。由于x64下内联hook进行跳转至少需要两条指令,因此在没有特殊条件下,至少14个字节完成如下两条指令:jump rax
{
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;
}
FltUnregisterFilter
即可稳定地摘除它的回调。六
有没有问题
MiniFilter
的模块再次使用这个filter对象时是有可能出现BSOD的,因为我在摘除后一直没有遇到问题所以暂时还没研究这个问题。七
这是不是最正确的姿势
FltUnregisterFilter
的行为,FltUnregisterFilter
反编译后可以看到其实并不是很复杂,如果手动循环调用FltpFreeInstance
后再在FltpWaitForRundownProtectionReleaseInternal
之前根据引用计数决定释放次数,然后结合调试执行其余关键的操作。我想理论上是可行的,这种办法还能避开不同系统版本的适配性问题,但这也无法解决0x5点中提到的问题,所以或许也不是拆除MiniFilter的最正确姿势。看雪ID:adc又死了
https://bbs.kanxue.com/user-home-969962.htm
# 往期推荐
2、Glibc-2.35下对tls_dtor_list的利用详解
3、对旅行APP的检测以及参数计算分析【Simplesign篇】
球分享
球点赞
球在看
点击阅读原文查看更多