Python Obfuscator — Obfuskacja Online

Obfuskuj, wirtualizuj i chroń skrypty Python dzięki zaawansowanym technikom obfuskacji, wirtualizacji, transformacjom automatem stanów, kontroli integralności oraz antydebugowaniu. Obfuscator był już użyty 130 razy!

Kod źródłowy skrypt Python (.py)

Prześlij skrypt .py

Strategie obfuskacji

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
    ...

Wybrane instrukcje zostaną zwirtualizowane przez ograniczoną maszynę wirtualną z tabelą dyspozycji, fałszywymi opcode'ami i zaciemnionymi licznikami. Zawsze stosowany jest dokładnie jeden tryb wirtualizacji.

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
    ...

Kod zostanie zastąpiony przez automat dwustanowy z nieczytelnymi zmiennymi harmonogramu; układ zmienia się co uruchomienie.

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

Kod liniowy zamieniany na zmienną stanu i pętlę dispatchera — przepływ nie jest już od góry do dołu.

Ochrona

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()
            # odcisk opcode'ów, nazw, typów stałych (nie wartości)
            _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()
# ... kod użytkownika i zaszyfrowane łańcuchy ...

Ostatnia faza ochrony wstawia sondę integralności AST, która haszuje drzewo CodeType skompilowanego modułu (działa dla .py i skompilowanego CPythona). Klucze deszyfrowania / trip pochodzą z tego tokenu — edycja AST lub bajtkodu truuje późniejsze transformacje (śmieci zamiast jawnego tekstu). Opcjonalna ponowna weryfikacja (oraz pętla haszująca w tle) ponownie haszuje to samo drzewo. Nie czyta skryptu z dysku przez __file__.

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
# ... honeypotowe resolvery i więcej pułapek ...


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


get_sum(1, 2, 3)

Późna faza łącząca helpery: honeypotowe resolvery, fałszywe wywołania z losowymi argumentami i pułapki na płytkim stosie aktywne przed bootstrapem. Normalne uruchomienie bez zmian; wyciągnięte fragmenty pozostają hałaśliwe.

Wykrywaj debuggery, wirtualne maszyny, piaskownice (tzw. sandbox) i emulatory CPU

  • Wykrywaj debuggery dołączone do procesu (sys.gettrace(); Windows IsDebuggerPresent)
  • Sprawdzaj podejrzanie małą liczbę rdzeni procesora (wyjdź jeśli jest ich mniej niż 3)
  • Sandboxie (biblioteki DLL SbieDll / SbieHide, rejestr)
  • Joe Sandbox (procesy)
  • Host / użytkownik / ścieżki / zmienne środowiskowe typowe dla analizy (Cuckoo i podobne)

  • VMware (procesy, pliki, sterowniki, rejestr BIOS, MAC OUI, ciągi platformy)
  • Oracle VirtualBox (procesy, pliki, sterowniki, rejestr BIOS)
  • Parallels (procesy, pliki)
  • KVM (sterowniki)
  • Linux DMI (nazwa produktu / dostawcy), gdy nie na Windows
  • WINE (niekonsystencje w funkcjach API, biblioteki DLL, specjalne funkcje API)
  • Bochs / QEMU (procesy, rejestr BIOS)
  • XEN (procesy)
  • Znaczniki emulatorów CPU/trybu użytkownika i krótki test czasu

Dodany kod jest uruchamiany na początku skryptu. W przypadku pozytywnej detekcji proces zostanie po cichu zamknięty bez jakiegokolwiek komunikatu błędu.

Zmiana nazw

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

Zmienne lokalne i globalne otrzymują losowe identyfikatory; wszystkie odwołania są automatycznie poprawiane. Zasięg zmiennych pozostaje zachowany.

def get_tag(name):
    return name


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


get_tag("x")

Nazwy parametrów i powiązania w ciele funkcji zastąpione zostaną losowymi ciągami znakowymi.

def invoke_work():
    return 1


invoke_work()
def _gt6188myssr_v():
    return 1


_gt6188myssr_v()

Nazwy funkcji zmienione, a wszystkie wywołania przekierowane; metody magiczne i wbudowane funkcje pozostają nietknięte.

def work():
    return 1


work()
def work():
    return 1


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

Bezpośrednie wywołania są przepisywane tak, aby cel był wyszukiwany w tabeli nazw/dyspozycji zamiast bezpośredniego identyfikatora.

def alpha():
    return beta()


def beta():
    return 1


alpha()
def beta():
    return 1


def alpha():
    return beta()


alpha()

Permutuje definicje funkcji najwyższego poziomu, gdy kolejność wykonania nadal jest poprawna.

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

Stałe zdefiniowane na poziomie modułu są wstawiane bezpośrednio w miejscu użycia, a nieużywane deklaracje usuwane.

Wybiera schemat nazewnictwa używany przez wszystkie strategie zmiany nazw (zmienne, parametry, funkcje).

Ciągi znakowe

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

Literały tekstowe zostaną podzielone i składane w trakcie działania z mniejszych fragmentów.

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

Literały zostaną zapisane jako przesunięte znaki, odtwarzane niewielką transformacją w czasie działania.

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)

Każde uruchomienie generuje unikalny deszyfrator ciągów z losowo dobranymi pętlami bitowymi — żadne dwa wyniki nie mają tego samego kształtu algorytmu.

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

Łańcuchy odtwarzane z kodów znaków zamiast tekstu w cudzysłowie.

Wartości numeryczne

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

Proste liczby całkowite zastępowane równoważnymi wyrażeniami arytmetycznymi i bitowymi.

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

Operatory binarne i bitowe zastępowane są równoważnymi tożsamościami mieszanymi boolowsko-arytmetycznymi, ukrywając oryginalny operator przed statycznym dopasowywaniem wzorców.

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)

To sprawia, że analiza jest trudniejsza, ponieważ wymaga wyszukiwania po indeksach w liście każdej wartości numerycznej, wraz z zaciemniającymi wartościami pomiędzy.

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

Odwracalne maski bitowe nie powodują zmian wartości liczbowych, na których przeprowadzają operacje.

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

Liczby całkowite odtwarzane z wyrażeń zmiennoprzecinkowych modułu math i rzutowane na int.

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

Liczby zmiennoprzecinkowe zostaną zaszyfrowane z wykorzystaniem funkcji modułu math, gwarantując poprawność obliczeń.

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)

Liczby zmiennoprzecinkowe zostaną przeniesione do list razem z celowo zaciemnionymi wartościami pomiędzy.

Szyfrowane literały tablicowe

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

Zawartość list liczb całkowitych jest przechowywana w formie zaszyfrowanej i dekodowana w trakcie działania przed użyciem.

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

Literały list znaków zastępowane są zaszyfrowanymi wartościami i odtwarzane tylko przez wygenerowany kod dekodujący.

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])

Stałe list zmiennoprzecinkowych są ukrywane jako zakodowane wzorce bitowe i odtwarzane w trakcie działania programu.

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])

Elementy list tekstowych są szyfrowane niezależnie i odtwarzane na żądanie przez wygenerowany kod w trakcie działania.

Pule wartości-przynęt

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

Włącza pule szumu na poziomie modułu, kontrolowane przez poniższe opcje.

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

Nieużywane przynęty liczbowe dodawane do puli.

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

Nieużywane przynęty w postaci list/tablic dodawane do puli.

x = 1
def _pool_fn_a():
    return 0


x = 1

Nieużywane funkcje-przynęty dodawane do puli.

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

Nieużywane przynęty znakowe dodawane do puli.

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

Literały o kształcie wyrażeń regularnych mające dezorientować naiwne skanery sygnaturowe.

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

Przynęty tekstowe przypominające ścieżki autostartu/trwałości, nigdy nie używane przez działający kod.

Nieprzezroczyste predykaty i szum

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

Proste rozgałęzienia są zwijane w wyrażenia warunkowe, łącząc logikę przypisania w jedną nieprzezroczystą linię.

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

Proste testy true/false zastępowane dłuższymi, równoważnymi warunkami.

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)

Gałęzie zawsze fałszywe wyglądające jak prawdziwy przepływ — zwiększają złożoność grafu.

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

Dodaje statyczne funkcje-miksery, wywołuje ich łańcuchy wewnątrz funkcji i wstawia zabezpieczenia, które nigdy się nie wykonają, zwiększając ilość kodu do przeanalizowania przez dekompilator.

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)

Generowane fałszywe instrukcje pod zawsze-fałszywymi warunkami, niezmieniające działania skryptu, zwiększające rozmiar i utrudniające analizę.

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

Pasywny szkielet try/finally dodaje zagnieżdżenie bez rzucania wyjątków i bez zmiany wartości.

Przynęty

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()

Wiarygodnie wyglądające funkcje nigdy nie wywoływane — szum dla reverse engineerów.

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

Nieużywane wyrażenia lambda na początku — nigdy nie wywoływane w 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)

Duża stała tekstowa przesuwa numery linii i powiększa plik — logika jej nie czyta.

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)

Komentarze imitują dodatkowe zależności modułów, sugerując fałszywe importy. Usuwane przy włączonym usuwaniu komentarzy.

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

Przepisuje bezwzględne import / from na __import__ i getattr. Importy względne, from __future__ oraz import * pozostają bez zmian. Nazwy modułów zostają jako stringi pod późniejsze szyfrowanie łańcuchów.

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

Cele wywołań budowane są z fragmentów i rozwiązywane przez getattr() — statyczne wyszukiwanie po nazwach funkcji już nie zadziała.

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

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

Fragmenty przypominają rejestrację callbacków/handlerów, ale nigdy nie podpinają prawdziwego źródła zdarzeń.

Inne

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

Komentarze (#) usuwane z wygenerowanego skryptu.

Pytania?

Jeśli masz jakieś wątpliwości lub pytania - napisz do mnie.