diff --git a/Dockerfile b/Dockerfile index 39b604424..430e61cca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ ARG BASE_IMAGE="${REGISTRY}/ubuntu:22.04" ARG VPN_VERSION="1.0.25" ARG BUSYBOX_VERSION="0.0.15" ARG LINUX_VERSION="3.5.16-beta" -ARG IGLOO_DRIVER_VERSION="0.0.29" +ARG IGLOO_DRIVER_VERSION="v0.0.31-pre.7340f4300" ARG LIBNVRAM_VERSION="0.0.23" ARG CONSOLE_VERSION="1.0.7" ARG GUESTHOPPER_VERSION="1.0.20" @@ -600,4 +600,4 @@ RUN cd /igloo_static && \ done \ done RUN date +%s%N > /igloo_static/container_timestamp.txt -RUN pip install py-spy \ No newline at end of file +RUN pip install py-spy diff --git a/pyplugins/apis/syscalls.py b/pyplugins/apis/syscalls.py index bbf0edcfc..5098e6e88 100644 --- a/pyplugins/apis/syscalls.py +++ b/pyplugins/apis/syscalls.py @@ -5,7 +5,7 @@ from penguin import plugins, Plugin import json -from typing import Dict, List, Any, Callable, Optional, Iterator +from typing import Dict, List, Any, Callable, Optional, Iterator, Generator from hyper.consts import value_filter_type as vft from hyper.consts import igloo_hypercall_constants as iconsts from hyper.consts import igloo_base_hypercalls as bconsts @@ -334,7 +334,6 @@ def __init__(self) -> None: # Track function -> hook_ptr and name -> hook_ptr for easier lookup self._func_to_hook_ptr = {} # Maps functions to hook pointers - self._name_to_hook_ptr = {} # Maps function names to hook pointers # Syscall type information - using dictionary for fast name-based # lookups @@ -366,6 +365,9 @@ def __init__(self) -> None: self._pending_hooks = [] self._syscall_event = plugins.portal.wrap(self._syscall_event) + # Wrap the unregister function + self.unregister_syscall_hook = plugins.portal.wrap(self._do_unregister_syscall_hook) + def _setup_syscall_handler(self, cpu: int) -> None: """ Handler for setting up syscall definitions. @@ -473,11 +475,6 @@ def _syscall_interrupt_handler(self) -> bool: # Track function to hook pointer mappings self._func_to_hook_ptr[func] = hook_ptr - # Store by function name if available - func_name = getattr(func, "__name__", None) - if func_name: - self._name_to_hook_ptr[func_name] = hook_ptr - ''' On repeated calls to the same syscall in portal we produce new syscall_event objects. However, it doesn't update the version of the object @@ -699,7 +696,7 @@ def _syscall_event(self, cpu: int, is_enter: Optional[bool] = None) -> Any: # 1. Get Event Object (No bytes serialization yet) sce = self._get_syscall_event(cpu, arg) hook_ptr = sce.hook.address - if hook_ptr not in self._hooks: + if hook_ptr not in self._hooks or self._hooks[hook_ptr] is None: return # 2. Unpack Hook Data including read_only flag @@ -993,3 +990,36 @@ def decorator(func): return func return decorator + + def _do_unregister_syscall_hook(self, func: Callable) -> None: + """Internal implementation of unregister_syscall_hook.""" + + if func not in self._func_to_hook_ptr: + self.logger.error("Function not registered as a syscall hook") + return + + hook_ptr = self._func_to_hook_ptr[func] + + if hook_ptr not in self._hooks: + self.logger.error(f"Hook pointer 0x{hook_ptr:x} not found in internal hooks when attempting to disable") + return + + try: + self.logger.info("yielding unregister_syscall_hook") + retval = yield PortalCmd("unregister_syscall_hook", hook_ptr) + self.logger.info("yielded unregister_syscall_hook") + + self._hooks.pop(hook_ptr, None) + self._func_to_hook_ptr.pop(func, None) + self._hook_info.pop(hook_ptr, None) + self._hook_proto_cache.pop(hook_ptr, None) + + if retval != 0: + self.logger.warning(f"Kernel returned non-zero ({retval}) for unregister_syscall_hook, but proceeding with cleanup") + + except Exception as e: + self.logger.error(f"Error unregistering syscall hook 0x{hook_ptr:x} ({func}): {e}") + import traceback + self.logger.error(f"Traceback: {traceback.format_exc()}") + + self.logger.info("_do_unregister_syscall_hook: END") diff --git a/pyplugins/testing/syscall_test.py b/pyplugins/testing/syscall_test.py index 2269c8a20..25550ffa3 100644 --- a/pyplugins/testing/syscall_test.py +++ b/pyplugins/testing/syscall_test.py @@ -25,8 +25,11 @@ def __init__(self): self.ioctl_ret_num = 0 self.ioctl_ret2_num = 0 self.ioctl_ret3_num = 0 + self.getpids = 0 # For unregister test + self.hook_unregistered = False syscalls.syscall("on_sys_ioctl_enter", comm_filter="send_syscall", arg_filters=[None, 0xabcd])(self.test_skip_retval) + self.getpid_hook = syscalls.syscall("on_sys_getpid_return")(self.getpid) def test_skip_retval(self, regs, proto, syscall, fd, op, arg): assert fd == 9, f"Expected fd 9, got {fd:#x}" @@ -72,21 +75,20 @@ def ioctl_noret(self, regs, proto, syscall, fd, op, arg): with open(join(self.outdir, "syscall_test.txt"), "a") as f: f.write("Syscall ioctl_noret: failure\n") - @syscalls.syscall("on_sys_getpid_return") def getpid(self, regs, proto, syscall, *args): - # NOTE: We've removed this check because it was causing issues - # It doesn't seem to indicate anything negative so we're skipping it - # proc = self.panda.plugins['osi'].get_current_process(cpu) - # if syscall.retval != proc.pid and syscall.retval != 0: - # self.logger.error( - # f"Syscall test failed: getpid returned {syscall.retval:#x}, expected {proc.pid:#x}") - # self.success_getpid = False - # self.report_getpid() - # return if "send_syscall" in self.panda.get_process_name(self.panda.get_cpu()): self.success_getpid = True self.report_getpid() + if not self.hook_unregistered: + result = plugins.syscalls.unregister_syscall_hook(self.getpid) + + if not result: + self.logger.error("Failed to unregister getpid hook!") + + self.hook_unregistered = True + self.getpids += 1 + @syscalls.syscall("on_sys_clone_enter") def syscall_test(self, regs, proto, syscall, *args): if self.reported_clone: @@ -150,6 +152,13 @@ def report_getpid(self): self.logger.info(f"Syscall getpid test: {result}") f.write(f"Syscall getpid test: {result}\n") + def report_unregister(self): + with open(join(self.outdir, "syscall_test.txt"), "a") as f: + result = "passed" if self.getpids == 1 else "failed" + self.logger.info(f"Syscall unregister test: {result} (getpid hook called {self.getpids} times)") + f.write(f"Syscall unregister test: {result}\n") + def uninit(self): self.report_clone() self.report_getpid() + self.report_unregister() diff --git a/tests/unit_tests/test_target/patches/tests/syscall.yaml b/tests/unit_tests/test_target/patches/tests/syscall.yaml index 1b0e79724..e1e6e144b 100644 --- a/tests/unit_tests/test_target/patches/tests/syscall.yaml +++ b/tests/unit_tests/test_target/patches/tests/syscall.yaml @@ -34,6 +34,10 @@ plugins: type: file_contains file: syscall_test.txt string: "Syscall ioctl_reg3: success 1" + syscall_hypercall+unregister: + type: file_contains + file: syscall_test.txt + string: "Syscall unregister test: passed" static_files: /tests/syscall.sh: @@ -46,8 +50,8 @@ static_files: /igloo/utils/send_syscall ioctl 0x13 0x0 0x1 /igloo/utils/send_syscall ioctl 0x13 0x1234 0xabce /igloo/utils/send_syscall ioctl 0x13 0x1234 0xabcd - - /igloo/utils/send_syscall ioctl 0x9 0xabcd + + /igloo/utils/send_syscall ioctl 0x9 0xabcd if [ $? -ne 43 ]; then echo "Error: send_syscall retval enter failed" @@ -60,6 +64,7 @@ static_files: exit 1 fi /igloo/utils/send_syscall getpid + /igloo/utils/send_syscall getpid # This one should not get through because we'll unregister echo "Syscall test: passed" exit 0 fi