Dll 注入 —— APC

背景

APC,即异步过程调用(Asynchronous Procedure Call)是函数(过程)在特定线程中被异步执行。在Microsoft Windows操作系统中,APC是一种并发机制,用于异步IO或者定时器。

每一个线程都有自己的APC队列,可以使用QueueUserAPC函数把一个APC函数压入APC队列中。当用户模式的APC压入线程APC队列后,该线程并不直接调用APC函数,除非该线程是处于可通知状态,调用的顺序为先入先出(FIFO)。

函数介绍

QueueUserAPC

// 如果函数成功,返回值为非零。
    DWORD WINAPI QueueUserAPC(
      _In_ PAPCFUNC  pfnAPC,			// 指向应用程序提供的APC函数的指针
      _In_ HANDLE    hThread,			// 线程的句柄
      _In_ ULONG_PTR dwData  			// 传递给由pfnAPC参数指向的APC函数的单个值
    );

实现过程

APC(Asynchronous Procedure Call),即异步程序调用。在Windows系统中,每个线程都会维护一个线程APC队列,通过 QueueUserAPC把一个APC函数添加到指定线程的APC队列。每个线程都由它自己的APC队列,这个APC队列纪录了要求线程去执行的一些APC函数。Windows系统会发出一个软中断去执行这些APC函数,对于用户模式下是APC队列,当线程处在alertable状态时才去执行这些APC函数。一个线程内部使用SignalObjectAndWait 、SleepEx、WaitForSingleObjectEx、WaitForMultipleObjectsEx或MsgWaitForMultipleObjectsEx等函数把自己挂起时就是进入alertable状态,此时便会执行APC队列的函数。

QueueUserAPC函数的第一个参数表示执行的函数地址,当开始执行该APC的时候,程序就会跳转到该函数地址执行。第二个参数表示插入APC的线程句柄,要求线程句柄必须包含THREAD_SET_CONTEXT访问权限。第三个参数表示传递给执行函数的参数。与远线程注入类似,如果QueueUserAPC函数的第一个参数,即函数地址设置的是LoadLibraryA函数地址,第三个参数,即传递参数设置的是DLL的路径。那么,当执行APC的时候,便会调用LoadLibraryA函数加载指定路径的DLL,完成DLL注入操作。

一个进程中,包含有多个线程,为了确保插入的APC能够被执行,所以,向目标进程的所有线程都插入相同的APC,实现加载DLL的操作。这样,只要进程中任意线程被唤醒,开始执行APC的时候,便会执行插入的APC,实现DLL注入。

那么,实现APC注入的具体流程如下所示:

  • 首先,通过OpenProcess函数打开目标进程,获取目标进程的句柄。
  • 然后,通过调用WIN32 API函数CreateToolhelp32Snapshot、Thread32First以及Thread32Next遍历线程快照,获取目标进程的所有线程ID。
  • 接着,调用VirtualAllocEx函数在目标进程中申请一块内存,并通过WriteProcessMemory函数向内存中写入注入的DLL路径。
  • 最后,遍历上述获取的线程ID,并调用OpenThread函数以THREAD_ALL_ACCESS访问权限打开线程,获取线程句柄。并调用QueueUserAPC函数向线程插入APC 函数,设置APC函数的地址为LoadLibraryA函数的地址,并设置APC函数参数为上述DLL路径地址。
  • 只要目标进程中任意线程被唤醒,便会执行APC,完成注入DLL操作

代码

    // APC注入
    BOOL ApcInjectDll(char *pszProcessName, char *pszDllName)
    {
        BOOL bRet = FALSE;
        DWORD dwProcessId = 0;
        DWORD *pThreadId = NULL;
        DWORD dwThreadIdLength = 0;
        HANDLE hProcess = NULL, hThread = NULL;
        PVOID pBaseAddress = NULL;
        PVOID pLoadLibraryAFunc = NULL;
        SIZE_T dwRet = 0, dwDllPathLen = 1 + ::lstrlen(pszDllName);
        DWORD i = 0;
        do
        {
            // 根据进程名称获取PID
            dwProcessId = GetProcessIdByProcessName(pszProcessName);
            if (0 >= dwProcessId)
            {
                bRet = FALSE;
                break;
            }
            // 根据PID获取所有的相应线程ID
            bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength);
            if (FALSE == bRet)
            {
                bRet = FALSE;
                break;
            }
            // 打开注入进程
            hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
            if (NULL == hProcess)
            {
                ShowError("OpenProcess");
                bRet = FALSE;
                break;
            }
            // 在注入进程空间申请内存
            pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
            if (NULL == pBaseAddress)
            {
                ShowError("VirtualAllocEx");
                bRet = FALSE;
                break;
            }
            // 向申请的空间中写入DLL路径数据 
            ::WriteProcessMemory(hProcess, pBaseAddress, pszDllName, dwDllPathLen, &dwRet);
            if (dwRet != dwDllPathLen)
            {
                ShowError("WriteProcessMemory");
                bRet = FALSE;
                break;
            }
            // 获取 LoadLibrary 地址
            pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
            if (NULL == pLoadLibraryAFunc)
            {
                ShowError("GetProcessAddress");
                bRet = FALSE;
                break;
            }
            // 遍历线程, 插入APC
            for (i = 0; i < dwThreadIdLength; i++)
            {
                // 打开线程
                hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]);
                if (hThread)
                {
                    // 插入APC
                    ::QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress);
                    // 关闭线程句柄
                    ::CloseHandle(hThread);
                    hThread = NULL;
                }
            }
            bRet = TRUE;
        } while (FALSE);
        // 释放内存
        if (hProcess)
        {
            ::CloseHandle(hProcess);
            hProcess = NULL;
        }
        if (pThreadId)
        {
            delete[]pThreadId;
            pThreadId = NULL;
        }
        return bRet;
    }

测试

将上述函数编译为64位程序,在64位Windows 10系统上,直接运行上述函数对资源管理器进程explorer.exe进行APC注入,注入完成后,立马弹出DLL的提示窗,如图所示,所以APC注入DLL成功完成。
在这里插入图片描述

APC注入的原理是利用当线程被唤醒时APC中的注册函数会被执行的机制,并以此去执行DLL加载代码,进而完成DLL注入。其中,为了增加APC被执行的可能性,所以向目标进程中所有的线程都插入的APC。

如果出现向指定进程的所有线程插入APC导致进程崩溃的问题,可以采取倒序遍历线程ID的方式进行倒序插入来解决程序崩溃问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值