Python Obfuscator — Online Obfuscation

Obfuscate, virtualize & protect Python scripts with advanced obfuscations, code virtualization, finite-state automata transformations, self-integrity checks, anti-debugging. Python Obfuscator has been used 129 times so far!

Source Python script (.py)

Upload script .py

Obfuscation strategies

a = 5
a = a + 1
print(a)
_gt6188m = ((794213996 - 736332205 + 736332205) ^ 488620608 ^ 488620608) + 243676734 - 243676734
_psr_vd1a = [631657144.4025, 79185892, 620943245.9317, ...]
while int(_gt6188m - ...) < 5:
    _op = _psr_vd1a[int(_gt6188m - ...)]
    if _op == 79185892:
        a = 5
        _gt6188m = ...
        break
    ...

Selected statements run through a bounded opcode VM: shuffled dispatch table, decoy opcodes, and obfuscated dispatch counters. Exactly one virtualization mode is applied.

a = 10
b = a + 5
print(b)
_gt6188mys = ((288198473 - 254521284 + 254521284) ^ 590886870) ^ 590886870
_yg_ud_bg11_wn7_gfs = 0
_ctg5keK_znnz = [0, 3, 0, 1, 2, 2, 0, 7]
while True:
    _fj__skc_ep = _ctg5keK_znnz[_yg_ud_bg11_wn7_gfs % len(_ctg5keK_znnz)]
    if _gt6188mys == 2495476:
        b = a + 5
        _yg_ud_bg11_wn7_gfs += 1
    ...

Statement blocks run through a dual-state automaton with opaque scheduler variables; layout varies per run.

x = 1
y = 2
z = x + y
print(z)
_gt6188m = 386839528
while True:
    if _gt6188m == 293992612:
        y = 2
        _gt6188m = 300692080
    elif _gt6188m == 282599652:
        print(z)
        _gt6188m = 754749286
    ...
    else:
        break

Linear code becomes a state variable and shuffled dispatcher loop — flow is no longer top-to-bottom.

Protection

def get_sum(a, b):
    return a + b


x = "hello"
get_sum(1, 2)
_g_gkq_tg = 0
_g_vjf_szic_sj5_rgl = 0


def _lag_lyd57_fn73_no1():
    try:
        _sys = __import__('sys')
        _code = _sys._getframe(0).f_code
        _stack = [_code]
        _parts = []
        while _stack:
            _cur = _stack.pop()
            # fingerprint opcodes, names, const types (not values)
            _parts.append(...)
            _stack.extend(reversed([
                c for c in _cur.co_consts
                if hasattr(c, 'co_code')
            ]))
        _live = hash('\n'.join(_parts))
        if ((_live ^ 0x3F1A) & 0xFFFF) != 0xA91C:
            _g_gkq_tg = 51
            _sys.exit(1)
        _g_vjf_szic_sj5_rgl = int(_live)
    except Exception:
        _g_gkq_tg = 51


_lag_lyd57_fn73_no1()
# ... user code and encrypted strings follow ...

Final anti-tamper pass inserts an AST integrity probe that hashes the compiled module CodeType tree (works for .py and compiled CPython). Decrypt / trip keys are derived from that live token — bytecode or AST edits poison later transforms (garbage instead of plaintext). Optional re-verify (and a background digest loop) re-hash the same tree. Does not read the script from disk.

def get_sum(a, b, c):
    return a + b


get_sum(1, 2, 3)
_g_bd_xn_hp_ws_ojc_mg = 375103


def _gom_upgi1s9wa(x, y):
    ...


import inspect
if (not _g_vjf_szic_sj5_rgl) and len(inspect.stack()) <= 2:
    try:
        get_sum(655, 353, 571)
    except Exception:
        pass
if (not _g_vjf_szic_sj5_rgl) and len(inspect.stack()) <= 1:
    try:
        _gom_upgi1s9wa(-249233, -249516)
    except Exception:
        pass
# ... honeypot resolvers and more tripwires ...


def get_sum(a, b, c):
    return a + b


get_sum(1, 2, 3)

Late pass linking protected helpers: honeypot resolvers, bogus calls with random args, and shallow-stack tripwires active only before bootstrap. Normal runs unchanged; extracted snippets stay noisy.

Detect debuggers, virtual machines, sandboxed environments, CPU emulators

  • Detect debuggers attached to the process (sys.gettrace(); Windows IsDebuggerPresent)
  • Check for an abnormally low number of CPU cores (exit if fewer than 3)
  • Sandboxie (DLL libraries SbieDll / SbieHide, registry)
  • Joe Sandbox (processes)
  • Analysis host / user / path / env tokens (Cuckoo and similar)

  • VMware (processes, files, drivers, BIOS registry, MAC OUI, platform strings)
  • Oracle VirtualBox (processes, files, drivers, BIOS registry)
  • Parallels (processes, files)
  • KVM (drivers)
  • Linux DMI product/vendor strings when not on Windows
  • WINE (API inconsistencies, DLL libraries, special API functions)
  • Bochs / QEMU (processes, BIOS registry)
  • XEN (processes)
  • CPU/user-mode emulator markers and a short timing self-check

The added code is executed at the beginning of the script. In case of positive detection, the process will be silently terminated, without any error message.

Renaming

user_count = 10
path = "C:\\data"
print(user_count)
_gt6188mys = 10
_o_vd1a362a_d = "C:\\data"
print(_gt6188mys)

Local and global variables become random identifiers; every reference updates together. Scope rules preserved.

def get_tag(name):
    return name


get_tag("x")
def get_tag(_gt6188mys):
    return _gt6188mys


get_tag("x")

Parameter names and in-body bindings rewritten to opaque tokens.

def invoke_work():
    return 1


invoke_work()
def _gt6188myssr_v():
    return 1


_gt6188myssr_v()

Function definitions renamed and calls retargeted; dunder/magic methods and built-ins untouched.

def work():
    return 1


work()
def work():
    return 1


_calls = {'a1': work}
_calls['a1']()

Direct call sites are rewritten to look up the target through a name/dispatch table instead of the literal identifier.

def alpha():
    return beta()


def beta():
    return 1


alpha()
def beta():
    return 1


def alpha():
    return beta()


alpha()

Permutes top-level function definitions when execution order still remains valid.

MAX_RETRIES = 5
for i in range(MAX_RETRIES):
    print(i)
for i in range(5):
    print(i)

Module-level constant assignments are inlined at every use site and unused declarations removed.

Selects the naming scheme used by all rename strategies (variables, parameters, functions).

Strings

msg = 'SecretKey'
print(msg)
msg = ('Se' + 'cr' + 'etK' + 'ey')
print(msg)

Literals split and joined at runtime — whole tokens no longer appear in one place.

msg = 'Secret'
print(msg)
msg = ''.join(chr(ord(c) - 1) for c in 'Tfdsfu')
print(msg)

Literals are rewritten as shifted characters rebuilt with a small runtime transform.

x = "payload"
print(x)
def _gt6188myssr_v():
    d = [257, 278, 190, 269, 272, 278, 277]
    r = ''
    for i in range(len(d)):
        v = d[i]
        for _x2a_d27g in range(0, -1, -1):
            ...
        if 0 <= v <= 0xFFFF:
            r += chr(v)
    return r


x = _gt6188myssr_v()
print(x)

Each run emits a unique per-string decryptor with randomly chosen bitwise loops — no two outputs share the same algorithm shape.

v = "Vault"
print(v)
v = ''.join([chr(86), chr(97), chr(117), chr(108), chr(116)])
print(v)

Strings rebuilt from numeric char codes instead of quoted text.

Numeric

k = 42
r = k + 10
print(r)
k = (2 * 21)
r = k + (-370068 + 370078)
print(r)

Plain integers become equivalent arithmetic or bitwise expressions.

r = a + b
m = a & b
r = (a ^ b) + 2 * (a & b)
m = (a | b) - (a ^ b)

Binary and bitwise operators are replaced with equivalent mixed boolean-arithmetic identities, hiding the original operator from static pattern matching.

n = 100
m = 200
print(n + m)
_gt6188mys = [[7648, 8688, 7405], [-9849, 200, 100]]
n = _gt6188mys[1][2]
m = _gt6188mys[1][1]
print(n + m)

Values stored in lists and read by index alongside decoy entries.

v = 255
print(v)
v = (255 ^ 3832) ^ 3832
print(v)

Reversible bit masks wrap integers without changing the result.

x = 10
print(x)
import math
x = int(math.ceil((10.0 + 31.866664 - 31.866664 + ...) - 0.3803732855154077))
print(x)

Whole numbers reconstructed from math module float expressions cast back to int.

pi = 3.14
print(pi)
import math
pi = math.pow(10.0, math.log10(3.14 * 0.3453511647258002 * 2.89560338038522))
print(pi)

Rewrites floating point literals into equivalent expressions based on math module calls, so constants are no longer visible as plain numeric values.

a = 2.5
b = 1.25
print(a + b)
_gt6188mys = [[7649.9344, 7405.0075, -703.1368], [9032.1081, 1.25, 2.5]]
a = _gt6188mys[1][2]
b = _gt6188mys[1][1]
print(a + b)

Floats lifted into list slots with decoy values mixed in.

Encrypted array literals

ports = [21, 80, 443]
print(ports[0])
ports = [43668, 43701, 43874]
ports = [p ^ 0xAAAA for p in ports]
print(ports[0])

Integer list contents are stored in encrypted form and decoded at runtime before use.

token = ['A', 'P', 'I']
print(token[2])
token = [0x1A, 0x0B, 0x12]
token = [chr(c ^ 0x5B) for c in token]
print(token[2])

Character list literals are replaced with encrypted values and restored only through generated decoder code.

scale = [1.5, 2.75]
print(scale[1])
import struct
data = [4609434218613702656, 4613374868287651840]
scale = [struct.unpack('<d', struct.pack('<Q', d ^ 0x55AA55AA55AA55AA))[0] for d in data]
print(scale[1])

Float list constants are hidden as encoded bit patterns and reconstructed while the program runs.

roles = ["admin", "guest"]
print(roles[0])
roles = ["\u4708\u470d\u4704\u4700\u4707", "\u470e\u471c\u470c\u471a\u471b"]
roles = [''.join(chr(ord(c) ^ 18257) for c in s) for s in roles]
print(roles[0])

String list elements are encrypted independently and rebuilt on demand by generated runtime code.

Decoy value pools

x = 1
print(x)
_pool_a = [93817, 205, -4471, 88123]
_pool_b = ['q1w2', 'z9x8']
x = 1
print(x)

Enables the module-level noise pools controlled by the sub-options below.

x = 1
_pool_ints = [93817, 205, -4471, 88123, 620]
x = 1

Unused integer decoys added to the pool.

x = 1
_pool_arrays = [[1, 2, 3], [7, 8], []]
x = 1

Unused list/array decoys added to the pool.

x = 1
def _pool_fn_a():
    return 0


x = 1

Unused decoy functions added to the pool.

x = 1
_pool_chars = ['q', 'z', '#', '9']
x = 1

Unused character decoys added to the pool.

x = 1
_pool_rx = ['(a|b)+', '[0-9]{3,}\\\\.py']
x = 1

Regex-shaped decoy literals designed to distract naive static-signature scanners.

x = 1
_pool_paths = ['~/.config/app/run', '/etc/xdg/autostart/app.desktop']
x = 1

Decoy string literals resembling autostart/persistence paths, never referenced by live code.

Opaque predicates & noise

if flag:
    x = 1
else:
    x = 2
x = 1 if flag else 2

Simple branches collapse into conditional expressions, blending assignment logic into a single opaque line.

if True:
    print(1)
if not (1 == 0):
    print(1)

Simple true/false tests rewritten into longer equivalent conditions.

s = 0
s += 1
s += 2
s += 4
print(s)
s = 0
s += 1
if len('HP7ZVzxW') < 7:
    break
s += 2
if int(3.14159) == 3:
    if int('28') == 29:
        ...
s += 4
print(s)

Always-false branches that look like real control flow — increases graph complexity.

def total(a, b):
    return a + b
def _qx_rf7mz(acc, s):
    u = acc + ((s & 0x7fff) + 1)
    return u ^ (32 - (s | 1).bit_length())


def _p_bk2_nw_v(acc, s):
    h = ((acc ^ s) << (s & 63)) & 0xFFFFFFFFFFFFFFFF
    return h ^ (s << 1)


def total(a, b):
    v = _qx_rf7mz(0, -402184)
    v = _p_bk2_nw_v(v, 881022)
    if v == -8372144055286123915:
        pass
    return a + b

Adds static mixer functions, invokes chains of them inside functions, and emits guards that should never execute, increasing the amount of code a decompiler has to analyze.

x = 1
print(x)
x = 1
_pyo_x188myssr_v = 55 * 457 + 17
_pyo_sa362a_d2 = abs(_pyo_x188myssr_v - 791)
_pyo_f_ud_bg11wn = max(_pyo_x188myssr_v, _pyo_sa362a_d2) - min(_pyo_x188myssr_v, _pyo_sa362a_d2)
print(x)

Statements under opaque never-true conditions — pads output without changing results.

x = 1
print(x)
x = 1
try:
    pass
finally:
    _ = []
print(x)

Inert try/finally scaffolding adds nesting without raising or changing values.

Decoys

def real_core():
    return 42


real_core()
def _q6188myssr_v(sa362a_d2=None, f_ud_bg11wn=0):
    w_ke_kz_nnz = {'bq_vp': 556, 'zv_nvf': f_ud_bg11wn}
    ...
    return w_ke_kz_nnz


def real_core():
    return 42


real_core()

Plausible-looking functions that are never called — noise for reverse engineers.

x = 1
print(x)
_s88myssr_vd1a362a = lambda: (173 + 190) - (173 + 190) + 54
x = 1
print(x)

Unused lambda bindings near the top — never invoked at runtime.

x = 1
print(x)
_lnz_w6uts24vknmx = """
 disable_app_background_task get_kds_root_key stop_pcsv_device set_net_ipsec ...
"""
_ = len(_lnz_w6uts24vknmx)
x = 1
print(x)

Bulky string constant shifts line numbers and inflates file size — never read by live logic.

x = 1
print(x)
# import cached_quay_19422 from bulk_worker.rapid_bridge
# from nested.profile import quarantine_inner_service
# requires: scheduled_tasks >= 30694
x = 1
print(x)

Comment lines mimic extra module dependencies to suggest fake imports. Removed when Strip comments is on.

import os
from pathlib import Path
os = __import__('os')
Path = getattr(__import__('pathlib', 0, 0, ['Path']), 'Path')

Rewrites absolute import / from into __import__ and getattr. Relative imports, from __future__, and star imports stay as-is. Module-name strings remain for later string encryption.

import os
os.getpid()
import os
getattr(os, 'Ge' + 'tpid')()

Call targets built from fragments and resolved via getattr() — static grep for function names fails.

x = 1
print(x)
if False:
    def _on_tick(evt):
        q6188myss = 1
        return q6188myss

    _handlers = {'tick': _on_tick}
x = 1
print(x)

Snippets resemble callback/handler registration setup but never wire a real event source.

Other

# secret plan
x = 1  # inline
print(x)
x = 1
print(x)

Comments (#) removed from emitted script.

Questions?

If you would like to ask about Python Obfuscator, mail us.