typetechniqueconfidencehighcreated2026-08-24updated2026-08-24obfuscationdefense-evasionscript

Rail-Fence Cipher Payload Obfuscation

A custom obfuscation technique observed in XLoader batch-script droppers. The attacker encrypts a binary payload with AES-256-CBC, then applies a rail-fence (zigzag) transposition cipher whose rail count and rail read-order permutation are both derived from a hardcoded seed passphrase via System.Random.

What It Does

  1. The plaintext payload is written into a zigzag pattern across N rails.
  2. The rails are then read out of order, where the read order is a permutation generated by a PRNG seeded with the ASCII sum of a passphrase.
  3. At runtime, the victim script reconstructs the original payload by reversing the permutation and zigzag pattern, using the same deterministic PRNG sequence.

This evades static signature matching on the encrypted payload because the byte order depends on a secret seed, not just a key.

Detection / Fingerprint

  • PowerShell or C# code containing "rail", "zigzag", or permutation logic seeded by a string.
  • A hardcoded passphrase followed by a System.Random(seed) call and swap loops.
  • Base64 blobs whose decoded bytes begin with ZM (byte-swapped MZ) or other scrambled PE signatures.

Implementation Pattern

Observed in XLoader 5a26fd46:

$masterSeedPhrase = "ofD1*Vc9%Vzh6CMM#s*qG*Mz"

function Derive-RailCountFromSeed {
    param ([string]$seedString, [int]$minRailCount = 2, [int]$maxRailVariance = 18)
    $cumulativeAscii = 0
    foreach ($char in $seedString.ToCharArray()) { $cumulativeAscii += [int]$char }
    return ($cumulativeAscii % $maxRailVariance) + $minRailCount
}

function Generate-SeededPermutation {
    param ([string]$seedString, [int]$arraySize)
    $seedNumeric = 0
    foreach ($char in $seedString.ToCharArray()) { $seedNumeric += [int]$char }
    $prng = [System.Random]::new($seedNumeric)
    $permutationArray = 0..($arraySize - 1)
    for ($i = $arraySize - 1; $i -ge 1; $i--) {
        $j = $prng.Next(0, $i + 1)
        $swapContainer = $permutationArray[$i]
        $permutationArray[$i] = $permutationArray[$j]
        $permutationArray[$j] = $swapContainer
    }
    return $permutationArray
}

Reproduce on Your Own VMs

A Python reimplementation:

import random

def rail_fence_scramble(data: bytes, seed: str) -> bytes:
    ascii_sum = sum(ord(c) for c in seed)
    rail_count = (ascii_sum % 18) + 2
    rng = random.Random(ascii_sum)
    
    # Build zigzag rail lengths
    rail_lengths = [0] * rail_count
    row, direction = 0, 1
    for _ in data:
        rail_lengths[row] += 1
        if row == 0: direction = 1
        elif row == rail_count - 1: direction = -1
        row += direction
    
    # Seeded permutation of rail read order
    read_order = list(range(rail_count))
    for i in range(rail_count - 1, 0, -1):
        j = rng.randint(0, i)
        read_order[i], read_order[j] = read_order[j], read_order[i]
    
    # Split data into rails using read_order
    rails = [[] for _ in range(rail_count)]
    idx = 0
    for r in read_order:
        rails[r] = list(data[idx:idx + rail_lengths[r]])
        idx += rail_lengths[r]
    
    # Read back in zigzag order
    counters = [0] * rail_count
    result = bytearray()
    row, direction = 0, 1
    for _ in data:
        result.append(rails[row][counters[row]])
        counters[row] += 1
        if row == 0: direction = 1
        elif row == rail_count - 1: direction = -1
        row += direction
    return bytes(result)

Defensive Countermeasures

  • Script-content analysis: flag custom permutation algorithms seeded by strings.
  • Entropy analysis: AES ciphertext already has high entropy; the rail-fence adds no entropy, only reordering. Frequency analysis of byte pairs may reveal the rail structure.

Pages Where Observed

  • xloader 5a26fd46 ^[/intel/analyses/5a26fd462a809c89b0448318591cb98a77a7b96fca658815dc0f547a51958bed.html]