Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 78 additions & 97 deletions qiling/loader/elf.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,88 +147,86 @@ def seg_perm_to_uc_prot(perm: int) -> int:
return prot

def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, argv: Sequence[str] = [], env: Mapping[str, str] = {}):
# get list of loadable segments; these segments will be loaded to memory
seg_pt_load = tuple(seg for seg in elffile.iter_segments() if seg['p_type'] == 'PT_LOAD')

# determine the memory regions that need to be mapped in order to load the segments.
# note that region boundaries are aligned to page, which means they may be larger than
# the segment they contain. to reduce mapping clutter, adjacent regions with the same
# perms are consolidated into one contigous memory region
load_regions: Sequence[Tuple[int, int, int]] = []

# iterate over loadable segments by vaddr
for seg in sorted(seg_pt_load, key=lambda s: s['p_vaddr']):
lbound = self.ql.mem.align(load_address + seg['p_vaddr'])
ubound = self.ql.mem.align_up(load_address + seg['p_vaddr'] + seg['p_memsz'])
perms = QlLoaderELF.seg_perm_to_uc_prot(seg['p_flags'])

if load_regions:
prev_lbound, prev_ubound, prev_perms = load_regions[-1]

# new region starts where the previous one ended
if lbound == prev_ubound:
# same perms? extend previous memory region
if perms == prev_perms:
load_regions[-1] = (prev_lbound, ubound, prev_perms)

# different perms? start a new one
else:

def load_elf_segments(elffile: ELFFile, load_address: int, info: str):
# get list of loadable segments; these segments will be loaded to memory
load_segments = sorted(elffile.iter_segments(type='PT_LOAD'), key=lambda s: s['p_vaddr'])

# determine the memory regions that need to be mapped in order to load the segments.
# note that region boundaries are aligned to page, which means they may be larger than
# the segment they contain. to reduce mapping clutter, adjacent regions with the same
# perms are consolidated into one contigous memory region
load_regions: Sequence[Tuple[int, int, int]] = []

# iterate over loadable segments
for seg in load_segments:
lbound = self.ql.mem.align(load_address + seg['p_vaddr'])
ubound = self.ql.mem.align_up(load_address + seg['p_vaddr'] + seg['p_memsz'])
perms = QlLoaderELF.seg_perm_to_uc_prot(seg['p_flags'])

if load_regions:
prev_lbound, prev_ubound, prev_perms = load_regions[-1]

# new region starts where the previous one ended
if lbound == prev_ubound:
# same perms? extend previous memory region
if perms == prev_perms:
load_regions[-1] = (prev_lbound, ubound, prev_perms)

# different perms? start a new one
else:
load_regions.append((lbound, ubound, perms))

# start a new memory region
elif lbound > prev_ubound:
load_regions.append((lbound, ubound, perms))

# start a new memory region
elif lbound > prev_ubound:
# overlapping segments? something probably went wrong
elif lbound < prev_ubound:
# EDL ELF files use 0x400 bytes pages, which might make some segments look as if they
# start at the same segment as their predecessor. though that is fixable, unicorn
# supports only 0x1000 bytes pages; this becomes problematic when using mem.protect
#
# this workaround unifies such "overlapping" segments, which may apply more permissive
# protection flags to that memory region.
if self.ql.arch.type == QL_ARCH.ARM64:
load_regions[-1] = (prev_lbound, ubound, prev_perms | perms)
continue

raise RuntimeError

else:
load_regions.append((lbound, ubound, perms))

# overlapping segments? something probably went wrong
elif lbound < prev_ubound:
# EDL ELF files use 0x400 bytes pages, which might make some segments look as if they
# start at the same segment as their predecessor. though that is fixable, unicorn
# supports only 0x1000 bytes pages; this becomes problematic when using mem.protect
#
# this workaround unifies such "overlapping" segments, which may apply more permissive
# protection flags to that memory region.
if self.ql.arch.type == QL_ARCH.ARM64:
load_regions[-1] = (prev_lbound, ubound, prev_perms | perms)
continue

raise RuntimeError

else:
load_regions.append((lbound, ubound, perms))

# map the memory regions
for lbound, ubound, perms in load_regions:
try:
self.ql.mem.map(lbound, ubound - lbound, perms, info=os.path.basename(self.path))
except QlMemoryMappedError:
self.ql.log.exception(f'Failed to map {lbound:#x}-{ubound:#x}')
else:
self.ql.log.debug(f'Mapped {lbound:#x}-{ubound:#x}')

# load loadable segments contents to memory
for seg in seg_pt_load:
self.ql.mem.write(load_address + seg['p_vaddr'], seg.data())

entry_point = load_address + elffile['e_entry']

# the memory space on which the program spans
mem_start = min(seg['p_vaddr'] for seg in seg_pt_load)
mem_end = max(seg['p_vaddr'] + seg['p_memsz'] for seg in seg_pt_load)

mem_start = self.ql.mem.align(mem_start)
mem_end = self.ql.mem.align_up(mem_end)
# map the memory regions
for lbound, ubound, perms in load_regions:
try:
self.ql.mem.map(lbound, ubound - lbound, perms, os.path.basename(info))
except QlMemoryMappedError:
self.ql.log.exception(f'Failed to map {lbound:#x}-{ubound:#x}')
else:
self.ql.log.debug(f'Mapped {lbound:#x}-{ubound:#x}')

# load loadable segments contents to memory
for seg in load_segments:
self.ql.mem.write(load_address + seg['p_vaddr'], seg.data())

return load_regions[0][0], load_regions[-1][1]

mem_start, mem_end = load_elf_segments(elffile, load_address, self.path)
self.elf_entry = entry_point = load_address + elffile['e_entry']

self.ql.log.debug(f'mem_start : {mem_start:#x}')
self.ql.log.debug(f'mem_end : {mem_end:#x}')

# by convention the loaded binary is first on the list
self.images.append(Image(load_address + mem_start, load_address + mem_end, os.path.abspath(self.path)))
self.images.append(Image(mem_start, mem_end, os.path.abspath(self.path)))

# note: 0x2000 is the size of [hook_mem]
self.brk_address = load_address + mem_end + 0x2000
self.brk_address = mem_end + 0x2000

# determine interpreter path
interp_seg = next((seg for seg in elffile.iter_segments() if type(seg) is InterpSegment), None)
interp_seg = next(elffile.iter_segments(type='PT_INTERP'), None)
interp_path = str(interp_seg.get_interp_name()) if interp_seg else ''

interp_address = 0
Expand All @@ -240,30 +238,19 @@ def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, arg

with open(interp_local_path, 'rb') as infile:
interp = ELFFile(infile)
min_vaddr = min(seg['p_vaddr'] for seg in interp.iter_segments(type='PT_LOAD'))

# determine interpreter base address
interp_address = int(self.profile.get('interp_address'), 0)
# some old interpreters may not be PIE: p_vaddr of the first LOAD segment is not zero
# we should load interpreter at the address p_vaddr specified in such situation
interp_address = int(self.profile.get('interp_address'), 0) if min_vaddr == 0 else 0
self.ql.log.debug(f'Interpreter addr: {interp_address:#x}')

interp_seg_pt_load = tuple(seg for seg in interp.iter_segments() if seg['p_type'] == 'PT_LOAD')

# determine memory size needed for interpreter
interp_mem_size = max((seg['p_vaddr'] + seg['p_memsz']) for seg in interp_seg_pt_load)
interp_mem_size = self.ql.mem.align_up(interp_mem_size)
self.ql.log.debug(f'Interpreter size: {interp_mem_size:#x}')

# map memory for interpreter
self.ql.mem.map(interp_address, interp_mem_size, info=os.path.basename(interp_local_path))
# load interpreter segments data to memory
interp_start, interp_end = load_elf_segments(interp, interp_address, interp_local_path)

# add interpreter to the loaded images list
self.images.append(Image(interp_address, interp_address + interp_mem_size, os.path.abspath(interp_local_path)))

# load interpterter segments data to memory
for seg in interp_seg_pt_load:
addr = interp_address + seg['p_vaddr']
data = seg.data()

self.ql.mem.write(addr, data)
self.images.append(Image(interp_start, interp_end, os.path.abspath(interp_local_path)))

# determine entry point
entry_point = interp_address + interp['e_entry']
Expand All @@ -273,7 +260,6 @@ def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, arg
self.ql.log.debug(f'mmap_address is : {mmap_address:#x}')

# set info to be used by gdb
self.interp_address = interp_address
self.mmap_address = mmap_address

# set elf table
Expand Down Expand Up @@ -315,10 +301,9 @@ def __push_str(top: int, s: str) -> int:
new_stack = execfn = __push_str(new_stack, argv[0])

# store aux vector data for gdb use
elf_phdr = load_address + elffile['e_phoff']
elf_phdr = elffile['e_phoff'] + mem_start
elf_phent = elffile['e_phentsize']
elf_phnum = elffile['e_phnum']
elf_entry = load_address + elffile['e_entry']

if self.ql.arch.bits == 64:
elf_hwcap = 0x078bfbfd
Expand All @@ -333,13 +318,13 @@ def __push_str(top: int, s: str) -> int:

# setup aux vector
aux_entries = (
(AUX.AT_PHDR, elf_phdr + mem_start),
(AUX.AT_PHDR, elf_phdr),
(AUX.AT_PHENT, elf_phent),
(AUX.AT_PHNUM, elf_phnum),
(AUX.AT_PAGESZ, self.ql.mem.pagesize),
(AUX.AT_BASE, interp_address),
(AUX.AT_FLAGS, 0),
(AUX.AT_ENTRY, elf_entry),
(AUX.AT_ENTRY, self.elf_entry),
(AUX.AT_UID, self.ql.os.uid),
(AUX.AT_EUID, self.ql.os.euid),
(AUX.AT_GID, self.ql.os.gid),
Expand All @@ -361,20 +346,16 @@ def __push_str(top: int, s: str) -> int:
new_stack = self.ql.mem.align(new_stack - len(elf_table), 0x10)
self.ql.mem.write(new_stack, bytes(elf_table))

# if enabled, gdb would need to retrieve aux vector data.
# note that gdb needs the AT_PHDR entry to hold the original elf_phdr value
self.aux_vec = dict(aux_entries)
self.aux_vec[AUX.AT_PHDR] = elf_phdr

self.elf_entry = elf_entry
self.stack_address = new_stack
self.load_address = load_address
self.init_sp = self.ql.arch.regs.arch_sp

self.ql.os.entry_point = self.entry_point = entry_point
self.ql.os.elf_mem_start = mem_start
self.ql.os.elf_entry = self.elf_entry
self.ql.os.function_hook = FunctionHook(self.ql, elf_phdr + mem_start, elf_phnum, elf_phent, load_address, load_address + mem_end)
self.ql.os.function_hook = FunctionHook(self.ql, elf_phdr, elf_phnum, elf_phent, load_address, mem_end)

# If there is a loader, we ignore exit
self.skip_exit_check = (self.elf_entry != self.entry_point)
Expand Down
5 changes: 2 additions & 3 deletions qiling/loader/mcu.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,8 @@ def guess_filetype(self):

def reset(self):
if self.filetype == 'elf':
for segment in self.elf.iter_segments():
if segment['p_type'] == 'PT_LOAD':
self.ql.mem.write(segment['p_paddr'], segment.data())
for segment in self.elf.iter_segments(type='PT_LOAD'):
self.ql.mem.write(segment['p_paddr'], segment.data())

# TODO: load symbol table

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"pefile>=2021.9.3",
"python-registry>=1.3.1",
"keystone-engine>=0.9.2",
"pyelftools>=0.26",
"pyelftools>=0.28",
"gevent>=20.9.0",
"multiprocess>=0.70.12.2",
"pyyaml>=6.0"
Expand Down