From 1404840025d72f344e6092914350d6f7c6f56751 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 14 Jun 2022 15:38:08 +0300 Subject: [PATCH] Introduce a minimal procfs to Linux OS --- qiling/debugger/gdb/gdb.py | 22 +++---- qiling/os/linux/linux.py | 13 ++++ qiling/os/linux/procfs.py | 119 +++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 15 deletions(-) create mode 100644 qiling/os/linux/procfs.py diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index f559a2a83..f77c0e35c 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -475,23 +475,15 @@ def handle_q(subcmd: str) -> Reply: return f'l{content}' elif feature == 'auxv' and op == 'read': - auxv_data = bytearray() + try: + with self.ql.os.fs_mapper.open('/proc/self/auxv', 'rb') as infile: + infile.seek(offset, 0) # SEEK_SET + auxv_data = infile.read(length) - if hasattr(self.ql.loader, 'auxv'): - nbytes = self.ql.arch.bits // 8 + except FileNotFoundError: + auxv_data = b'' - auxv_addr = self.ql.loader.auxv + offset - null_entry = bytes(nbytes * 2) - - # keep reading until AUXV.AT_NULL is reached - while not auxv_data.endswith(null_entry): - auxv_data.extend(self.ql.mem.read(auxv_addr, nbytes)) - auxv_addr += nbytes - - auxv_data.extend(self.ql.mem.read(auxv_addr, nbytes)) - auxv_addr += nbytes - - return b'l' + auxv_data[:length] + return b'l' + auxv_data elif feature == 'exec-file' and op == 'read': return f'l{os.path.abspath(self.ql.path)}' diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index ed95a7ecd..4aed180db 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -14,6 +14,7 @@ from qiling.const import QL_ARCH, QL_OS from qiling.os.fcall import QlFunctionCall from qiling.os.const import * +from qiling.os.linux.procfs import QlProcFS from qiling.os.posix.posix import QlOsPosix from . import futex @@ -118,6 +119,14 @@ def load(self): if getattr(self.fd[i], 'close_on_exec', 0): self.fd[i] = None + + def setup_procfs(self): + self.fs_mapper.add_fs_mapping(r'/proc/self/auxv', QlProcFS.self_auxv(self)) + self.fs_mapper.add_fs_mapping(r'/proc/self/cmdline', QlProcFS.self_cmdline(self)) + self.fs_mapper.add_fs_mapping(r'/proc/self/environ', QlProcFS.self_environ(self)) + self.fs_mapper.add_fs_mapping(r'/proc/self/exe', QlProcFS.self_exe(self)) + + def hook_syscall(self, ql, intno = None): return self.load_syscall() @@ -133,6 +142,10 @@ def run_function_after_load(self): def run(self): + # do not set-up procfs for drivers and shellcode + if not self.ql.code and not self.ql.loader.is_driver: + self.setup_procfs() + if self.ql.exit_point is not None: self.exit_point = self.ql.exit_point diff --git a/qiling/os/linux/procfs.py b/qiling/os/linux/procfs.py new file mode 100644 index 000000000..fa1bed298 --- /dev/null +++ b/qiling/os/linux/procfs.py @@ -0,0 +1,119 @@ + +from typing import TYPE_CHECKING, AnyStr, Optional, Sized + +if TYPE_CHECKING: + from qiling.os.linux.linux import QlOsLinux + + +class QlFileSeekable: + + def __init__(self): + self.buff: Sized + self.pos = 0 + + def seek(self, offset: int, whence: int) -> int: + assert whence in (0, 1, 2) + + # SEEK_SET + if whence == 0: + pos = offset + + # SEEK_CUR + elif whence == 1: + pos = self.pos + offset + + # SEEK_END + elif whence == 2: + pos = len(self.buff) + offset + + # make sure pos is within reasonabe boundaries + self.pos = min(max(pos, 0), len(self.buff)) + + return self.pos + + def ftell(self) -> int: + return self.pos + + +class QlFileReadable: + + def __init__(self, *, content: Optional[bytearray] = None): + self.buff = content or bytearray() + self.pos = 0 + + def read(self, length: int = -1) -> bytes: + if length == -1: + length = len(self.buff) + + content = self.buff[self.pos:length] + self.pos = min(self.pos + length, len(self.buff)) + + return bytes(content) + + +class QlFileProcFS(QlFileReadable, QlFileSeekable): + + def __init__(self, content: bytearray): + QlFileReadable.__init__(self, content=content) + QlFileSeekable.__init__(self) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_tb): + self.close() + + def close(self): + pass + + +class QlProcFS: + + @staticmethod + def self_auxv(os: 'QlOsLinux') -> QlFileProcFS: + nbytes = os.ql.arch.bits // 8 + + auxv_addr = os.ql.loader.auxv + null_entry = bytes(nbytes * 2) + + auxv_data = bytearray() + + # keep reading until AUXV.AT_NULL is reached + while not auxv_data.endswith(null_entry): + auxv_data.extend(os.ql.mem.read(auxv_addr, nbytes)) + auxv_addr += nbytes + + auxv_data.extend(os.ql.mem.read(auxv_addr, nbytes)) + auxv_addr += nbytes + + return QlFileProcFS(content=auxv_data) + + + @staticmethod + def self_cmdline(os: 'QlOsLinux') -> QlFileProcFS: + entries = (arg.encode('utf-8') for arg in os.ql.argv) + cmdline = bytearray(b'\x00'.join(entries) + b'\x00') + + return QlFileProcFS(content=cmdline) + + + @staticmethod + def self_environ(os: 'QlOsLinux') -> QlFileProcFS: + def __to_bytes(s: AnyStr) -> bytes: + if isinstance(s, str): + return s.encode('utf-8') + + return s + + entries = (b'='.join((__to_bytes(k), __to_bytes(v))) for k, v in os.ql.env.items()) + environ = bytearray(b'\x00'.join(entries) + b'\x00') + + return QlFileProcFS(content=environ) + + + @staticmethod + def self_exe(os: 'QlOsLinux') -> QlFileProcFS: + with open(os.ql.path, 'rb') as exefile: + content = bytearray(exefile.read()) + + return QlFileProcFS(content=content)