-- This Script is Part of the Prometheus Obfuscator by levno-710 -- -- SplitStrings.lua -- -- This Script provides a Simple Obfuscation Step for splitting Strings local Step = require("prometheus.step"); local Ast = require("prometheus.ast"); local visitAst = require("prometheus.visitast"); local Parser = require("prometheus.parser"); local util = require("prometheus.util"); local enums = require("prometheus.enums") local LuaVersion = enums.LuaVersion; local SplitStrings = Step:extend(); SplitStrings.Description = "This Step splits Strings to a specific or random length"; SplitStrings.Name = "Split Strings"; SplitStrings.SettingsDescriptor = { Threshold = { name = "Threshold", description = "The relative amount of nodes that will be affected", type = "number", default = 1, min = 0, max = 1, }, MinLength = { name = "MinLength", description = "The minimal length for the chunks in that the Strings are splitted", type = "number", default = 5, min = 1, max = nil, }, MaxLength = { name = "MaxLength", description = "The maximal length for the chunks in that the Strings are splitted", type = "number", default = 5, min = 1, max = nil, }, ConcatenationType = { name = "ConcatenationType", description = "The Functions used for Concatenation. Note that when using custom, the String Array will also be Shuffled", type = "enum", values = { "strcat", "table", "custom", }, default = "custom", }, CustomFunctionType = { name = "CustomFunctionType", description = "The Type of Function code injection This Option only applies when custom Concatenation is selected.\ Note that when choosing inline, the code size may increase significantly!", type = "enum", values = { "global", "local", "inline", }, default = "global", }, CustomLocalFunctionsCount = { name = "CustomLocalFunctionsCount", description = "The number of local functions per scope. This option only applies when CustomFunctionType = local", type = "number", default = 2, min = 1, } } function SplitStrings:init(settings) end local function generateTableConcatNode(chunks, data) local chunkNodes = {}; for i, chunk in ipairs(chunks) do table.insert(chunkNodes, Ast.TableEntry(Ast.StringExpression(chunk))); end local tb = Ast.TableConstructorExpression(chunkNodes); data.scope:addReferenceToHigherScope(data.tableConcatScope, data.tableConcatId); return Ast.FunctionCallExpression(Ast.VariableExpression(data.tableConcatScope, data.tableConcatId), {tb}); end local function generateStrCatNode(chunks) -- Put Together Expression for Concatenating String local generatedNode = nil; for i, chunk in ipairs(chunks) do if generatedNode then generatedNode = Ast.StrCatExpression(generatedNode, Ast.StringExpression(chunk)); else generatedNode = Ast.StringExpression(chunk); end end return generatedNode end local customVariants = 2; local custom1Code = [=[ function custom(table) local stringTable, str = table[#table], ""; for i=1,#stringTable, 1 do str = str .. stringTable[table[i]]; end return str end ]=]; local custom2Code = [=[ function custom(tb) local str = ""; for i=1, #tb / 2, 1 do str = str .. tb[#tb / 2 + tb[i]]; end return str end ]=]; local function generateCustomNodeArgs(chunks, data, variant) local shuffled = {}; local shuffledIndices = {}; for i = 1, #chunks, 1 do shuffledIndices[i] = i; end util.shuffle(shuffledIndices); for i, v in ipairs(shuffledIndices) do shuffled[v] = chunks[i]; end -- Custom Function Type 1 if variant == 1 then local args = {}; local tbNodes = {}; for i, v in ipairs(shuffledIndices) do table.insert(args, Ast.TableEntry(Ast.NumberExpression(v))); end for i, chunk in ipairs(shuffled) do table.insert(tbNodes, Ast.TableEntry(Ast.StringExpression(chunk))); end local tb = Ast.TableConstructorExpression(tbNodes); table.insert(args, Ast.TableEntry(tb)); return {Ast.TableConstructorExpression(args)}; -- Custom Function Type 2 else local args = {}; for i, v in ipairs(shuffledIndices) do table.insert(args, Ast.TableEntry(Ast.NumberExpression(v))); end for i, chunk in ipairs(shuffled) do table.insert(args, Ast.TableEntry(Ast.StringExpression(chunk))); end return {Ast.TableConstructorExpression(args)}; end end local function generateCustomFunctionLiteral(parentScope, variant) local parser = Parser:new({ LuaVersion = LuaVersion.Lua52; }); -- Custom Function Type 1 if variant == 1 then local funcDeclNode = parser:parse(custom1Code).body.statements[1]; local funcBody = funcDeclNode.body; local funcArgs = funcDeclNode.args; funcBody.scope:setParent(parentScope); return Ast.FunctionLiteralExpression(funcArgs, funcBody); -- Custom Function Type 2 else local funcDeclNode = parser:parse(custom2Code).body.statements[1]; local funcBody = funcDeclNode.body; local funcArgs = funcDeclNode.args; funcBody.scope:setParent(parentScope); return Ast.FunctionLiteralExpression(funcArgs, funcBody); end end local function generateGlobalCustomFunctionDeclaration(ast, data) local parser = Parser:new({ LuaVersion = LuaVersion.Lua52; }); -- Custom Function Type 1 if data.customFunctionVariant == 1 then local astScope = ast.body.scope; local funcDeclNode = parser:parse(custom1Code).body.statements[1]; local funcBody = funcDeclNode.body; local funcArgs = funcDeclNode.args; funcBody.scope:setParent(astScope); return Ast.LocalVariableDeclaration(astScope, {data.customFuncId}, {Ast.FunctionLiteralExpression(funcArgs, funcBody)}); -- Custom Function Type 2 else local astScope = ast.body.scope; local funcDeclNode = parser:parse(custom2Code).body.statements[1]; local funcBody = funcDeclNode.body; local funcArgs = funcDeclNode.args; funcBody.scope:setParent(astScope); return Ast.LocalVariableDeclaration(data.customFuncScope, {data.customFuncId}, {Ast.FunctionLiteralExpression(funcArgs, funcBody)}); end end function SplitStrings:variant() return math.random(1, customVariants); end function SplitStrings:apply(ast, pipeline) local data = {}; if(self.ConcatenationType == "table") then local scope = ast.body.scope; local id = scope:addVariable(); data.tableConcatScope = scope; data.tableConcatId = id; elseif(self.ConcatenationType == "custom") then data.customFunctionType = self.CustomFunctionType; if data.customFunctionType == "global" then local scope = ast.body.scope; local id = scope:addVariable(); data.customFuncScope = scope; data.customFuncId = id; data.customFunctionVariant = self:variant(); end end local customLocalFunctionsCount = self.CustomLocalFunctionsCount; local self2 = self; visitAst(ast, function(node, data) -- Previsit Function -- Create Local Function declarations if(self.ConcatenationType == "custom" and data.customFunctionType == "local" and node.kind == Ast.AstKind.Block and node.isFunctionBlock) then data.functionData.localFunctions = {}; for i = 1, customLocalFunctionsCount, 1 do local scope = data.scope; local id = scope:addVariable(); local variant = self:variant(); table.insert(data.functionData.localFunctions, { scope = scope, id = id, variant = variant, used = false, }); end end end, function(node, data) -- PostVisit Function -- Create actual function literals for local customFunctionType if(self.ConcatenationType == "custom" and data.customFunctionType == "local" and node.kind == Ast.AstKind.Block and node.isFunctionBlock) then for i, func in ipairs(data.functionData.localFunctions) do if func.used then local literal = generateCustomFunctionLiteral(func.scope, func.variant); table.insert(node.statements, 1, Ast.LocalVariableDeclaration(func.scope, {func.id}, {literal})); end end end -- Apply Only to String nodes if(node.kind == Ast.AstKind.StringExpression) then local str = node.value; local chunks = {}; local i = 1; -- Split String into Parts of length between MinLength and MaxLength while i <= string.len(str) do local len = math.random(self.MinLength, self.MaxLength); table.insert(chunks, string.sub(str, i, i + len - 1)); i = i + len; end if(#chunks > 1) then if math.random() < self.Threshold then if self.ConcatenationType == "strcat" then node = generateStrCatNode(chunks); elseif self.ConcatenationType == "table" then node = generateTableConcatNode(chunks, data); elseif self.ConcatenationType == "custom" then if self.CustomFunctionType == "global" then local args = generateCustomNodeArgs(chunks, data, data.customFunctionVariant); -- Add Reference for Variable Renaming data.scope:addReferenceToHigherScope(data.customFuncScope, data.customFuncId); node = Ast.FunctionCallExpression(Ast.VariableExpression(data.customFuncScope, data.customFuncId), args); elseif self.CustomFunctionType == "local" then local lfuncs = data.functionData.localFunctions; local idx = math.random(1, #lfuncs); local func = lfuncs[idx]; local args = generateCustomNodeArgs(chunks, data, func.variant); func.used = true; -- Add Reference for Variable Renaming data.scope:addReferenceToHigherScope(func.scope, func.id); node = Ast.FunctionCallExpression(Ast.VariableExpression(func.scope, func.id), args); elseif self.CustomFunctionType == "inline" then local variant = self:variant(); local args = generateCustomNodeArgs(chunks, data, variant); local literal = generateCustomFunctionLiteral(data.scope, variant); node = Ast.FunctionCallExpression(literal, args); end end end end return node, true; end end, data) if(self.ConcatenationType == "table") then local globalScope = data.globalScope; local tableScope, tableId = globalScope:resolve("table") ast.body.scope:addReferenceToHigherScope(globalScope, tableId); table.insert(ast.body.statements, 1, Ast.LocalVariableDeclaration(data.tableConcatScope, {data.tableConcatId}, {Ast.IndexExpression(Ast.VariableExpression(tableScope, tableId), Ast.StringExpression("concat"))})); elseif(self.ConcatenationType == "custom" and self.CustomFunctionType == "global") then table.insert(ast.body.statements, 1, generateGlobalCustomFunctionDeclaration(ast, data)); end end return SplitStrings;