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;
}

相关