mirror of
https://github.com/EchoStretch/kstuff.git
synced 2026-10-22 19:52:21 -05:00
1587 lines
68 KiB
Python
1587 lines
68 KiB
Python
import sys, json, threading, functools, os.path, collections, tarfile, io, re, time
|
|
|
|
if 'linux' not in sys.platform:
|
|
print('This tool only supports GNU/Linux! Use Docker or WSL on other OSes.')
|
|
input('Press Enter to exit')
|
|
exit(1)
|
|
elif len(sys.argv) not in (3, 4, 5):
|
|
print('usage: main.py <database> <ps5 ip> [port for payload loader] [kernel data dump]')
|
|
exit(0)
|
|
|
|
import gdb_rpc, traces
|
|
|
|
gdb = gdb_rpc.GDB(sys.argv[2]) if len(sys.argv) == 3 else gdb_rpc.GDB(sys.argv[2], int(sys.argv[3]))
|
|
|
|
with open(sys.argv[1]) as file:
|
|
symbols = json.load(file)
|
|
|
|
available_symbols = set()
|
|
|
|
def die(*args):
|
|
print(*args)
|
|
exit(1)
|
|
|
|
def get_symbol(k):
|
|
assert k in available_symbols, k
|
|
return symbols[k]
|
|
|
|
def set_symbol(k, v):
|
|
assert k not in symbols or symbols[k] == v
|
|
if k not in symbols:
|
|
print('offset found! %s = %s'%(k, hex(v) if isinstance(v, int) else v))
|
|
symbols[k] = v
|
|
with open(sys.argv[1], 'w') as file:
|
|
json.dump(symbols, file)
|
|
available_symbols.add(k)
|
|
|
|
if 'allproc' not in symbols:
|
|
die('`allproc` is not defined')
|
|
|
|
available_symbols.add('allproc')
|
|
|
|
R0GDB_FLAGS = ['-DMEMRW_FALLBACK', '-DNO_BUILTIN_OFFSETS']
|
|
SELF_DUMPER_FLAGS = ['-DMEMRW_FALLBACK']
|
|
KSTUFF_FLAGS = ['-DMEMRW_FALLBACK', '-DFIRMWARE_PORTING']
|
|
|
|
r0gdb = gdb_rpc.R0GDB(gdb, R0GDB_FLAGS)
|
|
|
|
def ostr(x):
|
|
return str(x % 2**64)
|
|
|
|
def retry_on_error(f):
|
|
@functools.wraps(f)
|
|
def f1(*args):
|
|
while True:
|
|
try: ans = f(*args)
|
|
except gdb_rpc.DisconnectedException:
|
|
print('\nPS5 disconnected, retrying %s...'%f.__name__)
|
|
continue
|
|
if ans == None or (isinstance(ans, tuple) and None in ans):
|
|
print('\nfailed to find some offsets related to %s, retrying...'%f.__name__)
|
|
continue
|
|
return ans
|
|
return f1
|
|
|
|
derivations = []
|
|
|
|
def derive_symbol(f):
|
|
derivations.append(f)
|
|
return f
|
|
|
|
def derive_symbols(*names):
|
|
def inner(f):
|
|
derivations.append((f, names))
|
|
return f
|
|
return inner
|
|
|
|
@retry_on_error
|
|
def dump_kernel():
|
|
if len(sys.argv) == 5 and os.path.exists(sys.argv[4]):
|
|
with open(sys.argv[4], 'rb') as file:
|
|
data = file.read()
|
|
if int.from_bytes(data[8:16], 'little') == len(data) - 16:
|
|
return data[16:], int.from_bytes(data[:8], 'little')
|
|
gdb.use_r0gdb(R0GDB_FLAGS)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
gdb.eval('offsets.allproc = '+ostr(kdata_base + get_symbol('allproc')))
|
|
if not gdb.ieval('rpipe'): gdb.eval('r0gdb_init_with_offsets()')
|
|
local_buf = bytearray()
|
|
with gdb_rpc.BlobReceiver(gdb, local_buf, 'dumping kdata') as addr:
|
|
remote_fd = gdb.ieval('r0gdb_open_socket("%s", %d)'%addr)
|
|
remote_buf = gdb.ieval('malloc(1048576)')
|
|
one_second = gdb.ieval('(void*)(uint64_t[2]){1, 0}')
|
|
total_sent = 0
|
|
while total_sent < (134 << 20):
|
|
chk0 = gdb.ieval('copyout(%d, %d, %d)'%(remote_buf, kdata_base+total_sent, min(1048576, (134 << 20) - total_sent)))
|
|
if chk0 <= 0: break
|
|
assert not gdb.ieval('r0gdb_sendall(%d, %d, %d)'%(remote_fd, remote_buf, chk0))
|
|
total_sent += chk0
|
|
#offset = 0
|
|
#while offset < chk0:
|
|
# chk = gdb.ieval('(int)write(%d, %d, %d)'%(remote_fd, remote_buf+offset, chk0-offset))
|
|
# assert chk > 0
|
|
# offset += chk
|
|
# total_sent += chk
|
|
# this loop is to detect panics while dumping
|
|
while len(local_buf) != total_sent:
|
|
gdb.eval('(int)nanosleep(%d)'%one_second)
|
|
gdb.eval('(int)close(%d)'%remote_fd)
|
|
if len(sys.argv) == 5:
|
|
with open(sys.argv[4], 'wb') as file:
|
|
file.write(kdata_base.to_bytes(8, 'little'))
|
|
file.write(len(local_buf).to_bytes(8, 'little'))
|
|
file.write(local_buf)
|
|
return bytes(local_buf), kdata_base
|
|
|
|
def get_kernel(_cache=[]):
|
|
if not _cache:
|
|
_cache.append(dump_kernel())
|
|
return _cache[0]
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def rootvnode():
|
|
kernel, kdata_base = get_kernel()
|
|
root_names = ['mini-syscore.elf', 'SceSysAvControl.elf', 'system', 'user', 'update', 'preinst']
|
|
ptrs = None
|
|
for i in root_names:
|
|
ii = bytearray(len(i)*2+2)
|
|
ii[:len(i)*2:2] = i.encode('ascii')
|
|
ptrs1 = {j[-8:] for j in kernel.split(ii)[:-1]}
|
|
if ptrs is None: ptrs = ptrs1
|
|
else: ptrs &= ptrs1
|
|
rootvnode_value, = ptrs
|
|
i = 0
|
|
candidates = []
|
|
while True:
|
|
i = kernel.find(rootvnode_value, i)
|
|
if i < 0: break
|
|
if kernel[i+16:i+24] == b'\xff\xff\xff\xff\x01\x00\x00\x00':
|
|
candidates.append(i)
|
|
i += 1
|
|
ans, = candidates
|
|
return ans
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def idt():
|
|
kernel, kdata_base = get_kernel()
|
|
ks = bytes(kernel[i+2:i+4] == b'\x20\x00' and kernel[i+4] < 8 and kernel[i+5] in (0x8e, 0xee) and kernel[i+8:i+16] == b'\xff\xff\xff\xff\x00\x00\x00\x00' for i in range(0, len(kernel), 16))
|
|
offset = ks.find(b'\1'*256)
|
|
assert ks.find(b'\1'*256, offset+1) < 0
|
|
return offset * 16
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def gdt_array():
|
|
kernel, kdata_base = get_kernel()
|
|
ks = kernel[5::8]
|
|
needle = b'\x00\x00\xf3\xf3\x9b\x93\xfb\xf3\xfb\x8b\x00\x00\x00' * 16
|
|
offset = ks.find(needle)
|
|
assert ks.find(needle, offset+1) < 0
|
|
return offset * 8
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def tss_array():
|
|
kernel, kdata_base = get_kernel()
|
|
gdt_array = get_symbol('gdt_array')
|
|
tss_array = []
|
|
for i in range(16):
|
|
j = gdt_array + 0x68 * i + 0x48
|
|
tss_array.append(int.from_bytes(kernel[j+2:j+5]+kernel[j+7:j+12], 'little'))
|
|
assert tss_array == list(range(tss_array[0], tss_array[-1]+0x68, 0x68))
|
|
return tss_array[0] - kdata_base
|
|
|
|
# XXX: relies on in-structure offsets, is it ok?
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def pcpu_array():
|
|
kernel, kdata_base = get_kernel()
|
|
planes = [b''.join(kernel[j+0x34:j+0x38]+kernel[j+0x730:j+0x738] for j in range(i, len(kernel), 0x900)) for i in range(0, 0x900, 4)]
|
|
needle = b''.join(i.to_bytes(4, 'little')*3 for i in range(16))
|
|
indices = [i.find(needle) for i in planes]
|
|
unique_indices = set(indices)
|
|
assert len(unique_indices) == 2 and -1 in unique_indices
|
|
unique_indices.discard(-1)
|
|
i = unique_indices.pop()
|
|
j = indices.index(i)
|
|
indices[j] = -1
|
|
assert set(indices) == {-1}
|
|
assert planes[j].find(needle, i+1) < 0
|
|
return (i // 12) * 0x900 + j * 4
|
|
|
|
def get_string_xref(name, offset):
|
|
kernel, kdata_base = get_kernel()
|
|
s = kernel.find((name+'\0').encode('ascii'))
|
|
return kernel.find((kdata_base+s).to_bytes(8, 'little')) - offset
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def sysentvec(): return get_string_xref('Native SELF', 0x48)
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def sysentvec_ps4(): return get_string_xref('PS4 SELF', 0x48)
|
|
|
|
def deref(name, offset=0):
|
|
kernel, kdata_base = get_kernel()
|
|
return int.from_bytes(kernel[get_symbol(name)+offset:get_symbol(name)+offset+8], 'little') - kdata_base
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def sysents(): return deref('sysentvec', 8)
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def sysents_ps4(): return deref('sysentvec_ps4', 8)
|
|
|
|
# XXX: do we need to also find (calculate?) the header size?
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def mini_syscore_header():
|
|
kernel, kdata_base = get_kernel()
|
|
gdb.use_r0gdb(R0GDB_FLAGS)
|
|
try: remote_fd = gdb.ieval('(int)open("/mini-syscore.elf", 0)')
|
|
except gdb_rpc.DisconnectedException:
|
|
message = ('''\
|
|
You probably have wrong OFFSET_KERNEL_DATA_BASE_ROOTVNODE in the WebKit exploit. Fix this before proceeding.
|
|
The correct offset is probably '''+hex(get_symbol('rootvnode'))).split('\n')
|
|
maxlen = max(map(len, message))
|
|
print('#'*(maxlen+2))
|
|
for i in message:
|
|
print('#'+i+' '*(maxlen-len(i))+'#')
|
|
print('#'*(maxlen+2))
|
|
raise
|
|
remote_buf = gdb.ieval('malloc(4096)')
|
|
assert gdb.ieval('(int)read(%d, %d, 4096)'%(remote_fd, remote_buf)) == 4096
|
|
gdb.execute('set print elements 0')
|
|
gdb.execute('set print repeats 0')
|
|
ans = gdb.eval('((int)close(%d), {unsigned int[1024]}%d)'%(remote_fd, remote_buf))
|
|
assert ans.startswith('{') and ans.endswith('}') and ans.count(',') == 1023, ans
|
|
header = b''.join(int(i).to_bytes(4, 'little') for i in ans[1:-1].split(','))
|
|
return kernel.find(header)
|
|
|
|
# https://github.com/cheburek3000/meme_dumper/blob/main/source/main.c#L80, guess_kernel_pmap_store_offset
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def kernel_pmap_store():
|
|
kernel, kdata_base = get_kernel()
|
|
needle = (0x1430000 | (4 << 128)).to_bytes(24, 'little')
|
|
i = 0
|
|
ans = []
|
|
while True:
|
|
i = kernel.find(needle, i)
|
|
if i < 0: break
|
|
if any(kernel[i+24:i+32]) and kernel[i+24:i+28] == kernel[i+32:i+36] and not any(kernel[i+36:i+40]):
|
|
ans.append(i - 8)
|
|
i += 1
|
|
return ans[-1]
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def lapic_map():
|
|
kernel, kdata_base = get_kernel()
|
|
dmem_base = deref('kernel_pmap_store', 32) - deref('kernel_pmap_store', 40)
|
|
needle = (dmem_base + 0xfee00000).to_bytes(8, 'little')
|
|
ans = kernel.find(needle)
|
|
assert ans % 8 == 0 and kernel.find(needle, ans+1) < 0
|
|
return ans
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def crypt_singleton_array():
|
|
kernel, kdata_base = get_kernel()
|
|
ks = kernel[6::8]
|
|
ks1 = kernel[7::8]
|
|
needle = b'\xff\x00\xff\xff\xff\x00\x00\xff\x00\xff\xff\x00\x00\xff\x00\x00\x00\x00\xff\x00\xff\x00'
|
|
offset = ks.find(needle)
|
|
assert offset >= 0 and ks.find(needle, offset+1) < 0
|
|
assert ks1[offset:offset+len(needle)] == needle
|
|
return offset * 8
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def s_shutdown_final():
|
|
kernel, kdata_base = get_kernel()
|
|
return kernel.find(b'shutdown_final\x00')
|
|
|
|
def virt2phys(virt, phys, addr):
|
|
#print(hex(virt), hex(phys), hex(addr))
|
|
assert phys == virt % 2**32
|
|
pml = phys
|
|
for i in range(39, 3, -9):
|
|
idx = (addr >> i) & 511
|
|
pml_next = gdb.ieval('{void*}%d'%(pml+idx*8+virt-phys))
|
|
if pml_next & 128:
|
|
ans = (pml_next & (2**48 - 2**i)) | (addr & (2**i - 1))
|
|
break
|
|
pml = pml_next & (2**48 - 2**12)
|
|
else:
|
|
ans = pml | (addr & 4095)
|
|
#print('->', hex(ans))
|
|
return ans
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def doreti_iret():
|
|
gdb.use_r0gdb(R0GDB_FLAGS)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
gdb.eval('offsets.allproc = '+ostr(kdata_base + get_symbol('allproc')))
|
|
if not gdb.ieval('rpipe'): gdb.eval('r0gdb_init_with_offsets()')
|
|
idt = kdata_base + get_symbol('idt')
|
|
tss_array = kdata_base + get_symbol('tss_array')
|
|
#buf = gdb.ieval('{void*}%d'%(tss_array+0x1c+4*8))
|
|
buf = gdb.ieval('kmalloc(2048)') + 2048
|
|
for i in range(16):
|
|
tss = tss_array + i * 0x68
|
|
gdb.ieval('{void*}%d = %d'%(tss+0x1c+4*8, buf))
|
|
gdb.ieval('{char}%d = 0'%(idt+1*16+4))
|
|
gdb.ieval('{char}%d = 4'%(idt+13*16+4))
|
|
ptr = gdb.ieval('{void*}({void*}(get_thread()+8)+0x200)+0x300')
|
|
virt = gdb.ieval('{void*}%d'%ptr)
|
|
phys = gdb.ieval('{void*}%d'%(ptr+8))
|
|
buf_phys = virt2phys(virt, phys, buf)
|
|
pages = set()
|
|
while True:
|
|
page = gdb.ieval('kmalloc(2048)') & -4096
|
|
if page in pages: break
|
|
pages.add(page)
|
|
gdb.ieval('(void*)({void*[512]}%d = {%s})'%(page, ', '.join(map(str, ((i<<39)|135 for i in range(512))))))
|
|
gdb.ieval('{void*}%d = %d'%(virt+8, virt2phys(virt, phys, page)|7))
|
|
buf_alias = buf_phys | (1 << 39)
|
|
#print(hex(buf), hex(buf_alias))
|
|
gdb.eval('bind_to_all_available_cpus()')
|
|
assert not gdb.ieval('(int)pthread_create(malloc(8), 0, hammer_thread, (uint64_t[2]){%d, malloc(65536)+65536})'%(buf_alias-32))
|
|
assert not gdb.ieval('bind_to_some_cpu(0)')
|
|
if 'Remote connection closed' in gdb.eval('jmp_setcontext(1ull<<50)'):
|
|
raise gdb_rpc.DisconnectedException('jmp_setcontext')
|
|
pc = gdb.ieval('$pc')
|
|
gdb.kill()
|
|
assert (pc >> 32) == 16
|
|
pc |= (2**64 - 2**32)
|
|
return pc - kdata_base
|
|
|
|
def do_use_r0gdb_raw():
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
gdb.eval('offsets.allproc = '+ostr(kdata_base + get_symbol('allproc')))
|
|
if not gdb.ieval('rpipe'): gdb.eval('r0gdb_init_with_offsets()')
|
|
gdb.eval('offsets.doreti_iret = '+ostr(kdata_base + get_symbol('doreti_iret')))
|
|
gdb.eval('offsets.add_rsp_iret = offsets.doreti_iret - 7')
|
|
gdb.eval('offsets.swapgs_add_rsp_iret = offsets.add_rsp_iret - 3')
|
|
gdb.eval('offsets.idt = '+ostr(kdata_base + get_symbol('idt')))
|
|
gdb.eval('offsets.tss_array = '+ostr(kdata_base + get_symbol('tss_array')))
|
|
|
|
use_r0gdb_raw = r0gdb.use_raw_fn(do_use_r0gdb_raw)
|
|
|
|
@derive_symbols('push_pop_all_iret', 'rdmsr_start', 'pop_all_iret', 'justreturn')
|
|
@retry_on_error
|
|
def justreturn():
|
|
use_r0gdb_raw()
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
idt = kdata_base + get_symbol('idt')
|
|
int244 = (gdb.ieval('{void*}%d'%(idt+244*16+6), 5) % 2**48) * 2**16 + gdb.ieval('{unsigned short}%d'%(idt+244*16), 5)
|
|
print('single-stepping...')
|
|
def step():
|
|
gdb.execute('stepi', 15)
|
|
print(hex(gdb.ieval('$pc')), hex(gdb.ieval('$rsp')))
|
|
gdb.ieval('$pc = %d'%int244)
|
|
step()
|
|
step()
|
|
# step until rdmsr
|
|
rsp0 = gdb.ieval('$rsp')
|
|
rax = gdb.ieval('$rax')
|
|
rdx = gdb.ieval('$rdx')
|
|
pc = gdb.ieval('$pc')
|
|
while True:
|
|
step()
|
|
assert gdb.ieval('$rsp') == rsp0
|
|
if gdb.ieval('$rax') != rax and gdb.ieval('$rdx') != rdx:
|
|
break
|
|
pc = gdb.ieval('$pc')
|
|
rdmsr = pc
|
|
assert gdb.ieval('$pc') == rdmsr + 2
|
|
# step until the function call & through it
|
|
while gdb.ieval('$rsp') == rsp0: step()
|
|
while gdb.ieval('$rsp') != rsp0: step()
|
|
pc = gdb.ieval('$pc')
|
|
step()
|
|
# check that we actually jumped (somewhere...)
|
|
assert (gdb.ieval('$pc') - pc) % 2**64 >= 16
|
|
justreturn = gdb.ieval('$pc') - 16
|
|
gdb.ieval('{void*}$rsp = 0x1337133713371337')
|
|
# step until ld_regs
|
|
while gdb.ieval('$rdi') != 0x1337133713371337:
|
|
pc = gdb.ieval('$pc')
|
|
step()
|
|
pop_all_iret = pc
|
|
# sanity check on justreturn
|
|
rsp0 = gdb.ieval('$rsp')
|
|
gdb.ieval('$pc = %d'%justreturn)
|
|
gdb.ieval('$rax = 0x4141414142424242')
|
|
step()
|
|
assert gdb.ieval('$rsp') == rsp0 - 8 and gdb.ieval('{void*}$rsp') == 0x4141414142424242
|
|
return int244-kdata_base, rdmsr-kdata_base, pop_all_iret-kdata_base, justreturn-kdata_base
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def wrmsr_ret():
|
|
use_r0gdb_raw()
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
gdb.ieval('$pc = %d'%(kdata_base+get_symbol('justreturn')))
|
|
print('single-stepping...')
|
|
while gdb.ieval('($eflags = 0x102, $rcx)') != 0x80b:
|
|
gdb.execute('stepi')
|
|
print(hex(gdb.ieval('$pc')), hex(gdb.ieval('$rsp')))
|
|
gdb.execute('stepi')
|
|
gdb.execute('stepi')
|
|
wrmsr = gdb.ieval('$pc')
|
|
try: gdb.execute('stepi')
|
|
except gdb_rpc.DisconnectedException: pass
|
|
else: assert False
|
|
return wrmsr-kdata_base
|
|
|
|
@derive_symbols('eventhandler_register', 'kproc_shutdown')
|
|
@retry_on_error
|
|
def eventhandler_register():
|
|
kernel, kdata_base = get_kernel()
|
|
vnlru_string = kernel.find(b'SceVnlru\x00')
|
|
assert vnlru_string >= 0 and kernel.find(b'SceVnlru\x00', vnlru_string+1) < 0
|
|
vnlru_ptr = (kdata_base + vnlru_string).to_bytes(8, 'little')
|
|
vnlru_xref = kernel.find(vnlru_ptr)
|
|
assert vnlru_xref >= 0 and vnlru_xref % 8 == 0 and kernel.find(vnlru_ptr, vnlru_xref+1) < 0
|
|
vnlru_proc = int.from_bytes(kernel[vnlru_xref+8:vnlru_xref+16], 'little') - kdata_base
|
|
use_r0gdb_raw()
|
|
gdb.ieval('$rsp = ((unsigned long long)$rsp & -16) | 8')
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
vnlru_proc += kdata_base
|
|
gdb.ieval('$pc = '+ostr(vnlru_proc))
|
|
gdb.execute('stepi')
|
|
pc = gdb.ieval('$pc')
|
|
while True:
|
|
gdb.execute('stepi')
|
|
pc1 = gdb.ieval('$pc')
|
|
if pc1 not in range(pc, pc+16):
|
|
assert gdb.ieval('$rsp') % 16 == 8
|
|
break
|
|
pc = pc1
|
|
pc = kdata_base + get_symbol('wrmsr_ret') + 2
|
|
gdb.ieval('$pc = '+ostr(pc))
|
|
while True:
|
|
gdb.execute('stepi')
|
|
if gdb.ieval('$rsp') % 16 == 8:
|
|
break
|
|
eventhandler_register = gdb.ieval('$pc') % 2**64
|
|
kproc_shutdown = gdb.ieval('$rdx') % 2**64
|
|
return eventhandler_register - kdata_base, kproc_shutdown - kdata_base
|
|
|
|
def do_use_r0gdb_trace():
|
|
do_use_r0gdb_raw()
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
gdb.ieval('offsets.rdmsr_start = '+ostr(kdata_base+get_symbol('rdmsr_start')))
|
|
gdb.ieval('offsets.wrmsr_ret = '+ostr(kdata_base+get_symbol('wrmsr_ret')))
|
|
gdb.ieval('offsets.nop_ret = '+ostr(kdata_base+get_symbol('wrmsr_ret')+2))
|
|
if 'rep_movsb_pop_rbp_ret' in symbols:
|
|
gdb.ieval('offsets.rep_movsb_pop_rbp_ret = '+ostr(kdata_base+get_symbol('rep_movsb_pop_rbp_ret')))
|
|
if 'cpu_switch' in symbols:
|
|
gdb.ieval('offsets.cpu_switch = '+ostr(kdata_base+get_symbol('cpu_switch')))
|
|
|
|
use_r0gdb_trace = r0gdb.use_trace_fn(do_use_r0gdb_trace)
|
|
|
|
def use_self_dumper():
|
|
if gdb.use_self_dumper(R0GDB_FLAGS, SELF_DUMPER_FLAGS):
|
|
do_use_r0gdb_trace()
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
gdb.ieval('offsets.mmap_self_fix_1_end = (offsets.mmap_self_fix_1_start = %s) + 2'%ostr(kdata_base + get_symbol('mmap_self_fix_1_start')))
|
|
gdb.ieval('offsets.mmap_self_fix_2_end = (offsets.mmap_self_fix_2_start = %s) + 2'%ostr(kdata_base + get_symbol('mmap_self_fix_2_start')))
|
|
gdb.ieval('offsets.sceSblAuthMgrSmIsLoadable2 = '+ostr(kdata_base + get_symbol('sceSblAuthMgrSmIsLoadable2')))
|
|
assert 'void' == gdb.eval('set_sigsegv_handler()')
|
|
|
|
def use_kstuff():
|
|
while not gdb.use_kstuff(R0GDB_FLAGS, KSTUFF_FLAGS):
|
|
gdb.kill()
|
|
do_use_r0gdb_raw()
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
syscall_parasites = symbols['syscall_parasites'] if 'syscall_parasites' in available_symbols else []
|
|
fself_parasites = symbols['fself_parasites'] if 'fself_parasites' in available_symbols else []
|
|
unsorted_parasites = symbols['unsorted_parasites'] if 'unsorted_parasites' in available_symbols else []
|
|
offsets_for_r0gdb = gdb.struct_fields('offsets')
|
|
for k in available_symbols:
|
|
if k in offsets_for_r0gdb:
|
|
gdb.ieval('offsets.%s = %s'%(k, ostr(kdata_base + symbols[k])))
|
|
gdb.ieval('offsets.nop_ret = '+ostr(kdata_base + get_symbol('wrmsr_ret') + 2))
|
|
gdb.ieval('offsets.justreturn_pop = '+ostr(kdata_base + get_symbol('justreturn') + 8))
|
|
gdb.ieval('offsets.mmap_self_fix_1_end = offsets.mmap_self_fix_1_start + 2')
|
|
gdb.ieval('offsets.mmap_self_fix_2_end = offsets.mmap_self_fix_2_start + 2')
|
|
gdb.ieval('offsets.mprotect_fix_end = '+ostr(kdata_base+get_symbol('mprotect_fix_start')+6))
|
|
gdb.ieval('offsets.pop_all_except_rdi_iret = '+ostr(kdata_base+get_symbol('pop_all_iret')+4))
|
|
parasites = syscall_parasites + fself_parasites + unsorted_parasites
|
|
assert len(parasites) <= 100
|
|
gdb.ieval('parasites_empty.lim_syscall = %d'%(len(syscall_parasites)))
|
|
gdb.ieval('parasites_empty.lim_fself = %d'%(len(syscall_parasites) + len(fself_parasites)))
|
|
gdb.ieval('parasites_empty.lim_total = %d'%(len(parasites)))
|
|
regs = [7, 6, 2, 1, 8, 9, 0, 3, 5, 10, 11, 12, 13, 14, 15, None, None, None, 4]
|
|
for i, (addr, reg) in enumerate(parasites):
|
|
assert reg in range(16)
|
|
gdb.ieval('(parasites_empty.parasites[%d].address = %d, parasites_empty.parasites[%d].reg = %d)'%(i, addr, i, regs.index(reg)))
|
|
args = []
|
|
if 'shellcore_offsets' in available_symbols:
|
|
n = len(symbols['shellcore_offsets'])
|
|
for addr, patch in symbols['shellcore_offsets']:
|
|
patch = bytes.fromhex(patch)
|
|
args.append('(void*)'+hex(addr))
|
|
args.append('"'+''.join(map('\\x%02x'.__mod__, patch))+'"')
|
|
args.append('(void*)'+str(len(patch)))
|
|
gdb.ieval('(shellcore_patches = (void*[%d]){%s}, n_shellcore_patches = %d)'%(3*n, ', '.join(args), n))
|
|
|
|
def init_kstuff():
|
|
assert 'void' == gdb.eval('kill_thread()')
|
|
assert 'void' == gdb.eval('ignore_signals()')
|
|
gdb.execute('cont')
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def rep_movsb_pop_rbp_ret():
|
|
use_r0gdb_trace(0)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
pc0 = gdb.ieval('$pc = getpid')
|
|
ptr = gdb.ieval('ptr_to_leaked_rep_movsq = kmalloc(8)')
|
|
gdb.ieval('trace_prog = leak_rep_movsq')
|
|
gdb.execute('stepi')
|
|
assert gdb.ieval('$pc == ({void*}&p_syscall) + 5')
|
|
rep_movsq = gdb.ieval('{void*}%d'%ptr)
|
|
r0gdb.trace_to_raw()
|
|
# trace from rep movsq to nearby rep movsb
|
|
rdi = rsi = gdb.ieval('($pc = %d, $rdi = $rsi = $rsp)'%rep_movsq) % 2**64
|
|
while True:
|
|
pc = gdb.ieval('($rcx = 1, $pc)')
|
|
print(hex(pc), hex(rdi), hex(rsi))
|
|
gdb.execute('stepi')
|
|
rdi1 = gdb.ieval('$rdi') % 2**64
|
|
rsi1 = gdb.ieval('$rsi') % 2**64
|
|
if rdi1 == rdi + 1 and rsi1 == rsi + 1 and gdb.ieval('$rcx') == 0:
|
|
break
|
|
rdi = rdi1
|
|
rsi = rsi1
|
|
rep_movsb = pc
|
|
# check epilogue
|
|
gdb.ieval('{void*}$rsp = 0x1234')
|
|
gdb.ieval('{void*}($rsp+8) = 0x5678')
|
|
gdb.execute('stepi')
|
|
gdb.execute('stepi')
|
|
assert gdb.ieval('$rbp == 0x1234 && $rip == 0x5678')
|
|
# set the offset now, so that the tracing does not need to be restarted
|
|
gdb.ieval('offsets.rep_movsb_pop_rbp_ret = '+ostr(rep_movsb))
|
|
return rep_movsb - kdata_base
|
|
|
|
@derive_symbols('cpu_switch', 'pmap_activate_sw')
|
|
@retry_on_error
|
|
def cpu_switch():
|
|
use_r0gdb_trace(16777216)
|
|
gdb.ieval('offsets.cpu_switch = 0')
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
candidates = []
|
|
gdb.ieval('call_trace_untrace_on_unaligned = 1')
|
|
while len(candidates) != 1:
|
|
del candidates[:]
|
|
trace = traces.Trace(r0gdb.trace('trace_calls', 'nanosleep', '(uint64_t[2]){1, 0}', '0'))
|
|
for i in range(1, len(trace)):
|
|
if trace.is_jump(i-1) and trace[i].rsp not in range(trace[i-1].rsp-8, trace[i-1].rsp+9) and trace[i-1].rip >= 2**63 and trace[i].rip >= 2**63:
|
|
candidates.append(i-1)
|
|
gdb.ieval('call_trace_untrace_on_unaligned = 0')
|
|
callee = candidates[0]
|
|
assert trace[callee].rsp % 16 == 0
|
|
caller1 = trace.find_caller(callee)
|
|
assert trace[caller1].rsp % 16 == 8
|
|
caller2 = trace.find_caller(caller1)
|
|
assert trace[caller2].rsp % 16 == 0
|
|
cpu_switch = trace[caller2+1].rip
|
|
# set the offset now, so that the tracing does not need to be restarted
|
|
gdb.ieval('offsets.cpu_switch = '+ostr(cpu_switch))
|
|
return cpu_switch - kdata_base, trace[callee].rip - kdata_base
|
|
|
|
@derive_symbols('lidt_lldt', 'ltr_ax', 'mov_cr4_rax', 'mov_cr3_rax_mov_ds')
|
|
@retry_on_error
|
|
def lidt_lldt():
|
|
use_r0gdb_raw()
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
dmap_base = gdb.ieval('{void*}%s - {void*}%s'%(ostr(kdata_base+get_symbol('kernel_pmap_store')+32), ostr(kdata_base+get_symbol('kernel_pmap_store')+40)))
|
|
# the kernel spills a pointer to resumectx somewhere around 0x6b000
|
|
ptr = dmap_base + 0x6b000
|
|
while True:
|
|
q = gdb.ieval('{void*}'+ostr(ptr))
|
|
if q in range(2**64-2**32, kdata_base): break
|
|
ptr += 8
|
|
resumectx = q
|
|
# compensate for the kcfi trampoline
|
|
gdb.ieval('$pc = '+ostr(resumectx))
|
|
gdb.execute('stepi')
|
|
resumectx = gdb.ieval('$pc') % 2**64
|
|
# resumectx seems to be unmodified from freebsd 11, except for struct offsets, thus we have these instructions:
|
|
# resumectx+7: mov cr3, rax
|
|
# resumectx+181: mov rax, cr4
|
|
# resumectx+191: lidt [rdi+0xea]
|
|
# resumectx+198: lgdt [rdi+0xf4]
|
|
# resumectx+225: ltr ax
|
|
# if decoded from 1 byte later, they decode as:
|
|
# resumectx+8: and bl, al
|
|
# resumectx+182: and ah, al
|
|
# resumectx+192: add [rdi+0xea], ebx
|
|
# resumectx+199: add [rdi+0xf4], dl
|
|
# resumectx+226: add al, bl
|
|
# if any of the asserts below fail, this means that this assumption is no longer true
|
|
gdb.ieval('$rdi = ($rsp -= 256)')
|
|
gdb.ieval('{int}($rsp+0xea) = 123456789')
|
|
gdb.ieval('$ebx = 987654321')
|
|
gdb.ieval('$pc = '+ostr(resumectx+192))
|
|
gdb.execute('stepi')
|
|
assert gdb.ieval('$pc == '+ostr(resumectx+198)+' && {int}($rsp+0xea) == 1111111110')
|
|
gdb.ieval('{int}($rsp+0xf4) = -15')
|
|
gdb.ieval('$edx = 30')
|
|
gdb.ieval('$pc += 1')
|
|
gdb.execute('stepi')
|
|
assert gdb.ieval('$pc == '+ostr(resumectx+205)+' && {int}($rsp+0xf4) == -241')
|
|
gdb.ieval('$eax = -15, $ebx = 30, $pc = '+ostr(resumectx+226))
|
|
gdb.execute('stepi')
|
|
assert gdb.ieval('$pc == '+ostr(resumectx+228)+' && $eax == -241')
|
|
gdb.ieval('$eax = 0xfffff31e, $pc = '+ostr(resumectx+182))
|
|
gdb.execute('stepi')
|
|
assert gdb.ieval('$pc == '+ostr(resumectx+184)+' && (unsigned)$eax == 0xffff121e')
|
|
gdb.ieval('$eax = 0x414141f3, $ebx = 0x4242421e, $pc = '+ostr(resumectx+8))
|
|
gdb.execute('stepi')
|
|
assert gdb.ieval('$pc == '+ostr(resumectx+10)+' && (unsigned)$ebx == 0x42424212')
|
|
gdb.execute('stepi')
|
|
assert gdb.ieval('$pc == '+ostr(resumectx+15)+' && $eax == 0x28')
|
|
return resumectx + 191 - kdata_base, resumectx + 225 - kdata_base, resumectx + 181 - kdata_base, resumectx + 7 - kdata_base
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def lgdt_rdi():
|
|
use_r0gdb_trace(1<<27)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
gdb.ieval('jprog = (void*[1]){0}')
|
|
addr = 0x1790000000
|
|
assert gdb.ieval('(void*)mmap('+ostr(addr)+', 4096, 3, 4098, -1, 0)') == addr
|
|
gdb.eval('{char[4]}'+ostr(addr)+' = "/179"')
|
|
trace1 = traces.Trace(r0gdb.trace('do_jprog', 'open', addr, 0))
|
|
copyinstr = next(i for i in range(len(trace1)) if trace1.is_jump(i-1) and trace1[i].rsp == trace1[i-1].rsp - 8 and trace1[i].rdi == addr and trace1[i].rdx == 1024)
|
|
copyinstr_ret = trace1.find_next_instr(copyinstr-1) - 1
|
|
copystr_addr = (trace1[copyinstr_ret].rip | 15) + 1
|
|
tmpbuf = gdb.ieval('kmalloc(8)')
|
|
getpid = gdb.ieval('{void*}%d'%(kdata_base+get_symbol('sysents')+20*48+8))
|
|
gdb.ieval('{void*}'+ostr(tmpbuf)+' = 0x100000000')
|
|
gdb.ieval('fncall_fn = '+ostr(copystr_addr))
|
|
gdb.ieval('(fncall_args[0] = %d, fncall_args[1] = %d, fncall_args[2] = 1, fncall_args[3] = %d)'%(tmpbuf+4, tmpbuf+6, tmpbuf))
|
|
gdb.ieval('fncall_no_untrace = 1')
|
|
gdb.ieval('sys_getpid = '+ostr(getpid))
|
|
trace2 = traces.Trace(r0gdb.trace('getpid_to_fncall', 'getpid'))
|
|
getpid = trace2.find_next_rip(0, getpid)
|
|
copystr_ret = trace2.find_next_instr(getpid-1) - 1
|
|
lgdt_addr = (trace2[copystr_ret].rip | 15) + 1
|
|
lgdt_addr -= kdata_base
|
|
# lgdt+1 should decode as "add [rdi], edx"
|
|
use_r0gdb_raw()
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
lgdt_addr += kdata_base
|
|
gdb.ieval('{int}($rdi = $rsp) = 123456789')
|
|
gdb.ieval('$rdx = 987654321')
|
|
gdb.ieval('$pc = '+ostr(lgdt_addr+1))
|
|
gdb.execute('stepi')
|
|
assert gdb.ieval('$pc == '+ostr(lgdt_addr+3)+' && {int}$rdi == 1111111110')
|
|
return lgdt_addr - kdata_base
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def mov_rdi_cr2():
|
|
kernel, kdata_base = get_kernel()
|
|
idt14 = kernel[get_symbol('idt')+14*16:get_symbol('idt')+15*16]
|
|
Xpage = int.from_bytes(idt14[:2]+idt14[6:12], 'little') - kdata_base
|
|
use_r0gdb_raw()
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
Xpage += kdata_base
|
|
gdb.ieval('$pc = '+ostr(Xpage))
|
|
gdb.eval('{void*[6]}($rsp = (unsigned long long)$rsp & -16) = {0}')
|
|
gdb.execute('stepi')
|
|
pc = gdb.ieval('$pc')
|
|
while True:
|
|
print(hex(pc))
|
|
gdb.execute('stepi')
|
|
pc1 = gdb.ieval('$pc')
|
|
if pc1 not in range(pc, pc+16):
|
|
break
|
|
pc = pc1
|
|
mov_rdi_cr2 = pc1
|
|
print(hex(mov_rdi_cr2))
|
|
gdb.ieval('$pc = '+ostr(mov_rdi_cr2+1))
|
|
# should decode as "and dl, bh"
|
|
gdb.ieval('$rbx = 0xffffffffffffbeff')
|
|
gdb.ieval('$rdx = 0x12345678')
|
|
gdb.execute('stepi')
|
|
print(hex(gdb.ieval('$pc')), hex(gdb.ieval('$rbx') % 2**64), hex(gdb.ieval('$rdx')))
|
|
assert gdb.ieval('$pc == '+ostr(mov_rdi_cr2+3)+' && (unsigned long long)$rbx == 0xffffffffffff38ff')
|
|
return mov_rdi_cr2 - kdata_base
|
|
|
|
@derive_symbols('syscall_before', 'syscall_after')
|
|
@retry_on_error
|
|
def syscall_before():
|
|
use_r0gdb_trace(16777216)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
trace = traces.Trace(r0gdb.trace('trace_skip_scheduler_only', 'getpid'))
|
|
sys_getpid = gdb.ieval('{void*}%d'%(kdata_base+get_symbol('sysents')+48*20+8))
|
|
idx_getpid = trace.find_next_rip(0, sys_getpid)
|
|
idx_syscall_after = trace.find_next_instr(idx_getpid-1)
|
|
idx_syscall_before = idx_getpid - 1
|
|
while sys_getpid in trace[idx_syscall_before]:
|
|
idx_syscall_before -= 1
|
|
return trace[idx_syscall_before].rip - kdata_base, trace[idx_syscall_after].rip - kdata_base
|
|
|
|
@derive_symbols('mov_rdi_cr3')
|
|
@retry_on_error
|
|
def mov_rdi_cr3():
|
|
use_r0gdb_raw(do_r0gdb=False)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
thread = gdb.ieval('get_thread()')
|
|
use_r0gdb_raw(do_r0gdb=True)
|
|
gdb.ieval('$pc = '+ostr(kdata_base+get_symbol('pmap_activate_sw')))
|
|
gdb.ieval('$rdi = '+ostr(thread))
|
|
print('single-stepping...')
|
|
def step():
|
|
gdb.execute('stepi')
|
|
print(hex(gdb.ieval('$pc')))
|
|
step()
|
|
while gdb.ieval('(void*)$rdi') == thread:
|
|
gdb.ieval('$eflags = 0x102')
|
|
pc = gdb.ieval('$pc')
|
|
step()
|
|
mov_rdi_cr3 = pc
|
|
assert gdb.ieval('$pc') - mov_rdi_cr3 == 3
|
|
return mov_rdi_cr3 - kdata_base
|
|
# the rest of the code is kept for reference. there's a better mov cr3, rax gadget in resumectx
|
|
cr3 = gdb.ieval('(void*)$rdi')
|
|
assert cr3 < 2**39 and not cr3 % 4096
|
|
while gdb.ieval('(void*)$rax') != cr3:
|
|
step()
|
|
# the next instruction is 3-byte, but not "mov cr3, rax"
|
|
step()
|
|
pc = gdb.ieval('$pc')
|
|
while gdb.ieval('$pc') != pc + 3:
|
|
gdb.ieval('$eflags = 0x102')
|
|
pc = gdb.ieval('$pc')
|
|
step()
|
|
# this is probably the one, check that it crashes
|
|
mov_cr3_rax = pc
|
|
gdb.ieval('$pc = '+ostr(pc))
|
|
gdb.ieval('$rax = 0')
|
|
try: gdb.execute('stepi')
|
|
except gdb_rpc.DisconnectedException: pass
|
|
else: assert False, "not mov cr3, rax"
|
|
return mov_rdi_cr3 - kdata_base, mov_cr3_rax - kdata_base
|
|
|
|
@derive_symbols('dr2gpr_start', 'gpr2dr_1_start', 'gpr2dr_2_start', 'mov_rax_cr0', 'mov_cr0_rax')
|
|
@retry_on_error
|
|
def dr2gpr_start():
|
|
use_r0gdb_trace(16777216)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
gdb.ieval('offsets.syscall_after = '+ostr(kdata_base+get_symbol('syscall_after')))
|
|
# enable the "has debug regs" flag
|
|
td = gdb.ieval('get_thread()')
|
|
pcb = gdb.ieval('{void*}%d'%(td+0x3f8))
|
|
assert gdb.ieval('{int}%d'%(pcb+0x100)) == 24 # sanity check in case offsets have shifted
|
|
gdb.ieval('{int}%d = 26'%(pcb+0x100))
|
|
# trace nanosleep to get the 3rd argument to cpu_switch
|
|
trace = traces.Trace(r0gdb.trace('trace_calls', 'nanosleep', '(uint64_t[2]){1, 0}', '0'))
|
|
cpu_switch = trace.find_next_rip(0, kdata_base + get_symbol('cpu_switch'))
|
|
assert td == trace[cpu_switch].rdi
|
|
mtx = trace[cpu_switch].rdx
|
|
# now trace the entirety of cpu_switch
|
|
getpid = gdb.ieval('{void*}%d'%(kdata_base+get_symbol('sysents')+20*48+8))
|
|
gdb.ieval('fncall_fn = '+ostr(kdata_base+get_symbol('cpu_switch')))
|
|
gdb.ieval('(fncall_args[0] = fncall_args[1] = %d, fncall_args[2] = %d)'%(td, mtx))
|
|
gdb.ieval('fncall_no_untrace = 1')
|
|
gdb.ieval('sys_getpid = '+ostr(getpid))
|
|
gdb.ieval('offsets.cpu_switch = 0')
|
|
trace2 = traces.Trace(r0gdb.trace('getpid_to_fncall', 'getpid_for_smsw_ax'))
|
|
gdb.ieval('offsets.cpu_switch = '+ostr(kdata_base + get_symbol('cpu_switch')))
|
|
cpu_switch = trace2.find_next_rip(0, kdata_base + get_symbol('cpu_switch'))
|
|
# we've traced the dbreg get/set code, now find it using magic values in registers
|
|
dr2gpr_start = j = trace2.find_next_reg(cpu_switch, 'r11', 0xffff4ff0)
|
|
while not trace2.is_jump(dr2gpr_start-1): dr2gpr_start -= 1
|
|
while trace2[j].r11 == 0xffff4ff0: j += 1
|
|
gpr2dr_1_start = trace2.find_next_reg(j, 'r11', 0xffff4ff0)
|
|
while trace2[gpr2dr_1_start].rcx != 0x400: gpr2dr_1_start += 1
|
|
gpr2dr_2_start = trace2.find_next_reg(gpr2dr_1_start, 'rcx', 0xc0011024)
|
|
while not trace2[gpr2dr_2_start].rdx: gpr2dr_2_start += 1
|
|
dr2gpr_start = trace2[dr2gpr_start].rip
|
|
gpr2dr_1_start = trace2[gpr2dr_1_start].rip
|
|
gpr2dr_2_start = trace2[gpr2dr_2_start].rip
|
|
j = cpu_switch
|
|
while trace2[j].rax % 65536 != 0x33:
|
|
j += 1
|
|
assert trace2[j+1].rax == trace2[j].rax | 8
|
|
if trace2[j].rax == 0x80050033: # 386-style (cr0)
|
|
assert trace2[j].rip - trace2[j-1].rip == trace2[j+2].rip - trace2[j+1].rip == 3
|
|
mov_rax_cr0 = trace2[j-1].rip # mov rax, cr0
|
|
mov_cr0_rax = trace2[j+1].rip # mov cr0, rax
|
|
else: # 286-style (msw)
|
|
assert trace2[j].rip - trace2[j-1].rip == trace2[j+2].rip - trace2[j+1].rip == 4
|
|
mov_rax_cr0 = trace2[j-1].rip + 1 # smsw ax -> smsw eax
|
|
mov_cr0_rax = trace2[j+1].rip + 1 # lmsw ax -> lmsw eax
|
|
# verify the newly-found offsets
|
|
buf = gdb.ieval('malloc(48)')
|
|
gdb.ieval('offsets.dr2gpr_start = '+ostr(dr2gpr_start))
|
|
gdb.ieval('offsets.gpr2dr_1_start = '+ostr(gpr2dr_1_start))
|
|
gdb.ieval('offsets.gpr2dr_2_start = '+ostr(gpr2dr_2_start))
|
|
assert 'void' == gdb.eval('r0gdb_read_dbregs(%d)'%buf)
|
|
regs = [gdb.ieval('{void*}%d'%(buf+8*i)) for i in range(6)]
|
|
assert regs == [0, 0, 0, 0, 0xffff4ff0, 0x400]
|
|
regs = expected = [0x123, 0x456, 0x789, 0xabc, 0, 0x455]
|
|
for i, j in enumerate(regs): gdb.ieval('{void*}%d = %d'%(buf+8*i, j))
|
|
assert 'void' == gdb.eval('r0gdb_write_dbregs(%d)'%buf)
|
|
for i, j in enumerate(regs): gdb.ieval('{void*}%d = 0'%(buf+8*i))
|
|
assert 'void' == gdb.eval('r0gdb_read_dbregs(%d)'%buf)
|
|
regs = [gdb.ieval('{void*}%d'%(buf+8*i)) for i in range(6)]
|
|
expected[4] = 0xffff4ff0
|
|
assert regs == expected, ("dbregs do not match after readout", list(map(hex, regs)), list(map(hex, expected)))
|
|
return dr2gpr_start - kdata_base, gpr2dr_1_start - kdata_base, gpr2dr_2_start - kdata_base, mov_rax_cr0 - kdata_base, mov_cr0_rax - kdata_base
|
|
|
|
@derive_symbols('malloc', 'M_something')
|
|
@retry_on_error
|
|
def malloc():
|
|
while True:
|
|
use_r0gdb_trace(16777216)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
# use the ipv6 rthdr allocation to find malloc
|
|
# 1224 is a valid size for rthdr, and prosper0gdb already has set_rthdr_size function that wraps the raw ioctl
|
|
remote_sock = gdb.ieval('(int)socket(28, 2, 0)') # udpv6
|
|
assert remote_sock >= 0
|
|
trace = traces.Trace(r0gdb.trace('trace_skip_scheduler_only', 'set_rthdr_size', remote_sock, 1224))
|
|
assert gdb.ieval('$rax') == 0
|
|
malloc_calls = [i for i in range(len(trace)) if trace.is_jump(i) and trace[i+1].rsp == trace[i].rsp-8 and trace[i].rdi == 1224 and trace[i].rdx == 1]
|
|
if len(malloc_calls) == 1: break
|
|
gdb.kill()
|
|
malloc_call = malloc_calls[0]
|
|
malloc = trace[malloc_call+1].rip
|
|
M_something = trace[malloc_call+1].rsi
|
|
return malloc - kdata_base, M_something - kdata_base
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def mprotect_fix_start():
|
|
use_r0gdb_trace(16777216)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
buf = gdb.ieval('malloc(16384)')
|
|
# get 2 traces to diff
|
|
trace1 = traces.Trace(r0gdb.trace('trace_skip_scheduler_only', 'mprotect', buf, 1, 3))
|
|
trace2 = traces.Trace(r0gdb.trace('trace_skip_scheduler_only', 'mprotect', buf, 1, 7))
|
|
# determine the point of divergence
|
|
i1 = trace1.find_next_rip(0, kdata_base + get_symbol('syscall_after'))
|
|
i2 = trace2.find_next_rip(0, kdata_base + get_symbol('syscall_after'))
|
|
j1 = trace1.find_last_callee_ret(trace1.find_last_callee_ret(i1))
|
|
j2 = trace2.find_last_callee_ret(trace2.find_last_callee_ret(i2))
|
|
k1 = trace1.find_caller(j1) + 1
|
|
k2 = trace2.find_caller(j2) + 1
|
|
while k1 < j1 and k2 < j2 and trace1[k1].rip == trace2[k2].rip:
|
|
k1 = trace1.find_next_instr(k1)
|
|
k2 = trace2.find_next_instr(k2)
|
|
assert k1 < j1 and k2 < j2 and trace1[k1-1].rip == trace2[k2-1].rip
|
|
return trace1[k1-1].rip - kdata_base
|
|
|
|
@derive_symbols('sigaction_fix_start', 'sigaction_fix_end')
|
|
@retry_on_error
|
|
def sigaction_fix_start():
|
|
use_r0gdb_trace(16777216)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
buf = gdb.ieval(r'&"\x01"') # sa_handler = SIG_IGN
|
|
tr = [
|
|
traces.Trace(r0gdb.trace('trace_skip_scheduler_only', 'sigaction', i, buf, 0))
|
|
for i in (15, 9, 17) # SIGTERM, SIGKILL, SIGSTOP
|
|
]
|
|
i = [i.find_next_rip(0, kdata_base+get_symbol('syscall_after')) for i in tr]
|
|
j = [i.find_caller(j-1)+1 for i, j in zip(tr, i)]
|
|
k = None
|
|
while len({i[j].rip for i, j in zip(tr, j)}) == 1:
|
|
k = j
|
|
j = [i.find_next_instr(j) for i, j in zip(tr, j)]
|
|
assert k is not None
|
|
source = tr[0][k[0]].rip
|
|
while tr[0][k[0]].rip in (tr[1][k[1]].rip, tr[2][k[2]].rip):
|
|
k = [i.find_next_instr(j) for i, j in zip(tr, k)]
|
|
dst = tr[0][k[0]].rip
|
|
return source - kdata_base, dst - kdata_base
|
|
|
|
def find_file_string(tail):
|
|
kdata, kdata_base = get_kernel()
|
|
q = kdata.find(tail+b'\0') - 18
|
|
assert kdata.find(tail, q+19) < 0
|
|
assert kdata[q:q+10] == b'W:\\Build\\'
|
|
assert kdata[q+10:q+18].decode('latin-1').isnumeric()
|
|
return q
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def mmap_self_fix_2_start():
|
|
use_r0gdb_trace(16777216)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
fd = gdb.ieval('(int)open("/system/common/lib/libScePlayerInvitationDialog.sprx", 0)')
|
|
assert fd >= 0
|
|
trace = traces.Trace(r0gdb.trace('trace_skip_scheduler_only', 'mmap', 0, 16384, 1, 0x80001, fd, 0))
|
|
gdb.ieval('(int)close(%d)'%fd)
|
|
i = trace.find_next_rip(0, kdata_base + get_symbol('syscall_after'))
|
|
for j in range(3):
|
|
i = trace.find_last_callee_ret(i)
|
|
j = trace.find_caller(i) + 1
|
|
while j < i and trace[j+1].rip != trace[j].rip + 16:
|
|
j = trace.find_next_instr(j)
|
|
assert j < i
|
|
return trace[j].rip - kdata_base
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def mmap_self_fix_1_start():
|
|
use_r0gdb_trace(16777216)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
gdb.ieval('offsets.mmap_self_fix_2_start = '+ostr(kdata_base+get_symbol('mmap_self_fix_2_start')))
|
|
gdb.ieval('offsets.mmap_self_fix_2_end = '+ostr(kdata_base+get_symbol('mmap_self_fix_2_start')+2))
|
|
fd = gdb.ieval('(int)open("/mini-syscore.elf", 0)')
|
|
assert fd >= 0
|
|
trace = traces.Trace(r0gdb.trace('fix_mmap_self', 'mmap', 0, 16384, 1, 0x80001, fd, 0))
|
|
gdb.ieval('(int)close(%d)'%fd)
|
|
# find the function that returns a specific error code
|
|
for i in range(len(trace)-1):
|
|
if trace.is_jump(i) and trace[i+1].rsp == trace[i].rsp + 8 and trace[i].rax == 0x800f0d3a:
|
|
break
|
|
else: assert False
|
|
i += 1
|
|
# trace the successful path from there on r0gdb
|
|
r0gdb.trace_to_raw()
|
|
gdb.ieval('$pc = '+ostr(trace[i].rip))
|
|
gdb.ieval('$rax = 0')
|
|
while gdb.ieval('$pc') == trace[i].rip:
|
|
i += 1
|
|
gdb.execute('stepi')
|
|
# we've found the source and destination, verify their relative offset
|
|
source = trace[i-1].rip
|
|
dst = gdb.ieval('$pc')
|
|
assert dst == source + 2
|
|
return source - kdata_base
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def mdbg_call_fix():
|
|
use_r0gdb_trace(16777216)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
# prepare mdbg_call arguments
|
|
buf = gdb.ieval('malloc(1)')
|
|
arg1 = gdb.ieval('(uint64_t)(uint64_t[4]){1, 0x12}')
|
|
arg2 = gdb.ieval('(uint64_t)(uint64_t[8]){(int)getpid(), getpid, %d, 1}'%buf)
|
|
arg3 = gdb.ieval('(uint64_t)(uint64_t[4]){}')
|
|
# run mdbg_call with and without the debugger cred
|
|
trace1 = traces.Trace(r0gdb.trace('trace_skip_scheduler_only', 'mdbg_call', arg1, arg2, arg3, 0, 0, 0))
|
|
gdb.ieval('{void*}({void*}({void*}(get_thread()+8)+0x40)+88) = 0x4800000000000036')
|
|
trace2 = traces.Trace(r0gdb.trace('trace_skip_scheduler_only', 'mdbg_call', arg1, arg2, arg3, 0, 0, 0))
|
|
# find the inner mdbg_call funcion
|
|
i = trace1.find_next_rip(0, kdata_base + get_symbol('syscall_after'))
|
|
j = trace1.find_last_callee_ret(i-1)
|
|
k1 = trace1.find_caller(j)+1
|
|
k2 = trace2.find_next_rip(0, trace1[k1].rip)
|
|
# trace until we find the function that returns different values (0 vs 1)
|
|
while not (trace1[k1].rax == 0 and trace2[k2].rax == 1):
|
|
k1 = trace1.find_next_instr(k1)
|
|
k2 = trace2.find_next_instr(k2)
|
|
assert trace1.is_jump(k1-1)
|
|
assert trace2.is_jump(k2-1)
|
|
k1 = trace1.find_caller(k1-1)
|
|
k2 = trace1.find_caller(k2-1)
|
|
assert trace1[k1].rip == trace2[k2].rip
|
|
# trace the inner cred checking function to find the point of divergence
|
|
while not (trace1[k1].rax == 0 and trace2[k2].rax == 1):
|
|
k1 += 1
|
|
k2 += 1
|
|
assert trace1[k1].rip == trace2[k2].rip
|
|
return trace1[k1].rip - kdata_base
|
|
|
|
@derive_symbols(
|
|
'sceSblServiceMailbox',
|
|
'sceSblServiceMailbox_lr_sceSblAuthMgrSmFinalize',
|
|
'sceSblServiceMailbox_lr_verifyHeader',
|
|
'sceSblAuthMgrSmIsLoadable2',
|
|
'sceSblServiceMailbox_lr_loadSelfSegment',
|
|
'sceSblServiceMailbox_lr_decryptSelfBlock',
|
|
)
|
|
@retry_on_error
|
|
def sceSblServiceMailbox():
|
|
# we need about 20 MB of log memory, allocate 64 MB just to be sure
|
|
use_r0gdb_trace(1<<26)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
# fill mmap_self offsets, 'coz we're tracing mmap_self for simplicity
|
|
gdb.ieval('offsets.mmap_self_fix_1_end = (offsets.mmap_self_fix_1_start = %s) + 2'%ostr(kdata_base+get_symbol('mmap_self_fix_1_start')))
|
|
gdb.ieval('offsets.mmap_self_fix_2_end = (offsets.mmap_self_fix_2_start = %s) + 2'%ostr(kdata_base+get_symbol('mmap_self_fix_2_start')))
|
|
# open some library
|
|
fd = gdb.ieval('(int)open("/system_ex/common_ex/lib/libSceNKWebKit.sprx", 0)')
|
|
assert fd >= 0
|
|
# now mmap and mlock first 64 KB of the first segment
|
|
trace = traces.Trace(
|
|
r0gdb.trace('fix_mmap_self', 'mmap', 0, 65536, 1, 0x80001, fd, 0) +
|
|
r0gdb.trace('fix_mmap_self', 'mlock', gdb.ieval('(void*)$rax'), 65536)
|
|
)
|
|
# filter callers for each function being called
|
|
lrs = collections.defaultdict(list)
|
|
for i in range(1, len(trace)):
|
|
if trace.is_jump(i-1) and trace[i].rsp == trace[i-1].rsp - 8:
|
|
lrs[trace[i].rip].append(trace[i-1].rip)
|
|
# expected callers for sceSblServiceMailbox:
|
|
# * sceSblAuthMgrSmFinalize (happens sometimes but not always)
|
|
# * verifyHeader
|
|
# * sceSblAuthMgrSmIsLoadable2
|
|
# * loadSelfSegment
|
|
# * decryptSelfBlock (4 times in a row)
|
|
candidates = [i for i, j in lrs.items() if len(j) in (7, 8) and len(set(j)) == len(j) - 3 and len(set(j[-4:])) == 1]
|
|
assert candidates
|
|
# the real mailbox call has rsi = rdx for all invocations. filter by that
|
|
mailbox = [i for i in candidates if all(j.rsi == j.rdx for j in trace if j.rip == i)]
|
|
assert len(mailbox) == 1
|
|
mailbox, = mailbox
|
|
lrs = lrs[mailbox]
|
|
verifyHeader, sceSblAuthMgrSmIsLoadable2, loadSelfSegment, decryptSelfBlock = lrs[-7:-3]
|
|
# for sceSblAuthMgrSmIsLoadable2 we need the function start, not the mailbox callsite
|
|
sceSblAuthMgrSmIsLoadable2 = trace[trace.find_caller(trace.find_next_rip(0, sceSblAuthMgrSmIsLoadable2))+1].rip
|
|
return (
|
|
mailbox - kdata_base,
|
|
lrs[0] + 5 - kdata_base if len(lrs) == 8 else None,
|
|
verifyHeader + 5 - kdata_base,
|
|
sceSblAuthMgrSmIsLoadable2 - kdata_base,
|
|
loadSelfSegment + 5 - kdata_base,
|
|
decryptSelfBlock + 5 - kdata_base,
|
|
)
|
|
|
|
def run_make_fself(elf_data, auth_info):
|
|
import make_fself
|
|
elf = make_fself.ElfFile(ignore_shdrs=True)
|
|
elf.load(io.BytesIO(elf_data))
|
|
self = make_fself.SignedElfFile(elf, paid=int.from_bytes(auth_info[:8], 'little'), ptype=1, app_version=0, fw_version=0, auth_info=auth_info)
|
|
self_file = io.BytesIO()
|
|
self.save(self_file)
|
|
return self_file.getvalue()
|
|
|
|
def dump_self(name, path):
|
|
use_self_dumper()
|
|
assert not gdb.ieval('($self_file_%s = &*(struct memfd[1]){dump_elf("%s")}, 0)'%(name, path))
|
|
assert not gdb.ieval('($self_auth_info_%s = &*(struct memfd[1]){dump_elf_auth_info("%s")}, 0)'%(name, path))
|
|
ba = bytearray()
|
|
with gdb_rpc.BlobReceiver(gdb, ba, 'retrieving dumped files') as addr:
|
|
remote_fd = gdb.ieval('r0gdb_open_socket("%s", %d)'%addr)
|
|
assert remote_fd >= 0
|
|
assert gdb.eval('send_tar_entry(%d, $self_file_%s, "binary.elf", (char*)0)'%(remote_fd, name))
|
|
assert gdb.eval('send_tar_entry(%d, $self_auth_info_%s, "binary.elf.auth_info", (char*)0)'%(remote_fd, name))
|
|
assert not gdb.ieval('r0gdb_sendall(%d, malloc(512), 512)'%remote_fd)
|
|
gdb.ieval('(int)close(%d)'%remote_fd)
|
|
files = {}
|
|
for i in tarfile.open(fileobj=io.BytesIO(bytes(ba))):
|
|
files[i.name] = ba[i.offset_data:i.offset_data+i.size]
|
|
elf_data = files['binary.elf']
|
|
auth_info = files['binary.elf.auth_info']
|
|
assert elf_data and len(auth_info) == 0x88
|
|
return elf_data, auth_info
|
|
|
|
def make_fself_and_upload(name, path):
|
|
full_name = name
|
|
name = name.split('.', 1)[0]
|
|
fd = gdb.ieval('(int)open("/data/%s", 0)'%full_name)
|
|
if fd >= 0:
|
|
assert not gdb.ieval('(int)close(%d)'%fd)
|
|
return True
|
|
elf_data, auth_info = dump_self(name, path)
|
|
self_data = run_make_fself(elf_data, auth_info)
|
|
with gdb_rpc.BlobSender(gdb, self_data, 'writing fself file to /data/%s'%full_name) as addr:
|
|
remote_fd = gdb.ieval('r0gdb_open_socket("%s", %d)'%addr)
|
|
assert remote_fd >= 0
|
|
file_fd = gdb.ieval('(int)open("/data/%s", 0x602, 0777)'%full_name)
|
|
assert file_fd >= 0
|
|
assert not gdb.ieval('r0gdb_sendfile(%d, %d)'%(remote_fd, file_fd))
|
|
assert not gdb.ieval('(int)close(%d) | (int)close(%d)'%(remote_fd, file_fd))
|
|
return False
|
|
|
|
def ensure_fselfs(fn):
|
|
fn_run = False
|
|
if gdb.popen == None:
|
|
fn()
|
|
fn_run = True
|
|
if (not make_fself_and_upload('libSceLibcInternal.sprx', '/system/common/lib/libSceLibcInternal.sprx')
|
|
or not make_fself_and_upload('libScePlayerInvitationDialog.sprx', '/system/common/lib/libScePlayerInvitationDialog.sprx')
|
|
or not fn_run):
|
|
fn()
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def sceSblServiceMailbox_lr_decryptMultipleSelfBlocks():
|
|
ensure_fselfs(lambda: use_r0gdb_trace(0))
|
|
# set mailbox-related symbols
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
for i in (
|
|
'sceSblServiceMailbox',
|
|
'sceSblServiceMailbox_lr_sceSblAuthMgrSmFinalize',
|
|
'sceSblServiceMailbox_lr_verifyHeader',
|
|
'sceSblAuthMgrSmIsLoadable2',
|
|
'sceSblServiceMailbox_lr_loadSelfSegment',
|
|
'sceSblServiceMailbox_lr_decryptSelfBlock',
|
|
'mini_syscore_header',
|
|
):
|
|
gdb.ieval('offsets.%s = %s'%(i, ostr(kdata_base + get_symbol(i))))
|
|
gdb.ieval('offsets.mmap_self_fix_1_end = (offsets.mmap_self_fix_1_start = %s) + 2'%ostr(kdata_base + get_symbol('mmap_self_fix_1_start')))
|
|
gdb.ieval('offsets.mmap_self_fix_2_end = (offsets.mmap_self_fix_2_start = %s) + 2'%ostr(kdata_base + get_symbol('mmap_self_fix_2_start')))
|
|
#buf = gdb.ieval('malloc(1)')
|
|
#n = 72
|
|
#gdb.ieval('(uint64_t)({void*[%d]}%d = {void*[%d]}&offsets)'%(n, buf, n))
|
|
#gdb.eval('set_offsets_403()')
|
|
#gdb.ieval('(uint64_t)({void*[%d]}&offsets = {void*[%d]}%d)'%(n, n, buf))
|
|
# map 64k of a fake self
|
|
gdb.ieval('do_fself = 31')
|
|
r0gdb.do_trace('trace_mailbox', 'mmap', 0, 65536, 1, 0x80001, '(int)open("/data/libSceLibcInternal.sprx", 0)', 0)
|
|
assert not (gdb.ieval('$eflags') & 1), gdb.ieval('$rax')
|
|
# the latter check will hang. arm a SIGALRM to interrupt us, 'coz sending ^C will break the python repl
|
|
gdb.execute('handle SIGALRM print stop nopass')
|
|
buf = gdb.ieval('malloc(sizeof(struct sigaction))')
|
|
assert not gdb.ieval('(int)sigaction(2, 0, %s)'%ostr(buf))
|
|
assert not gdb.ieval('(int)sigaction(14, %s, 0)'%ostr(buf))
|
|
#assert not gdb.ieval('((int(*)(void))dlsym(2, "alarm"))(10)')
|
|
assert not gdb.ieval('(int)setitimer(0, (uint64_t[4]){0, 0, 10, 0}, 0)')
|
|
# try to mlock it in one go. decryptMultipleSelfBlocks should get called
|
|
gdb.ieval('do_fself = 63')
|
|
r0gdb.do_trace('trace_mailbox', 'mlock', '$rax', 65536)
|
|
# check that the syscall hasn't completed normally
|
|
assert not gdb.ieval('$pc == {void*}&p_syscall + 5')
|
|
# now get its lr
|
|
lr = gdb.ieval('mailbox_lr[0]')
|
|
assert lr and not gdb.ieval('mailbox_lr[1]')
|
|
# the process is now fucked. panic now
|
|
try: gdb.eval('{short}(get_thread()+14) = 0xdeb7')
|
|
except gdb_rpc.DisconnectedException: pass
|
|
return lr - kdata_base
|
|
|
|
@derive_symbols('copyin', 'copyout')
|
|
@retry_on_error
|
|
def copyin():
|
|
use_r0gdb_trace(16777216)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
pipebuf = gdb.ieval('(void*)malloc(123)')
|
|
assert not gdb.ieval('(int)pipe(%d)'%pipebuf)
|
|
fd1 = gdb.ieval('{int}%d'%pipebuf)
|
|
fd2 = gdb.ieval('{int}%d'%(pipebuf+4))
|
|
gdb.ieval('jprog = (void*[1]){0}')
|
|
trace1 = traces.Trace(r0gdb.trace('do_jprog', 'write', fd2, pipebuf, 123))
|
|
trace2 = traces.Trace(r0gdb.trace('do_jprog', 'read', fd1, pipebuf, 123))
|
|
candidates1 = [i for i in range(1, len(trace1)) if trace1.is_jump(i-1) and trace1[i].rsp == trace1[i-1].rsp - 8 and trace1[i].rdi == pipebuf and trace1[i].rdx == 123]
|
|
assert len(candidates1) == 1
|
|
copyin = trace1[candidates1[0]].rip
|
|
kernel_buf = trace1[candidates1[0]].rsi
|
|
candidates2 = [i for i in range(1, len(trace2)) if trace2.is_jump(i-1) and trace2[i].rsp == trace2[i-1].rsp - 8 and trace2[i].rdi == kernel_buf and trace2[i].rsi == pipebuf and trace2[i].rdx == 123]
|
|
assert len(candidates2) == 1
|
|
copyout = trace2[candidates2[0]].rip
|
|
return copyin - kdata_base, copyout - kdata_base
|
|
|
|
def fetch_logs(cpu):
|
|
uelf_base = gdb.ieval('uelf_bases[%d]'%cpu)
|
|
offset = int(os.popen("nm ../uelf/uelf | grep ' log$'").read().split()[0], 16)
|
|
gdb.execute('set print elements 0')
|
|
gdb.execute('set print repeats 0')
|
|
return [int(i, 16) for i in gdb.eval('{void*[512]}%d'%(uelf_base+offset)).strip()[1:-1].split(', ')]
|
|
|
|
def get_parasites(kdata_base):
|
|
return sorted({(i - kdata_base, j) for k in map(fetch_logs, range(16)) for i, j in zip(k[::2], k[1::2]) if i})
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def syscall_parasites():
|
|
use_kstuff()
|
|
init_kstuff()
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
ans = get_parasites(kdata_base)
|
|
assert len(ans) == 3
|
|
return ans
|
|
|
|
@derive_symbols('loadSelfSegment_watchpoint', 'loadSelfSegment_epilogue', 'loadSelfSegment_watchpoint_lr', 'decryptSelfBlock_epilogue', 'decryptSelfBlock_watchpoint_lr', 'decryptMultipleSelfBlocks_epilogue', 'decryptMultipleSelfBlocks_watchpoint_lr')
|
|
@retry_on_error
|
|
def loadSelfSegment_watchpoint():
|
|
ensure_fselfs(lambda: use_r0gdb_trace(1<<26))
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
for i in (
|
|
'sceSblServiceMailbox',
|
|
'sceSblServiceMailbox_lr_sceSblAuthMgrSmFinalize',
|
|
'sceSblServiceMailbox_lr_verifyHeader',
|
|
'sceSblAuthMgrSmIsLoadable2',
|
|
'sceSblServiceMailbox_lr_loadSelfSegment',
|
|
'sceSblServiceMailbox_lr_decryptSelfBlock',
|
|
'sceSblServiceMailbox_lr_decryptMultipleSelfBlocks',
|
|
'kernel_pmap_store',
|
|
'mini_syscore_header',
|
|
):
|
|
gdb.ieval('offsets.%s = %s'%(i, ostr(kdata_base + get_symbol(i))))
|
|
gdb.ieval('offsets.mmap_self_fix_1_end = (offsets.mmap_self_fix_1_start = %s) + 2'%ostr(kdata_base + get_symbol('mmap_self_fix_1_start')))
|
|
gdb.ieval('offsets.mmap_self_fix_2_end = (offsets.mmap_self_fix_2_start = %s) + 2'%ostr(kdata_base + get_symbol('mmap_self_fix_2_start')))
|
|
gdb.ieval('do_fself = 95')
|
|
rt1 = r0gdb.trace('trace_mailbox', 'mmap', 0, 65536, 1, 0x80001, '(int)open("/data/libSceLibcInternal.sprx", 0)', 0)
|
|
assert not (gdb.ieval('$eflags') & 1)
|
|
mapping = gdb.ieval('$rax')
|
|
rt2 = r0gdb.trace('trace_mailbox', 'mlock', mapping, 16384)
|
|
rt3 = r0gdb.trace('trace_mailbox', 'mlock', mapping, 65536)
|
|
trace = traces.Trace(rt1+rt2+rt3)
|
|
use_kstuff()
|
|
init_kstuff()
|
|
assert not any(map(any, map(fetch_logs, range(16))))
|
|
fd = gdb.ieval('(int)open("/data/libSceLibcInternal.sprx", 0)')
|
|
assert fd >= 0
|
|
mapping = gdb.ieval('(void*)mmap(0, 65536, 1, 0x80001, %d, 0)'%fd)
|
|
assert mapping != 2**64-1
|
|
assert not gdb.ieval('(int)mlock(%d, 16384)'%mapping)
|
|
assert not gdb.ieval('(int)mlock(%d, 65536)'%mapping)
|
|
parasites = get_parasites(gdb.ieval('kdata_base'))
|
|
parasite_set = {kdata_base + i[0] for i in parasites}
|
|
watchpoints = set()
|
|
rest = []
|
|
for i, j in enumerate(('loadSelfSegment', 'decryptSelfBlock', 'decryptMultipleSelfBlocks')):
|
|
k = trace.find_caller(trace.find_next_rip(0, kdata_base + get_symbol('sceSblServiceMailbox_lr_'+j) - 5)) + 1
|
|
while trace.find_next_instr(k) == k + 1: k += 1
|
|
ps = [i.rip-kdata_base for i in trace[k:trace.find_next_instr(k)] if i.rip in parasite_set]
|
|
k = trace.find_next_instr(k)
|
|
watchpoints.add(tuple(ps))
|
|
lr = trace[k].rip - kdata_base
|
|
k0 = k
|
|
while k + 1 < len(trace) and not (trace.is_jump(k) and trace[k+1].rsp == trace[k].rsp + 8):
|
|
k = trace.find_next_instr(k)
|
|
while k > k0 and trace[k-1].rsp < trace[k].rsp:
|
|
k -= 1
|
|
assert trace[k-1].rsp == trace[k].rsp
|
|
epilogue = trace[k].rip - kdata_base
|
|
rest.append(epilogue)
|
|
rest.append(lr)
|
|
assert len(watchpoints) == 1
|
|
return (min(next(iter(watchpoints))),)+tuple(rest)
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def fself_parasites():
|
|
ensure_fselfs(use_kstuff)
|
|
init_kstuff()
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
assert gdb.ieval('(int)dynlib_load_prx("/data/libScePlayerInvitationDialog.sprx", 0, malloc(4), 0)') == 0
|
|
fd = gdb.ieval('(int)open("/data/libSceLibcInternal.sprx", 0)')
|
|
assert fd >= 0
|
|
mapping = gdb.ieval('(void*)mmap(0, 65536, 1, 0x80001, %d, 0)'%fd)
|
|
assert mapping != 2**64-1
|
|
gdb.ieval('{void*}%d'%mapping)
|
|
assert not gdb.ieval('(int)mlock(%d, 16384)'%mapping)
|
|
assert not gdb.ieval('(int)mlock(%d, 65536)'%mapping)
|
|
assert not gdb.ieval('(int)munmap(%d, 65536)'%mapping)
|
|
assert not gdb.ieval('(int)close(%d)'%fd)
|
|
ans = get_parasites(kdata_base)
|
|
assert len(ans) == 10
|
|
return [(i, j) for i, j in ans if not (j == 10 and (i, 7) in ans)]
|
|
|
|
def elf_to_flat(data):
|
|
assert data.startswith(b'\x7fELF') and len(data) >= 64
|
|
ans = bytearray()
|
|
phoff = int.from_bytes(data[32:40], 'little')
|
|
phnum = int.from_bytes(data[56:58], 'little')
|
|
assert len(data) >= phoff+56*phnum
|
|
for i in range(phoff, phoff+56*phnum, 56):
|
|
ptype = int.from_bytes(data[i:i+4], 'little')
|
|
if ptype != 1: continue
|
|
offset = int.from_bytes(data[i+8:i+16], 'little')
|
|
vaddr = int.from_bytes(data[i+16:i+24], 'little')
|
|
filesz = int.from_bytes(data[i+32:i+40], 'little')
|
|
memsz = int.from_bytes(data[i+40:i+48], 'little')
|
|
assert memsz >= filesz
|
|
assert len(data) >= offset + filesz
|
|
ans[vaddr:vaddr+filesz] = data[offset:offset+filesz]
|
|
ans[vaddr+filesz:vaddr+memsz] = bytes(memsz-filesz)
|
|
return ans
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def shellcore_offsets():
|
|
if 'SHELLCORE' in os.environ:
|
|
with open(os.environ['SHELLCORE'], 'rb') as file:
|
|
shellcore_elf = file.read()
|
|
else:
|
|
shellcore_elf = dump_self('SceShellCore', '/system/vsh/SceShellCore.elf')[0]
|
|
shellcore = elf_to_flat(shellcore_elf)
|
|
shellcore_txt = shellcore.decode('latin-1').replace('\n', '\u010a')
|
|
def get_offsets(regexp):
|
|
return [shellcore_txt.find(i) for i in re.compile(regexp).findall(shellcore_txt)]
|
|
ans = []
|
|
offset, = get_offsets(r'\x80\x3d....\x01\x75\u010a\x48\x89\xd1\x31\xd2\xe9....\xb8\x01\x00\xe9\x80\xc3\xcc{7}')
|
|
target_fn = int.from_bytes(shellcore[offset+15:offset+19], 'little', signed=True)+offset+19
|
|
ans.append((offset+14, '52eb086690'))
|
|
ans.append((offset+25, (b'\xe8'+(target_fn-offset-30).to_bytes(4, 'little', signed=True)+b'\x58\xc3').hex()))
|
|
offset, = get_offsets(r'\xcc{7}\x80\x3d....\x01\x75\x0d\x48\x89\xd1\xba\x01\x00\x00\x00\xe9....\xb8\x01\x00\xe9\x80\xc3')
|
|
target_fn = int.from_bytes(shellcore[offset+25:offset+29], 'little', signed=True)+offset+29
|
|
ans.append((offset+24, '31c050ebe3'))
|
|
ans.append((offset, (b'\xe8'+(target_fn-offset-5).to_bytes(4, 'little', signed=True)+b'\x58\xc3').hex()))
|
|
offset, = get_offsets(r'\x44\x89[\xe0\xe8\xf0\xf8]\xff\xc8\x83\xf8\x02\x0f\x83')
|
|
ans.append((offset+8, 'eb04'))
|
|
offsets = get_offsets(r'(\xe8....\x85\xc0\x0f\x88....\x49\x8b\x46\x20\x48\xba\x00\xff\x00\xff\x00\xff\x00\xff|\xe8....\x85\xc0\x0f\x84....\x0f\x88....\x49\x0f\x38)')
|
|
assert len(offsets) == 2
|
|
for i in offsets:
|
|
if shellcore[i+8] == 0x88:
|
|
ans.append((i+7, 'eb04'))
|
|
else:
|
|
ans.append((i+13, 'eb04'))
|
|
offset, = get_offsets(r'(\x41\x39[\xdc\xdd]\x74.\x48\x8d\x3d....|\x3b\x84\x24....\x75.\x31\xc9\xeb.)')
|
|
if shellcore[offset] == 0x41:
|
|
ans.append((offset+3, 'eb'))
|
|
else:
|
|
ans.append((offset+7, '9090'))
|
|
offset, = get_offsets(r'\x83\xbb....\x03\x0f\x84')
|
|
ans.append((offset+7, '90e9'))
|
|
offset, = get_offsets(r'(\x41\x81[\xfc\xff]\x60\x00\x02\x80\x0f\x85....\x80\x7c\x24.\x00\x74.|\x41\x81[\xfc\xfd]\x60\x00\x02\x80\x0f\x85....\x84\xc0\x74.)')
|
|
if shellcore[offset+2] == 0xff:
|
|
ans.append((offset+18, 'eb'))
|
|
else:
|
|
ans.append((offset+15, 'eb'))
|
|
offset, = get_offsets(r'\x83\xfb\x01\x0f\x84....\x83\xfb\x02\x0f\x85....')
|
|
target = int.from_bytes(shellcore[offset+5:offset+9], 'little', signed=True)+offset+9
|
|
ans.append((offset+14, (target-offset-18).to_bytes(4, 'little', signed=True).hex()))
|
|
for offset, data in ans:
|
|
data = bytes.fromhex(data)
|
|
shellcore[offset:offset+len(data)] = data
|
|
offset, = get_offsets(r'\xeb.\x48\x8b\x32\x48\x89\xdf\xe8....\xeb.\x48\x8b\x02\x49\x8b\x37')
|
|
target = int.from_bytes(shellcore[offset+9:offset+13], 'little', signed=True)+offset+13
|
|
target2 = int.from_bytes(shellcore[offset+14:offset+15], 'little', signed=True)+offset+15
|
|
cave1 = shellcore.find(b'\xcc'*14, offset)
|
|
cave2 = shellcore.find(b'\xcc'*11, cave1+14)
|
|
ans.append((cave1, (b'\xe8'+(target-cave1-5).to_bytes(4, 'little', signed=True)+b'\x31\xc9\xff\xc1\xe9'+(cave2-cave1-14).to_bytes(4, 'little', signed=True)).hex()))
|
|
ans.append((cave2, (b'\x83\xf8\x02\x0f\x43\xc1\xe9'+(target2-cave2-11).to_bytes(4, 'little', signed=True)).hex()))
|
|
ans.append((offset+8, (b'\xe9'+(cave1-offset-13).to_bytes(4, 'little', signed=True)).hex()))
|
|
return ans
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def printf():
|
|
use_r0gdb_raw()
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
# arm the exit syscall
|
|
gdb.ieval('$pc = {void*}%s'%ostr(kdata_base+get_symbol('sysents')+56))
|
|
gdb.ieval('$rdi = {void*}(16+{void*}%s)'%ostr(kdata_base+get_symbol('allproc')))
|
|
gdb.ieval('$rsi = $rsp + 8')
|
|
# singlestep
|
|
prev_rip = gdb.ieval('$pc')
|
|
prev_rsp = gdb.ieval('$rsp')
|
|
while True:
|
|
gdb.execute('stepi')
|
|
rip = gdb.ieval('$pc')
|
|
rsp = gdb.ieval('$rsp')
|
|
print(hex(rip), hex(rsp))
|
|
if rsp == prev_rsp - 8 and rip not in range(prev_rip, prev_rip+16): break
|
|
prev_rip = rip
|
|
prev_rsp = rsp
|
|
# we're now at the first printf
|
|
assert gdb.ieval('$rdi') % 2**64 == (kdata_base + get_kernel()[0].find(b'# process pid=%d, %s calls exit() exit_value=%x.\n\0')) % 2**64
|
|
return gdb.ieval('$pc') - kdata_base
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def strlen_trap():
|
|
use_r0gdb_trace(16777216)
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
gdb.ieval('fncall_fn = '+ostr(kdata_base+get_symbol('printf')))
|
|
gdb.ieval('(void*)({char[4]}(fncall_args[0] = kmalloc(4)) = "%s\\n")')
|
|
gdb.ieval('fncall_args[1] = kmalloc(8)+1')
|
|
gdb.ieval('fncall_no_untrace = 1')
|
|
getpid = gdb.ieval('{void*}%d'%(kdata_base+get_symbol('sysents')+20*48+8))
|
|
gdb.ieval('sys_getpid = '+ostr(getpid))
|
|
while True:
|
|
gdb.ieval('(void*)({char[7]}fncall_args[1] = "AAAAAA")')
|
|
trace1 = traces.Trace(r0gdb.trace('getpid_to_fncall', 'getpid'))
|
|
gdb.ieval('(void*)({char[7]}fncall_args[1] = "aaaaaa")')
|
|
trace2 = traces.Trace(r0gdb.trace('getpid_to_fncall', 'getpid'))
|
|
if len(trace1) == len(trace2): break
|
|
assert [i.rip for i in trace1] == [i.rip for i in trace2]
|
|
i = next(i for i in range(len(trace1)) if trace1[i].rax == 0x0041414141414100 and trace2[i].rax == 0x0061616161616100)
|
|
assert i
|
|
i -= 1
|
|
assert trace1[i].rax != 0x0041414141414100 and trace2[i].rax != 0x0061616161616100 and trace1[i].rdi == trace2[i].rdi == gdb.ieval('fncall_args[1]') % 2**64
|
|
assert trace1[i].rip == trace2[i].rip
|
|
return trace1[i].rip - kdata_base
|
|
|
|
@derive_symbols(
|
|
'sceSblServiceMailbox_lr_verifySuperBlock',
|
|
'sceSblServiceMailbox_lr_sceSblPfsClearKey_1',
|
|
'sceSblServiceMailbox_lr_sceSblPfsClearKey_2',
|
|
'sceSblPfsSetKeys',
|
|
'sceSblServiceCryptAsync',
|
|
)
|
|
@retry_on_error
|
|
def sceSblServiceMailbox_lr_verifySuperBlock():
|
|
use_kstuff()
|
|
assert 'void' == gdb.eval('kill_thread()')
|
|
assert 'void' == gdb.eval('ignore_signals()')
|
|
assert 'void' == gdb.eval('temp_patch_shellcore(shellcore_patches, n_shellcore_patches, 1)')
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
print('Launch a PS4 fake package.')
|
|
# wait for nmount to get called
|
|
while True:
|
|
nmount_args = gdb.ieval('get_nmount_args()')
|
|
if nmount_args != 2**64-1: break
|
|
time.sleep(5)
|
|
n_args = 0
|
|
fspath = None
|
|
while True:
|
|
key = gdb.ieval('{void*}%d'%(nmount_args+n_args*32))
|
|
if not key: break
|
|
if gdb.ieval('{unsigned long long}%d'%key) % 2**56 == int.from_bytes(b'fspath', 'little'):
|
|
fspath = gdb.ieval('{void*}%d'%(nmount_args+n_args*32+16))
|
|
n_args += 1
|
|
n_args *= 2
|
|
# enable debug registers for thread
|
|
gdb.ieval('{int}(0x100+{void*}(0x3f8+get_thread())) |= 2')
|
|
# set debug registers
|
|
assert 'void' == gdb.eval('r0gdb_write_dbreg(0, %d)'%(kdata_base + get_symbol('sceSblServiceMailbox')))
|
|
assert 'void' == gdb.eval('r0gdb_write_dbreg(7, 0x401)')
|
|
assert 'void' == gdb.eval('set_trace()')
|
|
# call nmount to get verifySuperBlock
|
|
gdb.ieval('trace_prog = trace_mailbox_for_fpkg')
|
|
gdb.ieval('p_kekcall = p_syscall')
|
|
def nmount():
|
|
ans = gdb.ieval('my_nmount(%d, %d, 1)'%(nmount_args, n_args))
|
|
print('nmount() =', ans)
|
|
if not ans: # we were given a legit package, ok
|
|
# unmount, to allow for retry
|
|
assert not gdb.ieval('(int)kekcall(%d, 0, 0, 0, 0, 0, 22)'%fspath)
|
|
nmount()
|
|
assert gdb.ieval('mailbox_n') == 2
|
|
sceSblServiceMailbox_lr_verifySuperBlock = gdb.ieval('offsets.sceSblServiceMailbox_lr_verifySuperBlock = mailbox_lr[0]')
|
|
gdb.ieval('mailbox_n = 0')
|
|
gdb.ieval('n_faked_decrypts = 1')
|
|
gdb.ieval('mailbox_fakeresp[0] = 1')
|
|
# call nmount to get sceSblPfsClearKey_{1,2}
|
|
nmount()
|
|
assert gdb.ieval('mailbox_n') == 5
|
|
lrs = [gdb.ieval('mailbox_lr[%d]'%i) for i in range(5)]
|
|
assert len(set(lrs)) == 4 and lrs[0] == sceSblServiceMailbox_lr_verifySuperBlock and lrs[1] == lrs[4]
|
|
sceSblServiceMailbox_lr_sceSblPfsClearKey_1 = lrs[2]
|
|
sceSblServiceMailbox_lr_sceSblPfsClearKey_2 = lrs[3]
|
|
gdb.ieval('mailbox_n = 0')
|
|
nmount()
|
|
# call nmount to get sceSblServiceCrypt caller function
|
|
assert 'void' == gdb.eval('r0gdb_write_dbreg(1, %d)'%(kdata_base+get_symbol('printf')))
|
|
assert 'void' == gdb.eval('r0gdb_write_dbreg(7, 0x405)')
|
|
assert 'void' == gdb.eval('set_trace()')
|
|
page1 = gdb.ieval('malloc_locked(16384)')
|
|
page2 = page1 + 4096
|
|
page3 = page2 + 4096
|
|
page4 = page3 + 4096
|
|
gdb.ieval('mailbox_n = 0')
|
|
gdb.ieval('trace_prog_after_mailbox = do_dump_at_rip')
|
|
gdb.ieval('dump_program = (uint64_t[18]){%d, 1, -1, %d, %d, %d, 1, 10, -1, %d, %d, 0}'%(kdata_base+get_symbol('printf'), page1, page2, kdata_base+get_symbol('printf'), page1+8, page3))
|
|
nmount()
|
|
cache = {}
|
|
def get(ptr, page, offset):
|
|
if ptr not in cache:
|
|
cache[ptr] = gdb.ieval('{void*}%d'%ptr)
|
|
assert cache[ptr]
|
|
page_offset = cache[ptr] % 4096
|
|
assert page_offset + offset + 8 <= 4096
|
|
return gdb.ieval('{void*}%d'%(page+page_offset+offset))
|
|
kdata = get_kernel()[0]
|
|
error_rdi = kdata_base + kdata.find(b'\0[0]%s() line=%d sceSblServiceCryptAsync() failed 0x%x\n\0') + 1
|
|
assert get(page1, page2, 12*8) == error_rdi
|
|
error_rsi = kdata_base + kdata.find(b'\0pfs_generate_icv_sub\0') + 1
|
|
assert get(page1, page2, 11*8) == error_rsi
|
|
pfs_generate_icv_sub_lr = get(page1+8, page3, 8)
|
|
# call nmount to get sceSblPfsSetKeys
|
|
assert 'void' == gdb.eval('r0gdb_write_dbreg(1, %d)'%sceSblServiceMailbox_lr_verifySuperBlock)
|
|
assert 'void' == gdb.eval('set_trace()')
|
|
gdb.ieval('dump_program = (uint64_t[7]){%d, 0, 10, -1, %d, %d, 0}'%(sceSblServiceMailbox_lr_verifySuperBlock, page1+16, page4))
|
|
nmount()
|
|
sceSblPfsSetKeys_lr = get(page1+16, page4, 8)
|
|
# get the pointer to pfs_generate_icv_sub & sceSblPfsSetKeys
|
|
kstack = gdb.ieval('kstack')
|
|
regs = gdb.ieval('(void*)malloc(sizeof(struct regs))')
|
|
REGS = '((struct regs*)%d)' % regs
|
|
gdb.ieval('%s->eflags = 0x102'%REGS)
|
|
gdb.ieval('%s->rip = %d'%(REGS, pfs_generate_icv_sub_lr-5))
|
|
gdb.ieval('%s->rsp = %d'%(REGS, kstack))
|
|
assert 'void' == gdb.eval('(void)run_in_kernel(%d)'%regs)
|
|
assert gdb.ieval('%s->rsp'%REGS) == kstack - 8
|
|
pfs_generate_icv_sub = gdb.ieval('%s->rip'%REGS)
|
|
gdb.ieval('%s->rip = %d'%(REGS, sceSblPfsSetKeys_lr-5))
|
|
assert 'void' == gdb.eval('(void)run_in_kernel(%d)'%regs)
|
|
assert gdb.ieval('%s->rsp'%REGS) == kstack - 16
|
|
sceSblPfsSetKeys = gdb.ieval('%s->rip'%REGS)
|
|
# trace pfs_generate_icv_sub to find sceSblServiceCryptAsync
|
|
assert 'void' == gdb.eval('r0gdb_trace(16777216)')
|
|
gdb.ieval('trace_prog = trace_mailbox_for_fpkg')
|
|
gdb.ieval('mailbox_n = 0')
|
|
gdb.ieval('trace_prog_after_mailbox = trace_on_break')
|
|
assert 'void' == gdb.eval('r0gdb_write_dbreg(1, %d)'%pfs_generate_icv_sub)
|
|
assert 'void' == gdb.eval('set_trace()')
|
|
assert 'void' == gdb.eval('r0gdb_trace_reset()')
|
|
nmount()
|
|
trace = traces.Trace(r0gdb.get_trace())
|
|
k = trace.find_next_rip(0, kdata_base + get_symbol('printf'))
|
|
for i in range(4):
|
|
while not (trace.is_jump(k) and trace[k+1].rsp == trace[k].rsp + 8): k -= 1
|
|
k = trace.find_caller(k)
|
|
assert trace[k].rsi == 0x138 and trace[trace.find_next_instr(k)].rax == 0
|
|
sceSblServiceCryptAsync = trace[k+1].rip
|
|
return (
|
|
sceSblServiceMailbox_lr_verifySuperBlock-kdata_base,
|
|
sceSblServiceMailbox_lr_sceSblPfsClearKey_1-kdata_base,
|
|
sceSblServiceMailbox_lr_sceSblPfsClearKey_2-kdata_base,
|
|
sceSblPfsSetKeys-kdata_base,
|
|
sceSblServiceCryptAsync-kdata_base,
|
|
)
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def sceSblServiceCryptAsync_deref_singleton():
|
|
use_kstuff()
|
|
init_kstuff()
|
|
print('Launch a PS4 fake package.')
|
|
while True:
|
|
ans = get_parasites(gdb.ieval('kdata_base'))
|
|
if ans: break
|
|
time.sleep(5)
|
|
assert ans and ans == [(ans[0][0], 0), (ans[0][0], 3)]
|
|
assert ans[0][0] - get_symbol('sceSblServiceCryptAsync') in range(256)
|
|
return ans[0][0]
|
|
|
|
@derive_symbol
|
|
@retry_on_error
|
|
def crypt_message_resolve():
|
|
use_r0gdb_raw()
|
|
kdata_base = gdb.ieval('kdata_base')
|
|
gdb.ieval('$pc = '+ostr(kdata_base + get_symbol('sceSblServiceCryptAsync')))
|
|
arg = gdb.ieval('{void*}($rdi = $rsp + 8) = $rsp + 8')
|
|
prev_rip = gdb.ieval('$pc')
|
|
prev_rsp = gdb.ieval('$rsp')
|
|
while True:
|
|
gdb.execute('stepi')
|
|
rip = gdb.ieval('$pc')
|
|
rsp = gdb.ieval('$rsp')
|
|
print(hex(rip), hex(rsp))
|
|
if rip not in range(prev_rip, prev_rip+16) and rsp == prev_rsp - 8: # function call
|
|
if gdb.ieval('$rdi') % 2**64 == arg % 2**64 and gdb.ieval('$rsi') == 0xffffffdb:
|
|
return rip - kdata_base
|
|
gdb.ieval('$rax = 0')
|
|
gdb.ieval('$pc = '+ostr(kdata_base + get_symbol('wrmsr_ret') + 2))
|
|
prev_rip = rip
|
|
prev_rsp = rsp
|
|
|
|
##These are not so important, just ignore for now
|
|
#@derive_symbol
|
|
#@retry_on_error
|
|
#def unsorted_parasites():
|
|
# use_kstuff()
|
|
# init_kstuff()
|
|
# print('Launch and close a PS4 fake package. Press Enter when the game is fully closed.')
|
|
# input('> ')
|
|
# time.sleep(10)
|
|
# kdata_base = gdb.ieval('kdata_base')
|
|
# ans = get_parasites(kdata_base)
|
|
# assert False, ans
|
|
|
|
print(len(symbols), 'offsets currently known')
|
|
print(sum(sum(j not in symbols for j in i[1]) if isinstance(i, tuple) else (i.__name__ not in symbols) for i in derivations), 'offsets to be found')
|
|
|
|
for i in derivations:
|
|
if isinstance(i, tuple):
|
|
i, names = i
|
|
if any(j not in symbols for j in names):
|
|
print('Probing offsets `%s`'%'`, `'.join(names))
|
|
try: value = i()
|
|
except Exception:
|
|
raise Exception("failed to derive `%s`, see above why"%'`, `'.join(names))
|
|
assert len(value) == len(names)
|
|
for i, j in zip(names, value):
|
|
set_symbol(i, j)
|
|
available_symbols |= set(names)
|
|
elif i.__name__ not in symbols:
|
|
print('Probing offset `%s`'%i.__name__)
|
|
try: value = i()
|
|
except Exception:
|
|
raise Exception("failed to derive `%s`, see above why"%i.__name__)
|
|
set_symbol(i.__name__, value)
|
|
else:
|
|
available_symbols.add(i.__name__)
|