From bfd4a257beba0cc114cc20909a12912f427062e8 Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Tue, 6 Jan 2026 12:21:22 -0500 Subject: [PATCH 1/9] kprobes: initial python side --- pyplugins/apis/kprobes.py | 364 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 pyplugins/apis/kprobes.py diff --git a/pyplugins/apis/kprobes.py b/pyplugins/apis/kprobes.py new file mode 100644 index 000000000..ef53e76d8 --- /dev/null +++ b/pyplugins/apis/kprobes.py @@ -0,0 +1,364 @@ +""" +.. include:: /docs/kprobes.md + :parser: myst_parser.sphinx_ +""" + +import os +from penguin import Plugin, plugins +from penguin.plugin_manager import resolve_bound_method_from_class +from typing import Dict, List, Any, Union, Callable, Optional, Iterator +from hyper.consts import igloo_hypercall_constants as iconsts +from hyper.consts import portal_type +from hyper.portal import PortalCmd +from wrappers.ptregs_wrap import get_pt_regs_wrapper + + +class Kprobes(Plugin): + """ + Kprobes Plugin + ============== + + Provides an interface for registering and handling kernel probes (kprobes) in the guest kernel. + Supports filtering by PID or process name, and coroutine-based event handling. + + Attributes + ---------- + probes : Dict[int, Dict[str, Any]] + Registered probe callbacks by probe ID. + probe_info : Dict[int, Dict[str, Any]] + Metadata for each registered probe. + _pending_kprobes : List[Dict[str, Any]] + Queue of kprobes pending registration. + _func_to_probe_id : Dict[Callable, int] + Maps callback functions to probe IDs. + _name_to_probe_id : Dict[str, int] + Maps function names to probe IDs. + """ + + def __init__(self): + self.outdir = self.get_arg("outdir") + self.projdir = self.get_arg("proj_dir") + if self.get_arg_bool("verbose"): + self.logger.setLevel("DEBUG") + + self.probes: Dict[int, Dict[str, Any]] = {} + self.probe_info = {} + self._pending_kprobes: List[Dict[str, Any]] = [] + self._func_to_probe_id = {} # Maps function to probe_id + self._name_to_probe_id = {} # Maps function name to probe_id + self.portal = plugins.portal + self.portal.register_interrupt_handler( + "kprobes", self._kprobe_interrupt_handler) + self.fs_init = False + self.panda.hypercall(iconsts.IGLOO_HYP_KPROBE_ENTER)( + self._kprobe_enter_handler) + self.panda.hypercall(iconsts.IGLOO_HYP_KPROBE_RETURN)( + self._kprobe_return_handler) + self.saved_regs_info = {} + self._kprobe_event = self.plugins.portal.wrap(self._kprobe_event) + + def _kprobe_event(self, cpu: Any, is_enter: bool) -> Any: + """ + Handle a kprobe event from the portal. + + Invokes the registered callback for the probe, passing a `pt_regs` wrapper. + + Parameters + ---------- + cpu : Any + CPU context. + is_enter : bool + True if entry probe, False if return probe. + + Returns + ------- + Any + Return value from the callback, if any. + """ + arg = self.panda.arch.get_arg(cpu, 2, convention="syscall") + # possible issue with registring multiple cpu _memregions + sce = plugins.kffi.read_type_panda(cpu, arg, "portal_event") + ptregs_addr = sce.regs.address + pt_regs_raw = plugins.kffi.read_type_panda(cpu, ptregs_addr, "pt_regs") + pt_regs = get_pt_regs_wrapper(self.panda, pt_regs_raw) + original_bytes = pt_regs.to_bytes()[:] + + if sce.id not in self.probes: + self.logger.error( + f"Kprobe ID {sce.id} not found in registered probes") + return + + probe_info = self.probes[sce.id] + cb = probe_info["callback"] + fn = resolve_bound_method_from_class(cb) + probe_info["callback"] = fn # Cache resolved function + fn_ret = fn(pt_regs) + if isinstance(fn_ret, Iterator): + fn_ret = yield from fn(pt_regs) + + new = pt_regs.to_bytes() + if original_bytes != new: + plugins.mem.write_bytes_panda(cpu, ptregs_addr, new) + return fn_ret + + def _kprobe_enter_handler(self, cpu: Any) -> None: + """ + Entry handler for kprobes. + """ + self._kprobe_event(cpu, True) + + def _kprobe_return_handler(self, cpu: Any) -> None: + """ + Return handler for kprobes. + """ + self._kprobe_event(cpu, False) + + @plugins.live_image.fs_init + def on_fs_init(self): + self.portal.queue_interrupt("kprobes") + self.fs_init = True + + def _kprobe_interrupt_handler(self) -> bool: + """ + Handle interrupts for pending kprobe registrations. + Processes one pending kprobe registration per call. + Returns True if more kprobes are pending, False otherwise. + """ + if not self._pending_kprobes: + return False + + pending_kprobes = self._pending_kprobes[:] + + while pending_kprobes: + kprobe_config, func = pending_kprobes.pop(0) + symbol = kprobe_config["symbol"] + offset = kprobe_config["offset"] + callback = kprobe_config["callback"] + options = kprobe_config["options"] + + is_method = hasattr(func, '__self__') or ( + hasattr(func, '__qualname__') and '.' in func.__qualname__) + qualname = getattr(func, '__qualname__', None) + + probe_id = yield from self._register_kprobe( + symbol, + offset, + process_filter=options.get('process_filter'), + on_enter=options.get('on_enter', True), + on_return=options.get('on_return', False), + pid_filter=options.get('pid_filter') + ) + + if probe_id: + self.probes[probe_id] = { + "callback": func, + "is_method": is_method, + "qualname": qualname, + } + self.probe_info[probe_id] = { + "symbol": symbol, + "offset": offset, + "callback": callback, + "options": options + } + # Track function to probe_id mappings + self._func_to_probe_id[func] = probe_id + if hasattr(func, "__name__"): + self._name_to_probe_id[func.__name__] = probe_id + self.logger.debug( + f"Successfully registered kprobe ID {probe_id} for {symbol}+{offset}") + else: + self.logger.error( + f"Failed to register kprobe for {symbol}+{offset}") + + return False + + def _register_kprobe( + self, + symbol: str, + offset: int, + process_filter: Optional[str] = None, + on_enter: bool = True, + on_return: bool = False, + pid_filter: Optional[int] = None + ) -> Iterator[Optional[int]]: + """ + Register a kprobe with the kernel using the portal. + + Parameters + ---------- + symbol : str + Kernel symbol name. + offset : int + Offset in the function to place the probe (usually 0). + process_filter : Optional[str] + Process name filter. + on_enter : bool + Trigger on function entry. + on_return : bool + Trigger on function return. + pid_filter : Optional[int] + PID filter. + + Yields + ------ + Optional[int] + Probe ID if registration succeeds, None otherwise. + """ + # Determine the probe type based on entry/return flags + if on_enter and on_return: + probe_type = portal_type.PORTAL_UPROBE_TYPE_BOTH + elif on_enter: + probe_type = portal_type.PORTAL_UPROBE_TYPE_ENTRY + elif on_return: + probe_type = portal_type.PORTAL_UPROBE_TYPE_RETURN + else: + self.logger.error( + "Invalid probe type: at least one of on_enter or on_return must be True") + return None + + # Set the PID filter, defaulting to 0xffffffff for "any PID" + filter_pid = pid_filter if pid_filter is not None else 0xffffffff + + # Debug output before registration + self.logger.debug(f"Registering kprobe: symbol={symbol}, offset={offset:#x}, type={probe_type}, " + f"filter_comm={process_filter}, filter_pid={filter_pid:#x}") + + # Create a registration struct that matches the C-side struct kprobe_registration + reg = plugins.kffi.new("kprobe_registration") + + # Fill in the symbol field (first 256 bytes, null-terminated) + sym_bytes = symbol.encode('latin-1') + # Ensure we leave room for null terminator + for i, b in enumerate(sym_bytes[:255]): + reg.symbol[i] = b + reg.symbol[min(len(sym_bytes), 255)] = 0 # Ensure null termination + + # Set the offset, type and pid + reg.offset = offset + reg.type = probe_type + reg.pid = filter_pid + + # Fill in the comm field (process filter) if provided - TASK_COMM_LEN is 16 + if process_filter: + comm_bytes = process_filter.encode('latin-1') + # Leave room for null terminator (16-1) + for i, b in enumerate(comm_bytes[:15]): + reg.comm[i] = b + reg.comm[min(len(comm_bytes), 15)] = 0 + else: + reg.comm[0] = 0 # Empty comm filter (match any process) + + # Serialize the registration struct to bytes + reg_bytes = reg.to_bytes() + + # Send the registration to the kernel via portal + result = yield PortalCmd("register_kprobe", offset, len(reg_bytes), None, reg_bytes) + + if result is None: + self.logger.error( + f"Failed to register kprobe at {symbol}+{offset:#x}") + return None + + probe_id = result + self.logger.debug( + f"Kprobe successfully registered with ID: {probe_id}") + return probe_id + + def _unregister_kprobe(self, probe_id: int) -> Iterator[bool]: + """ + Unregister a kprobe by its ID. + """ + self.logger.debug(f"unregister_kprobe called: probe_id={probe_id}") + result = yield PortalCmd("unregister_kprobe", probe_id, 0) + if result is True: + if probe_id in self.probes: + del self.probes[probe_id] + self.logger.debug(f"Kprobe {probe_id} successfully unregistered") + return True + else: + self.logger.error(f"Failed to unregister kprobe {probe_id}") + return False + + def kprobe( + self, + symbol: str, + offset: int = 0, + process_filter: Optional[str] = None, + on_enter: bool = True, + on_return: bool = False, + pid_filter: Optional[int] = None + ) -> Callable[[Callable], Callable]: + """ + Decorator to register a kprobe at the specified symbol and offset. + + Parameters + ---------- + symbol : str + Kernel symbol name. + offset : int + Offset in the function (default: 0). + process_filter : Optional[str] + Process name to filter events. + on_enter : bool + Trigger on function entry (default: True). + on_return : bool + Trigger on function return (default: False). + pid_filter : Optional[int] + PID to filter events for a specific process. + + Returns + ------- + Callable[[Callable], Callable] + Decorator function that registers the kprobe. + """ + def _register_decorator(kprobe_configs): + def decorator(func): + is_method = hasattr(func, '__self__') or ( + hasattr(func, '__qualname__') and '.' in func.__qualname__) + qualname = getattr(func, '__qualname__', None) + for kprobe_config in kprobe_configs: + kprobe_config["callback"] = func + kprobe_config["is_method"] = is_method + kprobe_config["qualname"] = qualname + self._pending_kprobes.append((kprobe_config, func)) + if self.fs_init: + self.portal.queue_interrupt("kprobes") + return func + return decorator + + options = { + 'process_filter': process_filter, + 'on_enter': on_enter, + 'on_return': on_return, + 'pid_filter': pid_filter + } + + kprobe_configs = [{ + "symbol": symbol, + "offset": offset, + "options": options.copy(), + }] + return _register_decorator(kprobe_configs) + + def kretprobe( + self, + symbol: str, + process_filter: Optional[str] = None, + on_enter: bool = False, + on_return: bool = True, + pid_filter: Optional[int] = None + ) -> Callable[[Callable], Callable]: + """ + Decorator to register a return kprobe (kretprobe). + + Equivalent to `kprobe()` with `on_enter=False, on_return=True`. + """ + return self.kprobe(symbol, 0, process_filter, + on_enter, on_return, pid_filter) + + def unregister(self, probe_id: int) -> None: + """ + Unregister a kprobe by its ID. + """ + self._unregister_kprobe(probe_id) From 8e8f774fab876c84db91f3edb816905014dbacfb Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Tue, 6 Jan 2026 12:50:52 -0500 Subject: [PATCH 2/9] kprobes: starting work on tests --- pyplugins/testing/kprobes_test.py | 38 +++++++++++++++++++ .../test_target/patches/tests/kprobes.yaml | 17 +++++++++ 2 files changed, 55 insertions(+) create mode 100644 pyplugins/testing/kprobes_test.py create mode 100644 tests/unit_tests/test_target/patches/tests/kprobes.yaml diff --git a/pyplugins/testing/kprobes_test.py b/pyplugins/testing/kprobes_test.py new file mode 100644 index 000000000..2cc935812 --- /dev/null +++ b/pyplugins/testing/kprobes_test.py @@ -0,0 +1,38 @@ +""" +This plugin verifies that hypercalls are being made correctly. +""" + +from penguin import Plugin, plugins +from os.path import join, realpath, basename +from glob import glob +import functools + +kprobes = plugins.kprobes +mem = plugins.mem +osi = plugins.osi + + +class KprobesTest(Plugin): + def __init__(self, panda): + self.panda = panda + self.outdir = self.get_arg("outdir") + + @kprobes.kprobe( + symbol="do_execveat_common", + on_enter=True, + ) + def do_execvat_common(self, pt_regs): + """ + static int do_execveat_common(int fd, struct filename *filename, + struct user_arg_ptr argv, + struct user_arg_ptr envp, + int flags) + """ + args = pt_regs.get_args(5) + struct_filename = yield from plugins.kffi.read_type(args[1], "struct filename") + filename = yield from plugins.mem.read_str(struct_filename.name.address) + if "/tests/kprobes_test.sh" in filename: + with open(join(self.outdir, "kprobe_exec_test.txt"), "w") as f: + f.write( + f"kprobe exec entry test passed: do_execveat_common called with filename={filename}\n" + ) diff --git a/tests/unit_tests/test_target/patches/tests/kprobes.yaml b/tests/unit_tests/test_target/patches/tests/kprobes.yaml new file mode 100644 index 000000000..525d66d4e --- /dev/null +++ b/tests/unit_tests/test_target/patches/tests/kprobes.yaml @@ -0,0 +1,17 @@ +plugins: + kprobes_test: {} + verifier: + conditions: + kprobes_exec: + type: file_contains + file: kprobe_exec_test.txt + string: "kprobe exec entry test passed" + +static_files: + /tests/kprobes_test.sh: + type: inline_file + contents: | + #!/igloo/utils/sh + exit 0 + + mode: 73 From 3b04c7339ecf37a1100668a0f7474b6d44b0bb86 Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Wed, 7 Jan 2026 10:20:54 -0500 Subject: [PATCH 3/9] another test, save some state --- pyplugins/apis/kprobes.py | 1 + pyplugins/testing/kprobes_test.py | 32 ++++++++++++++++++- .../test_target/patches/tests/kprobes.yaml | 5 +++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/pyplugins/apis/kprobes.py b/pyplugins/apis/kprobes.py index ef53e76d8..c4453d285 100644 --- a/pyplugins/apis/kprobes.py +++ b/pyplugins/apis/kprobes.py @@ -81,6 +81,7 @@ def _kprobe_event(self, cpu: Any, is_enter: bool) -> Any: ptregs_addr = sce.regs.address pt_regs_raw = plugins.kffi.read_type_panda(cpu, ptregs_addr, "pt_regs") pt_regs = get_pt_regs_wrapper(self.panda, pt_regs_raw) + pt_regs.is_enter = is_enter original_bytes = pt_regs.to_bytes()[:] if sce.id not in self.probes: diff --git a/pyplugins/testing/kprobes_test.py b/pyplugins/testing/kprobes_test.py index 2cc935812..4a54b7eb1 100644 --- a/pyplugins/testing/kprobes_test.py +++ b/pyplugins/testing/kprobes_test.py @@ -16,12 +16,14 @@ class KprobesTest(Plugin): def __init__(self, panda): self.panda = panda self.outdir = self.get_arg("outdir") + self.open_ret_pid = None @kprobes.kprobe( symbol="do_execveat_common", on_enter=True, + on_return=False, ) - def do_execvat_common(self, pt_regs): + def kprobe_do_execvat_common(self, pt_regs): """ static int do_execveat_common(int fd, struct filename *filename, struct user_arg_ptr argv, @@ -36,3 +38,31 @@ def do_execvat_common(self, pt_regs): f.write( f"kprobe exec entry test passed: do_execveat_common called with filename={filename}\n" ) + + @kprobes.kprobe( + symbol="do_filp_open", + on_enter=True, + on_return=True, + ) + def kprobe_do_filp_open(self, pt_regs): + """ + extern struct file *do_filp_open(int dfd, struct filename *pathname, + const struct open_flags *op); + """ + current = yield from plugins.osi.get_proc() + if not pt_regs.is_enter: + if self.open_ret_pid == current.pid: + rval = int(self.panda.ffi.cast("target_long", pt_regs.get_return_value())) + if rval == -2: # -ENOENT + with open(join(self.outdir, "kprobe_open_test.txt"), "w") as f: + f.write( + f"kprobe open return test passed: do_filp_open called with pathname={self.filp_open_pathname} and rval={rval}\n" + ) + else: + self.logger.error(f"do_filp_open returned unexpected value: {rval}, expected -2 (-ENOENT) for /doesnotexist") + else: + struct_filename = yield from plugins.kffi.read_type(pt_regs.get_args(5)[1], "struct filename") + pathname = yield from plugins.mem.read_str(struct_filename.name.address) + if pt_regs.is_enter and "/doesnotexist" in pathname: + self.filp_open_pathname = pathname + self.open_ret_pid = current.pid diff --git a/tests/unit_tests/test_target/patches/tests/kprobes.yaml b/tests/unit_tests/test_target/patches/tests/kprobes.yaml index 525d66d4e..996962e0d 100644 --- a/tests/unit_tests/test_target/patches/tests/kprobes.yaml +++ b/tests/unit_tests/test_target/patches/tests/kprobes.yaml @@ -6,12 +6,17 @@ plugins: type: file_contains file: kprobe_exec_test.txt string: "kprobe exec entry test passed" + kprobes_open: + type: file_contains + file: kprobe_open_test.txt + string: "kprobe open return test passed" static_files: /tests/kprobes_test.sh: type: inline_file contents: | #!/igloo/utils/sh + /igloo/utils/busybox cat /doesnotexist exit 0 mode: 73 From eee0ce506c066618e773c4bc8a0b62bc562249b6 Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Wed, 7 Jan 2026 17:19:49 -0500 Subject: [PATCH 4/9] update KPROBE enum; add CI built kernel+driver --- Dockerfile | 6 +++--- pyplugins/apis/kprobes.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 39b604424..b7ed77afa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,8 @@ ARG REGISTRY="docker.io" 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 LINUX_VERSION="v3.5.19-beta" +ARG IGLOO_DRIVER_VERSION="v0.0.34-pre.491180425" 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/kprobes.py b/pyplugins/apis/kprobes.py index c4453d285..d42d3baf2 100644 --- a/pyplugins/apis/kprobes.py +++ b/pyplugins/apis/kprobes.py @@ -208,11 +208,11 @@ def _register_kprobe( """ # Determine the probe type based on entry/return flags if on_enter and on_return: - probe_type = portal_type.PORTAL_UPROBE_TYPE_BOTH + probe_type = portal_type.PORTAL_KPROBE_TYPE_BOTH elif on_enter: - probe_type = portal_type.PORTAL_UPROBE_TYPE_ENTRY + probe_type = portal_type.PORTAL_KPROBE_TYPE_ENTRY elif on_return: - probe_type = portal_type.PORTAL_UPROBE_TYPE_RETURN + probe_type = portal_type.PORTAL_KPROBE_TYPE_RETURN else: self.logger.error( "Invalid probe type: at least one of on_enter or on_return must be True") From e652ac1266a5d28bca75d7ffc472024ea6164397 Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Wed, 7 Jan 2026 17:26:05 -0500 Subject: [PATCH 5/9] add kprobes tests to base tests --- tests/unit_tests/test_target/base_config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit_tests/test_target/base_config.yaml b/tests/unit_tests/test_target/base_config.yaml index 40d2e68c9..8c4e69015 100644 --- a/tests/unit_tests/test_target/base_config.yaml +++ b/tests/unit_tests/test_target/base_config.yaml @@ -45,6 +45,7 @@ patches: - ./patches/tests/syscalls_logger.yaml - ./patches/tests/lifeguard.yaml - ./patches/tests/nvram.yaml + - ./patches/tests/kprobes.yaml # - ./patches/tests/uboot_env_cmp.yaml From 9e408a92ed46c2be561f1bae8426abcd8ae7e257 Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Wed, 7 Jan 2026 17:49:12 -0500 Subject: [PATCH 6/9] kprobe: lint unused imports from uprobe copy+paste --- pyplugins/apis/kprobes.py | 4 ++-- pyplugins/testing/kprobes_test.py | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pyplugins/apis/kprobes.py b/pyplugins/apis/kprobes.py index d42d3baf2..11c8c68e4 100644 --- a/pyplugins/apis/kprobes.py +++ b/pyplugins/apis/kprobes.py @@ -3,10 +3,10 @@ :parser: myst_parser.sphinx_ """ -import os + from penguin import Plugin, plugins from penguin.plugin_manager import resolve_bound_method_from_class -from typing import Dict, List, Any, Union, Callable, Optional, Iterator +from typing import Dict, List, Any, Callable, Optional, Iterator from hyper.consts import igloo_hypercall_constants as iconsts from hyper.consts import portal_type from hyper.portal import PortalCmd diff --git a/pyplugins/testing/kprobes_test.py b/pyplugins/testing/kprobes_test.py index 4a54b7eb1..5dac71e88 100644 --- a/pyplugins/testing/kprobes_test.py +++ b/pyplugins/testing/kprobes_test.py @@ -3,9 +3,7 @@ """ from penguin import Plugin, plugins -from os.path import join, realpath, basename -from glob import glob -import functools +from os.path import join kprobes = plugins.kprobes mem = plugins.mem @@ -53,7 +51,7 @@ def kprobe_do_filp_open(self, pt_regs): if not pt_regs.is_enter: if self.open_ret_pid == current.pid: rval = int(self.panda.ffi.cast("target_long", pt_regs.get_return_value())) - if rval == -2: # -ENOENT + if rval == -2: # -ENOENT with open(join(self.outdir, "kprobe_open_test.txt"), "w") as f: f.write( f"kprobe open return test passed: do_filp_open called with pathname={self.filp_open_pathname} and rval={rval}\n" From 8c293839faab5e15c4b7d348b0f4c7cf72391682 Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Thu, 8 Jan 2026 11:13:52 -0500 Subject: [PATCH 7/9] adding ability to search for symbols to ctypes wrapper --- pyplugins/testing/kprobes_test.py | 1 + pyplugins/wrappers/ctypes_wrap.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/pyplugins/testing/kprobes_test.py b/pyplugins/testing/kprobes_test.py index 5dac71e88..fec9d6754 100644 --- a/pyplugins/testing/kprobes_test.py +++ b/pyplugins/testing/kprobes_test.py @@ -15,6 +15,7 @@ def __init__(self, panda): self.panda = panda self.outdir = self.get_arg("outdir") self.open_ret_pid = None + import IPython; IPython.embed() @kprobes.kprobe( symbol="do_execveat_common", diff --git a/pyplugins/wrappers/ctypes_wrap.py b/pyplugins/wrappers/ctypes_wrap.py index 16aac86c7..1fca0bfe7 100644 --- a/pyplugins/wrappers/ctypes_wrap.py +++ b/pyplugins/wrappers/ctypes_wrap.py @@ -963,6 +963,16 @@ def get_symbol(self, name: str) -> Optional[VtypeSymbol]: self._parsed_symbols_cache[name] = obj return obj + def search_symbols(self, keyword: str) -> List[VtypeSymbol]: + """Returns a list of symbols whose names contain the keyword.""" + results = [] + for name in self._raw_symbols: + if keyword in name: + sym = self.get_symbol(name) + if sym: + results.append(sym) + return results + def get_type(self, name: str) -> Optional[Union[VtypeUserType, VtypeBaseType, VtypeEnum]]: original_name = name name_lower = name.lower() @@ -1126,6 +1136,12 @@ def get_symbol(self, name: str): if res: return res + def search_symbols(self, keyword: str) -> List[VtypeSymbol]: + results = [] + for f in self._file_order: + results.extend(self.vtypejsons[f].search_symbols(keyword)) + return results + def get_type(self, name: str): for f in self._file_order: res = self.vtypejsons[f].get_type(name) @@ -1225,6 +1241,8 @@ def load_isf_json(json_input: Union[str, object]) -> VtypeJson: "--test-to-bytes", action="store_true", help="Run to_bytes() test.") cli_parser.add_argument("--test-base-enum-instance", action="store_true", help="Test creating instances of base/enum types.") + cli_parser.add_argument("--search-symbols", type=str, metavar="KEYWORD", + help="Search for symbols containing KEYWORD.") cli_parser.add_argument( "--get-type", type=str, help="Test the generic get_type method with the provided type name.") cli_parser.add_argument( @@ -1248,6 +1266,19 @@ def load_isf_json(json_input: Union[str, object]) -> VtypeJson: else: print(f" Type '{args.get_type}' not found.") + if args.search_symbols: + print(f"\n--- Searching symbols for keyword '{args.search_symbols}' ---") + matches = isf_data.search_symbols(args.search_symbols) + if matches: + print(f" Found {len(matches)} matching symbol(s):") + for i, sym in enumerate(matches): + if i >= 20: + print(f" ... and {len(matches) - 20} more.") + break + print(f" - {sym}") + else: + print(f" No symbols found containing '{args.search_symbols}'.") + if args.find_symbol_at is not None: print( f"\n--- Finding symbols at address {args.find_symbol_at:#x} ---") From 5e35401cd1740c4e2452e27402630bcfaa4c6dcc Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Thu, 8 Jan 2026 11:27:53 -0500 Subject: [PATCH 8/9] kprobes_test: find right symbol for do_exeveat_common --- pyplugins/testing/kprobes_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyplugins/testing/kprobes_test.py b/pyplugins/testing/kprobes_test.py index fec9d6754..ec0e590a7 100644 --- a/pyplugins/testing/kprobes_test.py +++ b/pyplugins/testing/kprobes_test.py @@ -15,13 +15,13 @@ def __init__(self, panda): self.panda = panda self.outdir = self.get_arg("outdir") self.open_ret_pid = None - import IPython; IPython.embed() - @kprobes.kprobe( - symbol="do_execveat_common", - on_enter=True, - on_return=False, - ) + # names can be like `do_execveat_common.isra.15` + for sym in plugins.kffi.ffi.search_symbols("do_execveat_common"): + if sym.name.startswith("do_execveat_common"): + plugins.kprobes.kprobe(symbol=sym.name, on_enter=True, on_return=False)(self.kprobe_do_execvat_common) + break + def kprobe_do_execvat_common(self, pt_regs): """ static int do_execveat_common(int fd, struct filename *filename, From 3a3a4c80ab2cbc2eaead449f5252515c58f996cb Mon Sep 17 00:00:00 2001 From: Zak Estrada Date: Sat, 10 Jan 2026 14:42:04 -0500 Subject: [PATCH 9/9] kprobes: trying mips kernel fix --- Dockerfile | 4 ++-- pyplugins/testing/kprobes_test.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index b7ed77afa..138555880 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,8 @@ ARG REGISTRY="docker.io" ARG BASE_IMAGE="${REGISTRY}/ubuntu:22.04" ARG VPN_VERSION="1.0.25" ARG BUSYBOX_VERSION="0.0.15" -ARG LINUX_VERSION="v3.5.19-beta" -ARG IGLOO_DRIVER_VERSION="v0.0.34-pre.491180425" +ARG LINUX_VERSION="v3.5.20-beta" +ARG IGLOO_DRIVER_VERSION="v0.0.34-pre.93935df6f" ARG LIBNVRAM_VERSION="0.0.23" ARG CONSOLE_VERSION="1.0.7" ARG GUESTHOPPER_VERSION="1.0.20" diff --git a/pyplugins/testing/kprobes_test.py b/pyplugins/testing/kprobes_test.py index ec0e590a7..fdfe9ff62 100644 --- a/pyplugins/testing/kprobes_test.py +++ b/pyplugins/testing/kprobes_test.py @@ -38,6 +38,7 @@ def kprobe_do_execvat_common(self, pt_regs): f"kprobe exec entry test passed: do_execveat_common called with filename={filename}\n" ) + # This tests aggregate probes as well as kretprobes @kprobes.kprobe( symbol="do_filp_open", on_enter=True,