Storing Payload in .text Section

.text

known as a text segment or simply as text, is a portion of an object file or the corresponding section of the program's virtual address space that contains executable instructions

this is the most common way to store your payload in a PE file, here what you do is try to add your malicious code to the main function of the program

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

int main(void) {
	void * exec_mem;
	BOOL rv;
	HANDLE th;
    	DWORD oldprotect = 0;

	// #4 byte payload (you can use any payload you want)
	unsigned char payload[] = {
		0x90,		// NOP
		0x90,		// NOP
		0xcc,		// INT3
		0xc3		// RET
	};
	unsigned int payload_len = 4;
	
	// #Allocate a memory buffer for payload
	exec_mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	printf("%-20s : 0x%-016p\n", "payload addr", (void *)payload);
	printf("%-20s : 0x%-016p\n", "exec_mem addr", (void *)exec_mem);

	// #Copy payload to new buffer
	RtlMoveMemory(exec_mem, payload, payload_len);
	
	// #Make new buffer as executable
	rv = VirtualProtect(exec_mem, payload_len, PAGE_EXECUTE_READ, &oldprotect);

	printf("\nHit me!\n");
	getchar();

	// #If all good, run the payload
	if ( rv != 0 ) {
			th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
			WaitForSingleObject(th, -1);
	}

	return 0;
}

to store the payload in .text section we should create a free memory space in the program process and it should be as long as the payload and give it read,write permissions so we can copy our payload to this memory space and then give the memory space read, execute permissions so we can execute the payload in a new Thread

you can be wondering why when we allocated the new memory buffer we gave it only read write permissions and when we copied the payload to this memory buffer then we gave it execute permission, why we don't give it read write execute permissions when we allocated the memory buffer ?

simply because it will be detected by the windows defender because allocating a memory space that have full permissions is suspicious so this is why we're doing like this to bypass this

exec_mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

It is using the Windows API function VirtualAlloc to allocate a block of memory in the process's virtual address space. The VirtualAlloc function takes four arguments:

docs => https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc

LPVOID VirtualAlloc(
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);
  1. lpAddress: This parameter is set to 0 in this example, which indicates that the operating system should determine the base address of the allocated memory block.

  2. dwSize: This parameter is set to payload_len, which specifies the size, in bytes, of the memory block to be allocated.

  3. flAllocationType: This parameter is set to MEM_COMMIT | MEM_RESERVE. MEM_COMMIT specifies that the pages should be committed, or physically allocated in the system's memory. MEM_RESERVE specifies that the pages should be reserved, but not initially committed. This means that the operating system will reserve the necessary space in the virtual address space of the process, but will not allocate any physical memory until it is actually needed.

  4. flProtect: This parameter is set to PAGE_READWRITE, which specifies the protection attributes of the memory pages. In this case, the pages will have read and write access, but not execute access.

  5. exec_mem: is a variable name that appears to be used to refer to a pointer to the block of memory that is being allocated by the VirtualAlloc() function call

  6. Overall, this line of code is allocating a block of memory in the process's virtual address space with a specified size, protection attributes, and allocation type. The memory block can be used to store executable code or other data in our case we will store our payload and execute it.

RtlMoveMemory(exec_mem, payload, payload_len);

RtlMoveMemory is a Windows API function that is used to copy a block of memory from one location to another. In the provided code snippet, RtlMoveMemory is being used to copy the payload buffer into the memory block that was allocated using VirtualAlloc() and stored in the exec_mem pointer.

VOID RtlMoveMemory(
  _Out_       VOID UNALIGNED *Destination,
  _In_  const VOID UNALIGNED *Source,
  _In_        SIZE_T         Length
);   

Here's a breakdown of the parameters being passed to RtlMoveMemory:

  1. Destination: The first parameter is the destination buffer, which is the exec_mem pointer. This tells the function where to copy the source buffer to.

  2. Source: The second parameter is the source buffer, which is payload. This is the data that will be copied into the destination buffer.

  3. Length: The third parameter is the length of the data to be copied, which is payload_len. This specifies the number of bytes to be copied from the source buffer to the destination buffer.

  4. Overall, the RtlMoveMemory function is being used in this code snippet to copy the contents of the payload buffer into the allocated memory block pointed to by exec_mem. This is typically done when the payload buffer contains executable code that needs to be executed later. By copying the code into the allocated memory block, it becomes possible to execute the code by jumping to the memory location pointed to by the exec_mem pointer.

rv = VirtualProtect(exec_mem, payload_len, PAGE_EXECUTE_READ, &oldprotect);

In the provided code snippet, rv is a variable that is being used to store the return value of the VirtualProtect() function call.

BOOL VirtualProtect(
  [in]  LPVOID lpAddress,
  [in]  SIZE_T dwSize,
  [in]  DWORD  flNewProtect,
  [out] PDWORD lpflOldProtect
);

Here's a breakdown of the parameters being passed to VirtualProtect():

  1. lpAddress: The first parameter is the starting address of the memory region to be modified, which is the exec_mem pointer. This tells the function which memory block to modify.

  2. dwSize: The second parameter is the size of the memory region to be modified, which is payload_len. This specifies the number of bytes to modify starting from the address pointed to by exec_mem.

  3. flNewProtect: The third parameter specifies the new memory protection level to apply to the memory region. In this case, it is set to PAGE_EXECUTE_READ, which allows the memory region to be executed as well as read.

  4. lpflOldProtect: The fourth parameter is a pointer to a variable that will receive the previous protection level of the memory region. In this case, it is the oldprotect variable.

The VirtualProtect() function returns a nonzero value if the function call succeeds, indicating that the memory protection level was changed successfully. If the function call fails, it returns zero.

The return value of VirtualProtect() is being stored in the rv variable, which can be used later in the program to determine if the function call was successful or not.

The last Piece of our Code

if ( rv != 0 ) {
    th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
    WaitForSingleObject(th, -1);
}

In the provided code snippet, the return value of the VirtualProtect() function call is being checked to see if it is nonzero. If the return value is nonzero, it indicates that the memory protection level was changed successfully.

If the return value of VirtualProtect() is nonzero, the code block inside the if statement is executed. Here is what is happening inside that code block:

A new thread is created using the CreateThread() function. This function creates a new thread to execute the code specified in the exec_mem pointer.

HANDLE CreateThread(
  [in, optional]  LPSECURITY_ATTRIBUTES   lpThreadAttributes,
  [in]            SIZE_T                  dwStackSize,
  [in]            LPTHREAD_START_ROUTINE  lpStartAddress,
  [in, optional]  __drv_aliasesMem LPVOID lpParameter,
  [in]            DWORD                   dwCreationFlags,
  [out, optional] LPDWORD                 lpThreadId
);
  1. lpThreadAttributes: The first parameter of CreateThread() is set to 0, which means that the new thread will inherit the default security attributes of the calling process.

  2. dwStackSize: The second parameter of CreateThread() is also set to 0, which means that the new thread will use the default stack size for the calling thread.

  3. lpStartAddress: The third parameter of CreateThread() is cast to an LPTHREAD_START_ROUTINE type, which is a function pointer that specifies the starting address of the thread. In this case, it is set to the exec_mem pointer, which points to the memory block that was previously filled with executable code.

  4. lpParameter: The fourth and fifth parameters of CreateThread() are set to 0, which means that no arguments are passed to the thread function and the thread will run immediately after creation.

  5. dwCreationFlags: The last parameter of CreateThread() is also set to 0, which means that the thread is created with default creation flags.

  6. lpThreadId: this parameter is optional and is not specified in the code snippet

DWORD WaitForSingleObject(
  [in] HANDLE hHandle,
  [in] DWORD  dwMilliseconds
);

The WaitForSingleObject() function is called with these parameters :

  1. hHandle: th handle is the first parameter (th is a handle to the thread executing the memory space where we have copied the payload)and this tells the function to wait for the thread to complete before continuing execution

  2. dwMilliseconds: The second parameter is -1 this tells the function to wait indefinitely.

Overall, the code block inside the if statement is being used to create a new thread that executes the code in the exec_mem memory block ,which was previously filled with executable code (Payload) using RtlMoveMemory(). By waiting for the thread to complete using WaitForSingleObject(), the program ensures that the code in the memory block has been executed before continuing with the rest of the program.

Last updated

Was this helpful?