Skip to content

Commit

Permalink
Implement bytecode intead of plaintext lua source
Browse files Browse the repository at this point in the history
  • Loading branch information
Anon Y. Mouse committed Apr 18, 2021
1 parent ddc1d2a commit b27ad34
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 14 deletions.
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ A simple Windows executable that frankensteins with Lua to create portable scrip

# How does it work?

You will have 2 files: `fuser.exe` and `luastub.bin`. `luastub.bin` is a simple Windows executable with the Lua runtime statically linked to it. It looks for a string in memory and tries to execute it as a Lua script. Said string actually resides in a PE section called .lua, which is empty and has a size of 0 unless the stub is modified. That modification is done by `fuser.exe`, which takes 3 arguments: the stub file, a Lua script, and the output filename. The fuser will make a copy of `luastub.bin` with the Lua script embedded, which should then be able to run without external dependencies. In fact, `fuser.exe` itself is nothing more than a Lua script appended to the stub.
You will have 2 files: `fuser.exe` and `luastub.bin`. `luastub.bin` is a simple Windows executable with the Lua runtime statically linked to it. It looks for Lua bytecode in memory and tries to run it. Said bytecode actually resides in a PE section called .lua, which is empty and has a size of 0 by default. The bytecode is patched into the .lua section by `fuser.exe`, which takes 3 arguments: the stub file, a Lua script, and the output filename. The fuser will compile the script, then make a copy of `luastub.bin` with the bytecode embedded, which should then be able to run without external dependencies. In fact, `fuser.exe` itself is nothing more than a Lua script compiled and embedded to the stub.

# How do I use it?

Expand All @@ -13,10 +13,6 @@ Usage: `fuser.exe <luastub.bin> <source OR file:source.lua> <output.exe>`

For example, if you have a file called hello.lua in the same directory as the fuser and stub that you wish to fuse into hello.exe: `fuser.exe luastub.bin file:hello.lua hello.exe`.

# Does it work with compiled Lua bytecode?

In my tests, it complained about a bad Lua header. I couldn't figure out why. My best guess is a version mismatch, but both the library and the Lua version on my system are Lua 5.1.5. If you want to try it, you'll have to modify the fuser script to write the size of the chunk to the .lua section, then read it in main.c so it can be passed to luaL_loadbuffer.

# Why not LuaJIT?

I already had Lua 5.1.5 working on my system. I didn't get LuaJIT for 2 reasons: I didn't think this project would be finished, and I'm stupid and struggle with linkers. It should be easy to replace.
Expand Down
22 changes: 18 additions & 4 deletions lua/fuser.lua.template
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@ end

local buff = ""

local function encodeUnsignedInt(n)
local encoded = ""
for i = 1, 4 do
encoded = encoded .. string.char(n % 256)
n = n/256
end
return encoded
end

local function skipAndAdd(file, offset, num)
buff = buff .. file:read(offset)
local dword = 0
for c in file:read(0x4):reverse():gmatch(".") do dword = dword * 256 + c:byte() end
dword = dword + num
print(("0x%.08x %d->%d"):format(file:seek()-4, dword-num, dword))
for i=1,4 do buff=buff..("").char(dword % 256)dword = dword/256 end
buff = buff .. encodeUnsignedInt(dword)
end

local source = arg[2]
Expand All @@ -22,14 +31,19 @@ if filename then
input:close()
end

local stub = io.open(arg[1], "rb")
local func, errmsg = loadstring(source, "=fuser")
if not func then
print("error compiling lua source: " .. errmsg)
return
end
source = string.dump(func)
source = encodeUnsignedInt(#source) .. source

local stub = io.open(arg[1], "rb")
skipAndAdd(stub, 0x{o1:08x}, #source)
skipAndAdd(stub, 0x{o2:08x}, #source)
skipAndAdd(stub, 0x{o3:08x}, #source)

buff = buff .. stub:read("*all") .. source

stub:close()

local output = io.open(arg[3], "wb")
Expand Down
1 change: 0 additions & 1 deletion lua/fuser.lua.template.min

This file was deleted.

5 changes: 3 additions & 2 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ int main(int argc, char* argv[]) {
lua_setglobal(L, "arg");

// attempt to load the lua script, wherever it may be
const char* source = (const char*)MAGIC_POINTER;
luaL_loadbuffer(L, source, strlen(source), "=lua");
uint32_t size = *(uint32_t*)MAGIC_POINTER;
const char* source = (const char*)MAGIC_POINTER + 0x4;
luaL_loadbuffer(L, source, size, "=lua");

// basic execute + error handling
if (lua_type(L, -1) == LUA_TFUNCTION)
Expand Down
4 changes: 2 additions & 2 deletions python/hack.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
sections = sdp.SectionDoubleP(pe)

# for reasons of poetry, our exe/lua fuser will, itself, be a fused lua executable running a generated version of our fuser script
with open('lua/fuser.lua.template.min', 'r') as f:
with open('lua/fuser.lua.template', 'r') as f:
fusesource = f.read()

# this function adds the .lua section for scripts to live in
Expand Down Expand Up @@ -55,7 +55,7 @@ def seek(offset):

# removing the empty lua section and replacing it with one that contains the fuser script
sections.pop_back()
pe = addLuaSection(fusesource)
pe = addLuaSection(struct.pack("I", len(bytes(fusesource))) + fusesource)

# writing the initial fuser
pe.write(filename='bin/fuser.exe')
Expand Down

0 comments on commit b27ad34

Please sign in to comment.