DLL 注入 —— 反射式注入(内存手动映射)

介绍

反射式注入 dll ,不会调用 LoadLibrary 这个 API,因此也无法使用 CreateToolhelp32Snapshot 遍历到这个模块。同时也不需要 DLL 留在磁盘上(可以通过网络下发,或加密后存放在磁盘),因此这种注入方式更加隐蔽。

原理

总的来说,就是我们要重新实现一遍 LoadLibrary 这个 API :

  • 假设现在我们已经使用 ReadFile 拿到了 DLL 的所有内容
  • 之后我们需要调用 VirtualAlloc 在目标进程中申请一块内存用来存放这个 DLL
  • 使用 WriteProcessMemory 将 DLL 的内容写入刚申请的虚拟内存中
  • 关键 这个 DLL 中需要有一个导出函数,我们暂且叫它 ReflectiveLoader,这个函数的功能就是装载自身。所以我们只需要等到 DLL 被载入内存后,使用 CreateRemoteThread 创建一个远程线程来调用这个导出函数。

最后总结一点,核心问题就是如何编写这个 ReflectiveLoader 函数

另外就是,因为调用 ReflectiveLoader 时, DLL 还没有加载(毕竟人家的功能就是加载 DLL。。),在编写这个函数的时候就会有很多限制(比如无法使用全局变量)。

DLL 自装载

因为 PE 文件包含了很多区段(节),为了节省空间,这些区段在磁盘上存储时是很紧凑的,如果把它们原模原样的放入内存中运行一定是会出问题的。所以 DLL 子装载函数的任务就是按照规则把这些区段按照规则映射到对应的虚拟地址中去。另外我们写的 DLL 会用到其他的 DLL (相对于被注入进程来说),这时我们还需要把我们 DLL 所依赖的 DLL 也装入内存,并修复导入表。

  • 首先使用 _ReturnAddress() 获取当前函数的返回地址,因为调用这个函数是在 ReflectiveLoader 的内部,因此从这个地址向上遍历,找 0x4d ,0x5a 就可以定位到 DLL 的 PE 文件头所在的虚拟地址 (o゚v゚)ノ

  • 使用内联汇编 mov EAX, FS:[0x30] 拿到 PEB ,用 PEB 遍历出目标进程所有模块的基地址,之后通过解析 PE 文件的导出表获取导出函数的偏移地址,基地址+偏移计算出我们需要的函数( LoadLibrary, GetProcAddress, VirtualAlloc,NtFlushInstructionCache)的虚拟地址

  • 虽然在调用 ReflectiveLoader 前,我们写的注入器程序已经在目标进程申请了一块空间,但是那是存放的是 DLL 在磁盘上的结构,要将 DLL 映射到内存需要重新分配内存。在 IMAGE_OPTIONAL_HEADER -> SizeOfImage 记录了这个 DLL 装入内存时占用的大小,用这个值作为 VirtualAlloc 的参数。

  • 将 DLL 的 PE文件头和各个节复制到对应的位置。

  • 被注入的 DLL可能还依赖于其他的 DLL,因此我们还需要使用 LoadLibrary 加载这些 DLL(LoadLibrary 的地址在上面已经拿到)

  • 被注入的 DLL 只有其 ReflectiveLoader 中的代码是故意写成地址无关、不需要重定位的,其他部分的代码则需要经过重定位才能正确运行。对于重定位问题,PE 的可选头中 DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC] 指向了重定位表:
    在这里插入图片描述
    IMAGE_BASE_RELOCATION结构和后面紧跟的若干个Typeoffset组成了一个块,其大小为结构体中的SizeOfBlock。因此,Typeoffset的数量可以根据SizeofBlock算出。当一个块结束时,后面紧跟的就是下一个块。若SizeofBlock为0则标志着重定位表结束了。Typeoffset的高4位代表重定位类型,一般为3,低12位则表示重定位地址。这个地址和IMAGE_BASE_RELOCATION中的VirtualAddress加起来则指向一个需要重定位的指令。

  • DLL重定位。首先计算得到基地址的偏移量,也就是实际的 DLL 加载地址减去 DLL 的推荐加载地址(保存在 NT可选头的 ImageBase 中,实际 DLL 加载地址是我们调用 VirtualAlloc()的返回值。然后将 VirtualAddress 和 Typeoffset 合力组成的地址所指向的双字加上这个偏移量,重定位就完成了。即:*(DWORD*)(VirtualAddress + Typeoffset的低12位) += (实际DLL加载地址 – 推荐DLL加载地址)

代码参考

https://github.com/SudoZhange/ProcessInjection
https://github.com/DarthTon/Xenos

以下代码来自 blackbone 框架 :

r3:


#define IOCTL_BLACKBONE_INJECT_DLL  (ULONG)CTL_CODE(FILE_DEVICE_BLACKBONE, 0x80B, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)

typedef enum _MmapFlags
{
    KNoFlags         = 0x00,    // No flags
    KManualImports   = 0x01,    // Manually map import libraries
    KWipeHeader      = 0x04,    // Wipe image PE headers
    KHideVAD         = 0x10,    // Make image appear as PAGE_NOACESS region
    KRebaseProcess   = 0x40,    // If target image is an .exe file, process base address will be replaced with mapped module value
    KNoThreads       = 0x80,    // Don't create new threads, use hijacking

    KNoExceptions    = 0x01000, // Do not create custom exception handler
    KNoSxS           = 0x08000, // Do not apply SxS activation context
    KNoTLS           = 0x10000, // Skip TLS initialization and don't execute TLS callbacks
} KMmapFlags;

typedef enum _InjectType
{
    IT_Thread,      // CreateThread into LdrLoadDll
    IT_Apc,         // Force user APC into LdrLoadDll
    IT_MMap,        // Manual map
} InjectType;

typedef struct _INJECT_DLL
{
    InjectType type;                // Type of injection
    wchar_t    FullDllPath[512];    // Fully-qualified path to the target dll
    wchar_t    initArg[512];        // Init routine argument
    ULONG      initRVA;             // Init routine RVA, if 0 - no init routine
    ULONG      pid;                 // Target process ID
    BOOLEAN    wait;                // Wait on injection thread
    BOOLEAN    unlink;              // Unlink module after injection
    BOOLEAN    erasePE;             // Erase PE headers after injection   
    KMmapFlags flags;               // Manual map flags
    ULONGLONG  imageBase;           // Image address in memory to manually map
    ULONG      imageSize;           // Size of memory image
    BOOLEAN    asImage;             // Memory chunk has image layout
} INJECT_DLL, *PINJECT_DLL;

/* -------------------------------------------------------- */

BOOL DriverControl::MmapDll( 
    DWORD pid, 
    void* address, 
    uint32_t size, 
    bool asImage,
    KMmapFlags flags,
    uint32_t initRVA /*= 0*/, 
    const std::wstring& initArg /*= L"" */ 
    )
{
	DWORD bytes = 0;
    INJECT_DLL data = { IT_MMap };

	memset( data.FullDllPath, 0, sizeof( data.FullDllPath ) );
    wcscpy_s( data.initArg, initArg.c_str() );

	data.pid = pid;
    data.initRVA = initRVA;
    data.wait = true;
    data.unlink = false;
    data.erasePE = false;
    data.flags = flags;
    data.imageBase = (ULONGLONG)address;
    data.imageSize = size;
    data.asImage = asImage;

	if (!DeviceIoControl(驱动设备句柄, IOCTL_BLACKBONE_INJECT_DLL, &data, sizeof( data ), nullptr, 0, &bytes, NULL ))
        return FALSE();

    return TRUE;
}

r0:


typedef enum _InjectType
{
    IT_Thread,      // CreateThread into LdrLoadDll
    IT_Apc,         // Force user APC into LdrLoadDll
    IT_MMap,        // Manual map
} InjectType;

typedef enum _PolicyOpt
{
    Policy_Disable,
    Policy_Enable,
    Policy_Keep,        // Don't change current value
} PolicyOpt;

typedef struct _SET_PROC_PROTECTION
{
    ULONG pid;              // Process ID
    PolicyOpt protection;   // Process protection
    PolicyOpt dynamicCode;  // DynamiCode policy
    PolicyOpt signature;    // BinarySignature policy
} SET_PROC_PROTECTION, *PSET_PROC_PROTECTION;

/*------------------- 篇幅原因,只贴上核心代码 -------------------*/



/// <summary>
/// Inject dll into process
/// </summary>
/// <param name="pid">Target PID</param>
/// <param name="pPath">TFull-qualified dll path</param>
/// <returns>Status code</returns>
NTSTATUS BBInjectDll( IN PINJECT_DLL pData )
{
    NTSTATUS status = STATUS_SUCCESS;
    NTSTATUS threadStatus = STATUS_SUCCESS;
    PEPROCESS pProcess = NULL;

    status = PsLookupProcessByProcessId( (HANDLE)pData->pid, &pProcess );
    if (NT_SUCCESS( status ))
    {
        KAPC_STATE apc;
        UNICODE_STRING ustrPath, ustrNtdll;
        SET_PROC_PROTECTION prot = { 0 };
        PVOID pNtdll = NULL;
        PVOID LdrLoadDll = NULL;
        PVOID systemBuffer = NULL;
        BOOLEAN isWow64 = (PsGetProcessWow64Process( pProcess ) != NULL) ? TRUE : FALSE;

        // Process in signaled state, abort any operations
        if (BBCheckProcessTermination( PsGetCurrentProcess() ))
        {
            DPRINT( "BlackBone: %s: Process %u is terminating. Abort\n", __FUNCTION__, pData->pid );
            if (pProcess)
                ObDereferenceObject( pProcess );

            return STATUS_PROCESS_IS_TERMINATING;
        }

        // Copy mmap image buffer to system space.
        // Buffer will be released in mapping routine automatically
        if (pData->type == IT_MMap && pData->imageBase)
        {
            __try
            {
                ProbeForRead( (PVOID)pData->imageBase, pData->imageSize, 1 );
                systemBuffer = ExAllocatePoolWithTag( PagedPool, pData->imageSize, BB_POOL_TAG );
                RtlCopyMemory( systemBuffer, (PVOID)pData->imageBase, pData->imageSize );
            }
            __except (EXCEPTION_EXECUTE_HANDLER)
            {
                DPRINT( "BlackBone: %s: AV in user buffer: 0x%p - 0x%p\n", __FUNCTION__, 
                        pData->imageBase, pData->imageBase + pData->imageSize );

                if (pProcess)
                    ObDereferenceObject( pProcess );

                return STATUS_INVALID_USER_BUFFER;
            }
        }

        KeStackAttachProcess( pProcess, &apc );

        RtlInitUnicodeString( &ustrPath, pData->FullDllPath );
        RtlInitUnicodeString( &ustrNtdll, L"Ntdll.dll" );

        // Handle manual map separately
        if (pData->type == IT_MMap)
        {
            MODULE_DATA mod = { 0 };

            __try {
                status = BBMapUserImage(
                    pProcess, &ustrPath, systemBuffer,
                    pData->imageSize, pData->asImage, pData->flags,
                    pData->initRVA, pData->initArg, &mod
                    );
            }
            __except (EXCEPTION_EXECUTE_HANDLER){
                DPRINT( "BlackBone: %s: Fatal exception in BBMapUserImage. Exception code 0x%x\n", __FUNCTION__, GetExceptionCode() );
            }

            KeUnstackDetachProcess( &apc );

            if (pProcess)
                ObDereferenceObject( pProcess );

            return status;
        }

        // Get ntdll base
        pNtdll = BBGetUserModule( pProcess, &ustrNtdll, isWow64 );

        if (!pNtdll)
        {
            DPRINT( "BlackBone: %s: Failed to get Ntdll base\n", __FUNCTION__ );
            status = STATUS_NOT_FOUND;
        }

        // Get LdrLoadDll address
        if (NT_SUCCESS( status ))
        {
            LdrLoadDll = BBGetModuleExport( pNtdll, "LdrLoadDll", pProcess, NULL );
            if (!LdrLoadDll)
            {
                DPRINT( "BlackBone: %s: Failed to get LdrLoadDll address\n", __FUNCTION__ );
                status = STATUS_NOT_FOUND;
            }
        }

        // If process is protected - temporarily disable protection
        if (PsIsProtectedProcess( pProcess ))
        {
            prot.pid         = pData->pid;
            prot.protection  = Policy_Disable;
            prot.dynamicCode = Policy_Disable;
            prot.signature   = Policy_Disable;
            BBSetProtection( &prot );
        }

        // Call LdrLoadDll
        if (NT_SUCCESS( status ))
        {
            SIZE_T size = 0;
            PINJECT_BUFFER pUserBuf = isWow64 ? BBGetWow64Code( LdrLoadDll, &ustrPath ) : BBGetNativeCode( LdrLoadDll, &ustrPath );

            if (pData->type == IT_Thread)
            {
                status = BBExecuteInNewThread( pUserBuf, NULL, THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER, pData->wait, &threadStatus );

                // Injection failed
                if (!NT_SUCCESS( threadStatus ))
                {
                    status = threadStatus;
                    DPRINT( "BlackBone: %s: User thread failed with status - 0x%X\n", __FUNCTION__, status );
                }
                // Call Init routine
                else
                {
                    if (pUserBuf->module != 0 && pData->initRVA != 0)
                    {
                        RtlCopyMemory( pUserBuf->buffer, pData->initArg, sizeof( pUserBuf->buffer ) );
                        BBExecuteInNewThread(
                            (PUCHAR)pUserBuf->module + pData->initRVA, 
                            pUserBuf->buffer,
                            THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER, 
                            TRUE, 
                            &threadStatus
                            );
                    }
                    else if (pUserBuf->module == 0)
                        DPRINT( "BlackBone: %s: Module base = 0. Aborting\n", __FUNCTION__ );
                }
            }
            else if (pData->type == IT_Apc)
            {
                status = BBApcInject( pUserBuf, pProcess, pData->initRVA, pData->initArg );
            }
            else
            {
                DPRINT( "BlackBone: %s: Invalid injection type specified - %d\n", __FUNCTION__, pData->type );
                status = STATUS_INVALID_PARAMETER;
            }

            // Post-inject stuff
            if (NT_SUCCESS( status ))
            {
                // Unlink module
                if (pData->unlink)
                    BBUnlinkFromLoader( pProcess, pUserBuf->module, isWow64 );

                // Erase header
                if (pData->erasePE)
                {
                    __try
                    {
                        PIMAGE_NT_HEADERS64 pHdr = RtlImageNtHeader( pUserBuf->module );
                        if (pHdr)
                        {
                            ULONG oldProt = 0;
                            size = (pHdr->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) ?
                                ((PIMAGE_NT_HEADERS32)pHdr)->OptionalHeader.SizeOfHeaders :
                                pHdr->OptionalHeader.SizeOfHeaders;

                            if (NT_SUCCESS( ZwProtectVirtualMemory( ZwCurrentProcess(), &pUserBuf->module, &size, PAGE_EXECUTE_READWRITE, &oldProt ) ))
                            {
                                RtlZeroMemory( pUserBuf->module, size );
                                ZwProtectVirtualMemory( ZwCurrentProcess(), &pUserBuf->module, &size, oldProt, &oldProt );

                                DPRINT( "BlackBone: %s: PE headers erased. \n", __FUNCTION__ );
                            }
                        }
                        else
                            DPRINT( "BlackBone: %s: Failed to retrieve PE headers for image\n", __FUNCTION__ );
                    }
                    __except (EXCEPTION_EXECUTE_HANDLER)
                    {
                        DPRINT( "BlackBone: %s: Exception during PE header erease: 0x%X\n", __FUNCTION__, GetExceptionCode() );
                    }
                }
            }

            ZwFreeVirtualMemory( ZwCurrentProcess(), &pUserBuf, &size, MEM_RELEASE );
        }

        // Restore protection
        if (prot.pid != 0)
        {
            prot.protection  = Policy_Enable;
            prot.dynamicCode = Policy_Enable;
            prot.signature   = Policy_Enable;
            BBSetProtection( &prot );
        }

        KeUnstackDetachProcess( &apc );
    }
    else
        DPRINT( "BlackBone: %s: PsLookupProcessByProcessId failed with status 0x%X\n", __FUNCTION__, status );

    if (pProcess)
        ObDereferenceObject( pProcess );

    return status;
}

相关文章

https://bbs.pediy.com/thread-266929.htm

原文地址:https://www.cnblogs.com/csnd/p/15613426.html