Assistance in Decrypting Lua script that is obfuscated with Base64 > SSL - ssl

Can anyone on here help me on decrypting the SSL encryption that protects this LUA script linked at the end of this topic? Basically they are encoded with Base64 then SSL, but I have no idea how to do the SSL portion. They are used with a program called Bot of Legends, and someone told me that it is possible to break the encryption by dumping the decryption function of said program and using that to get the SSL key, but I have no clue where to even start on that. Basically these scripts work by connecting to an authentication server that is coded into the script, and I have gotten a few on my own by sniffing the traffic to their auth server from network packets to get their server link and essentially created my own auth server with Apache, then redirected the network traffic that goes to their server to my own from the script to get the script validated response. For some scripts that have stronger encryption, its not that easy and I would have to get to the source code to remove the coding that runs the auth server checks. Up until a few days ago I had no knowledge on how lua coding worked and how to even compute how auth server checks could be even possible for coding in a simple text file due to lua obfuscation. So bear with me, I would like if someone can chime in and give me an idea on what I can do.
Regards,
Chris
*** PasteBin link to the script in question in raw format: http://pastebin.com/raw.php?i=bG0VqQGW
The Base64 section is first with the SSL section at the bottom.

print("SSL Decoder version 2.0")
print("Copyright (C) 2015")
print("Decoding Started...")
local infilename = select(1,...)
local outfilename = select(2,...)
local infile = io.open(infilename, "r")
if not infile then
error("Failed to open input file.")
end
local intext = infile:read("*a")
infile:close()
local ssltabletext = intext:match("SSL%s*%(%s*%{([%s,0-9]*)%}%s*%)")
if not ssltabletext then
error("Could not find ssl table in source file.")
end
local ssltable = load("return {"..ssltabletext.."}")()
if #ssltable < 255 then
error("SSL table is too short -- can't find table encryption key.")
end
-- find decryption key for the ssl table
local decrypt = {}
decrypt[0] = 0
for i = 1,255 do
local dec = i
local enc = ssltable[i]
assert(decrypt[enc] == nil)
decrypt[enc] = dec
end
-- decrypt ssl table
for i = 256, #ssltable - 256 do -- not sure what last 256 bytes are
ssltable[i] = decrypt[ssltable[i] ]
end
-- If this does a stack overflow, easy to change to something dumb but more robust
local sslcode = string.char(table.unpack(ssltable, 256, #ssltable - 256))
-- This is interesting --
--print(sslcode)
local keyindex = sslcode:match("local Key%s*=%s*'()")
if not keyindex then
error("Could not find key in decoded ssl table.")
end
local key = sslcode:sub(keyindex)
local length = 0
while true do
local c = key:sub(length+1, length+1)
if c == "" then
error("Key string was not terminated.")
elseif c == "'" then
break
elseif c == "\\" then
local c2 = key:sub(length+2, length+2)
if c2:match("%d") then
local c3 = key:sub(length+3, length+3)
if c3:match("%d") then
local c4 = key:sub(length+4, length+4)
if c4:match("%d") then
length = length + 4
else
length = length + 3
end
else
length = length + 2
end
elseif c2 == "x" then
length = length + 4
else
length = length + 2
end
else
length = length + 1
end
end
key = key:sub(1, length)
if #key == 0 then
error("Key is empty")
end
print("Key Found! > " .. key)
print("Decoding finished, outfile is at > " .. outfilename)
-- find base64
local b64 = intext:match("_G.ScriptCode%s*=%s*Base64Decode%s*%(%s*\"([a-zA-Z0-9/+]*=*)\"%s*%)")
if not b64 then
error("Could not find Base-64 encrypted code in source file.")
end
-- base64 decode
local b64val = {}
for i = 0, 25 do
do
local letter = string.byte("A")
b64val[string.char(letter+i)] = i
end
do
local letter = string.byte("a")
b64val[string.char(letter+i)] = i + 26
end
end
for i = 0, 9 do
local numeral = string.byte("0")
b64val[string.char(numeral+i)] = i + 52
end
b64val["+"] = 62
b64val["/"] = 63
b64val["="] = 0
local encoded = b64:gsub("(.)(.)(.)(.)",function(a,b,c,d)
local n = b64val[a] * (64 * 64 * 64) + b64val[b] * (64 * 64) + b64val[c] * 64 + b64val[d]
local b1 = n % 256; n = (n - b1) / 256
local b2 = n % 256; n = (n - b2) / 256
local b3 = n
if d == "=" then
if c == "=" then
assert(b1 == 0 and b2 == 0)
return string.char(b3)
else
assert(b1 == 0)
return string.char(b3, b2)
end
else
return string.char(b3, b2, b1)
end
end)
-- decode
local decoded = encoded:gsub("()(.)", function(i, c)
local b = c:byte()
local ki = ((i - 1) % #key) + 1
local k = key:byte(ki,ki)
b = b - k
if b < 0 then b = b + 256 end
return string.char(b)
end)
-- verify
local result, err = load(decoded)
if not result then
error("Decoded file could not be loaded -- it may be corrupt... ("..tostring(err)..")")
end
-- output
local outfile = io.open(outfilename, "wb")
if not outfile then
error("Failed to open output file.")
end
outfile:write(decoded)
outfile:close()
This code is by Extreme Coders (https://reverseengineering.stackexchange.com/users/1413/extreme-coders)
how to use it , u need to get lua52.exe
save the code into a text file and name it ssl.lua (for example)
now run cmd and type lua52 ssl yourscript.lua decryptedscript.lua
it will run and decrypt it.

Related

Reading Redis stream in Lua script

I am trying to read the following stream:
127.0.0.1:6379> xrevrange driver:70 + - count 1
1) 1) "1656531417451-0"
2) 1) "field1"
2) "value1"
3) "isbusy"
4) "true"
How can I read this stream in Lua script and reflect the field isbusy from the stream into the the local Lua variable is_busy?
I could not get my head around Lua collections.
local stream = KEYS[1]
local is_busy = false
local messages = redis.call("XREVRANGE", stream, "+", "-", "COUNT", "1")
for _, message in ipairs(messages) do
end
You need to parse the nested structure:
local id
for _, message in ipairs(messages) do
for i, sub_msg in ipairs(message) do
if i == 1 then
id = sub_msg
else
-- parse attributes
local i = 1
while i < #sub_msg do
local k = sub_msg[i]
local v = sub_msg[i + 1]
if k == "isbusy" then
is_busy = v
end
i = i + 2
end
end
end
end

When returning a table get {}

I am trying to create a maze generation script using the module listed below. I'm having this strange problem were the grid variable has a value, but when return grid is called, it returns and empty table? Here is the code to the module script.
local ServerStorage = game:GetService("ServerStorage")
local cellTemplate = ServerStorage:WaitForChild("Cell")
local Maze = {}
local cellObject = {}
local grid = {}
Maze.Size = 25
Maze.CellWidth = 9
Maze.CellLength = 9
Maze.Offset = Vector3.new(0,0,0)
function Maze.new(x,z) -- Create a new cell
local newCell = {}
setmetatable(newCell,{ -- Allows us to create functions for a cell easier
__index = cellObject
})
newCell.X = math.clamp(x,1,Maze.Size)
newCell.Z = math.clamp(z,1,Maze.Size)
newCell.Visited = false
newCell.Model = cellTemplate:Clone()
newCell.Base = newCell.Model:WaitForChild("Base")
newCell.Model.Name = newCell.Model.Name.. "_".. newCell.X.. "-".. newCell.Z
newCell.Walls = {
["Forward"] = newCell.Model:WaitForChild("Forward");
["Backward"] = newCell.Model:WaitForChild("Backward");
["Left"] = newCell.Model:WaitForChild("Left");
["Right"] = newCell.Model:WaitForChild("Right");
}
if not grid[x] then grid[x] = {} end -- We might not have anything on that x axis yet; inserts it into the table
grid[x][z] = newCell
print(grid)
return newCell
end
function Maze.RenderAll() -- Render every cell; exists for more readibility
for _,cellRow in pairs(grid) do -- Loop through every cell row
for _,cell in pairs(cellRow) do -- Loop through every cell in the row
cell:Render() -- Render the cell
end
end
end
function Maze.Grid() -- Allows other scripts to get the grid but not modify it
return grid
end
function Maze.FilterUnvisited(cells) -- Takes in a table and returns one with only the cells in the table that are unvisited
local unvisited = {}
for _,cell in pairs(cells) do -- Loop through every cell in the table passed
if not cell.Visitied then -- The cell hasn't been visited
table.insert(unvisited,cell)
end
end
return unvisited
end
function Maze.GetCell(x,z)
local cell
if grid[x] and grid[x][z] then
cell = grid[x][z]
else
cell = nil
end
return cell
end
function cellObject:Render() -- Render the cell
self.Model:SetPrimaryPartCFrame(CFrame.new(Vector3.new(self.X * Maze.CellLength,0,self.Z * Maze.CellWidth) + Maze.Offset)) -- Move the cell to the correct position
if self.Visited then -- We have gone through the cell
self.Model.PrimaryPart.Color3 = Color3.new(0,1,0) -- Show that the cell has been visited; used for debugging
end
self.Model.Parent = workspace
end
function cellObject:Neighbours() -- Returns the cell's neigbours
local neighbours = {}
-- Order: Right Left Up Down
if grid[self.X + 1] and grid[self.X + 1][self.Z] then -- A cell with +1 X exists
table.insert(neighbours,grid[self.X + 1][self.Z])
end
if grid[self.X - 1] and grid[self.X - 1][self.Z] then -- A cell with -1 X exists
table.insert(neighbours,grid[self.X - 1][self.Z])
end
if grid[self.X][self.Z + 1] then -- A cell with +1 Z exists
table.insert(neighbours,grid[self.X][self.Z + 1])
end
if grid[self.X][self.Z - 1] then -- A cell with -1 Z exists
table.insert(neighbours,grid[self.X][self.Z - 1])
end
return neighbours
end
function cellObject:RandomNeighbour()
local neighbours = self:Neighbours() -- Gets the neigbours of the current cell
if #neighbours > 0 then
return neighbours[math.random(1,#neighbours)] -- Returns a random neigbour
else
return nil
end
end
function cellObject:WallArray() -- Returns an array of the walls instead of a table
local wallArray = {}
wallArray[1] = self.Walls.Forward
wallArray[2] = self.Walls.Right
wallArray[3] = self.Walls.Left
wallArray[4] = self.Walls.Backward
return wallArray
end
function cellObject:Join(cell) -- Joins 2 cells together
local wallPosition = self.Base.Position:Lerp(cell.Base.Position,.5) -- This will return the position of the wall (excluding the Y)
local cell1Array = self:WallArray()
local cell2Array = cell:WallArray()
for wallIndex,wall in pairs(cell1Array) do
if wall.Position.X == wallPosition.X and wall.Position.Z == wallPosition.Z then -- Its the right wall
wall.Transparency = 1
wall.CanCollide = false
cell2Array[4 - (wallIndex - 1)].Transparency = 1
cell2Array[4 - (wallIndex - 1)].CanCollide = false
break -- We don't need to loop anymore, since we've already removed the walls
end
end
end
function cellObject:Unjoin(cell) -- Unjoins 2 cells
local wallPosition = self.Base.Position:Lerp(cell.Base.Position,.5) -- This will return the position of the wall (excluding the Y)
local cell1Array = self:WallArray()
local cell2Array = cell:WallArray()
for wallIndex,wall in pairs(cell1Array) do
if wall.Position.X == wallPosition.X and wall.Position.Z == wallPosition.Z then -- Its the right wall
wall.Transparency = 0
wall.CanCollide = true
cell2Array[4 - (wallIndex - 1)].Transparency = 0
cell2Array[4 - (wallIndex - 1)].CanCollide = true
break -- We don't need to loop anymore, since we've already added the walls
end
end
end
return Maze
Below is the piece of code I'm having diffuculty with.
function Maze.Grid() -- Allows other scripts to get the grid but not modify it
return grid
end
I have tested printing the grid variable and it's not {} but when I call the function in the script below, I always get {}! I have no idea why this is.
Is this a bug in roblox or am I being stupid?
local ServerScriptService = game:GetService("ServerScriptService")
local Maze = require(ServerScriptService:WaitForChild("Maze"))
local latestX = 0
local latestZ = 0
function CreatePath(cell)
local nextCell = cell:RandomNextCell(false)
if nextCell then -- We have a cell next to the current one
print("Joining cells:",cell,nextCell)
cell:Join(nextCell)
cell.Visited = true
cell:Render()
wait(.01)
CreatePath(nextCell)
else -- It must be the end of the maze
print("Path generated!")
print("Maze end: ".. cell.Model.Name)
cell.Base.Color = Color3.new(1,0,0)
end
end
function GenerateMaze()
local cell = Maze.new(latestX + 1,latestZ + 1) -- Create a new cell
cell:Render() -- Render it
print("Created cell")
latestZ += 1
if latestZ > Maze.Size then -- It has exceeded the max size, move on to a new row
latestX += 1
latestZ = 0
end
if #Maze.Grid() < Maze.Size ^ 2 then
wait(.01)
GenerateMaze()
else
print("Grid completed, generating path...")
CreatePath(Maze.GetCell(1,1))
end
end
GenerateMaze()
You are returning a cached value, or well i don't find out other reason.
You may do (and it's not editable):
function Maze.Grid(i, l)
local passed = 1
local response = {}
for index, v in pairs(grid) do
response[index] = i and passed >= i and l and passed <= l and v or not (i or l) and v
-- basically get more than `i` if it, and minor than `l` if it passed
i = i + 1
end
return response
end
It should work, I don't tested it sorry.

Getting an error in a currency script in ROBLOX studio

I am getting an error in my currency script in Roblox studio
local currencyName = "Coins"
local DataStore = game:GetService(("DataStoreService"):GetDataStore("TestDataStore")
game.Players.PlayerAdded:Connect(function(player)
local folder = Instance.new("Folder")
folder.Name = "leaderstats"
folder.Parent = player
local currency = Instance.new("IntValue")
currency.Name = currencyName
currency.Parent = folder
local ID =""currencyName.."-"..player.UserId
local savedData = nil
pcall(function())
savedData = DataStore:GetAsync(ID)
end)
if savedData ~= nil then
curreny.Value = savedData
print("Data loaded")
else
currency.Value = 10 -- amount to a new player
print("New player to the game")
end
end)
game.Plyers.PlayerRemoving:Connect(function(player)
local ID =""currencyName.."-"..player.UserId
DataStore:SetAsync(ID,player.leaderstats[currencyName].Value)
end)
game:BindToClose(function()
-- when game is ready to shut down
for i, player in pairs(game.Players:GetPlayers()) do
if player then
player:KICK("This game is shutting down")
end
end
wait(5)
end)
It is giving me this error at line 3:
10:30:47.749 - CurrencyScript.Script:3: Expected ')' (to close '(' at line 2), got 'game'
10:30:49.909 - InsertService cannot be used to load assets from the client
Expected ')' (to close '(' at line 2),
You have too many parentheses in line 2.
local DataStore = game:GetService("DataStoreService"):GetDataStore("TestDataStore")

128 bit hex keygen

So my professor gave me a challenge to build a decoder that could break his special formula. It was described to be 32 characters in length, alphanumeric numeric when entered but then "it has a system... the first 106 bits must be 50% 1's and the rest 0's, the remaining 22 bits are basically a hash of the previous bits so that the key can be checked..." were his exact words. Sounds to me like a 128 bit encryption with a twist. I found the below but I need VB2010 or VS2010, this says php.
<?php
function string_random($characters, $length)
{
$string = '';
for ($max = mb_strlen($characters) - 1, $i = 0; $i < $length; ++ $i)
{
$string .= mb_substr($characters, mt_rand(0, $max), 1);
}
return $string;
}
// 128 bits is 16 bytes; 2 hex digits to represent each byte
$random_128_bit_hex = string_random('0123456789abcdef', 32);
// $random_128_bit_hex might be: '4374e7bb02ae5d5bc6d0d85af78aa2ce'
Would that work? Or does it need converting? Please help. Oh and thank you :)
I wasn't promised extra credit but either way I would like to surprise him.
So the first 106 bit are 26 character and the first half of the 27.
You have first of all encode somehow the number of 0 and 1, while building the string you need to keep an eye to the number. An idea would be to build a map like this:
0 = 0000 = -4
1 = 0001 = -2
2 = 0010 = -2
3 = 0011 = 0
4 = -2
5 = 0
6 = 0
7 = +2
8 = -2
9 = 0
a = 0
b = +2
c = 0
d = +2
e = +2
f = +4
then everytime you extract a new random number you check the number associated to it and add it to a variable
balanceOfOneAndZero
your objective is have balanceOfOneAndZero = 0 when you hit your 27th character.
to do that you need a control function, that takes current balanceOfOneAndZero, the proposed character proposedChar, and current string lenght currLenght.
Would be better to split the problem into two part. First is reaching the 26th character of the sequence with balanceOfOneAndZero between -2 and 2. Any other value is not acceptable, because your 27th character can have maximum two 1 or two 0 to completely balance the first 106 characters.
so your function should do something like (I'll write in sort of pseudo code since I don't have an IDE right now)
function checkNextLetter(Dim balanceOfOneAndZero As Integer, Dim proposedChar As Char,
Dim currentLenght as Integer) As Boolean
If( ((26 - currentLenght - 1) * 4 + 2) < MOD(Map.ValueOf(proposedChar) + balanceOfOneAndZero) ) Then
Return true
Else
Return false
ENd If
End function
This function basically check if accepting the new character will still make possible to Balance the number of 0 and 1 before the 26th character.
So your main function should have a loop every time it propose a new character, something like
proposedChar = new RandomChar
While (Not checkNextLetter(balanceOfOneAndZero, proposedChar, len(currentString))
proposedChar = new RandomChar
End While
currentString = currentString & proposedChar
this only until you hit the 26th character.
Than you have to check balanceOfOneAndZero, if its 2 you add a character that begin with 00, if it's 0 you can either have 10 or 01, if it's -2 you have to add a character that begin with 11.
After this I can't help you about the rest 22 character, since there are not enough information. You could brute force the rest
EDIT:
so to brute force the rest (il start from when you reach the 26th character):
Dim stringa1, stringa2, stringa3, stringa4 As String
If balanceOfOneAndZero = 2 Then
stringa1 = currentString & '0'
stringa2 = currentString & '1'
stringa3 = currentString & '2'
stringa4 = currentString & '3'
ELse If balanceOfOneAndZero = 0 Then
stringa1 = currentString & '4'
stringa2 = currentString & '5'
stringa3 = currentString & '6'
stringa4 = currentString & '7'
Else
stringa1 = currentString & 'c'
stringa2 = currentString & 'd'
stringa3 = currentString & 'e'
stringa4 = currentString & 'f'
End if
Function GenerateAllCombination(ByVal iLenght As Integer)
Dim arrayLista As New List(Of String)()
Dim arraySubLista As New List(Of String)()
If (iLenght > 1) Then
arraySubLista = GenerateAllCombination(iLenght -1)
for each objString As String in arraySubLista
for each ele As String in arrayValori
arrayLista.add(objString & ele)
loop
loop
Else
for each ele As String in arrayValori
arrayLista.add(ele)
loop
End If
End Function
Now if you use generateAllCombination you will have a List of string with ALL the combination of 5 character.
Now you just create 4 list by concatenating those combination with your string1 to string4 (string1 & combination) etc..
put all those result on a List of string, and you have 100% that at least ONE of the string will break your teacher code
I forgot, arrayValori must be a List with all values from "0" to "f"

Lua table.toString(tableName) and table.fromString(stringTable) functions?

I am wanting to convert a 2d lua table into a string, then after converting it to a string convert it back into a table using that newly created string. It seems as if this process is called serialization, and is discussed in the below url, yet I am having a difficult time understanding the code and was hoping someone here had a simple table.toString and table.fromString function
http://lua-users.org/wiki/TableSerialization
I am using the following code in order to serialize tables:
function serializeTable(val, name, skipnewlines, depth)
skipnewlines = skipnewlines or false
depth = depth or 0
local tmp = string.rep(" ", depth)
if name then tmp = tmp .. name .. " = " end
if type(val) == "table" then
tmp = tmp .. "{" .. (not skipnewlines and "\n" or "")
for k, v in pairs(val) do
tmp = tmp .. serializeTable(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and "\n" or "")
end
tmp = tmp .. string.rep(" ", depth) .. "}"
elseif type(val) == "number" then
tmp = tmp .. tostring(val)
elseif type(val) == "string" then
tmp = tmp .. string.format("%q", val)
elseif type(val) == "boolean" then
tmp = tmp .. (val and "true" or "false")
else
tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\""
end
return tmp
end
the code created can then be executed using loadstring(): http://www.lua.org/manual/5.1/manual.html#pdf-loadstring if you have passed an argument to 'name' parameter (or append it afterwards):
s = serializeTable({a = "foo", b = {c = 123, d = "foo"}})
print(s)
a = loadstring(s)()
The code lhf posted is a much simpler code example than anything from the page you linked, so hopefully you can understand it better. Adapting it to output a string instead of printing the output looks like:
t = {
{11,12,13},
{21,22,23},
}
local s = {"return {"}
for i=1,#t do
s[#s+1] = "{"
for j=1,#t[i] do
s[#s+1] = t[i][j]
s[#s+1] = ","
end
s[#s+1] = "},"
end
s[#s+1] = "}"
s = table.concat(s)
print(s)
The general idea with serialization is to take all the bits of data from some data structure like a table, and then loop through that data structure while building up a string that has all of those bits of data along with formatting characters.
How about a JSON module? That way you have also a better exchangeable data. I usually prefer dkjson, which also supports utf-8, where cmjjson won't.
Under the kong works this
local cjson = require "cjson"
kong.log.debug(cjson.encode(some_table))
Out of the kong should be installed package lua-cjson https://github.com/openresty/lua-cjson/
Here is a simple program which assumes your table contains numbers only. It outputs Lua code that can be loaded back with loadstring()(). Adapt it to output to a string instead of printing it out. Hint: redefine print to collect the output into a table and then at the end turn the output table into a string with table.concat.
t = {
{11,12,13},
{21,22,23},
}
print"return {"
for i=1,#t do
print"{"
for j=1,#t[i] do
print(t[i][j],",")
end
print"},"
end
print"}"
Assuming that:
You don't have loops (table a referencing table b and b referencing a)
Your tables are pure arrays (all keys are consecutive positive integers, starting on 1)
Your values are integers only (no strings, etc)
Then a recursive solution is easy to implement:
function serialize(t)
local serializedValues = {}
local value, serializedValue
for i=1,#t do
value = t[i]
serializedValue = type(value)=='table' and serialize(value) or value
table.insert(serializedValues, serializedValue)
end
return string.format("{ %s }", table.concat(serializedValues, ', ') )
end
Prepend the string resulting from this function with a return, store it on a .lua file:
-- myfile.lua
return { { 1, 2, 3 }, { 4, 5, 6 } }
You can just use dofile to get the table back.
t = dofile 'myfile.lua'
Notes:
If you have loops, then you will have
to handle them explicitly - usually with an extra table to "keep track" of repetitions
If you don't have pure arrays, then
you will have to parse t differently,
as well as handle the way the keys are rendered (are they strings? are they other tables? etc).
If you have more than just integers
and subtables, then calculating
serializedValue will be more
complex.
Regards!
I have shorter code to convert table to string but not reverse
function compileTable(table)
local index = 1
local holder = "{"
while true do
if type(table[index]) == "function" then
index = index + 1
elseif type(table[index]) == "table" then
holder = holder..compileTable(table[index])
elseif type(table[index]) == "number" then
holder = holder..tostring(table[index])
elseif type(table[index]) == "string" then
holder = holder.."\""..table[index].."\""
elseif table[index] == nil then
holder = holder.."nil"
elseif type(table[index]) == "boolean" then
holder = holder..(table[index] and "true" or "false")
end
if index + 1 > #table then
break
end
holder = holder..","
index = index + 1
end
return holder.."}"
end
if you want change the name just search all compileTable change it to you preferred name because this function will call it self if it detect nested table but escape sequence I don't know if it work
if you use this to create a lua executable file that output the table it will ge compilation error if you put new line and " sequence
this method is more memory efficient
Note:
Function not supported
User data I don't know
My solution:
local nl = string.char(10) -- newline
function serialize_list (tabl, indent)
indent = indent and (indent.." ") or ""
local str = ''
str = str .. indent.."{"
for key, value in pairs (tabl) do
local pr = (type(key)=="string") and ('["'..key..'"]=') or ""
if type (value) == "table" then
str = str..nl..pr..serialize_list (value, indent)..','
elseif type (value) == "string" then
str = str..nl..indent..pr..'"'..tostring(value)..'",'
else
str = str..nl..indent..pr..tostring(value)..','
end
end
str = str:sub(1, #str-1) -- remove last symbol
str = str..nl..indent.."}"
return str
end
local str = serialize_list(tables)
print('return '..nl..str)