AutoItObfuscator.psm1

################################################################################
#
# AutoIt Obfuscator Web API client for PowerShell.
# Obfuscate and protect AutoIt v3 script source against analysis.
#
# Version : PowerShell SDK v1.0.0
# PowerShell : Windows PowerShell 5.1 / PowerShell 7+
# Author : Bartosz Wójcik (support@pelock.com)
# Project : https://www.pelock.com/products/autoit-obfuscator
# Homepage : https://www.pelock.com
#
################################################################################

Set-StrictMode -Version Latest

if ($PSVersionTable.PSVersion.Major -lt 6) {
    [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
}

Add-Type -AssemblyName System.IO.Compression | Out-Null

$script:AioApiUrl = 'https://www.pelock.com/api/autoit-obfuscator/v1'
$script:AioUserAgent = 'PELock AutoIt Obfuscator'
$script:AioTimeoutSec = 3600

function Get-AioAdler32 {
    param([Parameter(Mandatory)] [byte[]]$Data)

    $modAdler = 65521
    $a = 1
    $b = 0

    foreach ($byte in $Data) {
        $a = ($a + $byte) % $modAdler
        $b = ($b + $a) % $modAdler
    }

    return [uint32](($b -shl 16) -bor $a)
}

function Compress-AioZlib {
    param([Parameter(Mandatory)] [string]$Source)

    $bytes = [System.Text.Encoding]::UTF8.GetBytes($Source)
    $ms = New-Object System.IO.MemoryStream
    try {
        $deflate = New-Object System.IO.Compression.DeflateStream($ms, [System.IO.Compression.CompressionMode]::Compress, $true)
        try {
            $deflate.Write($bytes, 0, $bytes.Length)
        }
        finally {
            $deflate.Dispose()
        }

        $deflated = $ms.ToArray()
    }
    finally {
        $ms.Dispose()
    }

    $adler = Get-AioAdler32 -Data $bytes
    $output = New-Object byte[] ($deflated.Length + 6)
    $output[0] = 0x78
    $output[1] = 0xDA
    [Buffer]::BlockCopy($deflated, 0, $output, 2, $deflated.Length)
    $output[$output.Length - 4] = [byte](($adler -shr 24) -band 0xFF)
    $output[$output.Length - 3] = [byte](($adler -shr 16) -band 0xFF)
    $output[$output.Length - 2] = [byte](($adler -shr 8) -band 0xFF)
    $output[$output.Length - 1] = [byte]($adler -band 0xFF)

    return [Convert]::ToBase64String($output)
}

function Expand-AioZlib {
    param([Parameter(Mandatory)] [string]$CompressedBase64)

    $data = [Convert]::FromBase64String($CompressedBase64)
    if ($data.Length -lt 6) {
        throw 'Invalid zlib payload.'
    }

    $payloadLength = $data.Length - 6
    $ms = New-Object System.IO.MemoryStream($data, 2, $payloadLength)
    try {
        $deflate = New-Object System.IO.Compression.DeflateStream($ms, [System.IO.Compression.CompressionMode]::Decompress)
        try {
            $reader = New-Object System.IO.StreamReader($deflate, [System.Text.Encoding]::UTF8)
            try {
                return $reader.ReadToEnd()
            }
            finally {
                $reader.Dispose()
            }
        }
        finally {
            $deflate.Dispose()
        }
    }
    finally {
        $ms.Dispose()
    }
}

function ConvertTo-AioFormBody {
    param([Parameter(Mandatory)] [hashtable]$Fields)

    $parts = foreach ($key in $Fields.Keys) {
        $name = [uri]::EscapeDataString([string]$key)
        $value = [uri]::EscapeDataString([string]$Fields[$key])
        '{0}={1}' -f $name, $value
    }

    return ($parts -join '&')
}

function Invoke-AioApiRequest {
    param([Parameter(Mandatory)] [hashtable]$Fields)

    $body = ConvertTo-AioFormBody -Fields $Fields
    $headers = @{ 'User-Agent' = $script:AioUserAgent }

    try {
        return Invoke-RestMethod -Uri $script:AioApiUrl -Method Post -Body $body `
            -ContentType 'application/x-www-form-urlencoded' -Headers $headers `
            -TimeoutSec $script:AioTimeoutSec
    }
    catch {
        return $false
    }
}

class AutoItObfuscator {
    static [string] $API_URL = 'https://www.pelock.com/api/autoit-obfuscator/v1'

    hidden [string] $_api_key = ''

    [bool] $enable_compression = $false
    [bool] $anti_debug = $false
    [bool] $anti_vm = $false
    [bool] $anti_sandbox = $false
    [bool] $anti_emulator = $false
    [bool] $random_bucket_integers = $false
    [bool] $random_bucket_characters = $false
    [bool] $random_bucket_anti_regex = $false
    [bool] $random_bucket_arrays = $false
    [bool] $random_bucket_arrays_multidimensional = $false
    [bool] $random_bucket_functions = $false
    [bool] $random_bucket_autostart = $false
    [bool] $mix_code_flow = $false
    [bool] $rename_variables = $false
    [bool] $rename_functions = $false
    [bool] $rename_function_calls = $false
    [bool] $shuffle_functions = $false
    [bool] $resolve_const = $false
    [bool] $crypt_numbers = $false
    [bool] $split_strings = $false
    [bool] $modify_strings = $false
    [bool] $crypt_strings = $false
    [bool] $insert_ternary_operators = $false

    static [int] $ERROR_SUCCESS = 0
    static [int] $ERROR_INPUT_SIZE = 1
    static [int] $ERROR_INPUT = 2
    static [int] $ERROR_PARSING = 3
    static [int] $ERROR_OBFUSCATION = 4
    static [int] $ERROR_OUTPUT = 5

    AutoItObfuscator() {
        $this.Initialize($null)
    }

    AutoItObfuscator([string]$ApiKey) {
        $this.Initialize($ApiKey)
    }

    hidden [void] Initialize([string]$ApiKey) {
        $this._api_key = $ApiKey
        $this.enable_compression = $false
        $this.anti_debug = $false
        $this.anti_vm = $false
        $this.anti_sandbox = $false
        $this.anti_emulator = $false
        $this.random_bucket_integers = $false
        $this.random_bucket_characters = $false
        $this.random_bucket_anti_regex = $false
        $this.random_bucket_arrays = $false
        $this.random_bucket_arrays_multidimensional = $false
        $this.random_bucket_functions = $false
        $this.random_bucket_autostart = $false
        $this.mix_code_flow = $false
        $this.rename_variables = $false
        $this.rename_functions = $false
        $this.rename_function_calls = $false
        $this.shuffle_functions = $false
        $this.resolve_const = $false
        $this.crypt_numbers = $false
        $this.split_strings = $false
        $this.modify_strings = $false
        $this.crypt_strings = $false
        $this.insert_ternary_operators = $false
    }

    [object] Login() {
        $params = @{ command = 'login' }
        return $this.PostRequest($params)
    }

    [object] ObfuscateScriptFile([string]$ScriptFilePath) {
        if (-not (Test-Path -LiteralPath $ScriptFilePath -PathType Leaf)) {
            return $false
        }

        try {
            $source = [System.IO.File]::ReadAllText((Resolve-Path -LiteralPath $ScriptFilePath).Path)
        }
        catch {
            return $false
        }

        if ([string]::IsNullOrEmpty($source)) {
            return $false
        }

        return $this.ObfuscateScriptSource($source)
    }

    [object] obfuscate_script_file([string]$script_file_path) {
        return $this.ObfuscateScriptFile($script_file_path)
    }

    [object] ObfuscateScriptSource([string]$ScriptSource) {
        $params = @{
            command = 'obfuscate'
            source  = $ScriptSource
        }

        return $this.PostRequest($params)
    }

    [object] obfuscate_script_source([string]$script_source) {
        return $this.ObfuscateScriptSource($script_source)
    }

    [object] PostRequest([hashtable]$ParamsArray) {
        if ($this._api_key) {
            $ParamsArray['key'] = $this._api_key
        }

        $flags = @(
            'anti_debug',
            'anti_vm',
            'anti_sandbox',
            'anti_emulator',
            'random_bucket_integers',
            'random_bucket_characters',
            'random_bucket_anti_regex',
            'random_bucket_arrays',
            'random_bucket_arrays_multidimensional',
            'random_bucket_functions',
            'random_bucket_autostart',
            'mix_code_flow',
            'rename_variables',
            'rename_functions',
            'rename_function_calls',
            'shuffle_functions',
            'resolve_const',
            'crypt_numbers',
            'split_strings',
            'modify_strings',
            'crypt_strings',
            'insert_ternary_operators'
        )

        foreach ($flag in $flags) {
            if ($this.$flag) {
                $ParamsArray[$flag] = '1'
            }
        }

        if ($ParamsArray.ContainsKey('source') -and $this.enable_compression -and $ParamsArray['source']) {
            $ParamsArray['source'] = Compress-AioZlib -Source ([string]$ParamsArray['source'])
            $ParamsArray['compression'] = '1'
        }

        $result = Invoke-AioApiRequest -Fields $ParamsArray
        if ($result -eq $false -or $null -eq $result) {
            return $false
        }

        $errorCode = 0
        $errorProperty = $result.PSObject.Properties['error']
        if ($null -ne $errorProperty) {
            $errorCode = [int]$errorProperty.Value
        }

        $outputProperty = $result.PSObject.Properties['output']
        if ($null -ne $outputProperty -and $this.enable_compression -and $errorCode -eq [AutoItObfuscator]::ERROR_SUCCESS) {
            $result.output = Expand-AioZlib -CompressedBase64 ([string]$outputProperty.Value)
        }

        return $result
    }

    [object] post_request([hashtable]$params_array) {
        return $this.PostRequest($params_array)
    }
}

function New-AutoItObfuscator {
    <#
    .SYNOPSIS
        Creates an AutoIt Obfuscator Web API client.

    .DESCRIPTION
        Factory for the AutoItObfuscator class. Empty or invalid keys run demo mode.
        Strategy flags default to $false and are sent only when enabled.

    .PARAMETER ApiKey
        Activation key from PELock.

    .EXAMPLE
        $client = New-AutoItObfuscator -ApiKey 'ABCD-ABCD-ABCD-ABCD'
        $client.crypt_strings = $true
        $result = $client.ObfuscateScriptSource('ConsoleWrite("Hello World")')
    #>

    [CmdletBinding()]
    [OutputType([AutoItObfuscator])]
    param(
        [string]$ApiKey
    )

    if ([string]::IsNullOrWhiteSpace($ApiKey)) {
        return [AutoItObfuscator]::new($null)
    }

    return [AutoItObfuscator]::new($ApiKey)
}

Export-ModuleMember -Function @('New-AutoItObfuscator')