From 6f3ccb1f2fdad930e4d6fa6a12e469ad14ee68e4 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 12 May 2022 22:19:38 +0300 Subject: [PATCH] Change is_mapped semantics --- qiling/arch/x86_utils.py | 8 ++++--- qiling/loader/elf.py | 2 +- qiling/os/memory.py | 42 ++++++++++++++++++++++++++++++------ qiling/os/windows/windows.py | 14 +++++++----- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/qiling/arch/x86_utils.py b/qiling/arch/x86_utils.py index 39573c12..de6e24f2 100644 --- a/qiling/arch/x86_utils.py +++ b/qiling/arch/x86_utils.py @@ -4,7 +4,7 @@ from abc import abstractmethod from qiling import Qiling from qiling.arch.x86 import QlArchIntel from qiling.arch.x86_const import * -from qiling.exception import QlGDTError +from qiling.exception import QlGDTError, QlMemoryMappedError from qiling.os.memory import QlMemoryManager class GDTArray: @@ -50,8 +50,10 @@ class GDTManager: def __init__(self, ql: Qiling, base = QL_X86_GDT_ADDR, limit = QL_X86_GDT_LIMIT, num_entries = 16): ql.log.debug(f'Mapping GDT at {base:#x} with limit {limit:#x}') - if not ql.mem.is_mapped(base, limit): - ql.mem.map(base, limit, info="[GDT]") + if not ql.mem.is_available(base, limit): + raise QlMemoryMappedError('cannot map GDT, memory location is taken') + + ql.mem.map(base, limit, info="[GDT]") # setup GDT by writing to GDTR ql.arch.regs.write(UC_X86_REG_GDTR, (0, base, limit, 0x0)) diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index a1be0ef6..ada00229 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -368,7 +368,7 @@ class QlLoaderELF(QlLoader): _vsyscall_addr = int(self.profile.get('vsyscall_address'), 0) _vsyscall_size = int(self.profile.get('vsyscall_size'), 0) - if not self.ql.mem.is_mapped(_vsyscall_addr, _vsyscall_size): + if self.ql.mem.is_available(_vsyscall_addr, _vsyscall_size): # initialize with int3 instructions then insert syscall entry # each syscall should be 1KiB away self.ql.mem.map(_vsyscall_addr, _vsyscall_size, info="[vsyscall]") diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 42fdf239..19c1ea92 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -280,7 +280,7 @@ class QlMemoryManager: self.ql.log.debug(f'restoring memory range: {lbound:#08x} {ubound:#08x} {label}') size = ubound - lbound - if not self.is_mapped(lbound, size): + if self.is_available(lbound, size): self.ql.log.debug(f'mapping {lbound:#08x} {ubound:#08x}, mapsize = {size:#x}') self.map(lbound, size, perms, label) @@ -420,9 +420,34 @@ class QlMemoryManager: for begin, end, _ in self.ql.uc.mem_regions(): self.unmap(begin, end - begin + 1) + def __mapped_regions(self) -> Iterator[Tuple[int, int]]: + """Iterate through all mapped memory regions, consolidating adjacent regions + together to a continuous one. Protection bits and labels are ignored. + """ + + if not self.map_info: + return + + iter_memmap = iter(self.map_info) + + p_lbound, p_ubound, _, _, _ = next(iter_memmap) + + # map_info is assumed to contain non-overlapping regions sorted by lbound + for lbound, ubound, _, _, _ in iter_memmap: + if lbound == p_ubound: + p_ubound = ubound + else: + yield (p_lbound, p_ubound) + + p_lbound = lbound + p_ubound = ubound + + yield (p_lbound, p_ubound) + + def is_available(self, addr: int, size: int) -> bool: """Query whether the memory range starting at `addr` and is of length of `size` bytes - can be allocated. + is available for allocation. Returns: True if it can be allocated, False otherwise """ @@ -433,16 +458,21 @@ class QlMemoryManager: end = addr + size # make sure neither begin nor end are enclosed within a mapped range, or entirely enclosing one - return not any((lbound <= begin < ubound) or (lbound < end <= ubound) or (begin <= lbound < ubound <= end) for lbound, ubound, _, _, _ in self.map_info) + return not any((lbound <= begin < ubound) or (lbound < end <= ubound) or (begin <= lbound < ubound <= end) for lbound, ubound in self.__mapped_regions()) def is_mapped(self, addr: int, size: int) -> bool: """Query whether the memory range starting at `addr` and is of length of `size` bytes - is mapped, either partially or entirely. + is fully mapped. - Returns: True if any part of the specified memory range is taken, False otherwise + Returns: True if the specified memory range is taken fully, False otherwise """ - return not self.is_available(addr, size) + assert size > 0, 'expected a positive size value' + + begin = addr + end = addr + size + + return any((lbound <= begin < end <= ubound) for lbound, ubound in self.__mapped_regions()) def find_free_space(self, size: int, minaddr: int = None, maxaddr: int = None, align: int = None) -> int: """Locate an unallocated memory that is large enough to contain a range in size of diff --git a/qiling/os/windows/windows.py b/qiling/os/windows/windows.py index e9d2d0b0..db9b29b2 100644 --- a/qiling/os/windows/windows.py +++ b/qiling/os/windows/windows.py @@ -13,7 +13,7 @@ from qiling.arch.x86_const import GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, FS_SEGMENT_A from qiling.arch.x86_utils import GDTManager, SegmentManager86, SegmentManager64 from qiling.cc import intel from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT -from qiling.exception import QlErrorSyscallError, QlErrorSyscallNotFound +from qiling.exception import QlErrorSyscallError, QlErrorSyscallNotFound, QlMemoryMappedError from qiling.os.fcall import QlFunctionCall from qiling.os.memory import QlMemoryHeap from qiling.os.os import QlOs @@ -116,11 +116,15 @@ class QlOsWindows(QlOs): segm.setup_fs(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE) segm.setup_gs(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE) - if not self.ql.mem.is_mapped(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE): - self.ql.mem.map(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE, info='[FS]') + if not self.ql.mem.is_available(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE): + raise QlMemoryMappedError('cannot map FS segment, memory location is taken') - if not self.ql.mem.is_mapped(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE): - self.ql.mem.map(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, info='[GS]') + self.ql.mem.map(FS_SEGMENT_ADDR, FS_SEGMENT_SIZE, info='[FS]') + + if not self.ql.mem.is_available(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE): + raise QlMemoryMappedError('cannot map GS segment, memory location is taken') + + self.ql.mem.map(GS_SEGMENT_ADDR, GS_SEGMENT_SIZE, info='[GS]') def __setup_components(self):