Injection APC

Apc Injection



Contexte

APC, ou appel de procédure asynchrone, est une fonction (procédure) qui est exécutée de manière asynchrone dans un thread spécifique. Dans le système d'exploitation Microsoft Windows, APC est un mécanisme d'accès concurrentiel pour les E / S asynchrones ou les minuteries.

Chaque thread possède sa propre file d'attente APC et vous pouvez utiliser la fonction QueueUserAPC pour pousser une fonction APC dans la file d'attente APC. Lorsque l'APC en mode utilisateur est enfoncé dans la file d'attente APC de thread, le thread n'appelle pas directement la fonction APC, sauf si le thread est dans un état notifiable et que la séquence d'appel est premier entré, premier sorti (FIFO).



Introduction de la fonction

QueueUserAPC



// If the function succeeds, the return value is non-zero. DWORD WINAPI QueueUserAPC( _In_ PAPCFUNC pfnAPC, // Pointer to the APC function provided by the application _In_ HANDLE hThread, // The handle of the thread _In_ ULONG_PTR dwData // A single value passed to the APC function pointed to by the pfnAPC parameter )

Processus de mise en œuvre

APC (Asynchronous Procedure Call), à savoir appel de procédure asynchrone. Dans le système Windows, chaque thread gère une file d'attente APC de thread et ajoute une fonction APC à la file d'attente APC du thread spécifié via QueueUserAPC. Chaque thread a sa propre file d'attente APC, cette file d'attente APC enregistre certaines fonctions APC qui nécessitent l'exécution du thread. Le système Windows émettra une interruption logicielle pour exécuter ces fonctions APC. Pour la file d'attente APC en mode utilisateur, ces fonctions APC sont exécutées lorsque le thread est à l'état alertable. Lorsqu'un thread utilise SignalObjectAndWait, SleepEx, WaitForSingleObjectEx, WaitForMultipleObjectsEx ou MsgWaitForMultipleObjectsEx pour se suspendre, il entre dans l'état alertable, puis il exécute les fonctions de la file d'attente APC.



Le premier paramètre de la fonction QueueUserAPC représente l'adresse de la fonction à exécuter. Lorsque l'APC commence à être exécuté, le programme saute à l'adresse de la fonction pour l'exécution. Le deuxième paramètre représente le handle de thread inséré dans l'APC et le handle de thread doit contenir l'autorisation d'accès THREAD_SET_CONTEXT. Le troisième paramètre représente le paramètre passé à la fonction d'exécution. Similaire à l'injection de thread à distance, si le premier paramètre de la fonction QueueUserAPC, c'est-à-dire l'adresse de la fonction, est défini sur l'adresse de la fonction LoadLibraryA et le troisième paramètre, c'est-à-dire, le paramètre de transfert est défini sur le chemin de la DLL . Ensuite, lorsque l'APC est exécuté, la fonction LoadLibraryA sera appelée pour charger la DLL du chemin spécifié pour terminer l'opération d'injection de DLL.

Un processus contient plusieurs threads. Afin de garantir que l'APC inséré peut être exécuté, le même APC est inséré dans tous les threads du processus cible pour implémenter l'opération de chargement de la DLL. De cette manière, tant qu'un thread du processus est réveillé et que l'APC commence à être exécuté, l'APC inséré sera exécuté pour réaliser l'injection de DLL.

Ensuite, le processus spécifique de mise en œuvre de l'injection APC est le suivant:



  • Tout d'abord, ouvrez le processus cible via la fonction OpenProcess et obtenez le handle du processus cible.
  • Ensuite, en appelant les fonctions API WIN32 CreateToolhelp32Snapshot, Thread32First et Thread32Next, il parcourt les instantanés de thread pour obtenir tous les ID de thread du processus cible.
  • Ensuite, appelez la fonction VirtualAllocEx pour demander un morceau de mémoire dans le processus cible et écrivez le chemin DLL injecté dans la mémoire via la fonction WriteProcessMemory.
  • Enfin, parcourez l'ID de thread obtenu ci-dessus et appelez la fonction OpenThread pour ouvrir le thread avec l'autorisation d'accès THREAD_ALL_ACCESS pour obtenir le handle de thread. Et appelez la fonction QueueUserAPC pour insérer la fonction APC dans le thread, définissez l'adresse de la fonction APC sur l'adresse de la fonction LoadLibraryA et définissez le paramètre de fonction APC sur l'adresse de chemin DLL ci-dessus.
  • Tant qu'un thread du processus cible est réveillé, APC sera exécuté pour terminer l'opération d'injection de DLL

Code

// APC injection 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 { // Get PID based on process name dwProcessId = GetProcessIdByProcessName(pszProcessName) if (0 >= dwProcessId) { bRet = FALSE break } // Get all corresponding thread IDs according to PID bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength) if (FALSE == bRet) { bRet = FALSE break } // Open the injection process hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId) if (NULL == hProcess) { ShowError('OpenProcess') bRet = FALSE break } // Apply for memory in the injection process space pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE) if (NULL == pBaseAddress) { ShowError('VirtualAllocEx') bRet = FALSE break } // Write DLL path data to the requested space ::WriteProcessMemory(hProcess, pBaseAddress, pszDllName, dwDllPathLen, &dwRet) if (dwRet != dwDllPathLen) { ShowError('WriteProcessMemory') bRet = FALSE break } // Get LoadLibrary address pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle('kernel32.dll'), 'LoadLibraryA') if (NULL == pLoadLibraryAFunc) { ShowError('GetProcessAddress') bRet = FALSE break } // Traverse threads, insert APC for (i = 0 i < dwThreadIdLength i++) { // open thread hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]) if (hThread) { // Insert APC ::QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress) // Close the thread handle ::CloseHandle(hThread) hThread = NULL } } bRet = TRUE } while (FALSE) // release memory if (hProcess) { ::CloseHandle(hProcess) hProcess = NULL } if (pThreadId) { delete[]pThreadId pThreadId = NULL } return bRet }

test

Compilez la fonction ci-dessus dans un programme 64 bits. Sur un système Windows 10 64 bits, exécutez directement la fonction ci-dessus pour injecter APC dans l'explorateur processus explorer.exe. Une fois l'injection terminée, la fenêtre d'invite DLL apparaîtra immédiatement, comme illustré dans la figure, de sorte que la DLL d'injection APC s'est terminée avec succès.
image

Le principe de l'injection APC est d'utiliser le mécanisme selon lequel la fonction enregistrée dans l'APC est exécutée lorsque le thread est réveillé et d'exécuter le code de chargement de DLL pour terminer l'injection de DLL. Parmi eux, afin d'augmenter la possibilité d'exécution d'APC, APC est inséré dans tous les threads du processus cible.

S'il y a un problème où l'insertion d'APC dans tous les threads d'un processus spécifié provoque le blocage du processus, vous pouvez utiliser la méthode de traversée des ID de thread dans l'ordre inverse pour insérer dans l'ordre inverse pour résoudre le blocage du programme.