Skip to content

Commit 1fe1d67

Browse files
committed
Oh right, I forgot the new emission handler to break deobfuscators, here you go!
Also fixed 2 other inconsistencies and updated my comments in NumbersToExpressions.
1 parent 295ce63 commit 1fe1d67

3 files changed

Lines changed: 238 additions & 33 deletions

File tree

src/prometheus/compiler/emit.lua

Lines changed: 227 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,137 @@
11
-- This Script is Part of the Prometheus Obfuscator by Levno_710
22
--
33
-- emit.lua
4+
--
45
-- This Script contains the container function body emission for the compiler.
56

67
local Ast = require("prometheus.ast");
78
local Scope = require("prometheus.scope");
89
local util = require("prometheus.util");
910
local constants = require("prometheus.compiler.constants");
11+
local AstKind = Ast.AstKind;
1012

1113
local MAX_REGS = constants.MAX_REGS;
1214

1315
return function(Compiler)
16+
local function hasAnyEntries(tbl)
17+
return type(tbl) == "table" and next(tbl) ~= nil;
18+
end
19+
20+
local function unionLookupTables(a, b)
21+
local out = {};
22+
for k, v in pairs(a or {}) do
23+
out[k] = v;
24+
end
25+
for k, v in pairs(b or {}) do
26+
out[k] = v;
27+
end
28+
return out;
29+
end
30+
31+
local function canMergeParallelAssignmentStatements(statA, statB)
32+
if type(statA) ~= "table" or type(statB) ~= "table" then
33+
return false;
34+
end
35+
36+
if statA.usesUpvals or statB.usesUpvals then
37+
return false;
38+
end
39+
40+
local a = statA.statement;
41+
local b = statB.statement;
42+
if type(a) ~= "table" or type(b) ~= "table" then
43+
return false;
44+
end
45+
if a.kind ~= AstKind.AssignmentStatement or b.kind ~= AstKind.AssignmentStatement then
46+
return false;
47+
end
48+
49+
if type(a.lhs) ~= "table" or type(a.rhs) ~= "table" or type(b.lhs) ~= "table" or type(b.rhs) ~= "table" then
50+
return false;
51+
end
52+
53+
if #a.lhs ~= #a.rhs or #b.lhs ~= #b.rhs then
54+
return false;
55+
end
56+
57+
-- Avoid merging vararg/call assignments because they can affect multi-return behavior.
58+
local function hasUnsafeRhs(rhsList)
59+
for _, rhsExpr in ipairs(rhsList) do
60+
if type(rhsExpr) ~= "table" then
61+
return true;
62+
end
63+
local kind = rhsExpr.kind;
64+
if kind == AstKind.FunctionCallExpression or kind == AstKind.PassSelfFunctionCallExpression or kind == AstKind.VarargExpression then
65+
return true;
66+
end
67+
end
68+
return false;
69+
end
70+
if hasUnsafeRhs(a.rhs) or hasUnsafeRhs(b.rhs) then
71+
return false;
72+
end
73+
74+
local aReads = type(statA.reads) == "table" and statA.reads or {};
75+
local aWrites = type(statA.writes) == "table" and statA.writes or {};
76+
local bReads = type(statB.reads) == "table" and statB.reads or {};
77+
local bWrites = type(statB.writes) == "table" and statB.writes or {};
78+
79+
-- Allow merging even if one statement has no writes (e.g., x = o(x) style assignments)
80+
-- Only require that at least one of them has writes
81+
if not hasAnyEntries(aWrites) and not hasAnyEntries(bWrites) then
82+
return false;
83+
end
84+
85+
for r in pairs(aReads) do
86+
if bWrites[r] then
87+
return false;
88+
end
89+
end
90+
91+
for r, b in pairs(aWrites) do
92+
if bWrites[r] or bReads[r] then
93+
return false;
94+
end
95+
end
96+
97+
return true;
98+
end
99+
100+
local function mergeParallelAssignmentStatements(statA, statB)
101+
local lhs = {};
102+
local rhs = {};
103+
local aLhs, bLhs = statA.statement.lhs, statB.statement.lhs;
104+
local aRhs, bRhs = statA.statement.rhs, statB.statement.rhs;
105+
for i = 1, #aLhs do lhs[i] = aLhs[i]; end
106+
for i = 1, #bLhs do lhs[#aLhs + i] = bLhs[i]; end
107+
for i = 1, #aRhs do rhs[i] = aRhs[i]; end
108+
for i = 1, #bRhs do rhs[#aRhs + i] = bRhs[i]; end
109+
110+
return {
111+
statement = Ast.AssignmentStatement(lhs, rhs),
112+
writes = unionLookupTables(statA.writes, statB.writes),
113+
reads = unionLookupTables(statA.reads, statB.reads),
114+
usesUpvals = statA.usesUpvals or statB.usesUpvals,
115+
};
116+
end
117+
118+
local function mergeAdjacentParallelAssignments(blockstats)
119+
local merged = {};
120+
local i = 1;
121+
while i <= #blockstats do
122+
local stat = blockstats[i];
123+
i = i + 1;
124+
125+
while i <= #blockstats and canMergeParallelAssignmentStatements(stat, blockstats[i]) do
126+
stat = mergeParallelAssignmentStatements(stat, blockstats[i]);
127+
i = i + 1;
128+
end
129+
130+
table.insert(merged, stat);
131+
end
132+
return merged;
133+
end
134+
14135
function Compiler:emitContainerFuncBody()
15136
local blocks = {};
16137

@@ -70,8 +191,13 @@ return function(Compiler)
70191
end
71192
end
72193

194+
local mergedBlockStats = mergeAdjacentParallelAssignments(blockstats);
195+
for _=1, 7 do
196+
mergedBlockStats = mergeAdjacentParallelAssignments(mergedBlockStats);
197+
end
198+
73199
blockstats = {};
74-
for _, stat in ipairs(block.statements) do
200+
for _, stat in ipairs(mergedBlockStats) do
75201
table.insert(blockstats, stat.statement);
76202
end
77203

@@ -80,37 +206,114 @@ return function(Compiler)
80206
blocks[id] = block;
81207
end
82208

83-
table.sort(blocks, function(a, b)
84-
return a.id < b.id;
85-
end);
86-
87-
local function buildIfBlock(scope, id, lBlock, rBlock)
88-
local condition = Ast.LessThanExpression(self:pos(scope), Ast.NumberExpression(id));
89-
return Ast.Block({
90-
Ast.IfStatement(condition, lBlock, {}, rBlock);
91-
}, scope);
209+
table.sort(blocks, function(a, b) return a.id < b.id end);
210+
211+
-- Build a strict threshold condition between adjacent block IDs.
212+
-- Using a midpoint avoids exact-id comparisons while preserving dispatch.
213+
local function buildBlockThresholdCondition(scope, leftId, rightId, useAndOr)
214+
local bound = math.floor((leftId + rightId) / 2);
215+
local posExpr = self:pos(scope);
216+
local boundExpr = Ast.NumberExpression(bound);
217+
218+
if useAndOr then
219+
-- Kept for compatibility with caller variations.
220+
return Ast.LessThanExpression(posExpr, boundExpr);
221+
else
222+
local variant = math.random(1, 2);
223+
if variant == 1 then
224+
return Ast.LessThanExpression(posExpr, boundExpr);
225+
else
226+
return Ast.GreaterThanExpression(boundExpr, posExpr);
227+
end
228+
end
92229
end
93230

94-
local function buildWhileBody(tb, l, r, pScope, scope)
231+
-- Build an elseif chain for a range of blocks
232+
local function buildElseifChain(tb, l, r, pScope)
233+
-- Handle invalid range by returning an empty block
234+
if r < l then
235+
local emptyScope = Scope:new(pScope);
236+
return Ast.Block({}, emptyScope);
237+
end
238+
95239
local len = r - l + 1;
240+
241+
-- For single block
96242
if len == 1 then
97-
tb[r].block.scope:setParent(pScope);
98-
return tb[r].block;
99-
elseif len == 0 then
100-
return nil;
243+
tb[l].block.scope:setParent(pScope);
244+
return tb[l].block;
101245
end
102246

103-
local mid = l + math.ceil(len / 2);
104-
local bound = math.random(tb[mid - 1].id + 1, tb[mid].id);
105-
local ifScope = scope or Scope:new(pScope);
247+
-- For small ranges, use elseif chain
248+
if len <= 4 then
249+
local ifScope = Scope:new(pScope);
250+
local elseifs = {};
251+
252+
-- First block uses the first midpoint threshold
253+
tb[l].block.scope:setParent(ifScope);
254+
local firstCondition = buildBlockThresholdCondition(ifScope, tb[l].id, tb[l + 1].id, false);
255+
local firstBlock = tb[l].block;
256+
257+
-- Middle blocks use their upper midpoint threshold
258+
for i = l + 1, r - 1 do
259+
tb[i].block.scope:setParent(ifScope);
260+
local condition = buildBlockThresholdCondition(ifScope, tb[i].id, tb[i + 1].id, false);
261+
table.insert(elseifs, {
262+
condition = condition,
263+
body = tb[i].block
264+
});
265+
end
106266

107-
local lBlock = buildWhileBody(tb, l, mid - 1, ifScope);
108-
local rBlock = buildWhileBody(tb, mid, r, ifScope);
267+
-- Last block becomes else
268+
tb[r].block.scope:setParent(ifScope);
269+
local elseBlock = tb[r].block;
109270

110-
return buildIfBlock(ifScope, bound, lBlock, rBlock);
271+
return Ast.Block({
272+
Ast.IfStatement(firstCondition, firstBlock, elseifs, elseBlock);
273+
}, ifScope);
274+
end
275+
276+
-- For larger ranges, use binary split with and/or chaining
277+
local mid = l + math.ceil(len / 2);
278+
local leftMaxId = tb[mid - 1].id;
279+
local rightMinId = tb[mid].id;
280+
-- Float-safe split: any bound strictly between adjacent IDs works.
281+
-- Midpoint avoids integer-only math.random(min, max) behavior.
282+
local bound = math.floor((leftMaxId + rightMinId) / 2);
283+
local ifScope = Scope:new(pScope);
284+
285+
local lBlock = buildElseifChain(tb, l, mid - 1, ifScope);
286+
local rBlock = buildElseifChain(tb, mid, r, ifScope);
287+
288+
-- Randomly choose between different condition styles
289+
local condStyle = math.random(1, 3);
290+
local condition;
291+
local trueBlock, falseBlock;
292+
293+
if condStyle == 1 then
294+
-- pos < bound
295+
condition = Ast.LessThanExpression(self:pos(ifScope), Ast.NumberExpression(bound));
296+
trueBlock, falseBlock = lBlock, rBlock;
297+
elseif condStyle == 2 then
298+
-- bound > pos
299+
condition = Ast.GreaterThanExpression(Ast.NumberExpression(bound), self:pos(ifScope));
300+
trueBlock, falseBlock = lBlock, rBlock;
301+
else
302+
-- Equivalent split using strict > with branches reversed.
303+
condition = Ast.GreaterThanExpression(self:pos(ifScope), Ast.NumberExpression(bound));
304+
trueBlock, falseBlock = rBlock, lBlock;
305+
end
306+
307+
return Ast.Block({
308+
Ast.IfStatement(condition, trueBlock, {}, falseBlock);
309+
}, ifScope);
111310
end
112311

113-
local whileBody = buildWhileBody(blocks, 1, #blocks, self.containerFuncScope, self.whileScope);
312+
local whileBody = buildElseifChain(blocks, 1, #blocks, self.containerFuncScope);
313+
if self.whileScope then
314+
-- Ensure whileScope is properly connected
315+
self.whileScope:setParent(self.containerFuncScope);
316+
end
114317

115318
self.whileScope:addReferenceToHigherScope(self.containerFuncScope, self.returnVar, 1);
116319
self.whileScope:addReferenceToHigherScope(self.containerFuncScope, self.posVar);
@@ -137,6 +340,7 @@ return function(Compiler)
137340

138341
table.insert(stats, Ast.WhileStatement(whileBody, Ast.VariableExpression(self.containerFuncScope, self.posVar)));
139342

343+
140344
table.insert(stats, Ast.AssignmentStatement({
141345
Ast.AssignmentVariable(self.containerFuncScope, self.posVar)
142346
}, {
@@ -151,4 +355,4 @@ return function(Compiler)
151355

152356
return Ast.Block(stats, self.containerFuncScope);
153357
end
154-
end
358+
end

src/prometheus/steps/ConstantArray.lua

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,7 @@ local function callNameGenerator(generatorFunction, ...)
9999
return generatorFunction(...);
100100
end
101101

102-
function ConstantArray:init(settings)
103-
104-
end
102+
function ConstantArray:init(_) end
105103

106104
function ConstantArray:createArray()
107105
local entries = {};

src/prometheus/steps/NumbersToExpressions.lua

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ NumbersToExpressions.SettingsDescriptor = {
3131
},
3232
}
3333

34-
--[[
35-
This function removes trailing zeros from a floating point number
36-
]]
34+
35+
-- This is for the modulo generator. When I remove it, the obfuscated code breaks.
36+
-- I have no idea why, the odd part is it doesn't even use floating point numbers!
3737
local function filterNumber(number)
3838
local formatted = string.format("%.50f", number)
3939
formatted = formatted:gsub("%.?0+$", "")
@@ -50,7 +50,7 @@ local function generateModuloExpression(n)
5050
return lhs, rhs
5151
end
5252

53-
function NumbersToExpressions:init(settings)
53+
function NumbersToExpressions:init(_)
5454
self.ExpressionGenerators = {
5555
function(val, depth) -- Addition
5656
local val2 = math.random(-2 ^ 20, 2 ^ 20)
@@ -88,7 +88,10 @@ function NumbersToExpressions:init(settings)
8888
false
8989
)
9090
end,
91-
function(val, depth) -- Advanced Modulo
91+
92+
-- NOTE: Please don't enable depth with this generator.
93+
-- It's extremely unstable & breaks the step. I don't know why.
94+
function(val, _) -- Modulo
9295
if val <= 0 then
9396
return false
9497
end
@@ -108,7 +111,7 @@ function NumbersToExpressions:CreateNumberExpression(val, depth)
108111
end
109112

110113
local generators = util.shuffle({ unpack(self.ExpressionGenerators) })
111-
for i, generator in ipairs(generators) do
114+
for _, generator in ipairs(generators) do
112115
local node = generator(val, depth + 1)
113116
if node then
114117
return node
@@ -119,7 +122,7 @@ function NumbersToExpressions:CreateNumberExpression(val, depth)
119122
end
120123

121124
function NumbersToExpressions:apply(ast)
122-
visitast(ast, nil, function(node, data)
125+
visitast(ast, nil, function(node, _)
123126
if node.kind == AstKind.NumberExpression then
124127
if math.random() <= self.Threshold then
125128
return self:CreateNumberExpression(node.value, 0)

0 commit comments

Comments
 (0)