Win32 驱动隐藏进程内存
原理
在进程的 _EPROCESS 中有一个 _RTL_AVL_TREE 类型的 VadRoot 成员,它是一个存放进程内存块的二叉树结构,如果我们找到了这个二叉树中我们想要隐藏的内存,直接将这个内存在二叉树中 “抹去”(其实是让上一个节点的 EndingVpn 指向下个节点的 EndingVpn ,让两个节点融合) 就可以达到隐藏的效果。
过程
比如你在代码中申请了两块内存:
int main()
{
LPVOID p1 = VirtualAlloc(NULL, 0x10000, MEM_COMMIT, PAGE_READWRITE);
LPVOID p2 = VirtualAlloc(NULL, 0x10000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
printf("p1 = %p / p2 = %p", p1, p2);
getchar();
return 0;
}
这样打印出来:
找到这个进程的 _EPROCESS ,拿到它对应的 VadRoot:
使用 !vad 命令打印所有的节点,这里我们看到 windbg 中的值和进程中分配的内存地址并不完全一样,这是因为 x86 cpu 默认内存页大小 = 4k(也就是 0x1000),所以这里还要再乘以 0x1000 才是真正的内存地址。
代码实现
以下代码来自:
#include
//---------------------//
// MMVAD结构体简单定义 //
//---------------------//
typedef struct _MMVAD {
ULONG StartingVpn;
ULONG EndingVpn;
struct _MMVAD * Parent;
struct _MMVAD * LeftChild;
struct _MMVAD * RightChild;
}MMVAD,*PMMVAD;
VOID Unload(IN PDRIVER_OBJECT pDriverObject) {
DbgPrint("Driver UnLoad!");
}
//-----------//
// 遍历VAD树 //
//-----------//
PMMVAD vad_enum(PMMVAD pVad,ULONG target_StartingVpn) {
//---------------------------//
// 遍历目标VAD,并返回其指针 //
//---------------------------//
if (pVad) {
if (target_StartingVpn == pVad->StartingVpn) {
_asm int 3
return pVad;
}
else {
if (pVad->LeftChild) {
PMMVAD p1 = vad_enum(pVad->LeftChild, target_StartingVpn);
// 如果结点不为空,则直接返回就好。
// 否则继续判断其右子树结点。
if (p1)
return p1;
}
if (pVad->RightChild) {
PMMVAD p2 = vad_enum(pVad->RightChild, target_StartingVpn);
if (p2)
return p2;
}
return NULL;
}
}
return NULL;
}
//-------------------------------------------------------------//
// 在内核中进程遍历的原理就是先获取系统进程EPROCESS结构 //
// 然后依照其链表来获取其他的进程 //
// 依次遍历出来 //
//-------------------------------------------------------------//
NTSTATUS process_enum() {
PEPROCESS pEprocess = NULL; // 得到系统进程地址
PEPROCESS pFirstEprocess = NULL;
ULONG ulProcessName = 0; // 字符串指针,指向进程名称
ULONG ulProcessID = 0; // 进程ID
ANSI_STRING target_str; // 带检测进程的名称
ANSI_STRING ansi_string; //
ULONG VadRoot;
//----------------------------//
// 得到当前系统进程的EPROCESS //
//----------------------------//
pEprocess = PsGetCurrentProcess();
if (pEprocess == NULL) {
DbgPrint("获取当前系统进程EPROCESS错误..");
return STATUS_SUCCESS;
}
DbgPrint("pEprocess addr is %x0x8\r\n", pEprocess);
pFirstEprocess = pEprocess;
while (pEprocess) {
ulProcessName = (ULONG)pEprocess + 0x174;
ulProcessID = *(ULONG*)((ULONG)pEprocess + 0x84);
VadRoot = *(ULONG*)((ULONG)pEprocess + 0x11c);
//--------------------------------------//
// 将目标进程与当前进程的进程名进行对比 //
//--------------------------------------//
RtlInitAnsiString(&ansi_string, (PCSTR)ulProcessName);
RtlInitAnsiString(&target_str, "test.exe");
if (RtlEqualString(&ansi_string, &target_str, TRUE)) {
DbgPrint("检测到进程字符串,%x", ulProcessID);
PMMVAD p1 = vad_enum((PMMVAD)VadRoot,0x3a0); // 遍历第一个结点
PMMVAD p2 = vad_enum((PMMVAD)VadRoot, 0x3b0); // 遍历找到第二个结点
_asm int 3
if(p1 && p2)
p1->EndingVpn = p2->EndingVpn; // 将第二个结点完全隐藏起来
return STATUS_SUCCESS;
}
pEprocess = (PEPROCESS)(*(ULONG*)((ULONG)pEprocess + 0x88) - 0x88);
if (pEprocess == pFirstEprocess || *(ULONG*)((ULONG)pEprocess + 0x84) <= 0) {
DbgPrint("遍历结束!未检测到进程ID!\r\n");
break;
}
}
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING registeryPat) {
DbgPrint("Driver Loaded!");
pDriverObject->DriverUnload = Unload;
process_enum();
return STATUS_SUCCESS;
}