通过PsSetLoadImageNotifyRoutine学习模块监控与反模块监控
2021-12-20 17:59:0 Author: mp.weixin.qq.com(查看原文) 阅读量:27 收藏


本文为看雪论坛优秀文章
看雪论坛作者ID:1900

本文要实现的是对模块加载的监控,并卸载掉目标模块。实验是在WIN 7 X86系统上进行,需要使用的文件是InjectDll.dll和TestDriver.sys。这两个文件的内容非常简单,InjectDll.dll在加载进进程中以后只会弹出加载的对话框,而TestDriver.sys只是在驱动加载和卸载的时候打印字符提醒。

模块监控

1、实现原理

在内核中可以通过PsSetLoadImageNotifyRoutine来设置模块监控,监控系统中各个应用程序加载的DLL以及系统加载的驱动。在函数在中文档的定义如下:
NTSTATUS  PsSetLoadImageNotifyRoutine(    IN PLOAD_IMAGE_NOTIFY_ROUTINE  NotifyRoutine);

其中NotifyRoutine是一个LOAD_IMAGE_NOTIFY_ROUTINE的函数指针,该函数在文档中的定义如下:
typedefVOID(*PLOAD_IMAGE_NOTIFY_ROUTINE)(    __in PUNICODE_STRING FullImageName,    __in HANDLE ProcessId,                // pid into which image is being mapped    __in PIMAGE_INFO ImageInfo    );

参数
说明
FullImageName
指向模块的完整路径
ProcessId
加载模块的进程PID。如果是驱动文件,该值为0
ImageInfo
指向IMAGE_INFO结构体,包含了模块信息
IMAGE_INFO在文档中的定义如下:
typedef struct _IMAGE_INFO {    union {        ULONG Properties;        struct {            ULONG ImageAddressingMode  : 8;  // Code addressing mode            ULONG SystemModeImage      : 1;  // System mode image            ULONG ImageMappedToAllPids : 1;  // Image mapped into all processes            ULONG ExtendedInfoPresent  : 1;  // IMAGE_INFO_EX available            ULONG Reserved             : 21;        };    };    PVOID       ImageBase;    ULONG       ImageSelector;    SIZE_T      ImageSize;    ULONG       ImageSectionNumber;} IMAGE_INFO, *PIMAGE_INFO;

最关键的两个是ImageBase和ImageSize,分别代表了模块的基地址和模块的大小。
而要卸载模块监控,则需要使用PsRemoveLoadImageNotifyRoutine,该函数在文档中的定义如下,参数含义和设置模块监控一样。
NTSTATUS  PsRemoveLoadImageNotifyRoutine(    IN PLOAD_IMAGE_NOTIFY_ROUTINE  NotifyRoutine );

驱动的卸载和DLL的卸载是不一样的。
对于驱动的卸载,只需要找到驱动入口点,并且直接返回STATUS_ACCESS_DENIED(0xC0000022)错误码就好。这样,已经加载的驱动就会在执行的时候出错,导致驱动启动失败。
对于DLL的卸载则需要使用未到出函数MmUnmapViewOfSection用来进行卸载。当由于加载进程模块的时候,系统会有一个内部锁,为了避免死锁。要通过创建线程进程延迟等待,等待DLL加载完成以后在调用函数进行卸载。
具体代码如下:
#include <ntifs.h>#include <ntimage.h> #define DRIVER_NAME L"TestDriver.sys" //要拦截的驱动名#define DLL_NAME L"InjectDll.dll" //要拦截的DLL名 typedef struct _DLL_INFO{    HANDLE ProcessId;    PVOID pImageBase;}DLL_INFO, *PDLL_INFO; //Dll的信息,用来作为线程的参数传递 VOID DriverUnload(IN PDRIVER_OBJECT driverObject);VOID LoadImageNotifyRoutine(PUNICODE_STRING FullImageName, HANDLE ProcessId, PIMAGE_INFO ImageInfo);  //模块监控回调函数BOOLEAN DenyLoadDriver(PVOID pLoadImageBase); //对驱动的加载进行拦截BOOLEAN DenyLoadDll(HANDLE ProcessId, PVOID pImageBase);    //对DLL的加载进行拦截NTSTATUS MmUnmapViewOfSection(PEPROCESS Process, PVOID BaseAddr);   //未导出函数声明VOID ThreadProc(PVOID StartContext);  //运行的线程函数 NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath){    NTSTATUS status = STATUS_SUCCESS;     DbgPrint("驱动加载完成\r\n");    status = PsSetLoadImageNotifyRoutine(LoadImageNotifyRoutine);    if (!NT_SUCCESS(status))    {        DbgPrint("模块监控设置失败 0x%X\r\n", status);    }    else    {        DbgPrint("模块监控设置成功\r\n");    }exit:    driverObject->DriverUnload = DriverUnload;    return STATUS_SUCCESS;} VOID LoadImageNotifyRoutine(PUNICODE_STRING FullImageName, HANDLE ProcessId, PIMAGE_INFO ImageInfo){    PDLL_INFO pDllInfo = NULL;    HANDLE hThread = NULL;     /*    DbgPrint("===========================================================================\r\n");    DbgPrint("检测到新加载的模块,模块信息如下:\r\n");    DbgPrint("加载该模块的进程ID:%d 模块完整名:%wZ 模块基址:0x%X 模块大小:0x%X", ProcessId, FullImageName, ImageInfo->ImageBase, ImageInfo->ImageSize);    DbgPrint("===========================================================================\r\n");    */     // 是否是exe或者dll文件    if (ProcessId)    {        if (wcsstr(FullImageName->Buffer, DLL_NAME) != NULL)        {            pDllInfo = (PDLL_INFO)ExAllocatePool(NonPagedPool, sizeof(DLL_INFO));            if (!pDllInfo)            {                DbgPrint("ExAllocatePool Error");            }            else            {                pDllInfo->ProcessId = ProcessId;                pDllInfo->pImageBase = ImageInfo->ImageBase;                PsCreateSystemThread(&hThread, 0, NULL, NtCurrentProcess(), NULL, ThreadProc, pDllInfo);                if (hThread) ZwClose(hThread);            }        }    }    else    {        //加载的是驱动,判断是否是要拦截的驱动        if (wcsstr(FullImageName->Buffer, DRIVER_NAME) != NULL )        {            if (DenyLoadDriver(ImageInfo->ImageBase))            {                DbgPrint("成功拦截驱动%wZ的加载\r\n", FullImageName);            }        }    }} VOID ThreadProc(PVOID StartContext){    PDLL_INFO pDllInfo = (PDLL_INFO)StartContext;    LARGE_INTEGER liTime = { 0 };     //延时5秒    liTime.QuadPart = -50 * 1000 * 1000;    KeDelayExecutionThread(KernelMode, FALSE, &liTime);    //卸载DLL    if (DenyLoadDll(pDllInfo->ProcessId, pDllInfo->pImageBase))    {        DbgPrint("Dll卸载完成\r\n");    }     if (pDllInfo) ExFreePool(pDllInfo);} BOOLEAN DenyLoadDll(HANDLE ProcessId, PVOID pImageBase){    BOOLEAN  bRet = TRUE;    NTSTATUS status = STATUS_SUCCESS;    PEPROCESS pEprocess = NULL;  //保存加载DLL的进程的EPROCESS     //根据进程PID获取EPROCESS    status = PsLookupProcessByProcessId(ProcessId, &pEprocess);     if (!NT_SUCCESS(status))    {        DbgPrint("PsLookupProcessByProcessId Error 0x%X", status);        bRet = FALSE;        goto exit;    }     //卸载模块    status = MmUnmapViewOfSection(pEprocess, pImageBase);    if (!NT_SUCCESS(status))    {        DbgPrint("MmUnmapViewOfSection Error 0x%X\r\n", status);        bRet = FALSE;        goto exit;    }exit:    return bRet;} BOOLEAN DenyLoadDriver(PVOID pLoadImageBase){    BOOLEAN bRet = TRUE;    NTSTATUS status = STATUS_SUCCESS;    PVOID pVoid = NULL;    PIMAGE_DOS_HEADER pDosHead = NULL;    PIMAGE_NT_HEADERS pNtHeader = NULL;    PVOID pDriverEntry = NULL;    PMDL pMdl = NULL;    // 要写入的ShellCode,硬编码的意思是    // mov eax, 0xC0000022    // ret    UCHAR szShellCode[6] = { 0xB8, 0x22, 0x00, 0x00, 0xC0, 0xC3};    ULONG uShellCodeLength = 6;     pDosHead = (PIMAGE_DOS_HEADER)pLoadImageBase;    pNtHeader = (PIMAGE_NT_HEADERS)((ULONG)pLoadImageBase + pDosHead->e_lfanew);    pDriverEntry = (PVOID)((ULONG)pDosHead + pNtHeader->OptionalHeader.AddressOfEntryPoint); //获取驱动入口点位置     //创建MDL并为内存属性添加可写属性    pMdl = MmCreateMdl(NULL, pDriverEntry, uShellCodeLength);    if (pMdl == NULL)    {        bRet = FALSE;        goto exit;    }     //建立内存页的MDL描述    MmBuildMdlForNonPagedPool(pMdl);    //改变MDL的标记为可写    pMdl->MdlFlags |= MDL_MAPPED_TO_SYSTEM_VA;    //映射MDL空间    pVoid = MmMapLockedPages(pMdl, KernelMode);    //将shellcode拷到目标地址    RtlCopyMemory(pVoid, szShellCode, uShellCodeLength);    //释放MDL    MmUnmapLockedPages(pVoid, pMdl);    IoFreeMdl(pMdl);    pMdl = NULL;exit:    return bRet;} VOID DriverUnload(IN PDRIVER_OBJECT driverObject){    NTSTATUS status = STATUS_SUCCESS;     status = PsRemoveLoadImageNotifyRoutine(LoadImageNotifyRoutine);    if (!NT_SUCCESS(status))    {        DbgPrint("模块监控删除失败 0x%X\r\n", status);    }    else    {        DbgPrint("模块监控删除成功\r\n");    }    DbgPrint("驱动卸载完成\r\n");}

2、实验结果

对于DLL加载来说,当没有开起监控程序卸载的时候,在加载DLL文件以后可以在监控软件中查找到对应的DLL。
而当开起监控完成DLL卸载以后,就没办法找到相应的DLL了。
对于驱动来说,当没有开起监控的时候,可以正常的加载与卸载。
而开起监控以后,驱动的加载就会被拦截。

反模块监控

1、逆向分析PsSetLoadImageNotifyRoutine

PsSetLoadImageNotifyRoutine在IDA中的反汇编代码如下:
PAGE:005709B3 ; NTSTATUS __stdcall PsSetLoadImageNotifyRoutine(PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine)PAGE:005709B3                 public PsSetLoadImageNotifyRoutinePAGE:005709B3 PsSetLoadImageNotifyRoutine proc near   ; CODE XREF: sub_580241+215↓pPAGE:005709B3PAGE:005709B3 NotifyRoutine   = dword ptr  8PAGE:005709B3PAGE:005709B3                 mov     edi, ediPAGE:005709B5                 push    ebpPAGE:005709B6                 mov     ebp, espPAGE:005709B8                 push    ebxPAGE:005709B9                 push    esiPAGE:005709BA                 push    ediPAGE:005709BB                 xor     edi, edi        ; edi清0PAGE:005709BD                 push    edi             ; 将0入栈PAGE:005709BE                 push    [ebp+NotifyRoutine] ; 将函数地址入栈PAGE:005709C1                 call    AllocateAssign  ; 分配12字节的内存,低4位和高4位清0,中间4位存放函数地址PAGE:005709C6                 mov     ebx, eax        ; 申请到的内存地址赋给ebxPAGE:005709C8                 cmp     ebx, edi        ; 判断是否为0,也就是是否分配失败PAGE:005709CA                 jz      short loc_5709F1 ; 如果内存分配失败则退出PAGE:005709CC                 mov     esi, offset LoadImageFuncArray ; 将一个数组地址赋给esiPAGE:005709D1PAGE:005709D1 loc_5709D1:                             ; CODE XREF: PsSetLoadImageNotifyRoutine+36↓jPAGE:005709D1                 push    0               ; 将0入栈PAGE:005709D3                 mov     ecx, ebx        ; 将分配的地址赋给ecxPAGE:005709D5                 mov     eax, esi        ; 将数组地址赋给eaxPAGE:005709D7                 call    SetArrayPAGE:005709DC                 test    al, alPAGE:005709DE                 jnz     short loc_5709FD ; 判断返回值是否为0,不为0则执行成功,跳转PAGE:005709E0                 add     edi, 4          ; edi加4PAGE:005709E3                 add     esi, 4          ; esi,也就是数组地址+4,所以这是在取数组的下一个元素PAGE:005709E6                 cmp     edi, 20h        ; 判断edi是否小于0x20,小于则跳转过去执行函数PAGE:005709E9                 jb      short loc_5709D1 ; 根据前面的清0操作知道这里是为了保证循环调用8次,所以可以判断这个数组中最多保存7个地址PAGE:005709EB                 push    ebx             ; BufferPAGE:005709EC                 call    FreeAllocate    ; 否则函数执行失败,释放申请到的内存PAGE:005709F1PAGE:005709F1 loc_5709F1:                             ; CODE XREF: PsSetLoadImageNotifyRoutine+17↑jPAGE:005709F1                 mov     eax, STATUS_INSUFFICIENT_RESOURCES ; 赋值失败的返回值PAGE:005709F6PAGE:005709F6 loc_5709F6:                             ; CODE XREF: PsSetLoadImageNotifyRoutine+6B↓jPAGE:005709F6                 pop     ediPAGE:005709F7                 pop     esiPAGE:005709F8                 pop     ebxPAGE:005709F9                 pop     ebpPAGE:005709FA                 retn    4PAGE:005709FD ; ---------------------------------------------------------------------------PAGE:005709FDPAGE:005709FD loc_5709FD:                             ; CODE XREF: PsSetLoadImageNotifyRoutine+2B↑jPAGE:005709FD                 xor     ecx, ecxPAGE:005709FF                 mov     eax, offset unk_542BA0PAGE:00570A04                 inc     ecxPAGE:00570A05                 lock xadd [eax], ecxPAGE:00570A09                 mov     eax, dword_542B78PAGE:00570A0E                 test    al, 1PAGE:00570A10                 jnz     short loc_570A1CPAGE:00570A12                 mov     eax, offset dword_542B78PAGE:00570A17                 lock bts dword ptr [eax], 0PAGE:00570A1CPAGE:00570A1C loc_570A1C:                             ; CODE XREF: PsSetLoadImageNotifyRoutine+5D↑jPAGE:00570A1C                 xor     eax, eaxPAGE:00570A1E                 jmp     short loc_5709F6PAGE:00570A1E PsSetLoadImageNotifyRoutine endp

基本上和这篇一样:
通过对PsSetCreateProcessNotifyRoutineEx的逆向分析得出的结果来实现反进程监控(https://bbs.pediy.com/thread-269831.htm)
那这篇就不再详细说,唯一的区别就是这里的数组的大小是8。

2、反模块监控

接下来就根据下图选择0x7425BE作为硬编码来查找模块数组:
得出如下的反模块监控代码:
#include <ntifs.h>#include <ntimage.h> VOID DriverUnload(IN PDRIVER_OBJECT driverObject);PULONG GetImageArray(); NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath){    NTSTATUS status = STATUS_SUCCESS;    PULONG pImageArray = NULL;    PULONG pFuncAddr = NULL;    ULONG i = 0;     pImageArray = GetImageArray();    if (pImageArray)    {        for (i = 0; i < 8; i++)        {            if (pImageArray[i] & ~0x7)            {                pFuncAddr = (PULONG)(pImageArray[i] & ~0x7 + 4);                status = PsRemoveLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)*pFuncAddr);                if (NT_SUCCESS(status))                {                    DbgPrint("模块监控删除成功 模块地址:0x%X\r\n", *pFuncAddr);                }            }        }    }     exit:    driverObject->DriverUnload = DriverUnload;    return STATUS_SUCCESS;} VOID DriverUnload(IN PDRIVER_OBJECT driverObject){    DbgPrint("驱动卸载完成\r\n");} PULONG GetImageArray(){    PULONG pImageArray = NULL;    //要获取的函数地址的函数名    UNICODE_STRING uStrFuncName = RTL_CONSTANT_STRING(L"PsSetLoadImageNotifyRoutine");    PUCHAR pPsSetCreateThreadNotifyRoutine = NULL;     //获取函数地址    pPsSetCreateThreadNotifyRoutine = (PUCHAR)MmGetSystemRoutineAddress(&uStrFuncName);    while (*pPsSetCreateThreadNotifyRoutine != 0xC2)    {        if (*pPsSetCreateThreadNotifyRoutine == 0x74 &&             *(pPsSetCreateThreadNotifyRoutine + 1) == 0x25 &&             *(pPsSetCreateThreadNotifyRoutine + 2) == 0xBE)        {            pImageArray = (PULONG)*(PULONG)(pPsSetCreateThreadNotifyRoutine + 3);            break;        }        pPsSetCreateThreadNotifyRoutine++;    }     return pImageArray;}


3、运行截图

首先在驱动卸载前,可以看到所有模块的加载都会监控到:
接下来运行反监控代码可以看到模块的加载就不再被监控到:

 

看雪ID:1900

https://bbs.pediy.com/user-home-835440.htm

*本文由看雪论坛 1900 原创,转载请注明来自看雪社区

# 往期推荐

1.Kernel从0开始

2.常见的几种DLL注入技术

3.侠盗猎车 — 玩转固定码

4.Java正则表达式笔记总结

5.Frida动静态结合分析Base64签名校验的四个变种

6.二进制漏洞挖掘学习——MS09-050

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458410644&idx=1&sn=e36fcc9b2b7a1efffb84bc3673b248b6&chksm=b18f6e1e86f8e708e321843fbdc68404b28db00aa48fed2c974a402a7708db095f94820c3da1#rd
如有侵权请联系:admin#unsafe.sh