PowerShell Pro Obfuscator — Obfuskacja Online

Obfuskuj, wirtualizuj i chroń skrypty PowerShell dzięki zaawansowanym technikom obfuskacji, wirtualizacji, transformacjom automatem stanów, kontroli integralności oraz antydebugowaniu. Obfuscator był już użyty 211 razy!

Kod źródłowy PowerShell (.ps1)

Wgraj skrypt .ps1

Strategie obfuskacji

function Get-Sum { param([int]$a,[int]$b) $a + $b }
$x = "hello"
Get-Sum 1 2
#ppo-d:1847291056:3:7:2:918273645
$script:_gGkqTG = 0
$script:vjfSZIcSj5RGl = 0
function LAglYD57Fn73NO1 {
    try {
        $t = Get-Content -LiteralPath $MyInvocation.MyCommand.Path -Raw
        # usuń zakres sondy, znormalizuj LF/literały, FNV body-digest → $live
        ...
        if ((([int]$live -bxor 0x3F1A) -band 0xFFFF) -ne 0xA91C) {
            $script:_gGkqTG = 51; return
        }
        $script:vjfSZIcSj5RGl = [int]$live
    } catch { $script:_gGkqTG = 51 }
}
& LAglYD57Fn73NO1
# ... kod użytkownika i zaszyfrowane łańcuchy ...

Ostatnia faza sprawdzania integralności skryptu: sonda sprawdzająca wczytuje skrypt z dysku, wycina własny zakres sondy, normalizuje treść i liczy seeded FNV (body-digest). Klucze deszyfrowania ciągów pochodzą z tego tokenu — edycja treści psuje deszyfratory (śmieci zamiast jawnego tekstu). Wybrane późniejsze użycia ponownie haszują i sprawdzają ten sam digest.

$x = 1
Write-Output $x
if (($null -ne (Get-Variable -Name PSBoundParameters -Scope 0 -ErrorAction SilentlyContinue).Value) -and ((Get-Variable -Name PSBoundParameters -Scope 0 -ErrorAction SilentlyContinue).Value.ContainsKey('Debug'))) { $host.SetShouldExit(0); exit; return }
if ([bool](@(Get-PSBreakpoint -ErrorAction SilentlyContinue).Count)) { exit 0; return }
if (([System.Diagnostics.Debugger]::IsAttached) -eq $true) { [System.Environment]::Exit(0); return $null }
# ... kolejne sondy anty-debug ...
$x = 1
Write-Output $x

Do kodu, w losowych miejscach zostaną dodane detekcje debuggerów oraz pułapek (tzw. breakpoints). W przypadku detekcji debuggowania - skrypt zakończy działanie.

function Get-Sum { param([int]$a,[int]$b,[int]$c) $a + $b }
Get-Sum 1 2 3
$script:bdXNhpWsOjcMG = 375103
function GOMuPgi1s9wa { param([int]$x,[int]$y) ... }
if ((-not $script:vjfSZIcSj5RGl) -and (@((Get-PSCallStack)).Count) -le 2) {
    try { & Get-Sum 655 353 571 | Out-Null } catch { }
}
if ((-not $script:vjfSZIcSj5RGl) -and (@((Get-PSCallStack)).Count) -le 1) {
    try { & GOMuPgi1s9wa -249233 -249516 | Out-Null } catch { }
}
# ... honeypot resolvers and more tripwires ...
function Get-Sum { param([int]$a,[int]$b,[int]$c) $a + $b }
Get-Sum 1 2 3

Późna faza łącząca helpery: honeypotowe resolvery, fałszywe wywołania z losowymi argumentami i pułapki na płytkim stosie aktywne przed bootstrapem. Normalne uruchomienie bez zmian; wyciągnięte fragmenty pozostają hałaśliwe.

$userCount = 10
$path = "C:\data"
Write-Output $userCount
$gt6188mys = 10
$oVd1a362aD = "C:\data"
Write-Output $gt6188mys

Wszystkie odwołania do przemianowanych zmiennych są automatycznie poprawiane.

function Get-Tag { param($name) $name }
Get-Tag "x"
function Get-Tag { param($gt6188mys) $gt6188mys }
Get-Tag "x"

Nazwy w bloku param i powiązania w ciele funkcji zastąpione zostaną losowymi ciągami znakowymi.

function Invoke-Work { 1 }
Invoke-Work
function gt6188myssrV { 1 }
gt6188myssrV

Wszystkie odwołania do przemianowanych funkcji/filtrów są automatycznie poprawiane. Wbudowane cmdlety pozostają nietknięte.

function Alpha { Beta }
function Beta { 1 }
Alpha
function Beta { 1 }
function Alpha { Beta }
Alpha

Permutuje bloki funkcji najwyższego poziomu, gdy kolejność parsowania nadal jest poprawna.

$x = 1
$y = 2
$z = $x + $y
Write-Output $z
$gt6188m = 386839528
while ($true) {
    if ($gt6188m -eq 293992612) { $y = 2; $gt6188m = 300692080 }
    elseif ($gt6188m -eq 282599652) { Write-Output $z; $gt6188m = 754749286 }
    ...
    else { break }
}

Kod liniowy zamieniany na zmienną stanu i pętlę dispatchera — przepływ nie jest już od góry do dołu.

$a = 10
$b = $a + 5
Write-Output $b
$gt6188mys = ((((288198473) - 254521284) + 254521284) -bxor 590886870) -bxor 590886870
$YgUDBg11WN7GFS = 0
$Ctg5keKZnnz = @(0, 3, 0, 1, 2, 2, 0, 7)
while ($true) {
    $Fj_SkcEp = $Ctg5keKZnnz[$YgUDBg11WN7GFS % $Ctg5keKZnnz.Length]
    if ($gt6188mys -eq 2495476) { $b = $a + 5; $YgUDBg11WN7GFS++ }
    ...
}

Kod zostanie zastąpiony przez automat dwustanowy (tzw. finite automata) z nieczytelnymi zmiennymi harmonogramu; układ zmienia się co uruchomienie.

$a = 5
$a = $a + 1
Write-Output $a
$gt6188m = ((((((794213996) - 736332205) + 736332205) -bxor 488620608) -bxor 488620608) + 243676734) - 243676734
$psrVd1a = @(631657144.40251017, 79185892, 620943245.93176389, ...)
while (([int](($gt6188m - ...)) -lt 5) {
    switch ($psrVd1a[[int](($gt6188m - ...))]) {
        79185892 { $a = 5; $gt6188m = ...; break }
        ...
    }
}

Wybrane bloki kodu zostaną zwirtualizowane przez losowo wygenerowaną maszynę wirtualną: przemieszane case’y, fałszywe opcode’y i zaciemnione liczniki dispatchera.

$msg = 'SecretKey'
Write-Output $msg
$msg = ('Se' + 'cr' + 'etK' + 'ey')
Write-Output $msg

Literały tekstowe zostaną podzielone i składane w trakcie działania z mniejszych fragmentów.

$x = "payload"
Write-Output $x
function gt6188myssrV {
    $d = @(257, 278, 190, 269, 272, 278, 277)
    $r = ''
    for ($__i = 0; $__i -lt $d.Length; $__i++) {
        [long]$v = [long]$d[$__i]
        for ($X2aD27gUDBg11WN = 0; $X2aD27gUDBg11WN -ge 0; $X2aD27gUDBg11WN--) {
            ...
        }
        if ([long]$v -ge 0 -and [long]$v -le 0xFFFF) { $r += [char][int][long]$v }
    }
    return $r
}
$x = (gt6188myssrV)
Write-Output $x

Ciągi znakowe zostaną zaszyfrowane z wykorzystaniem wielopoziomowych, losowo wygenerowanych polimorficznych algorytmów szyfrowania, a w kodzie zostaną zastąpione losowo wyglądającymi funkcjami deszyfrującymi.

$v = "Vault"
Write-Output $v
$v = (-join @([char]86, [char]97, [char]117, [char]108, [char]116))
Write-Output $v

Łańcuchy odtwarzane z kodów znaków zamiast tekstu w cudzysłowie.

Get-Process | Select-Object -First 1 | Out-Null
& (([System.Type]::GetType('System.String')).InvokeMember('Concat', ... ,$null,@('Get-Pr','ocess'))) | Select-Object -First 1 | Out-Null

Nazwy cmdletów generowane dynamicznie z fragmentów lub refleksji — statyczne wyszukiwanie po nazwach poleceń już nie zadziała.

$k = 42
$r = $k + 10
Write-Output $r
$k = (2L * 21L)
$r = $k + (-370068L + 370078L)
Write-Output $r

Proste liczby całkowite zastępowane równoważnymi wyrażeniami arytmetycznymi i bitowymi.

$n = 100
$m = 200
Write-Output ($n + $m)
$gt6188mys = @(@(7648L, 8688L, 7405L), @(-9849L, 200L, 100L))
$n = $gt6188mys[1][2]
$m = $gt6188mys[1][1]
Write-Output ($n + $m)

To sprawia, że analiza jest trudniejsza, ponieważ wymaga wyszukiwania po indeksach w tabeli każdej wartości numerycznej. Jako dodatkowa obfuskacja, generowa są również zaciemniające wartości w tablicach.

$v = 255
Write-Output $v
$v = ((255 -bxor 3832) -bxor 3832)
Write-Output $v

Odwracalne maski bitowe nie powodują zmian wartości liczbowych, na których przeprowadzają operacje.

$x = 10
Write-Output $x
$x = ([int][Math]::Ceiling(((((10.0) + 31.866664 - 31.866664) + ... ) - 0.3803732855154077)))
Write-Output $x

Liczby całkowite odtwarzane z wyrażeń [Math] na float i rzutowane na int.

$pi = 3.14
Write-Output $pi
$pi = [Math]::Pow(10.0, [Math]::Log10(((3.14) * 0.3453511647258002 * 2.89560338038522)))
Write-Output $pi

Liczby zmiennoprzecinkowe zostaną zaszyfrowane, z wykorzystaniem funkcji matematycznych, gwarantując poprawność obliczeń.

$a = 2.5
$b = 1.25
Write-Output ($a + $b)
$gt6188mys = @(@(7649.9344473527435, 7405.007519566458, -703.1367835910696), @(9032.108065857603, 1.25, 2.5))
$a = $gt6188mys[1][2]
$b = $gt6188mys[1][1]
Write-Output ($a + $b)

Liczby zmiennoprzecinkowe zostaną przeniesione do tablic razem z celowo zaciemnionymi wartościami pomiędzy.

$x = 1
Write-Output $x
$x = 1
$__ppo_X188myssrV = 55 * 457 + 17
$__ppo_Sa362aD2 = [Math]::Abs($__ppo_X188myssrV - 791)
$__ppo_fUDBg11WN7GFSHIt = [Math]::Max($__ppo_X188myssrV, $__ppo_Sa362aD2) - [Math]::Min($__ppo_X188myssrV, $__ppo_Sa362aD2)
[void]$__ppo_fUDBg11WN7GFSHIt
Write-Output $x

Generowane fałszywe instrukcje, nie zmienijące działania skryptu, zwiększające rozmiar i utrudniające analizę.

if ($true) { Write-Output 1 }
if ((-not (1 -eq 0))) { Write-Output 1 }

Proste testy true/false zastępowane dłuższymi, równoważnymi warunkami.

function RealCore { return 42 }
RealCore
function q6188myssrV {
    param([object]$Sa362aD2 = $null, [int]$fUDBg11WN7GFSHIt = 0)
    $WkeKZnnz = [ordered]@{ 'BQvp' = 556; 'zvNVf' = $fUDBg11WN7GFSHIt }
    ...
    return $XUts24vknmxG1w
}
function RealCore { return 42 }
RealCore

Wiarygodnie wyglądające funkcje nigdy nie wywoływane — szum dla reverse engineerów.

$x = 1
Write-Output $x
# . \\mgmtwest.local\common\fork\cached_quay_19422.psm1
# `$env:LOCALAPPDATA\bulk_worker\rapid_bridge_93443.ps1
# UNC admin share . \\srv-10.contoso.test\C$\nested-cronjob\double_template_10346.ps1xml
# requires -Modules @ScheduledTasks # ref .\hidden_topic\nested-profile\quarantine_inner_service_30694.ps1
$x = 1
Write-Output $x

Komentarze imitują dot-sourcing innych plików. Usuwane przy włączonym Usuwaniu komentarzy.

$s = 0
$s += 1
$s += 2
$s += 4
$s += 8
$s += 16
Write-Output $s
$s = 0
$s += 1
if (('HP7ZVzxW').Length -lt 7) { break }
$s += 2
if (([int][Math]::Floor([Math]::PI)) -eq 3) { if (([Convert]::ToInt32('28') -eq 29)) { ... } else { ; } }
$s += 4
...
Write-Output $s

Gałęzie zawsze fałszywe wyglądające jak prawdziwy przepływ — zwiększają złożoność grafu.

$x = 1
Write-Output $x
$S88myssrVd1a362a={ ((173+190)-(173+190)+54) };$S88myssrVd1a362a|Out-Null
$x = 1
Write-Output $x

Nieużywane scriptblocki na początku — nigdy nie wywoływane w runtime.

$x = 1
Write-Output $x
$lnzW6Uts24vknmx = @'
 Disable-AppBackgroundTaskDiagnosticLog Get-KdsRootKey Stop-PcsvDevice Set-NetIPsecMainModeCryptoSet Uninstall-Package Undo-DtcDiagnosticTransaction Enable-StorageMaintenanceMode Get-PhysicalExtent Get-DtcNetworkSetting Get-FileIntegrity Get-DnsClientCache rm ...
'@
$null = $lnzW6Uts24vknmx.Length
$x = 1
Write-Output $x

Duży here-string przesuwa numery linii i powiększa plik — logika go nie czyta.

$x = 1
Write-Output $x
$x = 1
try {} finally {  @() | Out-Null  }
Write-Output $x

Pasywny szkielet try/finally dodaje zagnieżdżenie bez rzucania wyjątków i bez zmiany wartości.

$x = 1
Write-Output $x
if ($true -eq $false) { $null = Register-ObjectEvent -InputObject ([System.Timers.Timer]::new(659503783)) -EventName Elapsed -Action { $q6188myss = 1; $Od1a362aD2 = 2; [void]$Od1a362aD2 } -ErrorAction SilentlyContinue }
$x = 1
Write-Output $x

Fragmenty przypominają rejestrację zdarzeń, ale nie podpinają prawdziwego handlera.

# secret plan
$x = 1 <# inline #>
Write-Output $x

$x = 1 
Write-Output $x

Komentarze liniowe (#) i blokowe (<# #>) usuwane z wyniku.

Pytania?

Jeśli masz jakieś wątpliwości lub pytania - napisz do mnie.