APC: 异步过程调用。每个线程都有一个APC队列,队列里都是APC函数
用户 Apc严格在用户模式和仅当前线程处于可报警等待状态时运行。
用户模式?
用户模式下的应用程序,每一个进程都有自己独立 虚拟地址空间和句柄表,互不影响
内核模式下,所有代码都运行在一个虚拟地址空间
可报警等待状态?
就是线程暂时没有重要的事情要做。APC函数一般不会去干扰(中断)线程的运行,一个线程附带着两个APC队列(用户APC、系统APC),也就相当于这两个队列的APC函数都是由“线程本身”来储备调用的(APC函数就相当于奥运会比赛上的预备选手),只有当线程处于“可警告的线程等待状态”才会去调用APC函数(比赛时只有主将无法上场时,预备选手才会出现)。
当系统调用下面函数时,就认为线程暂时没有重要的事情要做,处于可报警状态
调用函数SleepEx、SignalObjectAndWait、WaitForSingleObjectEx、WaitForMultipleObjectsEx、MsgWaitForMultipleObjectsEx都可以使目标线程处于可警告的线程等待状态
QueueUserAPC函数把一个APC对象加入到指定线程的APC队列中。
从函数名称,也应该能推测到一个线程其实有两个APC队列:用户APC、系统APC。
Windows APC函数是被按照先进先出(FIFO)顺序放置在一个队列Queue上面的。同时,用户APC函数极为特别,它只有在线程处于“可警告的线程等待状态”时才能被线程调用。但是,线程一旦开始调用APC函数,就会一次性将所有APC队列上的函数全部执行完毕。
APC注入:先通过QueueUserAPC(LoadLibrary, hThread, DllBaseAddress)函数把APC函数入队
当进程处于可报警等待状态时,调用APC函数,
第一个参数LoadLibrary是APC函数的执行函数,APC函数一旦执行,就跳转到LoadLibrary函数地址开始执行
第二个参数hThread线程句柄,表示在这个线程中插入APC
第三个DllBaseAddress,是传递给执行函数的参数,即LoadLibrary(DllBaseAddress)
就加载了我们的dll
获取某个进程的所有线程Tid
代码入下
#include <Windows.h>
#include <iostream>
#include <stdio.h>
#include <tlhelp32.h>
#include <tchar.h>
BOOL ApcInjectDll(char *pszProcessName, char *pszDllFileName);
DWORD GetProcessIdByName(TCHAR *pProcess);
BOOL GetAllThreadIdByProcessId(DWORD dwProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength);
using namespace std;
int main() {
char proc[20];
cin.get(proc, 20); //输入我们要查找的进程名
if (ApcInjectDll(proc, "C:\\Users\\86132\\source\\repos\\CreateRemoteThreadDll\\x64\\Release\\CreateRemoteThreadDll.dll"))
printf("Inject Success");
else
printf("Inject Fail");
system("pause");
}
BOOL ApcInjectDll(char *pszProcessName, char *pszDllFileName) {
//获取进程pid
DWORD Pid = GetProcessIdByName(pszProcessName); //pszProcessName:进程名
if (NULL == Pid) {
return FALSE;
}
//获取进程中所有线程id
LPDWORD pThreadIdList; //声明一个作用在程序后边的线程Tid的列表
DWORD dwThreadIdListLength; //声明Tid列表的长度 这两个声明可以看作是全局的
BOOL bRet = GetAllThreadIdByProcessId(Pid, &pThreadIdList, &dwThreadIdListLength); //参数:1、进程号 2、线程号列表 3、线程列表长度
if (!bRet) {
MessageBoxA(NULL, "GetAllThreadIdByProcessId", "提示", MB_OK);
return FALSE;
}
return TRUE;
}
//根据进程名获取进程pid
DWORD GetProcessIdByName(TCHAR *pProcess)
{
DWORD ProcessId = NULL;
PROCESSENTRY32 lppe; //进程快照信息结构体
lppe.dwSize = sizeof(lppe); //进程快照大小
HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); //拍摄进程快照
if (hProcessSnap == INVALID_HANDLE_VALUE) //拍摄失败返回INVALID_HANDLE_VALUE
{
MessageBoxA(NULL, "CreateToolhelp32Snapshot", "提示", MB_OK);
return NULL;
}
BOOL bMore = ::Process32First(hProcessSnap, &lppe); //获取第一个进程,参数是:1、进程快照 2、进程结构体
while (bMore) {
if (strcmp(lppe.szExeFile, pProcess) == 0) { //szExeFile:进程结构体中可执行文件的名称,strcmp:比较两个字符串是否相等
ProcessId = lppe.th32ProcessID; //相等返回0;th32ProcessID:结构体中进程id
break;
}
bMore = ::Process32Next(hProcessSnap, &lppe); //查找下一个进程
}
::CloseHandle(hProcessSnap);
return ProcessId;
}
//根据pid获取线程pid
BOOL GetAllThreadIdByProcessId(DWORD dwProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength){
DWORD dwThreadIdListLength = 0; //初始化线程列表长度为0
DWORD dwThreadIdListMaxCount = 2000; //声明并初始化,线程列表最大长度为2000,即最多允许进程里有2000个线程
LPDWORD pThreadIdList = NULL; //初始化线程列表为空
pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); //为线程列表申请内存,大小为2000*sizeof(DWORD)
THREADENTRY32 te32 = { sizeof(te32) }; //初始化线程快照结构体的大小,如果不初始化大小,Thread32First会失败
HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwProcessID); //为这一进程id建立线程快照
BOOL bRet = Thread32First(hThreadSnap, &te32); //获取进程快照中第一个线程
while (bRet)
{
if (te32.th32OwnerProcessID == dwProcessID) //但前线程的所属进程如果是我们要查的进程的话,进入里面。不是的话,查找下一个线程。
{ //这里有点疑问,创建线程快照时,不是已经指定pid了,为什么还创建了那么多不是这个pid的线程
if (dwThreadIdListLength >= dwThreadIdListMaxCount) //如果进程列表满了,就停止执行
{
break;
}
printf("%d\n", te32.th32ThreadID); //输出获取到的线程Tid
pThreadIdList[dwThreadIdListLength++] = te32.th32ThreadID; //把当前线程放入线程列表中,同时线程列表长度+1
}
bRet = Thread32Next(hThreadSnap, &te32); //查找下一个线程
}
*pThreadIdListLength = dwThreadIdListLength; //*pThreadIdListLength指针指向线程列表长度
*ppThreadIdList = pThreadIdList; //*ppThreadIdList指向线程列表
return TRUE;
}
使用APC注入进程
代码如下
#include <Windows.h>
#include <iostream>
#include <stdio.h>
#include <tlhelp32.h>
#include <tchar.h>
BOOL ApcInjectDll(char *pszProcessName, char *pszDllFileName);
DWORD GetProcessIdByName(TCHAR *pProcess);
BOOL GetAllThreadIdByProcessId(DWORD dwProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength);
using namespace std;
int main() {
char proc[20];
cin.get(proc, 20);
if (ApcInjectDll(proc, "C:\\Users\\86132\\source\\repos\\CreateRemoteThreadDll\\x64\\Release\\CreateRemoteThreadDll.dll"))
printf("Inject Success");
else
printf("Inject Fail");
system("pause");
}
BOOL ApcInjectDll(char *pszProcessName, char *pszDllFileName) {
LPDWORD pThreadIdList; //线程列表
DWORD dwThreadIdListLength; //线程列表长度
HANDLE hProcess = NULL; //进程句柄
LPVOID pBaseAddress = NULL; //DLL加载到内存后返回的基地址
DWORD dwSize = 0; //dll的大小
SIZE_T dwRet = 0; //向内存中写入dll的字节数
FARPROC pLoadLibraryAFunc = NULL; //LoadLibraryA函数的地址
//获取进程pid
DWORD Pid = GetProcessIdByName(pszProcessName);
if (NULL == Pid) {
return FALSE;
}
//获取进程中所有线程id
BOOL bRet = GetAllThreadIdByProcessId(Pid, &pThreadIdList, &dwThreadIdListLength);
if (!bRet) {
MessageBoxA(NULL, "GetAllThreadIdByProcessId", "提示", MB_OK);
return FALSE;
}
//打开注入进程
hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
if (NULL == hProcess) {
MessageBoxA(NULL, "OpenProcess", "提示", MB_OK);
return FALSE;
}
//在注入的进程空间申请内存
dwSize = 1 + ::lstrlenA(pszDllFileName);
pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (NULL == pBaseAddress) {
MessageBoxA(NULL, "VirtualAllocEx", "提示", MB_OK);
return FALSE;
}
//向申请的内存空间中写入DLL
::WriteProcessMemory(hProcess, pBaseAddress, pszDllFileName, dwSize, &dwRet); //dwRet是向内存空间中写入dll的字节数
if (dwRet != dwSize) { //如果写入的字节数dwRet 不等于 dll的大小dwSize 说明写入失败
MessageBoxA(NULL, "WriteProcessMemory", "提示", MB_OK);
return FALSE;
}
//获取LoadLibrary的地址
pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (NULL == pLoadLibraryAFunc) {
MessageBoxA(NULL, "GetProcAddress", "提示", MB_OK);
return FALSE;
}
//循环每一个线程Tid,把APC函数添加到指定线程的APC队列
for ( size_t i = 0; i < dwThreadIdListLength; i++) {
HANDLE hThread = NULL;
hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]); //获取线程Tid的线程句柄
if (hThread) {
::QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress);
::CloseHandle(hThread);
hThread = NULL;
}
}
VirtualFree(pszDllFileName, 0, MEM_RELEASE); //释放DLL名的内存空间
VirtualFree(pszProcessName, 0, MEM_RELEASE); //释放进程名的内存空间
//ExitProcess(0);
return TRUE;
}
//根据进程名获取进程pid
DWORD GetProcessIdByName(TCHAR *pProcess)
{
DWORD ProcessId = NULL;
PROCESSENTRY32 lppe; //进程快照信息结构体
lppe.dwSize = sizeof(lppe); //进程快照大小
HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); //拍摄进程快照
if (hProcessSnap == INVALID_HANDLE_VALUE) //拍摄失败返回INVALID_HANDLE_VALUE
{
MessageBoxA(NULL, "CreateToolhelp32Snapshot", "提示", MB_OK);
return NULL;
}
BOOL bMore = ::Process32First(hProcessSnap, &lppe); //获取第一个进程
while (bMore) {
if (strcmp(lppe.szExeFile, pProcess) == 0) { //szExeFile:进程结构体中可执行文件的名称,strcmp:比较两个字符串是否相等
ProcessId = lppe.th32ProcessID; //相等返回0;th32ProcessID:结构体中进程id
break;
}
bMore = ::Process32Next(hProcessSnap, &lppe); //查找下一个进程
}
::CloseHandle(hProcessSnap);
return ProcessId;
}
//获取pid的线程Tid
BOOL GetAllThreadIdByProcessId(DWORD dwProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength){
//DWORD dwThreadID;
DWORD dwThreadIdListLength = 0;
DWORD dwThreadIdListMaxCount = 2000;
LPDWORD pThreadIdList = NULL;
pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
THREADENTRY32 te32 = { sizeof(te32) };
HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwProcessID);//为这一进程id建立线程快照
BOOL bRet = Thread32First(hThreadSnap, &te32);
while (bRet)
{
if (te32.th32OwnerProcessID == dwProcessID)
{
if (dwThreadIdListLength >= dwThreadIdListMaxCount)
{
break;
}
printf("%d\n", te32.th32ThreadID);
pThreadIdList[dwThreadIdListLength++] = te32.th32ThreadID;
}
bRet = Thread32Next(hThreadSnap, &te32);
}
*pThreadIdListLength = dwThreadIdListLength;
*ppThreadIdList = pThreadIdList;
return TRUE;
}
参考
《windows黑客编程技术详解》