首先枚举驱动的方法很多,这里不做过多介绍,此文仅简单说明x64系统,x86结构和偏移需重新收集.
在17134版本中,MI_SYSTEM_IMAGE_STATE结构新增了一个成员 即以下的最后一个成员ImageTree,此树保存驱动LdrSection
题外话:MI_SYSTEM_IMAGE_STATE是_MI_SYSTEM_INFORMATION的子结构,_MI_SYSTEM_INFORMATION是nt!MiState
nt!_MI_SYSTEM_IMAGE_STATE
+0x000 FixupList : _LIST_ENTRY
+0x010 LoadLock : _EX_PUSH_LOCK
+0x018 LoadLockOwner : Ptr64 _ETHREAD
+0x020 LoadLockCount : Uint4B
+0x024 FixupLock : Int4B
+0x028 FirstLoadEver : UChar
+0x029 LargePageAll : UChar
+0x030 LastPage : Uint8B
+0x038 LargePageList : _LIST_ENTRY
+0x048 StrongCodeLoadFailureList : _LIST_ENTRY
+0x058 SystemBase : [1] Ptr64 _KLDR_DATA_TABLE_ENTRY
+0x060 BeingDeleted : Ptr64 _KLDR_DATA_TABLE_ENTRY
+0x068 MappingRangesPushLock : _EX_PUSH_LOCK
+0x070 MappingRanges : [2] Ptr64 _MI_DRIVER_VA
+0x080 PageCount : Uint8B
+0x088 PageCounts : _MM_SYSTEM_PAGE_COUNTS
+0x098 CollidedLock : _EX_PUSH_LOCK
+0x0a0 ImageTree : _RTL_AVL_TREE
然后观察LDR_DATA_TABLE_ENTRY结构,最后几个成员也是变化了很多,在17134之后的版本基本固定
+0x0b8 ParentDllBase : Ptr64 Void
+0x0c0 SwitchBackContext : Ptr64 Void
+0x0c8 BaseAddressIndexNode : _RTL_BALANCED_NODE
+0x0e0 MappingInfoIndexNode : _RTL_BALANCED_NODE
+0x0f8 OriginalBase : Uint8B
+0x100 LoadTime : _LARGE_INTEGER
在e0处有一个Node,此Node在17134之前乱码? 17134及之后用于插入ImageTree
遍历ImageTree,得到Node,Node-0xe8即等于ldrSection(pDriverObject->Section),为什么是e8,可了解下BALANCED_NODE.
插入的行为在nt!MiProcessLoaderEntry中(是不是很熟悉???),哈哈.
首先要定位到ImageTree
有个取巧的办法,
MI_SYSTEM_IMAGE_STATE结构和MmSession的结构是相连的,MI_SYSTEM_IMAGE_STATE最后一个成员之后跟着的就是MmSession,那么找MmSeeion-8即可定位到ImageTree
MmSeeion可以通过MmMapViewInSystemSpace下的第一个lea得到.
code:
PVOID GetUndocumentFunctionAddress(
IN PCWSTR pFunName,
IN PUCHAR pStartAddress,
IN UCHAR* pFeatureCode,
IN ULONG FeatureCodeNum,
ULONG SerSize,
UCHAR SegCode,
LONG AddNum,
BOOLEAN ByName)
{
ULONG dwIndex = 0;
PUCHAR pFunAddress = NULL;
ULONG dwCodeNum = 0;
if (pFeatureCode == NULL)
return NULL;
if (FeatureCodeNum >= 50)
return NULL;
if (ByName)
{
if (pFunName == NULL || !MmIsAddressValid((PVOID)pFunName))
return NULL;
UNICODE_STRING usRoutine = { 0 };
RtlInitUnicodeString(&usRoutine, pFunName);
pFunAddress = (PUCHAR)MmGetSystemRoutineAddress(&usRoutine);
if (pFunAddress == NULL)
return NULL;
}
else
{
if (pStartAddress == NULL || !MmIsAddressValid(pStartAddress))
{
DPRINT("invalid start address:%p\n", pStartAddress);
return NULL;
}
pFunAddress = pStartAddress;
}
for (dwIndex = 0; dwIndex < SerSize; dwIndex++)
{
__try
{
if (!MmIsAddressValid(pFunAddress + dwIndex))
{
//DPRINT("invalid 2:%p\n", pFunAddress + dwIndex);
return 0;
}
if (pFunAddress[dwIndex] == pFeatureCode[dwCodeNum] || pFeatureCode[dwCodeNum] == SegCode)
{
dwCodeNum++;
if (dwCodeNum == FeatureCodeNum)
return pFunAddress + dwIndex - dwCodeNum + 1 + AddNum;
continue;
}
if (dwCodeNum)
dwIndex = dwIndex - dwCodeNum + 1;
dwCodeNum = 0;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
DPRINT("exp %p\n", pFunAddress + dwIndex);
return 0;
}
}
return 0;
}
PVOID GetOpCodePoint(PVOID pCallPoint, ULONG offsetToImm, ULONG dwCodeLen)
{
if (pCallPoint == NULL || !MmIsAddressValid(pCallPoint))
return NULL;
#ifndef AMD64
return (PVOID)((ULONG)pCallPoint + *(ULONG*)((PUCHAR)pCallPoint + offsetToImm) + dwCodeLen);
#else
LARGE_INTEGER tm;
LARGE_INTEGER FuncAddress;
tm.QuadPart = (LONGLONG)pCallPoint;
ULONG dwOffset = *(ULONG*)((PUCHAR)pCallPoint + offsetToImm);
FuncAddress.QuadPart = (ULONGLONG)pCallPoint + dwOffset + dwCodeLen;
tm.LowPart = FuncAddress.LowPart;
if (!MmIsAddressValid((PVOID)tm.QuadPart))
return nullptr;
return (PVOID)tm.QuadPart;
#endif // AMD64
}
PVOID GetMmSession()
{
PVOID p = nullptr;
do
{
auto leaRdx = GetUndocumentFunctionAddress(L"MmMapViewInSystemSpace", nullptr, (UCHAR*)"\x48\x8d\x15", 3, 0x300, 0x90, 0, true);
if (leaRdx == nullptr || !MmIsAddressValid(leaRdx))
break;
p = GetOpCodePoint(leaRdx, 3, 7);
if (p == nullptr || !MmIsAddressValid(p))
p = nullptr;
} while (false);
return p;
}
PVOID GetSystemImageTree()
{
if (*NtBuildNumber < 17134)
return nullptr;
auto MmSession = (PULONG_PTR)GetMmSession();
if (MmSession == nullptr)
return nullptr;
auto MmSystemImageTree = MmSession - 1;
/*
* 见MiState _MI_SYSTEM_INFORMATION
* MmSession的上一个成员是MI_SYSTEM_IMAGE_STATE结构的最后一个成员 Only >= win10 17134新增
*
* windows将ldr插入此成员进行管理,具体见MI_SYSTEM_IMAGE_STATE结构,entry- ldr对应偏移(0xe8) = ldr头部
*/
return MmSystemImageTree;
}
void EnumDrvSectionByImageTree()
{
PRTL_AVL_TREE info = (PRTL_AVL_TREE)GetSystemImageTree();
if (info == nullptr)
return;
PRTL_BALANCED_NODE* stack = (PRTL_BALANCED_NODE*)ExAllocatePoolWithTag(NonPagedPool, 0x5000 * sizeof(PRTL_BALANCED_NODE), 'xxxx');
if (stack == nullptr)
{
DPRINT("%s stack is null.\n");
return;
}
RtlZeroMemory(stack, 0x5000 * sizeof(PRTL_BALANCED_NODE));
int top = 0;
RTL_BALANCED_NODE* p = (PRTL_BALANCED_NODE)info->Root;
auto dwCount = 0;
do
{
while (p != nullptr)
{
// _RTL_BALANCED_NODE MappingInfoIndexNode+8 x64only
PLDR_DATA_TABLE_ENTRY entry = (PLDR_DATA_TABLE_ENTRY)((ULONG_PTR)p - 0xe8);
DPRINT("ldrSection[%p]:base:0x%p - size:0x%x drvBaseName:%wZ\n", entry, entry->DllBase, entry->SizeOfImage, &entry->BaseDllName);
/*
* 如果要扫描 需注意session 驱动
*/
dwCount++;
top = top + 1;
stack[top] = p;
p = p->Left;
}
if (top != 0)
{
p = stack[top];
top = top - 1;
p = p->Right;
}
} while (((top != 0) || (p != nullptr)));
ExFreePoolWithTag(stack, 'xxxx');
DPRINT("section count:%d\n", dwCount);
}
void DriverUnload(PDRIVER_OBJECT pDriverObject)
{
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegisterPath)
{
pDriverObject->DriverUnload = DriverUnload;
if (*NtBuildNumber < 17134)
return STATUS_NOT_SUPPORTED;
EnumDrvSectionByImageTree();
return STATUS_SUCCESS;
}
效果图:
最后:在win10启用了影子页之后,很多可以hook的函数都无法hook了,虽然可以把hook函数加入影子页,但这种操作只能在ntos刚启动没多久的情况下执行.
个人感觉以后的内核木马,都以efi,boot起步了.这甚至可以使PatchGuard来保护已经篡改的数据.
再再再最后:
几年前我的一篇博客,最后留言可谓是迷茫至极,那也是我黑暗的时光,前段时间,那篇博客收到了一个回复,有关太阳,有关阳光,虽然现在自身已无当时那样的迷茫,但多少还是有一些负能量的东西.
前几天看到一个新闻,有关信仰。让自己不得不重新审视自己。终究是太敏感,太脆弱了。是啊,只有自己转动,才能看到光。谢谢那位陌生人。
真正能让你走远的 都是自律积极和勤奋。共勉。