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