Files
Luac0re/lua/elf_loader.lua
T

357 lines
10 KiB
Lua

local MAPPING_ADDR = 0x926100000
local SHADOW_MAPPING_ADDR = 0x920100000
local PROT_RW = 0x3
local PROT_RWX = 0x7
local MAP_SHARED_FIXED = 0x11
local MAP_ANON_PRIVATE_FIXED = 0x1012
local IPPROTO_IPV6 = 41
local IPV6_PKTINFO = 46
local AF_INET6 = 28
local SOCK_DGRAM = 2
local IPPROTO_UDP = 17
-- error handling
local function fail(msg)
ulog("[elf] ERROR: " .. msg)
send_notification(msg)
return nil
end
-- helpers
local function get_ofiles()
local p_fd = kread64(curproc + OFF.PROC_FD)
local tbl = kread64(p_fd + OFF.FILEDESC_OFILES)
return tbl + OFF.FDESCENTTBL_HDR
end
local function fd_to_fp(ofiles, fd)
return kread64(ofiles + fd * OFF.FILEDESCENT_SIZE)
end
local function read_procname(proc, comm_off)
local buf = malloc(32)
kread(buf, proc + comm_off, 32)
local name = ""
for i = 0, 31 do
local ch = read8(buf + i)
if ch == 0 then break end
if ch < 0x20 or ch > 0x7E then return "" end
name = name .. string.char(ch)
end
return name
end
-- proc offset discovery
local function find_proc_offsets()
local buf = malloc(0x1000)
kread(buf, curproc, 0x1000)
local sysent_off
for i = 0, 0x1000 - 8 do
if read64(buf + i) == 0x7FFFFFFFFFFFFFFF then
sysent_off = i - 0x10
break
end
end
if not sysent_off or sysent_off < 0 then
return fail("PROC_SYSENT not found")
end
local comm_off
for i = 0, 0x1000 - 6 do
if read8(buf + i) == 0xCE and read8(buf + i+1) == 0xFA
and read8(buf + i+2) == 0xEF and read8(buf + i+3) == 0xBE
and read8(buf + i+4) == 0xCC and read8(buf + i+5) == 0xBB then
comm_off = i + 8
break
end
end
if not comm_off then
return fail("PROC_COMM not found")
end
return sysent_off, comm_off
end
-- sysent swap
local function find_native_process(comm_off)
local names = { "SceGameLiveStreaming", "SceRedisServer", "SceSysAvControl" }
local p = kread64(gpu.data_base + OFF.ALLPROC)
while p ~= 0 and (p >> 48) == 0xFFFF do
local name = read_procname(p, comm_off)
for _, target in ipairs(names) do
if name == target then return p, name end
end
p = kread64(p)
end
return fail("no native PS5 process found")
end
local function swap_sysent(sysent_off, target_proc)
local our = kread64(curproc + sysent_off)
local their = kread64(target_proc + sysent_off)
local saved = {
sv_size = kread32(our),
sv_table = kread64(our + 0x8),
sysent = our,
}
kwrite32(our, kread32(their))
kwrite64(our + 0x8, kread64(their + 0x8))
return saved
end
local function restore_sysent(saved)
kwrite32(saved.sysent, saved.sv_size)
kwrite64(saved.sysent + 0x8, saved.sv_table)
end
-- ELF loader
local function load_elf(path)
local fd = syscall.open(path, 0, 0)
if fd < 0 then return nil end
local size = syscall.lseek(fd, 0, 2)
syscall.lseek(fd, 0, 0)
local store = malloc(size)
syscall.read(fd, store, size)
syscall.close(fd)
if read32(store) ~= 0x464C457F then return nil end
return store
end
local function map_elf(store)
local e_entry = read64(store + 0x18)
local e_phoff = read64(store + 0x20)
local e_shoff = read64(store + 0x28)
local e_phnum = read16(store + 0x38)
local e_shnum = read16(store + 0x3C)
local exec_start, exec_end = 0, 0
for i = 0, e_phnum - 1 do
local ph = store + e_phoff + i * 0x38
local p_type = read32(ph)
local p_flags = read32(ph + 4)
local p_off = read64(ph + 0x08)
local p_vaddr = read64(ph + 0x10)
local p_filesz = read64(ph + 0x20)
local p_memsz = read64(ph + 0x28)
if p_type == 1 then
local aligned = (p_memsz + 0x3FFF) & 0xFFFFC000
if (p_flags & 1) == 1 then
exec_start = p_vaddr
exec_end = p_vaddr + p_memsz
local eh = syscall.jitshm_create(0, aligned, PROT_RWX)
local wh = syscall.jitshm_alias(eh, PROT_RW)
syscall.mmap(SHADOW_MAPPING_ADDR, aligned, PROT_RW, MAP_SHARED_FIXED, wh, 0)
if p_filesz > 0 then memcpy(SHADOW_MAPPING_ADDR, store + p_off, p_filesz) end
if p_memsz > p_filesz then memset(SHADOW_MAPPING_ADDR + p_filesz, 0, p_memsz - p_filesz) end
syscall.mmap(MAPPING_ADDR + p_vaddr, aligned, PROT_RWX, MAP_SHARED_FIXED, eh, 0)
else
syscall.mmap(MAPPING_ADDR + p_vaddr, aligned, PROT_RW, MAP_ANON_PRIVATE_FIXED, 0xFFFFFFFF, 0)
if p_filesz > 0 then memcpy(MAPPING_ADDR + p_vaddr, store + p_off, p_filesz) end
end
end
end
local reloc_count = 0
for i = 0, e_shnum - 1 do
local sh = store + e_shoff + i * 0x40
if read32(sh + 4) == 4 then
local sh_off = read64(sh + 0x18)
local sh_size = read64(sh + 0x20)
for j = 0, (sh_size // 0x18) - 1 do
local r = store + sh_off + j * 0x18
local r_offset = read64(r)
local r_info = read64(r + 8)
local r_addend = read64(r + 0x10)
if (r_info & 0xFF) == 0x08 then
local dst = (r_offset >= exec_start and r_offset < exec_end)
and (SHADOW_MAPPING_ADDR + r_offset)
or (MAPPING_ADDR + r_offset)
write64(dst, MAPPING_ADDR + r_addend)
reloc_count = reloc_count + 1
end
end
end
end
return MAPPING_ADDR + e_entry, reloc_count
end
-- kernel r/w primitives for the loaded ELF
local function create_pipe()
local fPipe = func_wrap(dlsym(LIBKERNEL_HANDLE, "pipe"))
local buf = malloc(8)
write32(buf, 0); write32(buf + 4, 0)
local ret = fPipe(buf)
local rfd, wfd = read32(buf), read32(buf + 4)
if ret ~= 0 or rfd <= 0 or wfd <= 0 then
return fail("pipe() failed")
end
local fp = fd_to_fp(get_ofiles(), rfd)
local kpipe = kread64(fp)
if kpipe == 0 then
return fail("kpipe is NULL")
end
return rfd, wfd, kpipe
end
local function create_overlapped_sockets()
local ms = create_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)
local vs = create_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)
local mbuf = malloc(20)
for i = 0, 4 do write32(mbuf + i * 4, 0) end
syscall.setsockopt(ms, IPPROTO_IPV6, IPV6_PKTINFO, mbuf, 20)
local vbuf = malloc(20)
for i = 0, 4 do write32(vbuf + i * 4, 0) end
syscall.setsockopt(vs, IPPROTO_IPV6, IPV6_PKTINFO, vbuf, 20)
local ofiles = get_ofiles()
local mso = kread64(fd_to_fp(ofiles, ms))
local mpcb = kread64(mso + 0x18)
local mp = kread64(mpcb + OFF.INPCB_PKTOPTS)
local vso = kread64(fd_to_fp(ofiles, vs))
local vpcb = kread64(vso + 0x18)
local vp = kread64(vpcb + OFF.INPCB_PKTOPTS)
if mp == 0 or vp == 0 then
return fail("pktopts allocation failed")
end
kwrite32(mso, kread32(mso) + 0x100)
kwrite32(vso, kread32(vso) + 0x100)
kwrite64(mp + 0x10, vp + 0x10)
return ms, vs
end
function load_elfldr()
ulog("====== elf_loader ======")
syscall.resolve({
open = 0x5, lseek = 0x1DE, mmap = 0x1DD, munmap = 0x49,
jitshm_create = 0x215, jitshm_alias = 0x216,
})
local fPJ = func_wrap(dlsym(LIBKERNEL_HANDLE, "scePthreadJoin"))
local PROC_SYSENT, PROC_COMM = find_proc_offsets()
if not PROC_SYSENT then return end
local target, tname = find_native_process(PROC_COMM)
if not target then return end
ulog("target: " .. tname)
local saved = swap_sysent(PROC_SYSENT, target)
local title_id = get_title_id()
local ok, err = pcall(function()
local fw = tonumber(FW_VERSION)
if fw > 12.00 then
error("Unsupported firmware " .. FW_VERSION)
end
local elf
if fw > 10.01 then
elf = "elfldr_1200.elf"
else
elf = "elfldr_1001.elf"
end
local search = {
"/mnt/sandbox/" .. title_id .. "_000/savedata0/lua/elf_jb/" .. elf,
"/mnt/sandbox/" .. title_id .. "_001/savedata0/lua/elf_jb/" .. elf,
"/mnt/sandbox/" .. title_id .. "_002/savedata0/lua/elf_jb/" .. elf,
}
local store, path
for _, p in ipairs(search) do
store = load_elf(p)
if store then path = p; break end
end
if not store then fail("elfldr.elf not found"); return end
ulog("loaded " .. path)
local entry, nrelocs = map_elf(store)
ulog("entry=" .. to_hex(entry) .. " relocs=" .. nrelocs)
local prfd, pwfd, kpipe = create_pipe()
if not prfd then return end
local ofiles = get_ofiles()
local prfp = fd_to_fp(ofiles, prfd)
local pwfp = fd_to_fp(ofiles, pwfd)
kwrite32(prfp + 0x28, kread32(prfp + 0x28) + 0x100)
kwrite32(pwfp + 0x28, kread32(pwfp + 0x28) + 0x100)
local ms, vs = create_overlapped_sockets()
if not ms then return end
local rwpipe = malloc(8)
write32(rwpipe, prfd); write32(rwpipe + 4, pwfd)
local rwpair = malloc(8)
write32(rwpair, ms); write32(rwpair + 4, vs)
local payloadout = malloc(4)
write32(payloadout, 0)
local args = malloc(0x30)
write64(args + 0x00, dlsym(LIBKERNEL_HANDLE, "getpid"))
write64(args + 0x08, rwpipe)
write64(args + 0x10, rwpair)
write64(args + 0x18, kpipe)
write64(args + 0x20, gpu.data_base)
write64(args + 0x28, payloadout)
local th = malloc(8); write64(th, 0)
local at = malloc(0x100)
fAI(at); fAS(at, 0x80000)
local ret = fPC(th, at, entry, args, "elfldr")
fAD(at)
if ret ~= 0 then
fail("pthread_create failed: " .. to_hex(ret))
return
end
ulog("running...")
fPJ(read64(th), 0)
ulog("payloadout=" .. to_hex(read32(payloadout)))
end)
restore_sysent(saved)
ulog("sysent restored")
if not ok then
ulog("[elf] ERROR: " .. tostring(err))
send_notification("elf_loader: " .. tostring(err))
end
ulog("====== done ======")
end