| Dialekt źródła |
|
Automatycznie próbuje źródła jako 5.1, potem 5.2, 5.3, 5.4, 5.5, a następnie Luau i zostawia najstarszy dialekt, który się parsuje. LuaJIT nie jest wnioskowany. Skrypt, którego jedyną nowszą cechą jest goto, jest wykrywany jako 5.2, bo goto pojawiło się w Lua 5.2. Wybierz LuaJIT jawnie, gdy skrypt ma być traktowany jako LuaJIT.
|
|
local a = 5
a = a + 1
print(a)
|
local __ins = __code[__pc]
__op, __a, __b, __c, __bx = __ins[1], __ins[2], __ins[3], __ins[4], __ins[5]
__pc = __pc + 1
__hs[__map[__op]]()
|
Kompiluje każdą funkcję do zaszyfrowanego kodu rejestrowego i wykonuje go interpreterem handlerów. Kodowanie opcode i wygenerowane nazwy zależą od ziarna. Suma kontrolna kodu jest sprawdzeniem integralności.
|
local a = 10
local b = a + 5
print(b)
|
repeat
if __op < 10 then __band0()
else if __op < 20 then __band1()
end end
until __halt == 1
|
Wykonuje ten kod jako skończony automat stanów. Kody operacji są pogrupowane w stany i wykonywane z tych stanów. Kodowanie i nazwy zależą od ziarna.
|
local n = 1
if n > 0 then
n = n + 2
end
print(n)
|
local __w = __tape[__ip]
__tape[__ip] = 0
__tape[0] = __w[__fall]
__arith(__w)
|
Wykonuje ten kod na jednej zmiennej taśmie. Superoperatory przesuwają taśmę i zapisują ją w trakcie wykonania. Kodowanie i nazwy zależą od ziarna.
|
local x = 1
local y = 2
local z = x + y
print(z)
|
while __block ~= 0 do
if __block == 1 then
__take()
__block = __sched[__op]
end
if __block == 12 then
__block = 1
end
end
|
Spłaszcza dyspozycję do harmonogramu numerowanych bloków. Bieżący blok wybiera następny. Kodowanie i nazwy zależą od ziarna.
|
| Ochrona |
local function get_sum(a, b)
return a + b
end
local x = "hello"
get_sum(1, 2)
|
local _g_gkq_tg = 0
local _g_vjf = 0
local function _probe()
local info = debug.getinfo(1, "S")
local src = (info and info.source) or ""
local live = 0
for i = 1, #src do
live = (live * 33 + string.byte(src, i)) % 65536
end
if live ~= 0xA91C then
_g_gkq_tg = 51
os.exit(1)
end
_g_vjf = live
end
_probe()
|
Ostatnia faza ochrony wstawia sondę integralności, która odciska załadowany chunk. Klucze deszyfrowania i pułapek pochodzą z tego tokenu — edycja skryptu truuje późniejsze transformacje (śmieci zamiast jawnego tekstu). Opcjonalna ponowna weryfikacja haszuje ten sam chunk. Sonda nie potrzebuje osobnej kopii skryptu na dysku.
|
local function get_sum(a, b, c)
return a + b
end
get_sum(1, 2, 3)
|
local _g_bd = 375103
local function _gom(x, y)
end
if not _g_vjf then
pcall(get_sum, 655, 353, 571)
pcall(_gom, -249233, -249516)
end
local function get_sum(a, b, c)
return a + b
end
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.
|
| Zmiana nazw |
local user_count = 10
local path = "C:\\data"
print(user_count)
|
local _gt6188mys = 10
local _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.
|
local function get_tag(name)
return name
end
get_tag("x")
|
local function get_tag(_gt6188mys)
return _gt6188mys
end
get_tag("x")
|
Nazwy parametrów i powiązania w ciele funkcji zastąpione zostaną losowymi ciągami znakowymi.
|
local function invoke_work()
return 1
end
invoke_work()
|
local function _gt6188myssr_v()
return 1
end
_gt6188myssr_v()
|
Nazwy funkcji zmienione, a wszystkie wywołania przekierowane; metametody takie jak __index i wbudowane funkcje bibliotek pozostają nietknięte.
|
local function work()
return 1
end
work()
|
local function work()
return 1
end
local _calls = { a1 = work }
_calls.a1()
|
Bezpośrednie wywołania są przepisywane tak, aby cel był wyszukiwany w tabeli nazw/dyspozycji zamiast bezpośredniego identyfikatora.
|
function alpha()
return beta()
end
function beta()
return 1
end
alpha()
|
local function beta()
return 1
end
local function alpha()
return beta()
end
alpha()
|
Permutuje definicje funkcji najwyższego poziomu, gdy kolejność wykonania nadal jest poprawna.
|
local MAX_RETRIES = 5
for i = 1, MAX_RETRIES do
print(i)
end
|
for i = 1, 5 do
print(i)
end
|
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 |
local msg = "SecretKey"
print(msg)
|
local msg = "Se" .. "cr" .. "etK" .. "ey"
print(msg)
|
Literały tekstowe zostaną podzielone i składane w trakcie działania z mniejszych fragmentów.
|
local msg = "Secret"
print(msg)
|
local src, msg = "Tfdsfu", ""
for i = 1, #src do
msg = msg .. string.char(string.byte(src, i) - 1)
end
print(msg)
|
Literały zostaną zapisane jako przesunięte znaki, odtwarzane niewielką transformacją w czasie działania.
|
local x = "payload"
print(x)
|
local function _gt6188myssr_v()
local d = {257, 278, 190, 269, 272, 278, 277}
local r = ""
for i = 1, #d do
local v = (d[i] - 189) % 256
r = r .. string.char(v)
end
return r
end
local 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.
|
local v = "Vault"
print(v)
|
local v = string.char(86, 97, 117, 108, 116)
print(v)
|
Łańcuchy odtwarzane z kodów znaków zamiast tekstu w cudzysłowie.
|
| Wartości numeryczne |
local k = 42
local r = k + 10
print(r)
|
local k = (2 * 21)
local r = k + (-370068 + 370078)
print(r)
|
Proste liczby całkowite zastępowane równoważnymi wyrażeniami arytmetycznymi i bitowymi.
|
local pi = 3.14
print(pi)
|
local pi = math.pow(10, math.log(3.14 * 0.3453511647258002 * 2.89560338038522) / math.log(10))
print(pi)
|
Liczby zmiennoprzecinkowe zostaną zapisane równoważnymi wyrażeniami biblioteki math, więc stałe nie są już widoczne jako zwykłe wartości.
|
local x = 10
print(x)
|
local x = math.floor(math.ceil((10.0 + 31.866664 - 31.866664) - 0.3803732855154077))
print(x)
|
Liczby całkowite odtwarzane z wyrażeń zmiennoprzecinkowych biblioteki math i rzutowane przez math.floor.
|
local n = 100
local m = 200
print(n + m)
|
local _gt6188mys = {{7648, 8688, 7405}, {-9849, 200, 100}}
local n = _gt6188mys[2][3]
local m = _gt6188mys[2][2]
print(n + m)
|
Wartości trafiają do tablic i są odczytywane po indeksie, razem z celowo zaciemnionymi wpisami.
|
local a = 2.5
local b = 1.25
print(a + b)
|
local _gt6188mys = {{7649.9344, 7405.0075, -703.1368}, {9032.1081, 1.25, 2.5}}
local a = _gt6188mys[2][3]
local b = _gt6188mys[2][2]
print(a + b)
|
Liczby zmiennoprzecinkowe zostaną przeniesione do tablic razem z celowo zaciemnionymi wartościami pomiędzy.
|
local v = 255
print(v)
|
local v = _bxor(_bxor(255, 3832), 3832)
print(v)
|
Odwracalne maski bitowe nie zmieniają wartości liczbowych. Helper jest dobierany do wybranego dialektu.
|
local r = a + b
local m = a - b
|
local r = (a + b + 0) - ((a - a) * (b - b))
local m = (a + (0 - b))
|
Operatory binarne zastępowane są równoważnymi tożsamościami mieszanymi boolowsko-arytmetycznymi, ukrywając oryginalny operator przed statycznym dopasowywaniem wzorców.
|
| Szyfrowane literały tablicowe |
local ports = {21, 80, 443}
print(ports[1])
|
local ports = {43668, 43701, 43874}
for i = 1, #ports do
ports[i] = _bxor(ports[i], 0xAAAA)
end
print(ports[1])
|
Zawartość tablic liczb całkowitych jest przechowywana w formie zaszyfrowanej i dekodowana w trakcie działania przed użyciem.
|
local token = {"A", "P", "I"}
print(token[3])
|
local token = {0x1A, 0x0B, 0x12}
for i = 1, #token do
token[i] = string.char(_bxor(token[i], 0x5B))
end
print(token[3])
|
Literały tablic znaków zastępowane są zaszyfrowanymi wartościami i odtwarzane tylko przez wygenerowany kod dekodujący.
|
local scale = {1.5, 2.75}
print(scale[2])
|
local data = {4609434218613702656, 4613374868287651840}
local scale = {}
for i = 1, #data do
scale[i] = _decode_double(_bxor(data[i], 0x55AA))
end
print(scale[2])
|
Stałe tablic zmiennoprzecinkowych są ukrywane jako zakodowane wzorce bitowe i odtwarzane w trakcie działania programu.
|
local roles = {"admin", "guest"}
print(roles[1])
|
local roles = {"\71\112\004\0\7", "\71\14\12\26\27"}
for i = 1, #roles do
local o = ""
for j = 1, #roles[i] do
o = o .. string.char(_bxor(string.byte(roles[i], j), 7))
end
roles[i] = o
end
print(roles[1])
|
Elementy tablic tekstowych są szyfrowane niezależnie i odtwarzane na żądanie przez wygenerowany kod w trakcie działania.
|
| Pule wartości-przynęt |
local x = 1
print(x)
|
local _pool_a = {93817, 205, -4471, 88123}
local _pool_b = {"q1w2", "z9x8"}
local x = 1
print(x)
|
Włącza pule szumu na poziomie modułu, kontrolowane przez poniższe opcje.
|
local x = 1
|
local _pool_ints = {93817, 205, -4471, 88123, 620}
local x = 1
|
Nieużywane przynęty liczbowe dodawane do puli.
|
local x = 1
|
local _pool_arrays = {{1, 2, 3}, {7, 8}, {}}
local x = 1
|
Nieużywane przynęty w postaci tablic dodawane do puli.
|
local x = 1
|
local function _pool_fn_a()
return 0
end
local x = 1
|
Nieużywane funkcje-przynęty dodawane do puli.
|
local x = 1
|
local _pool_chars = {"q", "z", "#", "9"}
local x = 1
|
Nieużywane przynęty znakowe dodawane do puli.
|
local x = 1
|
local _pool_rx = {"(a|b)+", "[0-9]{3,}\\.lua"}
local x = 1
|
Literały o kształcie wyrażeń regularnych mające dezorientować naiwne skanery sygnaturowe.
|
local x = 1
|
local _pool_paths = {"~/.config/app/run", "/etc/xdg/autostart/app.desktop"}
local 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 then
x = 1
else
x = 2
end
|
x = flag and 1 or 2
|
Proste rozgałęzienia są zwijane w wyrażenia warunkowe, łącząc logikę przypisania w jedną nieprzezroczystą linię.
|
if true then
print(1)
end
|
if not (1 == 0) then
print(1)
end
|
Proste testy true/false zastępowane dłuższymi, równoważnymi warunkami.
|
local s = 0
s = s + 1
s = s + 2
s = s + 4
print(s)
|
local s = 0
s = s + 1
if #"HP7ZVzxW" < 7 then
s = s - 99
end
s = s + 2
if math.floor(3.14159) == 4 then
s = 0
end
s = s + 4
print(s)
|
Gałęzie zawsze fałszywe wyglądające jak prawdziwy przepływ — zwiększają złożoność grafu.
|
local function total(a, b)
return a + b
end
|
local function _qx_rf7mz(acc, s)
return acc + ((s % 32768) + 1)
end
local function total(a, b)
local v = _qx_rf7mz(0, -402184)
if v == -8372144055286123915 then
return 0
end
return a + b
end
|
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.
|
local x = 1
print(x)
|
local x = 1
local _lvl_x = 55 * 457 + 17
local _lvl_s = math.abs(_lvl_x - 791)
print(x)
|
Generowane fałszywe instrukcje pod zawsze-fałszywymi warunkami, niezmieniające działania skryptu, zwiększające rozmiar i utrudniające analizę.
|
local x = 1
print(x)
|
local x = 1
local function _finally()
local _ = {}
end
pcall(function() end)
_finally()
print(x)
|
Lua nie ma try/finally, więc przebieg wstawia obojętne pcall i sprzątanie. Dodatkowe zagnieżdżenie nie zmienia wartości.
|
| Przynęty |
local function real_core()
return 42
end
real_core()
|
local function _q6188myssr_v(sa, n)
local bag = { bq_vp = 556, zv_nvf = n }
return bag
end
local function real_core()
return 42
end
real_core()
|
Wiarygodnie wyglądające funkcje nigdy nie wywoływane — szum utrudniający analizę.
|
local x = 1
print(x)
|
local _s88 = function()
return (173 + 190) - (173 + 190) + 54
end
local x = 1
print(x)
|
Nieużywane funkcje anonimowe na początku — nigdy nie wywoływane w runtime.
|
local x = 1
print(x)
|
local _pad = [[
disable_app_background_task get_kds_root_key stop_pcsv_device
]]
local _ = #_pad
local x = 1
print(x)
|
Duża stała tekstowa przesuwa numery linii i powiększa plik — logika jej nie czyta.
|
local x = 1
print(x)
|
-- require("cached_quay_19422")
-- local quarantine = require("nested.profile")
local x = 1
print(x)
|
Komentarze imitują dodatkowe wywołania require. Usuwane przy włączonym usuwaniu komentarzy.
|
local os = require("os")
os.clock()
|
local os = require("os")
os["cl" .. "ock"]()
|
Cele wywołań budowane są z fragmentów i rozwiązywane przez odczyt z tabeli — statyczne wyszukiwanie po nazwach funkcji już nie zadziała.
|
local cjson = require("cjson")
|
local cjson = _G["require"]("cj" .. "son")
|
Przepisuje require tak, aby nazwa modułu powstawała w czasie działania. Nazwy modułów zostają jako ciągi pod późniejsze szyfrowanie.
|
local x = 1
print(x)
|
if false then
local function _on_tick(evt)
return 1
end
local _handlers = { tick = _on_tick }
end
local x = 1
print(x)
|
Fragmenty przypominają rejestrację callbacków/handlerów, ale nigdy nie podpinają prawdziwego źródła zdarzeń.
|
| Inne |
Wykrywaj debuggery, wirtualne maszyny, piaskownice (tzw. sandbox) i emulatory CPU
|
|
- Wykrywaj debuggery dołączone do procesu (
debug.gethook(); 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.
|
-- secret plan
local x = 1 -- inline
print(x)
|
local x = 1
print(x)
|
Komentarze (--) usuwane z wygenerowanego skryptu.
|