Skip to content

Instantly share code, notes, and snippets.

@bobby-tablez
Created June 11, 2025 19:36
Show Gist options
  • Save bobby-tablez/bb1f13c10231192a8e0ebc58548951d3 to your computer and use it in GitHub Desktop.
Save bobby-tablez/bb1f13c10231192a8e0ebc58548951d3 to your computer and use it in GitHub Desktop.
Kramer Python Deobfuscator
# This Python script decrypts Kramer obfuscation by reversing its obfuscation and bruteforces the key.
# Detects and uses al CPU threads, so your milage may vary with how long it takes.
# Defaults to key ranges from 3-1000000 as generated in kramer.py
# If the key is found it will print the result to stdout
# Obfuscator: https://github.com/billythegoat356/Kramer
import sys
import marshal
import types
import dis
from multiprocessing import Pool, cpu_count
from binascii import unhexlify
# === Core Kramer Decryption Logic ===
strings = "abcdefghijklmnopqrstuvwxyz0123456789"
def dkyrie(text):
result = ""
for c in text:
if c in strings:
i = strings.index(c) + 1
if i >= len(strings):
i = 0
result += strings[i]
else:
result += c
return result
def decrypt_kramer(blob, key):
try:
lines = [unhexlify(line).decode('utf-8', errors='ignore') for line in blob.split('/') if line]
joined = ''.join(lines).replace('ζ', '\n')
shifted = ''.join(chr(ord(c) - key) if c != '\n' else '\n' for c in joined)
return dkyrie(shifted)
except Exception:
return None
# === PYC Parsing Helpers ===
def load_pyc(filename):
with open(filename, 'rb') as f:
f.seek(16) # Skip magic number, flags and header
return marshal.load(f)
def extract_large_constants(code_obj, min_size=1000):
if isinstance(code_obj, types.CodeType):
for const in code_obj.co_consts:
if isinstance(const, str) and len(const) > min_size:
yield const
elif isinstance(const, types.CodeType):
yield from extract_large_constants(const, min_size)
# === Brute-force Logic ===
def attempt_key(args):
blob, key = args
result = decrypt_kramer(blob, key)
if result and "import" in result and "def" in result:
return key, result
return None
keymin = 3
keymax = 1000000
def run_brute_force(pyc_path, fmin, fmax):
print(f"[+] Loading {pyc_path}")
code = load_pyc(pyc_path)
candidates = list(extract_large_constants(code))
if not candidates:
print("[-] No large string constants found.")
return
print(f"[+] Found {len(candidates)} large candidate string(s).")
blob = candidates[0]
print(f"[*] Trying keys from {fmin} to {fmax} using {cpu_count()} processes...")
with Pool(cpu_count()) as pool:
for i, result in enumerate(pool.imap_unordered(attempt_key, ((blob, k) for k in range(fmin, fmax)), chunksize=500)):
if result:
key, decrypted = result
print(f"\n[+] Decryption success with key: {key}")
print(decrypted)
return
if (i+1) % 1000 == 0:
print(f"[.] Tried {i+1} keys...")
print("[-] Decryption failed with current key space.")
# === Entry Point ===
if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"Usage: python {sys.argv[0]} <input.pyc>")
sys.exit(1)
run_brute_force(sys.argv[1], fmin=keymin, fmax=keymax)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment