DLL injection s využitím APC

Existuje poměrně velké množství způsobů, jak nainjektovat kód do cizího procesu. Kromě těch notoricky známých technik, jakými jsou využití API funkce SetWindowsHookEx, VirtualAllocEx/CreateRemoteThread, registr AppInit_DLLs nebo SuspendThread/SetThreadContext (viz některý z příštích článků) existují i techniky, o kterých se až tak moc nemluví. Jednou z těchto technik je injekce kódu pomocí APC. Využitelná je jak v user-modu, tak kernel-modu. Pro potřeby tohoto článku si tentokrát vystačíme s metodou user-modu (Ring3).

Úvod
APC, neboli Asynchronous Procedure Call[1], je funkcionalita umožňující volat na Windows asynchronně procedury/funkce v kontextu aktuálně vykonávaného vlákna. Co to v praxi znamená? Každé vlákno má svou vlastní frontu. Zavoláním API funkce QueueUserAPC[2] dojde k nastavené procedury konkrétnímu vláknu. Vlákno čeká, dokud nedojde k přepnutí do pohotovostního stavu. Ve chvíli, kdy je vlákno přepnuto do pohotovostního stavu, dojde k přesměrování vykonávání kódu do APC fronty a k vykonání jednotlivých funkcí/procedur. Do pohotovostního stavu může být vlákno přepnuto například voláním API funkcí jako jsou SleepEx, SignalObjectAndWait, MsgWaitForMultipleObjectsEx, WaitForMultipleObjectsEx nebo WaitForSingleObjectEx. Jinými slovy se jedná o API funkce, které nějakým způsobem dočasně ‚přerušují‘ běh aplikace.

Teoretický přístup
Pro potřeby tohoto článku předpokládejme, že budeme vytvářet nový proces, ovšem výsledný kód se dá pohodlně upravit i pro běžící procesy. V prvním kroku si tedy vytvoříme nový proces v susendovaném stavu. V jeho adresním prostoru si alokujeme paměť pro argument API funkce LoadLibrary, tedy cestu k naší DLL knihovně a zapíšeme ji na tuto adresu. Nyní zařadíme do APC fronty naši funkci/proceduru. Na konec aplikaci uvolníme/spustíme ze suspendovaného stavu a čekáme, až se vykoná injektovaná DLL knihovna.

Praktický přístup
Pomocí API funkce CreateProcess vytvoříme nový proces (v našem případě třeba notepad.exe) v suspendovaném stavu. Protože proces vytváříme pomocí API funkce CreateProcess, není pro nás problém získat handle procesu. S jeho pomocí a API funkcí VirtualAlloc alokujeme v adresním prostoru procesu prostor pro argument API funkce LoadLibrary a pomocí API funkce WriteProcessMemory tento string do dané paměti zapíšeme. Teď přichází ta hlavní část: Voláním API funkce QueueUserAPC vytvoříme asynchronní volání pro API funkci LoadLibrary. Jako druhý argument potřebujeme zadat handle vlákna. Zde nám opět pomůže API funkce CreateProcess, jenž vrací handle hlavního vlákna v posledním parametru. Následuje závěrečné volání API funkce ResumeThread a čekání, až se hlavní vlákno aplikace dostane do pohotovostního stavu. V okamžiku, kdy k této skutečnosti dojde, dojde ke spuštění naší funkce/procedury (LoadLibrary).

Ukázkový kód
Aplikace se pokusí vytvořit nový proces notepad.exe a s pomocí APC v něm spustit DLL knihovnu s názvem test.dll, která obsahuje kód s API funkcí MessageBox. DLL knihovna není součástí kódu a předpokládá se, že si ji čtenář sám vytvoří.

#include <windows.h>
#include <stdio.h>
 
int main(int argc, char *argv[]){
  STARTUPINFOA si = {};
  PROCESS_INFORMATION pi = {};
  HANDLE hMemory = NULL;
  char *szDllPath = "test.dll";
  char *szTargetProcess = "notepad.exe";
 
  printf("[+] Create suspended process\n");
  if(CreateProcessA(NULL, szTargetProcess, NULL, NULL, 
           FALSE, CREATE_SUSPENDED, NULL, 
           NULL, &si, &pi)){
 
    printf("[+] Allocate space for DLL path\n");
    hMemory = VirtualAllocEx(pi.hProcess, NULL, 
                 lstrlenA(szDllPath), 
                 MEM_COMMIT | MEM_RESERVE, 
                 PAGE_READWRITE);
 
    if(hMemory != NULL){
      printf("[+] Write DLL path into remote process\n");
      WriteProcessMemory(pi.hProcess, hMemory, 
                 szDllPath, lstrlenA(szDllPath), 
                 NULL);
 
      printf("[+] Add my procedure call into APC queue\n");
      QueueUserAPC((PAPCFUNC)GetProcAddress(LoadLibraryA("kernel32.dll"), "LoadLibraryA"), 
             pi.hThread, (ULONG_PTR)hMemory);
 
      printf("[+] Run remote thread\n");
      ResumeThread(pi.hThread);
 
      CloseHandle(pi.hThread);
      CloseHandle(pi.hProcess);
 
      printf("[+] Done!\n-------------------\n");
    }else{
      printf("[-] ERROR! Space for DLL path wasn't allocated\n");
      printf("Error: %d\n", GetLastError());
    }
  }else{
    printf("[-] Suspended process wasn't created\n");
    printf("Error: %d\n", GetLastError());
  }
 
  return 0;
}
MessgeBox z DLL knihovny spuštěné pomocí APC

MessgeBox z DLL knihovny spuštěné pomocí APC

Odkazy
[1] – Asynchronous Procedure Calls
[2] – API funkce QueueUserAPC

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *