An untraceable route of obfuscated scripts is lurking in my game

Please do not necropost on this thread.

I have managed to discover an interconnected series of scripts in my game obfuscated by the “Luraph Obfuscator”. Does anyone know where they come from, and what they are? Here’s what I’ve identified:

First one.
Script1.lua (48.1 KB)
Second one.
Script2.lua (42.5 KB)
Third one.
Script3.lua (593 Bytes)
Fourth one.
Script4.lua (45.8 KB)
Fifth one.
Script5.lua (43.7 KB)

That’s all that I’ve identified. They’ve thrown up errors in numerous spots, allowing me to figure out what names they go by before they switch to " " and variations of it.

Luraph Script
Main
jerk

I also note that there seems to be a boot up that prints out:
BuilderX Backup Loaded

Has anyone else had a problem with this? Anyone know how to track down the source? Anyone know how to decode the obfuscated scripts? I do not know where they came from, as I joined the project later on and only script. I’ve been looking through find all, and clicking on their sources but until I figured out their names, could not track down the source code.

5 Likes

Most likely these scripts are some malicious script that was downloaded ether by a plugin or a free model on the tool box. If you can it would be best to make sure all plugins you have downloaded are actual plugins and not fake ones.

Usually you can find all those scripts by searching for “require” or in your case searching for “Luraph” might also find some more.

There is also a chance that this might of been the work of a past programmer in your group trying to hide code for some reason but the chances of that are low.

Finally you should delete them all and talk with your group about downloading plugins and free models off the toolbox.

3 Likes

I’ve already searched for Luraph and the such.

Have you checked all your plugins? Make sure that they are real and not a copy by someone.

When your in studio testing click on the exact message in the output and it should direct you to the script, otherwise use control + shift + f to search in all scripts for a message.

1 Like

Yes. I have. The plugins are fine.

When you delete all the scripts do they come back?

Yes, they do come back. XXXXXXXXXXX

Then there is ether a script your not seeing or you missed a malicious plugin. Try searching for anything with “require”. Or alternatively you can try and download some plugins that try and find these scripts.

2 Likes

Then there is ether a script your not seeing or you missed a malicious plugin. Try searching for anything with “require”. Or alternatively you can try and download some plugins that try and find these scripts.

If they come back without you needing to run the game, it’s 100% a plugin (either one you’ve got or one that somebody else in the team has). Take it in turns to close and open the place to discover which one of you has the bad plugin. Once you’ve narrowed it down to a person, have them disable all plugins and progressively enable each one until the scripts return.

If instead they only come back when you run the game, then it could either be a plugin or you missed a script. Instead of searching for a random term like Luraph, which might not be in the problem script, instead just search for Script in the Explorer pane and delete any scripts that are not part of the game’s codebase. If the previous programmer didn’t leave any sort of user-guide or instructions then that’s not great practice, but in this case you might be able to open each script, if it’s obfuscated then delete it, and then try to run the game once they’re all gone.

If you find the game fails to run without the obfuscated scripts then the previous programmer has made this incredibly difficult for you and their behaviour is very suspicious. Consider the possibility of a potential backdoor in this final case, which would require you to rip out the old code and recreate the functionality.

5 Likes

Luraph is the obfuscator developed by an exploiter/scripter, memcorrupt.
Due to this, this is probably a backdoor.
To my extent of knowledge, Luraph is almost impossible to deobfuscate.
My advice would be to go through each script of your game or review your PLUGINS. Malicious plugins may have caused this, so review them and find out if they are the reason behind this.
If you cannot find the source behind this, start over for your game or revert back to a version of your game until these scripts do not appear.
Good luck.

2 Likes

Yeah, we found some dude who had blackmailed one of our devs with his address. We ended up getting the dude to remove our game and IPs from his database because the FBI got involved.

3 Likes

Script 1 contains an interpreter. I don’t have time to… but I’m decoding it anyways :smile:. Just finished making sense of this function from script 1:

	local function getDoubleFP() 
		local a, b = getDoubleWord(), getDoubleWord()
		if a == 0 and b == 0 then
			return 0
		end
		return (-decode(b, 32) * 2 ^ (decode(b, 21, 31) - 1023) * ((decode(b, 1, 20) * 2^32 + a) / 2^52 + 1)
	end

I recognized it as the format for floating point numbers… someone with a degree of intelligence made this interpreter. (I can also tell because even the obfuscated code makes sense, they have good style!) It may be that the script kiddies who made this took this interpreter from a reputable source. If I stop working on it, I’ll post what I’ve got so far.

I’ve noticed that the obfuscation does a simple text replacement for variables names. The same variable name in the obfuscated source is ALWAYS the same variable name in the original source. I know this because variables with the same name are used in the same sorts of places. For example, iterators are commonly the same name.

I’m getting pretty far with parsing the interpreter. Once it is done, you can run it on the hexcode strings embedded in the 3 obfuscated scripts.

BTW, the attacker probably got your developer’s physical address from his IP. In studio if you have HttpEnabled on while developing then all HTTP messages are sent and received from your computer. The bad news is that your IP can be seen by attackers that phone home. The good news is that we can also find out how they phoned home. Where any threats made? If so this is a serious issue and we may have a way to track them down.

I’d be impressed if the attacker gave the full address, was it in the same city / area instead of exact? Generally ISPs don’t release the exact addresses for every IP (for obvious security reasons).

BTW, it seems that whoever made the interpreter wasn’t involved in the attack; the attackers used a prebuilt one. It even has debug info / line numbers in it…

Update: It is a Lua interpreter. I see the Bx, sBx, and other portions of the lua 5.1 bytecode format. Does anyone remember why there is a variable in the chunk header that holds an index dividing the register indexes from the [some other list] index? For example, in an instruction if an index is above this threshold, than it isn’t a register but a different type? The parser is zero index based, so it seems to be a translation from another language.

1 Like

Alright, here is what I’ve got. Unfortunately I have other tasks I need to take scare of so I’ll pass on the baton to whoever has an interest in solving this problem. It may be awhile before I can sink more time into this.

From what I can tell, the interpreter has some modifications. There are also some internals that I was not aware of, so perhaps someone with more Lua interpreter knowledge can help out.

local i = 1

local function parse(source, env) 
	local llil1i1iIiIIiIil1li source = string.gsub(string.sub(source, 5), "..", function(str)
		if string.byte(str, 2) == 71 then
			llil1i1iIiIIiIil1li = tonumber(string.sub(str, 1, 1))
			return ""
		else 
			local hexcode = string.char(tonumber(str, 16))
			if llil1i1iIiIIiIil1li then
				local IlIi11I1i11l1IlIIli = string.rep(hexcode, llil1i1iIiIIiIil1li)
				llil1i1iIiIIiIil1li = nil
				return IlIi11I1i11l1IlIIli
			else
				return hexcode
			end
		end
	end

	local function getByte() 
		local a = string.byte(source, i, i)
		i = i + 1
		return a
	end

	local function getDoubleword() 
		local a, b, c, d = string.byte(source, i, i + 3)
		i = i + 4
		return d * 256^3 + c * 256^2 + b * 256 + a
	end

	local function decode(num, base, base_final)
		if base_final then
			local sum, place = 0, 0
			for j = base, base_final do
				sum = sum + 2 ^ place * decode(num, j)
				place = place + 1
			end
			return sum
		else
			return (base/2) <= num % base and 1 or 0
		end
	end

	local function getDoubleFP() 
		local a, b = getDoubleword(), getDoubleword()
		if a == 0 and b == 0 then
			return 0
		end
		return (-decode(b, 32) * 2 ^ (decode(b, 21, 31) - 1023) * ((decode(b, 1, 20) * 2^32 + a) / 2^52 + 1)
	end

	local function getMaskedDoubleword(mask) 
		local bytes = { string.byte(source, i, i + 3) }
		i = i + 4

		local bit_mask = { nil, nil, nil, nil, nil, nil, nil, nil }
		for j = 1, 8 do
			bit_mask[j] = decode(mask, j)
		end

		local result = {}
		for j = 1, 4 do
			local raw_byte = bytes[j]
			local byte, place = 0, 0
			for k = 1, 8 do
				local bit = decode(raw_byte, k)
				if bit_mask[k] == 1 then
					bit = bit == 1 and 0 or 1
				end
				byte = byte + 2 ^ place * bit
				place = place + 1
			end
			result[j] = byte
		end

		local a, b, c, d = unpack(result)
		return d * 256^3 + c * 256^2 + b * 256 + a
	end

	local function getMaskedString(mask) 
		local len = getDoubleword()
		i = i + len 
		local bit_mask = { nil, nil, nil, nil, nil, nil, nil, nil }
		for j = 1, 8 do
			bit_mask[j] = decode(mask, j)
		end

		local str = ""
		for j = 1, len do
			local byte, place = 0, 0
			for k = 1, 8 do
				local bit = decode(string.byte(source, i - len + j - 1), k)
				if bit_mask[k] == 1 then
					bit = bit == 1 and 0 or 1
				end
				byte = byte + 2 ^ place * bit
				place = place + 1
			end
			str = str .. str.char(byte)
		end
		return str
	end

	local string_mask = getByte() 
	local dw_mask = getByte() 
	local function parseChunk() 
		local state = {
			['instructions'] = {},
			['lines'] = {},
			['constants'] = {},
			['functions'] = {}
		}

		getByte()
		getDoubleword()
		getDoubleword()

		state[91457] = getByte()
		for j = 1, getDoubleword() - 133760 do
			local value = {} 
			local base = getMaskedDoubleword(dw_mask)
			value['Bx'] = decode(base, 1, 18)
			value['C'] = decode(base, 10, 18)
			value['opcode'] = decode(base, 27, 32)
			value['B'] = decode(base, 1, 9)
			value['A'] = decode(base, 19, 26)
			state['instructions'][j] = value
		end

		state['registers'] = getByte()

		getDoubleword() 

		for j = 1, getDoubleword() - 133737 do
			local values = {} 
			local bytecode = getByte()
			if bytecode == 240 then
				values['data'] = true
			elseif bytecode == 192 then
				values['data'] = getMaskedString(120)
			elseif bytecode == 139 then
				values['data'] = false
			elseif bytecode == 42 then
				values['data'] = getDoubleFP()
			elseif bytecode == 0 then
				values['data'] = getDoubleword()
			elseif bytecode == 231 then
				values['data'] = getByte()
			elseif bytecode == 231 then
				values['data'] = getByte() + getDoubleword() + getDoubleFP()
			elseif bytecode == 201 then
				values['data'] = getDoubleword()
			elseif bytecode == 40 then
				values['data'] = getMaskedString(string_mask)
			end
			state['constants'][j - 1] = values
		end

		getDoubleword()
		getByte()
		getDoubleword()
		getByte()

		for j = 1, getDoubleword() do
			state['lines'][j] = getDoubleword()
		end

		state[24370] = getByte()
		for j = 1, getDoubleword() do
			state['functions'][j - 1] = parseChunk()
		end

		getDoubleword()
		getByte()
		getByte()
		getByte()

		return state
	end

	local function execute(state, env, scope) 
		local instructions = state['instructions'] 
		local constants = setmetatable({}, {
			__index = function(self, key)
				local constant = state['constants'][key]
				if string.sub(type(constant['data']), 1, 1) == "s" then
					return {
						['data'] = string.sub(constant['data'], 3)
					}
				end
				return constant
			end
		}) 
		local functions = state['functions']
		local II1i1lIIiI111iIiIl1 = state[24370]
		local lines = state['lines']
		local function main(...) 
			local lIi11iiII1ii11ilIIl = 0 
			local registers = { unpack({}, 1, state['registers']) } 
			local PC = 1 
			local sub_scopes = {} 
			local I111ilIlilliiIilIll = {} 
			local Il1I1II1i1I11IlIi1i = 1 
			local env = getfenv()
			local args = { ... } 
			local lIlilIii1Ii1Il1ili1ll = {}

			local numArgs = #args - 1
			for j = 0, numArgs do
				if II1i1lIIiI111iIiIl1 >= j + 1 then
					registers[j] = args[j + 1]
				end
				lIlilIii1Ii1Il1ili1ll[j] = args[j + 1]
			end

			local function getNArgs(...)
				return select("#", ...), { ... }
			end

			local actions
			actions = {
				function(instruction)
					-- LT
					local B = instruction['B'] 
					local A = instruction['A'] 
					local Bx = instruction['Bx'] 
					local sBx = instruction['sBx'] - 131071 
					local C = instruction['C']
					A = A ~= 0
					if B > 255 then
						B = constants[B - 256]['data']
					else
						B = registers[B]
					end
					if Bx > 255 then
						Bx = constants[Bx - 256]['data']
					else
						Bx = registers[Bx]
					end
					if B < Bx ~= A then
						PC = PC + 1
					end
				end,
	 			function(instruction) 
					local A = instruction['A'] 
					local sBx = instruction['sBx'] - 131071 
					local B = instruction['B'] 
					local Bx = instruction['Bx'] 
					local C = instruction['C']
					PC = PC + sBx
				end,
	 			nil,
	 			function(instruction) 
					local A = instruction['A'] 
					local Bx = instruction['Bx'] 
					local C = instruction['C'] 
					local B = instruction['B'] 
					local sBx = instruction['sBx'] - 131071
					if B > 255 then
						B = constants[B - 256]['data']
					else
						B = registers[B]
					end
					if Bx > 255 then
						Bx = constants[Bx - 256]['data']
					else
						Bx = registers[Bx]
					end
					registers[A] = B - Bx
				end,
	 			nil,
	 			function(instruction) 
					local C = instruction['C'] 
					local sBx = instruction['sBx'] - 131071 
					local A = instruction['A'] 
					local B = instruction['B'] 
					local Bx = instruction['Bx']
					registers[A] = not registers[B]
				end,
				nil,
				function(instruction) 
					local sBx = instruction['sBx'] - 131071 
					local C = instruction['C'] 
					local Bx = instruction['Bx'] 
					local A = instruction['A'] 
					local B = instruction['B'] 
					local offset = B > 0 and B - 1 or numArgs - II1i1lIIiI111iIiIl1
					for j = A, A + offset do
						if j - A <= numArgs then
							registers[j] = lIlilIii1Ii1Il1ili1ll[II1i1lIIiI111iIiIl1 + (j - A)]
						else
							registers[j] = nil
						end
					end
					lIi11iiII1ii11ilIIl = A + offset
				end,
				function(instruction)
					-- LEN
					local A = instruction['A'] 
					local B = instruction['B'] 
					local Bx = instruction['Bx'] 
					local sBx = instruction['sBx'] - 131071 
					local C = instruction['C']
					registers[A] = #registers[B]
				end,
				function(instruction)
					-- FORPREP
					local Bx = instruction['Bx'] 
					local B = instruction['B'] 
					local sBx = instruction['sBx'] - 131071 
					local A = instruction['A'] 
					local C = instruction['C']
					registers[A] = assert(tonumber(registers[A]), "`for` initial value must be a number")
					registers[A + 1] = assert(tonumber(registers[A + 1]), "`for` limit value must be a number")
					registers[A + 2] = assert(tonumber(registers[A + 2]), "`for` step value must be a number")
					registers[A] = registers[A] - registers[A + 2]
					PC = PC + sBx
				end,
				nil,
				nil,
				nil,
				function(instruction) 
					local C = instruction['C'] 
					local Bx = instruction['Bx'] 
					local B = instruction['B'] 
					local sBx = instruction['sBx'] - 131071 
					local A = instruction['A']
					if Bx == 15 then
						return actions[10]({
							['A'] = (A - 181) % 256,
							['B'] = (B - 181) % 256,
							['sBx'] = 0
						})
					end
					for j = A, B do
						registers[j] = nil
					end
				end,
	 			nil,
	 			function(instruction) 
					local B = instruction['B'] 
					local C = instruction['C'] 
					local A = instruction['A'] 
					local sBx = instruction['sBx'] - 131071 
					local Bx = instruction['Bx'] 
					local base = A + 2 
					local IIiIiilIIIiillil11i = {
						registers[A](
							registers[A + 1],
							registers[A + 2])
					}
					for j = 1, Bx do
						registers[base + j] = IIiIiilIIIiillil11i[j]
					end
					if registers[A + 3] ~= nil then
						registers[A + 2] = registers[A + 3]
					else
						PC = PC + 1
					end
				end,
				nil,
				nil,
				function(instruction) 
					local C = instruction['C'] 
					local sBx = instruction['sBx'] - 131071 
					local B = instruction['B'] 
					local A = instruction['A'] 
					local Bx = instruction['Bx']
					if Bx == 190 then
						return actions[21]({ ['A'] = (A - 17) % 256, ['B'] = (B - 17) % 256, ['sBx'] = 0 })
					end
					registers[A] = registers[B]
				end,
				nil,
				function(instruction) 
					local A = instruction['A'] 
					local B = instruction['B'] 
					local C = instruction['C'] 
					local sBx = instruction['sBx'] - 131071 
					local Bx = instruction['Bx'] 
					local IIiIiilIIIiillil11i = registers[B]
					for j = B + 1, Bx do
						IIiIiilIIIiillil11i = IIiIiilIIIiillil11i .. registers[j]
					end
					registers[A] = IIiIiilIIIiillil11i
				end,
				nil,
				function(instruction) 
					local Bx = instruction['Bx'] 
					local C = instruction['C'] 
					local B = instruction['B'] 
					local sBx = instruction['sBx'] - 131071 
					local A = instruction['A'] B = registers[B]
					if Bx > 255 then
						Bx = constants[Bx - 256]['data']
					else
						Bx = registers[Bx]
					end
					registers[A + 1] = B
					registers[A] = B[Bx]
				end,
				function(instruction)
					-- GETGLOBAL
					local Bx = instruction['Bx'] 
					local A = instruction['A'] 
					local C = instruction['C'] 
					local B = instruction['B'] 
					local sBx = instruction['sBx'] - 131071 
					local name
					if C == 100000 then
						name = registers[251]
					else
						name = constants[C]['data']
					end
					registers[A] = env[name]
				end,
				function(instruction) 
					local C = instruction['C'] 
					local A = instruction['A'] 
					local B = instruction['B'] 
					local sBx = instruction['sBx'] - 131071 
					local Bx = instruction['Bx']
					if Bx == 102 then
						return actions[3]({
							['A'] = (A - 99) % 256,
							['B'] = (B - 99) % 256,
							['sBx'] = 0
						})
					end
					if Bx == 153 then
						return actions[8]({
							['A'] = (A - 215) % 256,
							['Bx'] = (B - 215) % 256,
							['sBx'] = 0
						})
					end

					local limit, count, lIiII1lli1l11I1Iiii
					if B == 1 then
						return true
					end
					if B == 0 then
						limit = lIi11iiII1ii11ilIIl
					else
						limit = A + B - 2
					end
					lIiII1lli1l11I1Iiii = {}
					count = 0
					for j = A, limit do
						count = count + 1
						lIiII1lli1l11I1Iiii[count] = registers[j]
					end
					return true, lIiII1lli1l11I1Iiii, count
				end,
				function(instruction) 
					local sBx = instruction['sBx'] - 131071 
					local C = instruction['C'] 
					local B = instruction['B'] 
					local Bx = instruction['Bx'] 
					local A = instruction['A']
					for j = A, #registers do

						local index = Il1I1II1i1I11IlIi1i
						for _, upvalues in next, sub_scopes, nil do
							for name, upvalue in next, upvalues, nil do
								if registers == upvalue[1] and upvalue[2] == j then
									if not I111ilIlilliiIilIll[index] then
										I111ilIlilliiIilIll[index] = registers[j]
										Il1I1II1i1I11IlIi1i = Il1I1II1i1I11IlIi1i + 1
									end
									upvalues[name] = {I111ilIlilliiIilIll, index}
								end
							end
						end
					end
				end,
				function(instruction) 
					local C = instruction['C'] 
					local sBx = instruction['sBx'] - 131071 
					local Bx = instruction['Bx'] 
					local A = instruction['A'] 
					local B = instruction['B']
					if B > 255 then
						B = constants[B - 256]['data']
					else
						B = registers[B]
					end
					if Bx > 255 then
						Bx = constants[Bx - 256]['data']
					else
						Bx = registers[Bx]
					end
					registers[A] = B ^ Bx
				end,
				function(instruction) 
					local B = instruction['B'] 
					local A = instruction['A'] 
					local sBx = instruction['sBx'] - 131071 
					local Bx = instruction['Bx'] 
					local C = instruction['C']
					registers[A] = {
						unpack({}, 1, B > 7999 and 7999 or B)
					}
				end,
				function(instruction) 
					local sBx = instruction['sBx'] - 131071 
					local C = instruction['C'] 
					local A = instruction['A'] 
					local Bx = instruction['Bx'] 
					local B = instruction['B'] 
					local args, results, limit, run
					args = {}
					if B ~= 1 then
						if B ~= 0 then
							limit = A + B - 1
						else
							limit = lIi11iiII1ii11ilIIl
						end
						run = 0
						for j = A + 1, limit do
							run = run + 1
							args[run] = registers[j]
						end
						limit, results = getNArgs(registers[A](unpack(args, 1, limit - A)))
					else
						limit, results = getNArgs(registers[A]())
					end
					if Bx ~= 1 then
						if Bx ~= 0 then
							limit = A + Bx - 2
						else
							limit = limit + A
						end
						run = 0
						for j = A, limit do
							run = run + 1
							registers[j] = results[run]
						end
					end
					lIi11iiII1ii11ilIIl = limit - 1
				end,
				function(instruction) 
					local B = instruction['B'] 
					local A = instruction['A'] 
					local Bx = instruction['Bx'] 
					local sBx = instruction['sBx'] - 131071 
					local C = instruction['C'] 
					local base = (Bx - 1) * 50
					if B == 0 then
						B = lIi11iiII1ii11ilIIl - A
					end
					for j = 1, B do
						registers[A][base + j] = registers[A + j]
					end
				end,
				nil
			}
			actions[17] = function(instruction) 
				local A = instruction['A'] 
				local B = instruction['B'] 
				local Bx = instruction['Bx'] 
				local C = instruction['C'] 
				local sBx = instruction['sBx'] - 131071
				if B > 255 then
					B = constants[B - 256]['data']
				else
					B = registers[B]
				end
				if Bx > 255 then
					Bx = constants[Bx - 256]['data']
				else
					Bx = registers[Bx]
				end
				registers[A][B] = Bx
			end
			actions[15] = function(instruction) 
				local A = instruction['A'] 
				local sBx = instruction['sBx'] - 131071 
				local C = instruction['C'] 
				local Bx = instruction['Bx'] 
				local B = instruction['B']
				registers[A] = scope[B]
			end
			actions[5] = function(instruction) 
				local C = instruction['C'] 
				local B = instruction['B'] 
				local sBx = instruction['sBx'] - 131071 
				local A = instruction['A'] 
				local Bx = instruction['Bx'] 
				local number = registers[A + 2] 
				local i = registers[A] + number
				registers[A] = i
				if number > 0 then
					if i <= registers[A + 1] then
						PC = PC + sBx
						registers[A + 3] = i
					end
				elseif i >= registers[A + 1] then
					PC = PC + sBx
					registers[A + 3] = i
				end
			end
			actions[7] = function(instruction)
				-- CALL
				local Bx = instruction['Bx'] 
				local sBx = instruction['sBx'] - 131071 
				local A = instruction['A'] 
				local B = instruction['B'] 
				local C = instruction['C'] 
				local func = functions[C] 
				local upvalues = {} 
				local sub_scope = setmetatable({}, {
					__index = function(self, key) 
						local upvalue = upvalues[key]
						return upvalue[1][upvalue[2]]
					end
					__newindex = function(self, key, value) 
						local upvalue = upvalues[key]
						upvalue[1][upvalue[2]] = value
					end
				})
				for j = 1, func[91475] do
					local inst = instructions[PC]
					if inst['opcode'] == 2 then
						upvalues[j - 1] = { registers, inst['B'] }
					elseif inst['opcode'] == 5 then
						upvalues[j - 1] = { scope, inst['B'] }
					end
					PC = PC + 1
				end
				if func[91475] > 0 then
					sub_scopes[#sub_scopes + 1] = upvalues
				end
				local result = execute(func, env, sub_scope)
				registers[A] = result
			end
			actions[22] = function(instruction) 
				local B = instruction['B'] 
				local Bx = instruction['Bx'] 
				local sBx = instruction['sBx'] - 131071 
				local C = instruction['C'] 
				local A = instruction['A']
				if Bx > 255 then
					Bx = constants[Bx - 256]['data']
				else
					Bx = registers[Bx]
				end
				registers[A] = registers[B][Bx]
			end
			actions[12] = function(instruction) 
				local sBx = instruction['sBx'] - 131071 
				local C = instruction['C'] 
				local A = instruction['A'] 
				local B = instruction['B'] 
				local Bx = instruction['Bx']
				registers[A] = constants[C]['data']
			end
			actions[31] = function(instruction) 
				local A = instruction['A'] 
				local B = instruction['B'] 
				local C = instruction['C'] 
				local sBx = instruction['sBx'] - 131071 
				local Bx = instruction['Bx']
				if not not registers[A] == (Bx == 0) then
					PC = PC + 1
				end
			end
			actions[20] = function(instruction) 
				local C = instruction['C'] 
				local B = instruction['B'] 
				local sBx = instruction['sBx'] - 131071 
				local A = instruction['A'] 
				local Bx = instruction['Bx']
				if B > 255 then
					B = constants[B - 256]['data']
				else
					B = registers[B]
				end
				if Bx > 255 then
					Bx = constants[Bx - 256]['data']
				else
					Bx = registers[Bx]
				end
				registers[A] = B * Bx
			end
			actions[11] = function(instruction) 
				local sBx = instruction['sBx'] - 131071 
				local Bx = instruction['Bx'] 
				local C = instruction['C'] 
				local B = instruction['B'] 
				local A = instruction['A'] 
				local args, results, limit, run
				args = {}
				run = 0
				if B ~= 1 then
					if B ~= 0 then
						limit = A + B - 1
					else
						limit = lIi11iiII1ii11ilIIl
					end
					for j = A + 1, limit do
						run = run + 1
						args[run] = registers[j]
					end
					limit, results = getNArgs(
						registers[A](
							unpack(args, 1, limit - A)
						)
					)
				else
					limit, results = getNArgs(registers[A]())
				end
				return true, results, limit
			end
			actions[13] = function(instruction) 
				-- SETGLOBAL
				local Bx = instruction['Bx'] 
				local C = instruction['C'] 
				local B = instruction['B'] 
				local sBx = instruction['sBx'] - 131071 
				local A = instruction['A'] 
				local name
				if C == 100000 then
					name = registers[251]
				else
					name = constants[C]['data']
				end
				env[name] = registers[A]
			end
			actions[3] = function(instruction) 
				local sBx = instruction['sBx'] - 131071 
				local A = instruction['A'] 
				local B = instruction['B'] 
				local C = instruction['C'] 
				local Bx = instruction['Bx']
				if Bx == 104 then
					return actions[6]({
						['A'] = (A - 44) % 256,
						['B'] = (B - 44) % 256,
						['sBx'] = 0
					})
				end
				if Bx == 117 then
					return actions[27]({
						['A'] = (A - 122) % 256,
						['B'] = (B - 122) % 256,
						['sBx'] = 0
					})
				end
				registers[A] = -registers[B]
			end
			actions[0] = function(instruction) 
				local sBx = instruction['sBx'] - 131071 
				local A = instruction['A'] 
				local Bx = instruction['Bx'] 
				local B = instruction['B'] 
				local C = instruction['C']
				if Bx == 169 then
					return actions[32]({
						['A'] = (A - 191) % 256,
						['B'] = (B - 191) % 256,
						['sBx'] = 0
					})
				end
				if Bx == 25 then
					return actions[4]({
						['A'] = (A - 240) % 256,
						['B'] = (B - 240) % 256,
						['sBx'] = 0
					})
				end
				scope[B] = registers[A]
			end
			actions[18] = function(instruction) 
				local Bx = instruction['Bx'] 
				local B = instruction['B'] 
				local A = instruction['A'] 
				local sBx = instruction['sBx'] - 131071 
				local C = instruction['C'] A = A ~= 0
				if B > 255 then
					B = constants[B - 256]['data']
				else
					B = registers[B]
				end
				if Bx > 255 then
					Bx = constants[Bx - 256]['data']
				else
					Bx = registers[Bx]
				end
				if B == Bx ~= A then
					PC = PC + 1
				end
			end
			actions = {
				actions[1],
				actions[8],
				actions[19],
				actions[6],
				actions[12],
				actions[15],
				actions[0],
				actions[31],
				actions[18],
				actions[9],
				actions[16],
				actions[3],
				actions[22],
				actions[11],
				actions[23],
				actions[10],
				actions[21],
				actions[17],
				actions[5],
				actions[24],
				actions[25],
				actions[28],
				actions[13],
				actions[2],
				actions[30],
				actions[27],
				actions[26],
				actions[4],
				actions[20],
				actions[29],
				actions[7],
				actions[14]
			}

			local function run()
				while true do
					local instruction = instructions[PC]
					PC = PC + 1 
					local success, value, count = actions[instruction['opcode'] + 1](instruction)
					if success then
						return value, count
					end
				end
			end

			local success, value, count = pcall(run)
			if success then
				if value and count > 0 then
					return unpack(value, 1, count)
				end
			else 
				local msg = string.gsub("Luraph Script:" .. (lines[PC - 1] or "") .. ": " .. tostring(value), "[^:]+:%d*: ", function(str)
				if not string.match(str, "Luraph Script:%d") then
					return ""
				end
			end
	 		error(msg, 0)
		end
	end
	setfenv(main, env)
	return main
end

	local chunk = parseChunk()
	return execute(chunk, env)()
end
parse("LPH|ADF651758AD709B498691400870A02002GF6F4AAF72GF68E3GF68E3GF6AE3GF6BAF72GF6823GF6A600F8EF3F176A0A02002A5G00E49440E459A93CE005817542595G00013G00528BD96109DBC4940C003D0B0200F7F6F4AAF72GF68EE02GF6BA3GF6BA2GF6F4AAE02GF6BAF62GF4CEF4F6FEE62GF6F4AABFA40BECF5F2F682F62GF0CEF2F6FAE62GF6F4AA1FA4F7EDF52GF2822GF6FEBA2GF6F4AAE0F6FEBA2GF4FCCEF4F62GE62GF6F4AAF8A4F3EDF5F2FE82F3F6FAE62GF6E6BA2GF6F4AAE0F6E6BAF2F4E4CEF0F6EEE62GF6F4AA2DF7E68AF5F2E682F2F8E4CE2GF6EEBA2GF6F4AAE0F6EEBAF0E6ECC6F4F6F4AAE0F6EEBA2GF6F4AAD1A4FBEDF5F2E682FFF6E2BA2GF6F4AAE0F6E2BAF3E2E0C62GF6F4AAE0F6E2BA2GF6EE8EF32GF6FEF6F2EAA2FDEFE8B2F7F6D6A2F6FCD2A2F8E9D0B2E6D5D0B2E4D1D0B2E3F6DEBA2GF6F4AAE0F6DEBAE0F6DAE62GF6F4AA7CF7DE8AF4F2DE82E2E3D2B2F5F6DEA2F6F0DAA2EFF6C6E6F2C2C0C62GF6F4AAE0F6C2BAFAECC6B6EEEFDAB2EAF6C6E62GF6C2BA2GF6F4AAE0F6C2BAFBE6C0C62GF6F4AAE0F6C2BAFAECC6B6EDEFDAB2EBCBD8B2F6F0C6A2E9F6C2E62GF6CEBA2GF6F4AAE0F6CEBAF8F4CCCED6F6B6E62GF6F4AA3CA4C3EDF5F2CE82F8B4CCCE2GF6B6BA2GF6F4AAE0F6B6BAE6B2B4C6F4F6F4AAE0F6B6BA2GF6F4AA76F7CE8AF5F2CE82FBEAC2B6EEEDC6B2D5F6C2E62GF6CEBA2GF6F4AAE0F6CEBAF8B2CCC62GF6F4AAE0F6CEBAFBEAC2B62GEDC6B2EBCBC4B2F6F0C2A2EEBFC0B2EDF5C2B2EBCBC0B2F5F4DE96E1E3D2B2F7F4D696FBE7EAB2F4BCD4CE9C3A42A72GF6F4AA4AA4EBEDF5F2D682F4BAD0CE903A4EA79D3A4AA72GF6F4AAC1A4D7EDF2F4D2822GF6D2BA2GF6F4AAE0F6D2BAFFF4D0CEF4F6DAE62GF6F4AA75A4D7EDF5F2D282F6F4DEA2E9F6DAE62GF6C6BA2GF6F4AAE0F6C6BAFAF4C4CED6F6CEE62GF6F4AA30A4DBEDF5F2C682FAB4C4CE2GF6CEBA2GF6F4AAE0F6CEBAF8B2CCC6F4F6F4AAE0F6CEBA2GF6F4AA3EF7C68AF5F2C682D1F6C2E6923A32A7FDEADAB6FDE1DEB2FFBCD8CE9B3A36A72GF6F4AADBA4DFEDF5F2DA82983A42A7FFBAD8CEF3F6C2E69B3A32A72GF6F4AA32A4DFEDF2F4DA82E48AB3FE5E6EE2AEA71CFED80FC492EC18490085D57985BF702A24A9BA7BB0DA5BA467EC27E88B90115A66876D920A020028063G002GADCACCC0C8280C3G002GADEAC8D9FEC8DFDBC4CEC8280D3G002GADE52GD9DDFEC8DFDBC4CEC8280A3G002GADEAC8D9ECDED4C3CE28183G002GADC52GD9DDDE972G82CCDDC483C4DDC4CBD483C2DFCA82287D3G002GADC52GD9DDDE972G82C9C4DECEC2DFC9CC2GDD83CEC2C082CCDDC482DAC8CFC52GC2C6DE829B99959C9B9D9F992G9D9E959D989C95989D82D8FFE29EDDF2FD9BDBCEC3C4EFEEC2DCE5EE80EBFEE5F7E8F8E7C7E89F9CC4999CDBC9EED7DAEC99D99EFFE2E780C6E0C5F4DBF7DB95EFFEFA95CEF895EF2G80ECF794FD28143G002GADE0CCDFC6C8D9DDC1CCCEC8FEC8DFDBC4CEC828103G002GADEAC8D9FDDFC2C9D8CED9E4C3CBC228093G002GADFDC1CCCEC8E4C928063G002GADC0CCD9C528083G002GADDFCCC3C9C2C028093G002GADCEC2C3D9C8C3D928023G002GAD28083G002GADC8C0CFC8C9DE28073G002GADD9C4D9C1C8280B3G002GADE4FD8DE1E22GEAE8E9280D3G002GADC9C8DECEDFC4DDD9C4C2C3280E3G002GADC2C0CA8DDEC28DCE2GC2C18C28063G002GADD9D4DDC828063G002GADDFC4CEC528073G002GADCEC2C1C2DF280A3G002GADD9C2C3D8C0CFC8DF2A4G00E0C8EA4028083G002GADCBC4C8C1C9DE28063G002GADC3CCC0C828093G002GADEACCC0C88D808D28063G002GADE3CCC0C828073G002GADDBCCC1D8C8281F3G002GADC52GD9DDDE972G823GDA83DFC2CFC1C2D583CEC2C082CACCC0C8DE8228083G002GADC4C3C1C4C3C88B28113G002GADEACCC0C88DEEDFC8CCD9C2DF8D808D28093G002GADFDC1CCD4C8DFDE28183G002GADEAC8D9E3CCC0C8EBDFC2C0F8DEC8DFE4C9ECDED4C3CE280B3G002GADEEDFC8CCD9C2DFE4C9281F3G002GADC52GD9DDDE972G823GDA83DFC2CFC1C2D583CEC2C082D8DEC8DFDE82280C3G002GADE4FD8DEAFFEC2GEFE8E9280C3G002GADE7FEE2E3E8C3CEC2C9C8280B3G002GADFDC2DED9ECDED4C3CE28083G002GAD8DE4FD8D808D28023G002GAD757CD7472EC02A9A2BEBB49G006G00019G002G00013G00019G002G00013G00033G00039G002G00033G00059G002G00053G00059G002G00053G00063G00079G002G00073G00079G002G00073G00073G00079G002G00079G009G001G00073G00099G002G00099G002G00103G00103G00123G00143G00153G00153G00163G00173G00183G00199G002G00199G002G00193G00193G001A3G001A3G001C3G001C9G002G001C3G001C3G001D3G001D9G002G001D9G002G001D3G001D3G001E3G001F3G00213G00219G002G00213G00219G002G00213G00213G00219G002G00219G009G001G00213G00213G00213G00223G00229G002G00229G002G00223G00223G00233G00243G00263G00273G00283G002A3G002A3G002B3G002B3G002D3G002D9G002G002D3G002E3G002E3G002E9G002G002E3G00309G002G00303G00309G002G00303G00313G00323G00329G002G00323G00329G002G00323G00323G00329G002G00329G009G001G00323G00323G00323G00323G00323G00353G00359G002G00353G00353G00373G00373G00379G002G00373G00374G00013G006D6497031A84D2F431019A0A0200F7F6F4AAF72GF68EF22GF6BA3GF6E6F7F6F2BA2GF6F4AAF2F6F2BAF7F2F0C62GF6F4AAF2F6F2BA953A62A7F5F6FAE62GF6E68E3GF6E2F2F6F2C2E78ABFFEE48AB3FE51D802F633FA9DDD691CEAD974F61CC2428F8E9F565F0FC93938DFC962CD94F2CDE88A8905EFBE140E6F0A020028263G002GAD8GD5804GD580993GD580D43GD5809GD53GD528083G002GADDED9DFC4C3CA28063G002GADCADED8CF28063G002GADF6D5D4F02A5G00E4944028023G002GAD0A79942G727F883E53F3119G006G000B3G000C9G002G000C9G002G000C3G000C3G000F3G000F3G000C3G000F3G00104G00013G00E6A7E22E04C67D896501AF0A0200F7F6F4AA3GF68EF72GF6BA2GF6F4D62GF6F4AA2GF6F4AAFDF6F4AADA2642DA2GF6F4AAF7F6F2BAF7F6FEE6F4F6FAE62GF6F4AAD5F7F28AF52GF2822EC497A52GF6F4AA2GF6F4AAF1F6F4AADA2642DA2GF6F4AAF7F6F2BAF5F6FEE6F2F6FAE62GF6F4AA1EA4F7EDF52GF282F3F6FEBA2GF6F4AAF7F6FEBAF4FAFCC62GF6F4AAF7F6FEBAF1F6FAE6923A6AA7F5F6FEC2E78ABBFEE48AB3FE6F8A4FFD7020DE8C841E8B984784D2804BEA2BC7E8C2E4D99DB3F5EB6D9F218665D107D5059193FF74720A020028033G002GADD52A8G002A6G002E402A6G0020402A6G00264028083G002GADDED9DFC4C3CA28083G002GADCBC2DFC0CCD928043G002GAD88D528023G002GADA5C0F87BF3CD68D462C8269G006G000D9G002G000D3G000D9G002G000D3G000D9G002G000D3G000D9G002G000D3G000D9G002G000D3G000D9G002G000D3G000E9G002G000E9G002G000E3G000E3G000E3G000E3G000F3G00014G0039387045CE8BB51ED3446EF6AC293D83B64D613A4DE859D42EB2C4DA", getfenv()) 

It may be worthwhile printing out the constants and subfunction constants without running the interpreter. You can also use my sandbox here to trace exactly how it interacts with its environment. You should be able to find between these two techniques how the script phones home even without knowing the full source.

If anyone is bored just take IdomicLanguage’s beautified code and compare it to https://github.com/JustAPerson/lbi/blob/master/src/lbi.lua to ‘unobfuscate’ his byte-code. It just seems like this is a hastily-edited version of LBI with some Bit Ops meant to confuse you. Writing a tool to do the necessary substitutions should be a fun little challenge for someone to do.

what if you checked script activity and identified their sources?
like :

This has already been done for older versions of Luraph before. Felt like mentioning it.

There’s a plugin shared in community resources for detecting scripts inserted by plugins