Skip to content
Obfuscationbeginner

RC4 String Encryption

Sensitive strings are stored as RC4 ciphertext inside the binary and decrypted on demand with an embedded key, keeping URLs, API names, and config out of static string output.

Instead of leaving C2 URLs, registry keys, or API names as plaintext literals, malware stores them as RC4-encrypted blobs and decrypts each string only when it is needed. RC4 is popular for this because it is tiny, has no external dependencies, needs no key schedule tables baked into the binary, and works on arbitrary byte lengths.

The key is usually embedded right next to the ciphertext — sometimes a short ASCII string, sometimes a hard-coded byte array. That makes the scheme trivially reversible once you locate the key, but it is highly effective against strings(1), IDA's string window, and naive YARA string rules, which see only high-entropy noise.

How it works

RC4 is a symmetric stream cipher: a key-scheduling algorithm (KSA) permutes a 256-byte state array, then a pseudo-random generation algorithm (PRGA) produces a keystream XORed against the data.

c
void rc4(const uint8_t *key, size_t keylen,
         uint8_t *data, size_t datalen)
{
    uint8_t S[256];
    for (int i = 0; i < 256; i++) S[i] = (uint8_t)i;

    // KSA
    for (int i = 0, j = 0; i < 256; i++) {
        j = (j + S[i] + key[i % keylen]) & 0xFF;
        uint8_t t = S[i]; S[i] = S[j]; S[j] = t;   // swap
    }

    // PRGA — decrypt in place (RC4 is symmetric)
    for (size_t n = 0, i = 0, j = 0; n < datalen; n++) {
        i = (i + 1) & 0xFF;
        j = (j + S[i]) & 0xFF;
        uint8_t t = S[i]; S[i] = S[j]; S[j] = t;
        data[n] ^= S[(S[i] + S[j]) & 0xFF];
    }
}

// Usage: ciphertext + embedded key sit in .rdata
static uint8_t key[]  = { 0x6D, 0x79, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74 }; // "mysecret"
static uint8_t blob[] = { 0xA3, 0x1C, 0x77, 0x0B, /* ... C2 URL ciphertext ... */ };

const char *get_c2(void) {
    rc4(key, sizeof(key), blob, sizeof(blob));
    return (const char *)blob;   // now plaintext
}

The decrypted string is typically cached so the KSA runs only once per string, or wiped after use to keep plaintext out of memory dumps.

Detection & analysis

Static analysis:

  • The RC4 KSA is highly recognisable: a 256-iteration loop initialising S[i] = i, followed by a swap loop indexed by a key. IDA/Ghidra signatures and CAPA rules detect it reliably.
  • Locate the ciphertext arrays and the adjacent key in .rdata/.data; then run RC4 offline (CyberChef has an RC4 recipe) to recover every string in one pass.
  • High-entropy byte arrays referenced by a single decrypt helper are a strong indicator.

Dynamic analysis:

  • Set a breakpoint at the end of the RC4 routine (after the PRGA loop) and read the output buffer — the plaintext string is fully assembled there.
  • Frida: hook the decrypt wrapper and log its return value to dump all strings as they are used.

Detection rule hint:

Flag any function containing a 256-iteration array initialisation immediately followed by a second 256-iteration loop performing byte swaps indexed by a modular key — that two-loop KSA shape is RC4-specific and almost never appears in benign code outside crypto libraries.

Votes

Comments(0)