mirror of https://github.com/Shizmob/smol
177 lines
5.8 KiB
Python
Executable File
177 lines
5.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import os.path, struct, sys
|
|
import argparse, glob, shutil, subprocess
|
|
|
|
import smol.hackyelf as hackyelf
|
|
import smol.linkmap as linkmap
|
|
from smol.shared import *
|
|
from smol.parse import *
|
|
|
|
def readbyte(blob, off): return struct.unpack('<B', blob[off:off+1])[0], (off+1)
|
|
def readint(blob, off): return struct.unpack('<I', blob[off:off+4])[0], (off+4)
|
|
def readlong(blob, off): return struct.unpack('<Q', blob[off:off+8])[0], (off+8)
|
|
def readstr(blob, off):
|
|
text = bytearray()
|
|
while True:
|
|
char, off = readbyte(blob, off)
|
|
if char == 0: break
|
|
|
|
text.append(char)
|
|
|
|
return text.decode('utf-8'), off
|
|
|
|
def get_def_libpaths(cc_bin, is32bit):
|
|
# FIXME: HACK
|
|
if is32bit: return ['/usr/lib32/','/lib32/']
|
|
|
|
return get_cc_paths(cc_bin)['libraries']
|
|
|
|
def find_libs(deflibs, libname):
|
|
dirs = os.environ.get('LD_LIBRARY_PATH','').split(':') + deflibs
|
|
|
|
for d in dirs:
|
|
for f in glob.glob(glob.escape(d + '/' + libname) + '*'): yield f
|
|
|
|
def build_hashtab(scanelf_bin, lib):
|
|
out = subprocess.check_output([scanelf_bin, '-B', '-F', '%s', '-s', '%pd%*', lib],
|
|
stderr=subprocess.DEVNULL)
|
|
|
|
blah = set(out.decode('utf-8').split('\n'))
|
|
ret = dict({})
|
|
|
|
for x in blah:
|
|
y = x.split()
|
|
if len(y) != 7: continue
|
|
ret[hash_djb2(y[6])] = y[6]
|
|
|
|
return ret
|
|
|
|
def addr2off(elf, addr):
|
|
for x in elf.phdrs:
|
|
if x.ptype != hackyelf.PT_LOAD: continue
|
|
|
|
if addr >= x.vaddr and addr < x.vaddr + x.memsz:
|
|
aoff = addr - x.vaddr
|
|
assert aoff < x.filesz, ".bss address!"
|
|
return aoff + x.off
|
|
|
|
error("E: Address %08x not in the static address range!" % addr)
|
|
|
|
def get_needed_libs(elf, blob):
|
|
assert elf.dyn is not None, "No DYNAMIC table present in the ELF file!"
|
|
|
|
strtabs = [x.val for x in elf.dyn if x.tag == hackyelf.DT_STRTAB]
|
|
assert len(strtabs) == 1, "Only one DT_STRTAB may be present in an ELF file."
|
|
strtab = strtabs[0]
|
|
|
|
return [readstr(blob,addr2off(elf, strtab+x.val))[0]
|
|
for x in elf.dyn if x.tag == hackyelf.DT_NEEDED]
|
|
|
|
def get_hashtbl(elf, blob, args):
|
|
htaddr = None
|
|
if args.map is not None:
|
|
lmap = linkmap.parse(args.map.read())
|
|
tabs = [x for x in lmap.mmap if x.sym == '_symbols']
|
|
assert len(tabs) == 1, "One '_symbols' symbol must be present."
|
|
htaddr = tabs[0].org
|
|
elif elf.is32bit:
|
|
txtoff = addr2off(elf, elf.entry)
|
|
# scan for 'push IMM32'
|
|
while blob[txtoff] != 0x68:
|
|
txtoff = txtoff + 1
|
|
assert txtoff < len(blob), "wtf??? (can't find a push IMM32 instruction which pushes the hashtable address)"
|
|
txtoff = txtoff + 1
|
|
|
|
htaddr = struct.unpack('<I', blob[txtoff:txtoff+4])[0]
|
|
else: # 64-bit
|
|
txtoff = addr2off(elf, elf.entry)
|
|
# scan for 'push IMM32'
|
|
# but the first one we'll encounter pushes the entrypoint addr!
|
|
while blob[txtoff] != 0x68:
|
|
txtoff = txtoff + 1
|
|
assert txtoff < len(blob), "wtf??? (can't find a push IMM32 instruction which pushes the entrypoint address)"
|
|
txtoff = txtoff + 1
|
|
# now we can look for the interesting address
|
|
while blob[txtoff] != 0x68:
|
|
txtoff = txtoff + 1
|
|
assert txtoff < len(blob), "wtf??? (can't find a push IMM32 instruction which pushes the hashtable address)"
|
|
txtoff = txtoff + 1
|
|
|
|
htaddr = struct.unpack('<I', blob[txtoff:txtoff+4])[0]
|
|
|
|
assert htaddr is not None, "wtf? (no hashtable address)"
|
|
#print("Hash table address: 0x%08x" % htaddr)
|
|
htoff = addr2off(elf, htaddr)
|
|
|
|
tbl = []
|
|
while True:
|
|
if len(blob)-htoff < 4:
|
|
if struct.unpack('<B', blob[htoff:htoff+1])[0] == 0:
|
|
break
|
|
val = struct.unpack('<I', blob[htoff:htoff+4])[0]
|
|
if (val & 0xFF) == 0: break
|
|
tbl.append(val)
|
|
htoff = htoff + (4 if elf.is32bit else 8)
|
|
|
|
return tbl
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('input', type=argparse.FileType('rb'),
|
|
default=sys.stdin.buffer, help="input file")
|
|
parser.add_argument('--cc',
|
|
default=shutil.which('cc'), help="C compiler binary")
|
|
parser.add_argument('--scanelf',
|
|
default=shutil.which('scanelf'), help="scanelf binary")
|
|
parser.add_argument('--map', type=argparse.FileType('r'), help=\
|
|
"Get the address of the symbol hash table from the "+\
|
|
"linker map output instead of attempting to parse the"+\
|
|
" binary.")
|
|
args = parser.parse_args()
|
|
|
|
blob = args.input.read()
|
|
elf = hackyelf.parse(blob)
|
|
|
|
deflibs = get_def_libpaths(args.cc, elf.is32bit)
|
|
needed = get_needed_libs(elf, blob)
|
|
neededpaths = dict((l,list(find_libs(deflibs, l))[0]) for l in needed)
|
|
|
|
htbl = get_hashtbl(elf, blob, args)
|
|
|
|
libhashes = dict((l, build_hashtab(args.scanelf, neededpaths[l])) for l in needed)
|
|
|
|
hashresolves = dict({})
|
|
noresolves = []
|
|
# TODO: group by libs
|
|
for x in htbl:
|
|
done = False
|
|
for l in libhashes.keys():
|
|
v = libhashes[l]
|
|
if x in v:
|
|
if l not in hashresolves: hashresolves[l] = dict({})
|
|
hashresolves[l][x] = v[x]
|
|
done = True
|
|
break
|
|
if not done: noresolves.append(x)
|
|
|
|
for l in hashresolves.keys():
|
|
print("%s:" % l)
|
|
v = hashresolves[l]
|
|
for x in v.keys():
|
|
print("\t%08x -> %s" % (x, v[x]))
|
|
|
|
if len(noresolves) > 0:
|
|
print("UNRESOLVED:")
|
|
for x in noresolves: print("\t%08x" % x)
|
|
|
|
return 0
|
|
|
|
if __name__ == '__main__':
|
|
rv = main()
|
|
if rv is None: pass
|
|
else:
|
|
try: sys.exit(int(rv))
|
|
except: sys.exit(1)
|
|
|