diff --git a/examples/shellcode_run.py b/examples/shellcode_run.py index c45f9ed27..c7e189696 100644 --- a/examples/shellcode_run.py +++ b/examples/shellcode_run.py @@ -1,58 +1,107 @@ #!/usr/bin/env python3 -# +# # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from binascii import unhexlify import sys sys.path.append("..") from qiling import Qiling -from qiling.const import QL_VERBOSE - -X86_LIN = unhexlify('31c050682f2f7368682f62696e89e3505389e1b00bcd80') -X8664_LIN = unhexlify('31c048bbd19d9691d08c97ff48f7db53545f995257545eb03b0f05') -MIPS32EL_LIN = unhexlify('ffff0628ffffd004ffff05280110e4270ff08424ab0f02240c0101012f62696e2f7368') -X86_WIN = unhexlify('fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b592001d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe05f5f5a8b12eb8d5d6a01eb2668318b6f87ffd5bbf0b5a25668a695bd9dffd53c067c0a80fbe07505bb4713726f6a0053ffd5e8d5ffffff63616c6300') -X8664_WIN = unhexlify('fc4881e4f0ffffffe8d0000000415141505251564831d265488b52603e488b52183e488b52203e488b72503e480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed5241513e488b52203e8b423c4801d03e8b80880000004885c0746f4801d0503e8b48183e448b40204901d0e35c48ffc93e418b34884801d64d31c94831c0ac41c1c90d4101c138e075f13e4c034c24084539d175d6583e448b40244901d0663e418b0c483e448b401c4901d03e418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a3e488b12e949ffffff5d49c7c1000000003e488d95fe0000003e4c8d850f0100004831c941ba45835607ffd54831c941baf0b5a256ffd548656c6c6f2c2066726f6d204d534621004d657373616765426f7800') -ARM_LIN = unhexlify('01108fe211ff2fe102200121921a0f02193701df061c08a11022023701df3f270221301c01df0139fbd505a0921a05b469460b2701dfc046020012340a0002022f73797374656d2f62696e2f736800') -ARM64_LIN = unhexlify('420002ca210080d2400080d2c81880d2010000d4e60300aa01020010020280d2681980d2010000d4410080d2420002cae00306aa080380d2010000d4210400f165ffff54e0000010420002ca210001caa81b80d2010000d4020004d27f0000012f62696e2f736800') -X8664_FBSD = unhexlify('6a61586a025f6a015e990f054897baff02aaaa80f2ff524889e699046680c2100f05046a0f05041e4831f6990f0548976a035852488d7424f080c2100f0548b8523243427730637257488d3e48af74084831c048ffc00f055f4889d04889fe48ffceb05a0f0575f799043b48bb2f62696e2f2f73685253545f5257545e0f05') -X8664_MACOS = unhexlify('4831f65648bf2f2f62696e2f7368574889e74831d24831c0b00248c1c828b03b0f05') +from qiling.const import QL_ARCH, QL_OS, QL_VERBOSE + +X86_LIN = bytes.fromhex('31c050682f2f7368682f62696e89e3505389e1b00bcd80') +X8664_LIN = bytes.fromhex('31c048bbd19d9691d08c97ff48f7db53545f995257545eb03b0f05') + +MIPS32EL_LIN = bytes.fromhex(''' + ffff0628ffffd004ffff05280110e4270ff08424ab0f02240c0101012f62696e + 2f7368 +''') + +X86_WIN = bytes.fromhex(''' + fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c + 617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b5920 + 01d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475 + e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ff + e05f5f5a8b12eb8d5d6a01eb2668318b6f87ffd5bbf0b5a25668a695bd9dffd5 + 3c067c0a80fbe07505bb4713726f6a0053ffd5e8d5ffffff63616c6300 +''') + +X8664_WIN = bytes.fromhex(''' + fc4881e4f0ffffffe8d0000000415141505251564831d265488b52603e488b52 + 183e488b52203e488b72503e480fb74a4a4d31c94831c0ac3c617c022c2041c1 + c90d4101c1e2ed5241513e488b52203e8b423c4801d03e8b80880000004885c0 + 746f4801d0503e8b48183e448b40204901d0e35c48ffc93e418b34884801d64d + 31c94831c0ac41c1c90d4101c138e075f13e4c034c24084539d175d6583e448b + 40244901d0663e418b0c483e448b401c4901d03e418b04884801d0415841585e + 595a41584159415a4883ec204152ffe05841595a3e488b12e949ffffff5d49c7 + c1000000003e488d95fe0000003e4c8d850f0100004831c941ba45835607ffd5 + 4831c941baf0b5a256ffd548656c6c6f2c2066726f6d204d534621004d657373 + 616765426f7800 +''') + +ARM_LIN = bytes.fromhex(''' + 01108fe211ff2fe102200121921a0f02193701df061c08a11022023701df3f27 + 0221301c01df0139fbd505a0921a05b469460b2701dfc046020012340a000202 + 2f73797374656d2f62696e2f736800 +''') + +ARM64_LIN = bytes.fromhex(''' + 420002ca210080d2400080d2c81880d2010000d4e60300aa01020010020280d2 + 681980d2010000d4410080d2420002cae00306aa080380d2010000d4210400f1 + 65ffff54e0000010420002ca210001caa81b80d2010000d4020004d27f000001 + 2f62696e2f736800 +''') + +X8664_FBSD = bytes.fromhex(''' + 6a61586a025f6a015e990f054897baff02aaaa80f2ff524889e699046680c210 + 0f05046a0f05041e4831f6990f0548976a035852488d7424f080c2100f0548b8 + 523243427730637257488d3e48af74084831c048ffc00f055f4889d04889fe48 + ffceb05a0f0575f799043b48bb2f62696e2f2f73685253545f5257545e0f05 +''') + +X8664_MACOS = bytes.fromhex(''' + 4831f65648bf2f2f62696e2f7368574889e74831d24831c0b00248c1c828b03b + 0f05 +''') + if __name__ == "__main__": print("\nLinux ARM 64bit Shellcode") - ql = Qiling(code=ARM64_LIN, archtype="arm64", ostype="linux", verbose=QL_VERBOSE.DEBUG) + ql = Qiling(code=ARM64_LIN, archtype=QL_ARCH.ARM64, ostype=QL_OS.LINUX, verbose=QL_VERBOSE.DEBUG) ql.run() print("\nLinux ARM 32bit Shellcode") - ql = Qiling(code=ARM_LIN, archtype="arm", ostype="linux", verbose=QL_VERBOSE.DEBUG) + ql = Qiling(code=ARM_LIN, archtype=QL_ARCH.ARM, ostype=QL_OS.LINUX, verbose=QL_VERBOSE.DEBUG) ql.run() - print("\nLinux X86 32bit Shellcode") - ql = Qiling(code=X86_LIN, archtype="x86", ostype="linux", verbose=QL_VERBOSE.DEBUG) + print("\nLinux x86 32bit Shellcode") + ql = Qiling(code=X86_LIN, archtype=QL_ARCH.X86, ostype=QL_OS.LINUX, verbose=QL_VERBOSE.DEBUG) ql.run() print("\nLinux MIPS 32bit EL Shellcode") - ql = Qiling(code=MIPS32EL_LIN, archtype="mips", ostype="linux", verbose=QL_VERBOSE.DEBUG) + ql = Qiling(code=MIPS32EL_LIN, archtype=QL_ARCH.MIPS, ostype=QL_OS.LINUX, verbose=QL_VERBOSE.DEBUG) ql.run() - print("\nLinux X86 64bit Shellcode") - ql = Qiling(code=X8664_LIN, archtype="x8664", ostype="linux", verbose=QL_VERBOSE.DEBUG) + print("\nLinux x86-64 Shellcode") + ql = Qiling(code=X8664_LIN, archtype=QL_ARCH.X8664, ostype=QL_OS.LINUX, verbose=QL_VERBOSE.DEBUG) ql.run() - print("\nWindows X86 32bit Shellcode") - ql = Qiling(code=X86_WIN, archtype="x86", ostype="windows", rootfs="rootfs/x86_windows") + print("\nWindows x86 Shellcode") + ql = Qiling(code=X86_WIN, archtype=QL_ARCH.X86, ostype=QL_OS.WINDOWS, rootfs=r'rootfs/x86_windows') ql.run() - print("\nWindows X8664 64bit Shellcode") - ql = Qiling(code=X8664_WIN, archtype="x8664", ostype="windows", rootfs="rootfs/x8664_windows") + print("\nWindows x86-64 Shellcode") + ql = Qiling(code=X8664_WIN, archtype=QL_ARCH.X8664, ostype=QL_OS.WINDOWS, rootfs=r'rootfs/x8664_windows') ql.run() - print("\nFreeBSD X86 64bit Shellcode") - ql = Qiling(code=X8664_FBSD, archtype="x8664", ostype="freebsd", verbose=QL_VERBOSE.DEBUG) - ql.run() + # FIXME: freebsd sockets are currently broken. + # + # print("\nFreeBSD x86-64 Shellcode") + # ql = Qiling(code=X8664_FBSD, archtype=QL_ARCH.X8664, ostype=QL_OS.FREEBSD, verbose=QL_VERBOSE.DEBUG) + # ql.run() - print("\nmacos X86 64bit Shellcode") - ql = Qiling(code=X8664_MACOS, archtype="x8664", ostype="macos", verbose=QL_VERBOSE.DEBUG) - ql.run() + # FIXME: macos shellcode loader is currently broken + # + # print("\nMacOS x86-64 Shellcode") + # ql = Qiling(code=X8664_MACOS, archtype=QL_ARCH.X8664, ostype=QL_OS.MACOS, verbose=QL_VERBOSE.DEBUG) + # ql.run() diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index fba93a018..9a9e67694 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# +# # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # @@ -26,6 +26,7 @@ from qiling.os.linux.kernel_api.hook import * from qiling.os.linux.kernel_api.kernel_api import hook_sys_open, hook_sys_read, hook_sys_write + # auxiliary vector types # see: https://man7.org/linux/man-pages/man3/getauxval.3.html class AUXV(IntEnum): @@ -53,6 +54,7 @@ class AUXV(IntEnum): AT_HWCAP2 = 26 AT_EXECFN = 31 + # start area memory for API hooking # we will reserve 0x1000 bytes for this (which contains multiple slots of 4/8 bytes, each for one api) API_HOOK_MEM = 0x1000000 @@ -60,6 +62,7 @@ class AUXV(IntEnum): # memory for syscall table SYSCALL_MEM = API_HOOK_MEM + 0x1000 + class QlLoaderELF(QlLoader): def __init__(self, ql: Qiling): super().__init__(ql) @@ -77,16 +80,11 @@ def run(self): return - section = { - 32 : 'OS32', - 64 : 'OS64' - }[self.ql.arch.bits] - - self.profile = self.ql.os.profile[section] + self.profile = self.ql.os.profile[f'OS{self.ql.arch.bits}'] # setup program stack - stack_address = int(self.profile.get('stack_address'), 0) - stack_size = int(self.profile.get('stack_size'), 0) + stack_address = self.profile.getint('stack_address') + stack_size = self.profile.getint('stack_size') self.ql.mem.map(stack_address, stack_size, info='[stack]') self.path = self.ql.path @@ -110,7 +108,7 @@ def run(self): # is it a shared object? elif elftype == 'ET_DYN': - load_address = int(self.profile.get('load_address'), 0) + load_address = self.profile.getint('load_address') self.load_with_ld(elffile, stack_address + stack_size, load_address, self.argv, self.env) @@ -242,7 +240,7 @@ def load_elf_segments(elffile: ELFFile, load_address: int, info: str): # determine interpreter base address # 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 + interp_address = self.profile.getint('interp_address') if min_vaddr == 0 else 0 self.ql.log.debug(f'Interpreter addr: {interp_address:#x}') # load interpreter segments data to memory @@ -255,7 +253,7 @@ def load_elf_segments(elffile: ELFFile, load_address: int, info: str): entry_point = interp_address + interp['e_entry'] # set mmap addr - mmap_address = int(self.profile.get('mmap_address'), 0) + mmap_address = self.profile.getint('mmap_address') self.ql.log.debug(f'mmap_address is : {mmap_address:#x}') # set info to be used by gdb @@ -658,7 +656,7 @@ def get_elfdata_mapping(self, elffile: ELFFile) -> bytes: # pick up loadable sections for sec in elffile.iter_sections(): if sec['sh_flags'] & SH_FLAGS.SHF_ALLOC: - # pad aggregated elf data to the offset of the current section + # pad aggregated elf data to the offset of the current section elfdata_mapping.extend(b'\x00' * (sec['sh_offset'] - len(elfdata_mapping))) # aggregate section data diff --git a/qiling/os/linux/procfs.py b/qiling/os/linux/procfs.py index 2cc9af6b7..9fbf2a338 100644 --- a/qiling/os/linux/procfs.py +++ b/qiling/os/linux/procfs.py @@ -9,6 +9,16 @@ from qiling.os.memory import QlMemoryManager +class FsMappedStream(io.BytesIO): + """Wrap stream objects to make them look like a QlFsMappedObject. + """ + + def __init__(self, fname: str, *args) -> None: + super().__init__(*args) + + self.name = fname + + class QlProcFS: @staticmethod @@ -28,14 +38,14 @@ def self_auxv(os: 'QlOsLinux') -> QlFsMappedObject: auxv_data.extend(os.ql.mem.read(auxv_addr, nbytes)) auxv_addr += nbytes - return io.BytesIO(bytes(auxv_data)) + return FsMappedStream(r'/proc/self/auxv', auxv_data) @staticmethod def self_cmdline(os: 'QlOsLinux') -> QlFsMappedObject: entries = (arg.encode('utf-8') for arg in os.ql.argv) cmdline = b'\x00'.join(entries) + b'\x00' - return io.BytesIO(cmdline) + return FsMappedStream(r'/proc/self/cmdline', cmdline) @staticmethod def self_environ(os: 'QlOsLinux') -> QlFsMappedObject: @@ -48,14 +58,14 @@ def __to_bytes(s: AnyStr) -> bytes: entries = (b'='.join((__to_bytes(k), __to_bytes(v))) for k, v in os.ql.env.items()) environ = b'\x00'.join(entries) + b'\x00' - return io.BytesIO(environ) + return FsMappedStream(r'/proc/self/environ', environ) @staticmethod def self_exe(os: 'QlOsLinux') -> QlFsMappedObject: with open(os.ql.path, 'rb') as exefile: content = exefile.read() - return io.BytesIO(content) + return FsMappedStream(r'/proc/self/exe', content) @staticmethod def self_map(mem: 'QlMemoryManager') -> QlFsMappedObject: @@ -65,4 +75,4 @@ def self_map(mem: 'QlMemoryManager') -> QlFsMappedObject: for lbound, ubound, perms, label, container in mapinfo: content += f"{lbound:x}-{ubound:x}\t{perms}p\t0\t00:00\t0\t{container if container else label}\n".encode("utf-8") - return io.BytesIO(bytes(content)) + return FsMappedStream(r'/proc/self/map', content) diff --git a/qiling/os/linux/thread.py b/qiling/os/linux/thread.py index ffa0154c9..0c3864bfd 100644 --- a/qiling/os/linux/thread.py +++ b/qiling/os/linux/thread.py @@ -239,7 +239,7 @@ def _run(self): self.ql.log.debug(f"Scheduled from {hex(start_address)}.") try: # Known issue for timeout: https://github.com/unicorn-engine/unicorn/issues/1355 - self.ql.emu_start(start_address, self.exit_point, count=30000) + self.ql.emu_start(start_address, self.exit_point, count=31337) except UcError as e: self.ql.os.emu_error() self.ql.log.exception("") diff --git a/qiling/os/memory.py b/qiling/os/memory.py index 49b2bb5b3..a2f01a2f8 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 -# +# # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import os, re -from typing import Any, Callable, Iterator, List, Mapping, MutableSequence, Optional, Pattern, Sequence, Tuple, Union +import bisect +import os +import re +from typing import Any, Callable, Iterator, List, Mapping, Optional, Pattern, Sequence, Tuple, Union from unicorn import UC_PROT_NONE, UC_PROT_READ, UC_PROT_WRITE, UC_PROT_EXEC, UC_PROT_ALL @@ -17,6 +19,7 @@ MmioReadCallback = Callable[[Qiling, int, int], int] MmioWriteCallback = Callable[[Qiling, int, int, int], None] + class QlMemoryManager: """ some ideas and code from: @@ -25,13 +28,13 @@ class QlMemoryManager: def __init__(self, ql: Qiling): self.ql = ql - self.map_info: MutableSequence[MapInfoEntry] = [] + self.map_info: List[MapInfoEntry] = [] self.mmio_cbs = {} bit_stuff = { - 64 : (1 << 64) - 1, - 32 : (1 << 32) - 1, - 16 : (1 << 20) - 1 # 20bit address line + 64: (1 << 64) - 1, + 32: (1 << 32) - 1, + 16: (1 << 20) - 1 # 20bit address line } if ql.arch.bits not in bit_stuff: @@ -91,8 +94,7 @@ def add_mapinfo(self, mem_s: int, mem_e: int, mem_p: int, mem_info: str, is_mmio is_mmio: memory range is mmio """ - self.map_info.append((mem_s, mem_e, mem_p, mem_info, is_mmio)) - self.map_info = sorted(self.map_info, key=lambda tp: tp[0]) + bisect.insort(self.map_info, (mem_s, mem_e, mem_p, mem_info, is_mmio)) def del_mapinfo(self, mem_s: int, mem_e: int): """Subtract a memory range from map. @@ -102,30 +104,33 @@ def del_mapinfo(self, mem_s: int, mem_e: int): mem_e: memory range end """ - tmp_map_info: MutableSequence[MapInfoEntry] = [] + overlap_ranges = [idx for idx, (lbound, ubound, _, _, _) in enumerate(self.map_info) if (mem_s < ubound) and (mem_e > lbound)] - for s, e, p, info, mmio in self.map_info: - if e <= mem_s: - tmp_map_info.append((s, e, p, info, mmio)) - continue + def __split_overlaps(): + for idx in overlap_ranges: + lbound, ubound, perms, label, is_mmio = self.map_info[idx] - if s >= mem_e: - tmp_map_info.append((s, e, p, info, mmio)) - continue + if lbound < mem_s: + yield (lbound, mem_s, perms, label, is_mmio) - if s < mem_s: - tmp_map_info.append((s, mem_s, p, info, mmio)) + if mem_e < ubound: + yield (mem_e, ubound, perms, label, is_mmio) - if s == mem_s: - pass + # indices of first and last overlapping ranges. since map info is always + # sorted, we know that all overlapping rages are consecutive, so i1 > i0 + i0 = overlap_ranges[0] + i1 = overlap_ranges[-1] - if e > mem_e: - tmp_map_info.append((mem_e, e, p, info, mmio)) + # create new entries by splitting overlapping ranges. + # this has to be done before removing overlapping entries + new_entries = tuple(__split_overlaps()) - if e == mem_e: - pass + # remove overlapping entries + del self.map_info[i0:i1 + 1] - self.map_info = tmp_map_info + # add new ones + for entry in new_entries: + bisect.insort(self.map_info, entry) def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: Optional[int] = None, mem_info: Optional[str] = None): tmp_map_info: Optional[MapInfoEntry] = None @@ -295,8 +300,9 @@ def restore(self, mem_dict): for lbound, ubound, perms, label, read_cb, write_cb in mem_dict['mmio']: self.ql.log.debug(f"restoring mmio range: {lbound:#08x} {ubound:#08x} {label}") + size = ubound - lbound if not self.is_mapped(lbound, size): - self.map_mmio(lbound, ubound - lbound, read_cb, write_cb, info=label) + self.map_mmio(lbound, size, read_cb, write_cb, info=label) def read(self, addr: int, size: int) -> bytearray: """Read bytes from memory. @@ -325,10 +331,10 @@ def read_ptr(self, addr: int, size: int = 0) -> int: size = self.ql.arch.pointersize __unpack = { - 1 : self.ql.unpack8, - 2 : self.ql.unpack16, - 4 : self.ql.unpack32, - 8 : self.ql.unpack64 + 1: self.ql.unpack8, + 2: self.ql.unpack16, + 4: self.ql.unpack32, + 8: self.ql.unpack64 }.get(size) if __unpack is None: @@ -360,10 +366,10 @@ def write_ptr(self, addr: int, value: int, size: int = 0) -> None: size = self.ql.arch.pointersize __pack = { - 1 : self.ql.pack8, - 2 : self.ql.pack16, - 4 : self.ql.pack32, - 8 : self.ql.pack64 + 1: self.ql.pack8, + 2: self.ql.pack16, + 4: self.ql.pack32, + 8: self.ql.pack64 }.get(size) if __pack is None: @@ -422,6 +428,24 @@ def unmap(self, addr: int, size: int) -> None: if (addr, addr + size) in self.mmio_cbs: del self.mmio_cbs[(addr, addr+size)] + def unmap_between(self, mem_s: int, mem_e: int) -> None: + """Reclaim any allocated memory region within the specified range. + + Args: + mem_s: range start + mem_s: range end (exclusive) + """ + + # map info is about to change during the unmapping loop, so we have to + # determine the relevant ranges beforehand + mapped = [(lbound, ubound) for lbound, ubound, _, _, _ in self.map_info if (mem_s < ubound) and (mem_e > lbound)] + + for lbound, ubound in mapped: + lbound = max(mem_s, lbound) + ubound = min(mem_e, ubound) + + self.unmap(lbound, ubound - lbound) + def unmap_all(self) -> None: """Reclaim the entire memory space. """ @@ -513,13 +537,16 @@ def find_free_space(self, size: int, minaddr: Optional[int] = None, maxaddr: Opt assert minaddr < maxaddr + if (maxaddr - minaddr) < size: + raise ValueError('search domain is too small') + # get gap ranges between mapped ones and memory bounds gaps_ubounds = tuple(lbound for lbound, _, _, _, _ in self.map_info) + (mem_ubound,) gaps_lbounds = (mem_lbound,) + tuple(ubound for _, ubound, _, _, _ in self.map_info) - gaps = zip(gaps_lbounds, gaps_ubounds) + gaps = ((lbound, ubound) for lbound, ubound in zip(gaps_lbounds, gaps_ubounds) if lbound < maxaddr and minaddr < ubound) for lbound, ubound in gaps: - addr = self.align_up(lbound, align) + addr = self.align_up(max(minaddr, lbound), align) end = addr + size # is aligned range within gap and satisfying min / max requirements? diff --git a/qiling/os/posix/const.py b/qiling/os/posix/const.py index 4aea9b76e..49aa56cd5 100644 --- a/qiling/os/posix/const.py +++ b/qiling/os/posix/const.py @@ -3,6 +3,8 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from enum import Flag + # OS Threading Constants THREAD_EVENT_INIT_VAL = 0 THREAD_EVENT_EXIT_EVENT = 1 @@ -622,6 +624,104 @@ 'O_BINARY' : None } + +# see: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/mman-common.h +class linux_mmap_flags(Flag): + MAP_FILE = 0x00000000 + MAP_SHARED = 0x00000001 + MAP_PRIVATE = 0x00000002 + + MAP_FIXED = 0x00000010 + MAP_ANONYMOUS = 0x00000020 + MAP_GROWSDOWN = 0x00000100 + MAP_DENYWRITE = 0x00000800 + MAP_EXECUTABLE = 0x00001000 + MAP_LOCKED = 0x00002000 + MAP_NORESERVE = 0x00004000 + MAP_POPULATE = 0x00008000 + MAP_NONBLOCK = 0x00010000 + MAP_STACK = 0x00020000 + MAP_HUGETLB = 0x00040000 + MAP_SYNC = 0x00080000 + MAP_FIXED_NOREPLACE = 0x00100000 + MAP_UNINITIALIZED = 0x04000000 + + +# see: https://github.com/freebsd/freebsd-src/blob/master/sys/sys/mman.h +class freebsd_mmap_flags(Flag): + MAP_FILE = 0x00000000 + MAP_SHARED = 0x00000001 + MAP_PRIVATE = 0x00000002 + + MAP_FIXED = 0x00000010 + MAP_STACK = 0x00000400 + MAP_NOSYNC = 0x00000800 + MAP_ANONYMOUS = 0x00001000 + MAP_GUARD = 0x00002000 + MAP_EXCL = 0x00004000 + MAP_NOCORE = 0x00020000 + + # define this alias for compatibility with other os flags + MAP_FIXED_NOREPLACE = MAP_EXCL + +# see: https://github.com/torvalds/linux/blob/master/arch/mips/include/uapi/asm/mman.h +class mips_mmap_flags(Flag): + MAP_FILE = 0x00000000 + MAP_SHARED = 0x00000001 + MAP_PRIVATE = 0x00000002 + + MAP_FIXED = 0x00000010 + MAP_NORESERVE = 0x00000400 + MAP_ANONYMOUS = 0x00000800 + MAP_GROWSDOWN = 0x00001000 + MAP_DENYWRITE = 0x00002000 + MAP_EXECUTABLE = 0x00004000 + MAP_LOCKED = 0x00008000 + MAP_POPULATE = 0x00010000 + MAP_NONBLOCK = 0x00020000 + MAP_STACK = 0x00040000 + MAP_HUGETLB = 0x00080000 + MAP_FIXED_NOREPLACE = 0x00100000 + + +# see: https://github.com/apple/darwin-xnu/blob/main/bsd/sys/mman.h +class macos_mmap_flags(Flag): + MAP_FILE = 0x00000000 + MAP_SHARED = 0x00000001 + MAP_PRIVATE = 0x00000002 + + MAP_FIXED = 0x00000010 + MAP_RENAME = 0x00000020 + MAP_NORESERVE = 0x00000040 + MAP_NOEXTEND = 0x00000100 + MAP_HASSEMAPHORE = 0x00000200 + MAP_NOCACHE = 0x00000400 + MAP_JIT = 0x00000800 + MAP_ANONYMOUS = 0x00001000 + + +# see: https://github.com/vocho/openqnx/blob/master/trunk/lib/c/public/sys/mman.h +class qnx_mmap_flags(Flag): + MAP_FILE = 0x00000000 + MAP_SHARED = 0x00000001 + MAP_PRIVATE = 0x00000002 + + MAP_FIXED = 0x00000010 + MAP_ELF = 0x00000020 + MAP_NOSYNCFILE = 0x00000040 + MAP_LAZY = 0x00000080 + MAP_STACK = 0x00001000 + MAP_BELOW = 0x00002000 + MAP_NOINIT = 0x00004000 + MAP_PHYS = 0x00010000 + MAP_NOX64K = 0x00020000 + MAP_BELOW16M = 0x00040000 + MAP_ANONYMOUS = 0x00080000 + MAP_SYSRAM = 0x01000000 + + # define this alias for compatibility with other os flags + MAP_UNINITIALIZED = MAP_NOINIT + # fcntl flags F_DUPFD = 0 F_GETFD = 1 diff --git a/qiling/os/posix/structs.py b/qiling/os/posix/structs.py index 619d8f413..71851af40 100644 --- a/qiling/os/posix/structs.py +++ b/qiling/os/posix/structs.py @@ -9,6 +9,22 @@ from qiling.os import struct +# FIXME: freebsd socket structures differ from the unix ones by specifying the +# sa_len and sa_family fields, one byte each, instead of using one short int +# for sa_family. +# +# using the sturcutres as they defined here causes freebsd socket structures to +# show high (hence unrecognized) values for sa_family. messing all sturctures +# with "if ql.os.type == QL_OS.FREEBSD" is a cumbersome workaround and not +# maintainable, let alone the code should also refer to sa_len and populate it +# appropriately. +# +# unfortunately, until there is an elegant implemetation that takes freebsd +# sockets into account freebsd sockets are broken. +# +# for more details: https://docs.freebsd.org/en/books/developers-handbook/sockets/ + + def make_sockaddr(archbits: int, endian: QL_ENDIAN): Struct = struct.get_aligned_struct(archbits, endian) diff --git a/qiling/os/posix/syscall/mman.py b/qiling/os/posix/syscall/mman.py index 4c9a4edcc..6d80a74e1 100755 --- a/qiling/os/posix/syscall/mman.py +++ b/qiling/os/posix/syscall/mman.py @@ -4,31 +4,50 @@ # import os +import re +from enum import IntFlag +from typing import Optional from qiling import Qiling +from qiling.const import QL_ARCH, QL_OS from qiling.exception import QlMemoryMappedError from qiling.os.filestruct import ql_file from qiling.os.posix.const_mapping import * def ql_syscall_munmap(ql: Qiling, addr: int, length: int): + try: + # find addr's enclosing memory range + label = next(label for lbound, ubound, _, label, _ in ql.mem.get_mapinfo() if (lbound <= addr < ubound) and label.startswith(('[mmap]', '[mmap anonymous]'))) + except StopIteration: + # nothing to do; cannot munmap what was not originally mmapped + ql.log.debug(f'munmap: enclosing area for {addr:#x} was not mmapped') + else: + # extract the filename from the label by removing the boxed prefix + fname = re.sub(r'^\[.+\]\s*', '', label) - # get all mapped fd with flag MAP_SHARED and we definitely dont want to wipe out share library - mapped_fd = [fd for fd in ql.os.fd if isinstance(fd, ql_file) and fd._is_map_shared and not (fd.name.endswith(".so") or fd.name.endswith(".dylib"))] - - if mapped_fd: - all_mem_info = [_mem_info for _, _, _, _mem_info, _ in ql.mem.map_info if _mem_info not in ("[mapped]", "[stack]", "[hook_mem]")] - - for _fd in mapped_fd: - if _fd.name in [each.split()[-1] for each in all_mem_info]: - ql.log.debug("Flushing file: %s" % _fd.name) - # flushes changes to disk file - _buff = ql.mem.read(addr, length) - _fd.lseek(_fd._mapped_offset) - _fd.write(_buff) - - length = ql.mem.align_up(length) - ql.mem.unmap(addr, length) + # if this is an anonymous mapping, there is no trailing file name + if fname: + try: + # find the file that was originally mmapped into this region, to flush the changes. + # if the fd was already closed, there is nothing left to do + fd = next(fd for fd in ql.os.fd if isinstance(fd, ql_file) and os.path.basename(fd.name) == fname) + except StopIteration: + ql.log.debug(f'munmap: could not find matching fd, it might have been closed') + else: + # flushing memory contents to file is required only if mapping is shared / not private + if fd._is_map_shared: + ql.log.debug(f'munmap: flushing "{fname}"') + content = ql.mem.read(addr, length) + + fd.lseek(fd._mapped_offset) + fd.write(content) + + # unmap the enclosing memory region + lbound = ql.mem.align(addr) + ubound = ql.mem.align_up(addr + length) + + ql.mem.unmap(lbound, ubound - lbound) return 0 @@ -53,97 +72,135 @@ def ql_syscall_mprotect(ql: Qiling, start: int, mlen: int, prot: int): return 0 -def syscall_mmap_impl(ql: Qiling, addr: int, mlen: int, prot: int, flags: int, fd: int, pgoffset: int, ver: int): - MAP_FAILED = -1 - MAP_SHARED = 0x01 - MAP_FIXED = 0x10 - MAP_ANONYMOUS = 0x20 +def syscall_mmap_impl(ql: Qiling, addr: int, length: int, prot: int, flags: int, fd: int, pgoffset: int, ver: int): + def __select_mmap_flags(archtype: QL_ARCH, ostype: QL_OS): + """The mmap flags definitions differ between operating systems and architectures. + This method provides the apropriate flags set based on those properties. + """ + + osflags = { + QL_OS.LINUX: mips_mmap_flags if archtype == QL_ARCH.MIPS else linux_mmap_flags, + QL_OS.FREEBSD: freebsd_mmap_flags, + QL_OS.MACOS: macos_mmap_flags, + QL_OS.QNX: qnx_mmap_flags # FIXME: only for arm? + }[ostype] + + class mmap_flags(IntFlag): + MAP_FILE = osflags.MAP_FILE.value + MAP_SHARED = osflags.MAP_SHARED.value + MAP_FIXED = osflags.MAP_FIXED.value + MAP_ANONYMOUS = osflags.MAP_ANONYMOUS.value + + # the following flags do not exist on all flags sets. + # if not exists, set it to 0 so it would fail all bitwise-and checks + MAP_FIXED_NOREPLACE = osflags.MAP_FIXED_NOREPLACE.value if hasattr(osflags, 'MAP_FIXED_NOREPLACE') else 0 + MAP_UNINITIALIZED = osflags.MAP_UNINITIALIZED.value if hasattr(osflags, 'MAP_UNINITIALIZED') else 0 + + return mmap_flags + + api_name = ('old_mmap', 'mmap', 'mmap2')[ver] + mmap_flags = __select_mmap_flags(ql.arch.type, ql.os.type) pagesize = ql.mem.pagesize - api_name = { - 0 : 'old_mmap', - 1 : 'mmap', - 2 : 'mmap2' - }[ver] + mapping_size = ql.mem.align_up(length + (addr & (pagesize - 1))) + + ################################ + # determine mapping boundaries # + ################################ + + # as opposed to other systems, QNX allows specifying an unaligned base address + # for fixed mappings. to keep it consistent across all systems we allocate the + # enclosing pages of the requested area, while returning the requested fixed + # base address. + # + # to help track this, we use the following variables: + # addr : the address that becomes available, from program point of view + # lbound : lower bound of actual mapping range (aligned to page) + # ubound : upper bound of actual mapping range (aligned to page) + # mapping_size : actual mapping range size + # + # note that on non-QNX systems addr and lbound are equal. + # + # for example, assume requested base address and length are 0x774ec8d8 and 0x1800 + # respectively, then we allocate 3 pages as follows: + # [align(0x7700c8d8), align_up(0x7700c8d8 + 0x1800)] -> [0x7700c000, 0x7700f000] + + # if mapping is fixed, use the base address as-is + if flags & (mmap_flags.MAP_FIXED | mmap_flags.MAP_FIXED_NOREPLACE): + # on non-QNX systems, base must be aligned to page boundary + if addr & (pagesize - 1) and ql.os.type != QL_OS.QNX: + return -1 # errno: EINVAL + + # if not, use the base address as a hint but always above or equal to + # the value specified in /proc/sys/vm/mmap_min_addr (here: mmap_address) + else: + addr = ql.mem.find_free_space(mapping_size, max(addr, ql.loader.mmap_address)) + + # on non-QNX systems addr is already aligned to page boundary + lbound = ql.mem.align(addr) + ubound = lbound + mapping_size + + ################################## + # make sure memory can be mapped # + ################################## - if ql.arch.bits == 64: - fd = ql.unpack64(ql.pack64(fd)) + if flags & mmap_flags.MAP_FIXED_NOREPLACE: + if not ql.mem.is_available(lbound, mapping_size): + return -1 # errno: EEXIST - elif ql.arch.type == QL_ARCH.MIPS: - MAP_ANONYMOUS = 2048 - if ver == 2: - pgoffset = pgoffset * pagesize + elif flags & mmap_flags.MAP_FIXED: + ql.log.debug(f'{api_name}: unmapping memory between {lbound:#x}-{ubound:#x} to make room for fixed mapping') + ql.mem.unmap_between(lbound, ubound) - elif ql.arch.type == QL_ARCH.ARM and ql.os.type == QL_OS.QNX: - MAP_ANONYMOUS = 0x00080000 - fd = ql.unpack32s(ql.pack32s(fd)) + ############################# + # determine mapping content # + ############################# + + if flags & mmap_flags.MAP_ANONYMOUS: + data = b'' if flags & mmap_flags.MAP_UNINITIALIZED else b'\x00' * length + label = '[mmap anonymous]' else: - fd = ql.unpack32s(ql.pack32(fd)) - if ver == 2: - pgoffset = pgoffset * pagesize + fd = ql.unpacks(ql.pack(fd)) - need_mmap = True - mmap_base = addr - mmap_size = ql.mem.align_up(mlen - (addr & (pagesize - 1))) + if fd not in range(NR_OPEN): + return -1 # errno: EBADF - if ql.os.type != QL_OS.QNX: - mmap_base = ql.mem.align(mmap_base) + f: Optional[ql_file] = ql.os.fd[fd] - if (flags & MAP_FIXED) and mmap_base != addr: - return MAP_FAILED + if f is None: + return -1 # errno: EBADF - # initial ql.loader.mmap_address - if mmap_base != 0 and ql.mem.is_mapped(mmap_base, mmap_size): - if (flags & MAP_FIXED) > 0: - ql.log.debug("%s - MAP_FIXED, mapping not needed" % api_name) - try: - ql.mem.protect(mmap_base, mmap_size, prot) - except Exception as e: - ql.log.debug(e) - raise QlMemoryMappedError("Error: change protection at: 0x%x - 0x%x" % (mmap_base, mmap_base + mmap_size - 1)) - need_mmap = False - else: - mmap_base = 0 - - # initialized mapping - if need_mmap: - if mmap_base == 0: - mmap_base = ql.loader.mmap_address - ql.loader.mmap_address = mmap_base + mmap_size - ql.log.debug("%s - mapping needed for 0x%x" % (api_name, mmap_base)) - try: - ql.mem.map(mmap_base, mmap_size, prot, "[syscall_%s]" % api_name) - except Exception as e: - raise QlMemoryMappedError("Error: mapping needed but failed") - ql.log.debug("%s - addr range 0x%x - 0x%x: " % (api_name, mmap_base, mmap_base + mmap_size - 1)) - - # FIXME: MIPS32 Big Endian - try: - ql.mem.write(mmap_base, b'\x00' * mmap_size) - except Exception as e: - raise QlMemoryMappedError("Error: trying to zero memory") + # set mapping properties for future unmap + f._is_map_shared = bool(flags & mmap_flags.MAP_SHARED) + f._mapped_offset = pgoffset - if ((flags & MAP_ANONYMOUS) == 0) and fd in range(NR_OPEN): - f = ql.os.fd[fd] + fname = f.name - if f is not None: - f.seek(pgoffset) - data = f.read(mlen) - mem_info = str(f.name) - f._is_map_shared = flags & MAP_SHARED - f._mapped_offset = pgoffset - ql.log.debug("mem write : " + hex(len(data))) - ql.log.debug("mem mmap : " + mem_info) + if isinstance(fname, bytes): + fname = fname.decode() - ql.mem.change_mapinfo(mmap_base, mmap_base + mmap_size, mem_info=f'[{api_name}] {os.path.basename(mem_info)}') - try: - ql.mem.write(mmap_base, data) - except Exception as e: - ql.log.debug(e) - raise QlMemoryMappedError("Error: trying to write memory: ") + f.seek(pgoffset) - return mmap_base + data = f.read(length) + label = f'[mmap] {os.path.basename(fname)}' + + try: + # finally, we have everything we need to map the memory. + # + # we have to map it first as writeable so we can write data in it. + # permissions are adjusted afterwards with protect. + ql.mem.map(lbound, mapping_size, info=label) + except QlMemoryMappedError: + ql.log.debug(f'{api_name}: out of memory') + return -1 # errono: ENOMEM + else: + if data: + ql.mem.write(addr, data) + + ql.mem.protect(lbound, mapping_size, prot) + + return addr def ql_syscall_old_mmap(ql: Qiling, struct_mmap_args: int): @@ -164,6 +221,9 @@ def ql_syscall_mmap(ql: Qiling, addr: int, length: int, prot: int, flags: int, f def ql_syscall_mmap2(ql: Qiling, addr: int, length: int, prot: int, flags: int, fd: int, pgoffset: int): + if ql.os.type != QL_OS.QNX: + pgoffset *= ql.mem.pagesize + return syscall_mmap_impl(ql, addr, length, prot, flags, fd, pgoffset, 2) diff --git a/qiling/os/posix/syscall/net.py b/qiling/os/posix/syscall/net.py index fe9dd89a4..3576f900a 100644 --- a/qiling/os/posix/syscall/net.py +++ b/qiling/os/posix/syscall/net.py @@ -44,6 +44,7 @@ def ql_syscall_socketcall(ql: Qiling, call: int, args: int): SOCKETCALL.SYS_ACCEPT: ql_syscall_accept, SOCKETCALL.SYS_GETSOCKNAME: ql_syscall_getsockname, SOCKETCALL.SYS_GETPEERNAME: ql_syscall_getpeername, + SOCKETCALL.SYS_SOCKETPAIR: ql_syscall_socketpair, SOCKETCALL.SYS_SEND: ql_syscall_send, SOCKETCALL.SYS_RECV: ql_syscall_recv, SOCKETCALL.SYS_SENDTO: ql_syscall_sendto, diff --git a/qiling/os/posix/syscall/socket.py b/qiling/os/posix/syscall/socket.py index 862222813..4fd56dafb 100644 --- a/qiling/os/posix/syscall/socket.py +++ b/qiling/os/posix/syscall/socket.py @@ -8,7 +8,7 @@ from typing import Optional, Tuple from qiling import Qiling -from qiling.const import QL_ARCH, QL_VERBOSE +from qiling.const import QL_ARCH, QL_OS, QL_VERBOSE from qiling.os.posix.const_mapping import socket_type_mapping, socket_level_mapping, socket_domain_mapping, socket_ip_option_mapping, socket_tcp_option_mapping, socket_option_mapping from qiling.os.posix.const import * from qiling.os.posix.filestruct import ql_socket @@ -84,26 +84,79 @@ def ql_unix_socket_path(ql: Qiling, sun_path: bytearray) -> Tuple[str, str]: return (hpath, vpath) +def __host_socket_type(vsock_type: int, arch_type: QL_ARCH) -> int: + """Convert emulated socket type value to a host socket type. + """ + + try: + vsock_type_name = socket_type_mapping(vsock_type, arch_type) + except KeyError: + raise NotImplementedError(f'Could not convert emulated socket type {vsock_type} to a socket type name') + + try: + hsock_type = getattr(socket, vsock_type_name) + except AttributeError: + raise NotImplementedError(f'Could not convert emulated socket type name {vsock_type_name} to a host socket type') + + return hsock_type + + +def __host_socket_level(vsock_level: int, arch_type: QL_ARCH) -> int: + """Convert emulated socket level value to a host socket level. + """ + + try: + vsock_level_name = socket_level_mapping(vsock_level, arch_type) + except KeyError: + raise NotImplementedError(f'Could not convert emulated socket level {vsock_level} to a socket level name') + + try: + hsock_level = getattr(socket, vsock_level_name) + except AttributeError: + raise NotImplementedError(f'Could not convert emulated socket level name {vsock_level_name} to a host socket level') + + return hsock_level + + +def __host_socket_option(vsock_level: int, vsock_opt: int, arch_type: QL_ARCH, os_type: QL_OS) -> int: + """Convert emulated socket option value to a host socket option. + """ + + try: + if vsock_level == 0x0000: # IPPROTO_IP + vsock_opt_name = socket_ip_option_mapping(vsock_opt, arch_type, os_type) + + elif vsock_level == 0x0006: # IPPROTO_TCP + vsock_opt_name = socket_tcp_option_mapping(vsock_opt, arch_type) + + else: + vsock_opt_name = socket_option_mapping(vsock_opt, arch_type) + + # Fix for mips + if arch_type == QL_ARCH.MIPS: + if vsock_opt_name.endswith(('_NEW', '_OLD')): + vsock_opt_name = vsock_opt_name[:-4] + + except KeyError: + raise NotImplementedError(f'Could not convert emulated socket option {vsock_opt} to a socket option name') + + try: + hsock_opt = getattr(socket, vsock_opt_name) + except AttributeError: + raise NotImplementedError(f'Could not convert emulated socket option name {vsock_opt_name} to a host socket option') + + return hsock_opt + + def ql_syscall_socket(ql: Qiling, domain: int, socktype: int, protocol: int): idx = next((i for i in range(NR_OPEN) if ql.os.fd[i] is None), -1) if idx != -1: + # ql_socket.open should use host platform based socket type vsock_type = socktype + hsock_type = __host_socket_type(vsock_type, ql.arch.type) - # ql_socket.open should use host platform based socket_type. - try: - vsock_type_name = socket_type_mapping(vsock_type, ql.arch.type) - except KeyError: - ql.log.error(f'Could not convert emulated socket type {vsock_type} to a socket type name') - raise - - try: - hsock_type = getattr(socket, vsock_type_name) - except AttributeError: - ql.log.error(f'Could not convert emulated socket type name {vsock_type_name} to host socket type') - raise - - ql.log.debug(f'Converted emulated socket type {vsock_type_name} to host socket type {hsock_type}') + ql.log.debug(f'Converted emulated socket type {vsock_type} to host socket type {hsock_type}') try: sock = ql_socket.open(domain, hsock_type, protocol) @@ -119,55 +172,49 @@ def ql_syscall_socket(ql: Qiling, domain: int, socktype: int, protocol: int): else: ql.os.fd[idx] = sock - s_socktype = socket_type_mapping(socktype, ql.arch.type) s_domain = socket_domain_mapping(domain, ql.arch.type, ql.os.type) + s_socktype = socket_type_mapping(socktype, ql.arch.type) ql.log.debug("socket(%s, %s, %s) = %d" % (s_domain, s_socktype, protocol, idx)) return idx -def ql_syscall_socketpair(ql: Qiling, socket_domain, socket_type, socket_protocol, sv: int): - idx_list = [i for i in range(NR_OPEN) if ql.os.fd[i] is None] - if len(idx_list) > 1: - idx1, idx2 = idx_list[:2] +def ql_syscall_socketpair(ql: Qiling, domain: int, socktype: int, protocol: int, sv: int): + unpopulated_fd = (i for i in range(NR_OPEN) if ql.os.fd[i] is None) - emu_socket_value = socket_type + idx1 = next(unpopulated_fd, -1) + idx2 = next(unpopulated_fd, -1) - # ql_socket.open should use host platform based socket_type. - try: - emu_socket_type = socket_type_mapping(socket_type, ql.arch.type) - except KeyError: - ql.log.error(f'Cannot convert emu_socket_type {emu_socket_value} to host platform based socket_type') - raise + regreturn = -1 - try: - socket_type = getattr(socket, emu_socket_type) - except AttributeError: - ql.log.error(f'Cannot convert emu_socket_type {emu_socket_type}:{emu_socket_value} to host platform based socket_type') - raise + if (idx1 != -1) and (idx2 != -1): + # ql_socket.socketpair should use host platform based socket type + vsock_type = socktype + hsock_type = __host_socket_type(vsock_type, ql.arch.type) - ql.log.debug(f'Convert emu_socket_type {emu_socket_type}:{emu_socket_value} to host platform based socket_type {emu_socket_type}:{socket_type}') + ql.log.debug(f'Converted emulated socket type {vsock_type} to host socket type {hsock_type}') try: - sock1, sock2 = ql_socket.socketpair(socket_domain, socket_type, socket_protocol) + sock1, sock2 = ql_socket.socketpair(domain, hsock_type, protocol) - # save sock to ql + # May raise error: Protocol not supported + except OSError as e: + ql.log.debug(f'{e}: {domain=}, {socktype=}, {protocol=}, {sv=}') + regreturn = -1 + + else: ql.os.fd[idx1] = sock1 ql.os.fd[idx2] = sock2 # save fd to &sv - ql.mem.write(sv, ql.pack32(idx1)) - ql.mem.write(sv+4, ql.pack32(idx2)) - regreturn = 0 + ql.mem.write_ptr(sv + 0, idx1) + ql.mem.write_ptr(sv + 4, idx2) - # May raise error: Protocol not supported - except OSError as e: - ql.log.debug(f'{e}: {socket_domain=}, {socket_type=}, {socket_protocol=}, {sv=}') - regreturn = -1 + regreturn = 0 - socket_type = socket_type_mapping(socket_type, ql.arch.type) - socket_domain = socket_domain_mapping(socket_domain, ql.arch.type, ql.os.type) - ql.log.debug("socketpair(%s, %s, %s, %d) = %d" % (socket_domain, socket_type, socket_protocol, sv, regreturn)) + s_domain = socket_domain_mapping(domain, ql.arch.type, ql.os.type) + s_type = socket_type_mapping(socktype, ql.arch.type) + ql.log.debug("socketpair(%s, %s, %d, %d) = %d" % (s_domain, s_type, protocol, sv, regreturn)) return regreturn @@ -243,48 +290,14 @@ def ql_syscall_getsockopt(ql: Qiling, sockfd: int, level: int, optname: int, opt return -1 vsock_level = level + hsock_level = __host_socket_level(vsock_level, ql.arch.type) - try: - vsock_level_name = socket_level_mapping(vsock_level, ql.arch.type) - except KeyError: - ql.log.error(f'Could not convert emulated socket level {vsock_level} to a socket level name') - raise - - try: - hsock_level = getattr(socket, vsock_level_name) - except AttributeError: - ql.log.error(f'Could not convert emulated socket level name {vsock_level_name} to host socket level') - raise - - ql.log.debug(f'Converted emulated socket level {vsock_level_name} to host socket level {hsock_level}') + ql.log.debug(f'Converted emulated socket level {vsock_level} to host socket level {hsock_level}') vsock_opt = optname + hsock_opt = __host_socket_option(vsock_level, vsock_opt, ql.arch.type, ql.os.type) - try: - # emu_opt_name is based on level - if vsock_level_name == 'IPPROTO_IP': - vsock_opt_name = socket_ip_option_mapping(vsock_opt, ql.arch.type, ql.os.type) - elif vsock_level_name == 'IPPROTO_TCP': - vsock_opt_name = socket_tcp_option_mapping(vsock_opt, ql.arch.type) - else: - vsock_opt_name = socket_option_mapping(vsock_opt, ql.arch.type) - - # Fix for mips - if ql.arch.type == QL_ARCH.MIPS: - if vsock_opt_name.endswith("_NEW") or vsock_opt_name.endswith("_OLD"): - vsock_opt_name = vsock_opt_name[:-4] - - except KeyError: - ql.log.error(f'Could not convert emulated socket option {vsock_opt} to socket option name') - raise - - try: - hsock_opt = getattr(socket, vsock_opt_name) - except AttributeError: - ql.log.error(f'Could not convert emulated socket option name {vsock_opt_name} to host socket option') - raise - - ql.log.debug(f'Converted emulated socket option {vsock_opt_name} to host socket option {hsock_opt}') + ql.log.debug(f'Converted emulated socket option {vsock_opt} to host socket option {hsock_opt}') optlen = min(ql.unpack32s(ql.mem.read(optlen_addr, 4)), 1024) @@ -315,48 +328,14 @@ def ql_syscall_setsockopt(ql: Qiling, sockfd: int, level: int, optname: int, opt else: vsock_level = level + hsock_level = __host_socket_level(vsock_level, ql.arch.type) - try: - vsock_level_name = socket_level_mapping(vsock_level, ql.arch.type) - except KeyError: - ql.log.error(f'Could not convert emulated socket level {vsock_level} to a socket level name') - raise - - try: - hsock_level = getattr(socket, vsock_level_name) - except AttributeError: - ql.log.error(f'Could not convert emulated socket level name {vsock_level_name} to host socket level') - raise - - ql.log.debug(f'Converted emulated socket level {vsock_level_name} to host socket level {hsock_level}') + ql.log.debug(f'Converted emulated socket level {vsock_level} to host socket level {hsock_level}') vsock_opt = optname + hsock_opt = __host_socket_option(vsock_level, vsock_opt, ql.arch.type, ql.os.type) - try: - # emu_opt_name is based on level - if vsock_level_name == 'IPPROTO_IP': - vsock_opt_name = socket_ip_option_mapping(vsock_opt, ql.arch.type, ql.os.type) - elif vsock_level_name == 'IPPROTO_TCP': - vsock_opt_name = socket_tcp_option_mapping(vsock_opt, ql.arch.type) - else: - vsock_opt_name = socket_option_mapping(vsock_opt, ql.arch.type) - - # Fix for mips - if ql.arch.type == QL_ARCH.MIPS: - if vsock_opt_name.endswith("_NEW") or vsock_opt_name.endswith("_OLD"): - vsock_opt_name = vsock_opt_name[:-4] - - except KeyError: - ql.log.error(f'Could not convert emulated socket option {vsock_opt} to socket option name') - raise - - try: - hsock_opt = getattr(socket, vsock_opt_name) - except AttributeError: - ql.log.error(f'Could not convert emulated socket option name {vsock_opt_name} to host socket option') - raise - - ql.log.debug(f'Converted emulated socket option {vsock_opt_name} to host socket option {hsock_opt}') + ql.log.debug(f'Converted emulated socket option {vsock_opt} to host socket option {hsock_opt}') optval = ql.mem.read(optval_addr, optlen) diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index 6ce90c2de..b52f5e4b7 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -62,45 +62,76 @@ def ql_syscall_issetugid(ql: Qiling): return 0 -def ql_syscall_getuid(ql: Qiling): +def __getuid(ql: Qiling): return ql.os.uid -def ql_syscall_getuid32(ql: Qiling): +def __setuid(ql: Qiling, uid: int): + # TODO: security checks + ql.os.uid = uid + return 0 -def ql_syscall_getgid32(ql: Qiling): +def __getgid(ql: Qiling): + return ql.os.gid + + +def __setgid(ql: Qiling, gid: int): + # TODO: security checks + ql.os.gid = gid + return 0 -def ql_syscall_geteuid(ql: Qiling): - return ql.os.euid +def ql_syscall_getuid(ql: Qiling): + return __getuid(ql) -def ql_syscall_getegid(ql: Qiling): - return ql.os.egid +def ql_syscall_setuid(ql: Qiling, uid: int): + return __setuid(ql, uid) + + +def ql_syscall_getuid32(ql: Qiling): + return __getuid(ql) + + +def ql_syscall_setuid32(ql: Qiling, uid: int): + return __setuid(ql, uid) def ql_syscall_getgid(ql: Qiling): - return ql.os.gid + return __getgid(ql) -def ql_syscall_setgroups(ql: Qiling, gidsetsize: int, grouplist: int): - return 0 +def ql_syscall_setgid(ql: Qiling, gid: int): + return __setgid(ql, gid) -def ql_syscall_setgid(ql: Qiling): - return 0 +def ql_syscall_getgid32(ql: Qiling): + return __getgid(ql) + + +def ql_syscall_setgid32(ql: Qiling, gid: int): + return __setgid(ql, gid) + + +def ql_syscall_geteuid(ql: Qiling): + return ql.os.euid -def ql_syscall_setgid32(ql: Qiling): +def ql_syscall_seteuid(ql: Qiling): return 0 -def ql_syscall_setuid(ql: Qiling): +def ql_syscall_getegid(ql: Qiling): + return ql.os.egid + + +def ql_syscall_setgroups(ql: Qiling, gidsetsize: int, grouplist: int): return 0 + def ql_syscall_setresuid(ql: Qiling): return 0 @@ -202,17 +233,16 @@ def ql_syscall__llseek(ql: Qiling, fd: int, offset_high: int, offset_low: int, r def ql_syscall_brk(ql: Qiling, inp: int): - # current brk_address will be modified if inp is not NULL(zero) - # otherwise, just return current brk_address - if inp: cur_brk_addr = ql.loader.brk_address new_brk_addr = ql.mem.align_up(inp) - if inp > cur_brk_addr: # increase current brk_address if inp is greater + if new_brk_addr > cur_brk_addr: + ql.log.debug(f'brk: increasing program break from {cur_brk_addr:#x} to {new_brk_addr:#x}') ql.mem.map(cur_brk_addr, new_brk_addr - cur_brk_addr, info="[brk]") - elif inp < cur_brk_addr: # shrink current bkr_address to inp if its smaller + elif new_brk_addr < cur_brk_addr: + ql.log.debug(f'brk: decreasing program break from {cur_brk_addr:#x} to {new_brk_addr:#x}') ql.mem.unmap(new_brk_addr, cur_brk_addr - new_brk_addr) ql.loader.brk_address = new_brk_addr @@ -752,7 +782,7 @@ def _type_mapping(ent): # For some reason MACOS return int value is 64bit try: packed_d_ino = (ql.pack(d_ino), n) - except: + except: packed_d_ino = (ql.pack64(d_ino), n) if is_64: diff --git a/tests/test_android.py b/tests/test_android.py index 04407433b..1556bc02d 100644 --- a/tests/test_android.py +++ b/tests/test_android.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -import os, platform, sys, unittest +import platform, sys, unittest from collections import defaultdict sys.path.append("..") @@ -13,47 +13,69 @@ class Fake_maps(QlFsMappedObject): - def __init__(self, ql): + def __init__(self, ql: Qiling): self.ql = ql + def read(self, size): - stack = next(filter(lambda x : x[3]=='[stack]', self.ql.mem.map_info)) - return ('%x-%x %s\n' % (stack[0], stack[1], stack[3])).encode() + return ''.join(f'{lbound:x}-{ubound:x} {perms}p {label}\n' for lbound, ubound, perms, label, _ in self.ql.mem.get_mapinfo()).encode() + def fstat(self): return defaultdict(int) + def close(self): return 0 -def my_syscall_close(ql, fd): - if fd in [0, 1, 2]: + +def my_syscall_close(ql: Qiling, fd: int) -> int: + if fd in (0, 1, 2): return 0 + return syscall.ql_syscall_close(ql, fd) +# addresses specified on non-fixed mmap calls are used as hints, where the allocated +# address can never be less than the value set for mmap_address. nevertheless, android +# uses a non-fixed mmap call to map "/system/framework/arm64/boot.art" at 0x70000000 +# and fails if mmap allocates it elsewhere. +# +# this override sets a lower value for mmap_address to allow android map the file using +# a non-fixed mmap call to exactly where it wants it to be. +OVERRIDES = {'mmap_address': 0x68000000} + + class TestAndroid(unittest.TestCase): @unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux') def test_android_arm64(self): - test_binary = "../examples/rootfs/arm64_android6.0/bin/arm64_android_jniart" rootfs = "../examples/rootfs/arm64_android6.0" - env = {"ANDROID_DATA":"/data", "ANDROID_ROOT":"/system"} + env = { + 'ANDROID_DATA': r'/data', + 'ANDROID_ROOT': r'/system' + } + + ql = Qiling([test_binary], rootfs, env, profile={'OS64': OVERRIDES}, multithread=True) - ql = Qiling([test_binary], rootfs, env, multithread=True) ql.os.set_syscall("close", my_syscall_close) ql.add_fs_mapper("/proc/self/task/2000/maps", Fake_maps(ql)) ql.run() - del ql + del ql @unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux') def test_android_arm(self): test_binary = "../examples/rootfs/arm64_android6.0/bin/arm_android_jniart" rootfs = "../examples/rootfs/arm64_android6.0" - env = {"ANDROID_DATA":"/data", "ANDROID_ROOT":"/system"} + env = { + 'ANDROID_DATA': r'/data', + 'ANDROID_ROOT': r'/system' + } + + ql = Qiling([test_binary], rootfs, env, profile={'OS32': OVERRIDES}, multithread=True) - ql = Qiling([test_binary], rootfs, env, multithread=True) ql.os.set_syscall("close", my_syscall_close) ql.add_fs_mapper("/proc/self/task/2000/maps", Fake_maps(ql)) ql.run() + del ql