pwndbg ksighand 命令详解:在内核调试会话中查看 Linux 用户任务的信号处理器
【免费下载链接】pwndbgExploit Development and Reverse Engineering with GDB & LLDB Made Easy项目地址: https://gitcode.com/GitHub_Trending/pw/pwndbg
本指南系统讲解 pwndbg 的ksighand命令——用于在 QEMU 内核调试场景下展示指定用户任务(user task)的信号处理器(signal handler)信息。你将掌握该命令的完整用法、输出字段含义、底层内核结构(sighand_struct/struct k_sigaction)的解析原理,以及它与ktask、kcurrent等内核命令的配合方式,可直接用于内核漏洞利用与逆向分析中的进程信号状态审计。
ksighand 是什么
ksighand是 pwndbg 内核命令族(KERNEL 分类)的一员,功能为"Displays information about the signal handlers of a user task"(展示用户任务的信号处理器信息)。它读取内核task_struct中的sighand指针所指向的struct sighand_struct,把其中action[_NSIG]数组记录的每个信号的处理函数地址与标志位枚举出来,并以人类可读的方式呈现。
在内核调试(尤其是通过 QEMU 加载内核镜像调试)时,你往往需要确认某个进程对SIGSEGV、SIGABRT等信号注册了什么样的处理逻辑:是默认行为(SIG_DFL)、忽略(SIG_IGN),还是指向用户态/内核态某地址的自定义 handler。这正是ksighand的输出内容。
命令用法与参数
ksighand的完整用法如下(摘自 docs/commands/kernel/ksighand.md):
usage: ksighand [-h] [pid]位置参数
| 位置参数 | 说明 |
|---|---|
pid | 目标任务(线程)的进程号;省略时默认使用"当前内核任务"(见下文kcurrent协作一节) |
可选参数
| 短选项 | 长选项 | 说明 |
|---|---|---|
-h | --help | 显示帮助信息并退出 |
命令定义位于 pwndbg/commands/kcurrent.py,其 argparse 解析器描述与帮助文本与上述文档自动生成部分完全一致(该文档由scripts/generate-docs.sh自动维护)。
典型调用示例:
pwndbg> ksighand 1234 # 查看 pid=1234 任务的信号处理器 pwndbg> ksighand # 查看当前内核任务 pwndbg> ksighand -h # 查看帮助输出内容解读
ksighand首先打印任务头部信息(线程地址、pid、cpu、uid/gid、是否用户任务等),随后以缩进方式逐行列出信号处理器表。每一行的格式为:
<信号名> <sa_flags> <sa_handler>以源码逻辑(pwndbg/commands/kcurrent.py)为准:
- 信号名:通过
pwndbg.aglib.signal.PER_ARCH_SIGNAL_MAPPINGS[pwndbg.aglib.arch.name]将信号编号映射为SIGxxx名称,以蓝色显示并左对齐填充({m[i]:<10}); sa_flags:信号处理标志位,以黄色 16 进制显示(f"0x{flags:016x}");sa_handler:处理函数地址,按以下规则渲染:- 值为
0→ 红色SIG_DFL(默认处理); - 值为
1→ 红色SIG_IGN(忽略); - 其他值 → 通过
pwndbg.chain.format()格式化为地址(即自定义 handler 或内核统一处理入口)。
- 值为
这里0与1对应内核约定的SIG_DFL与SIG_IGN哨兵值。可以理解为:一行输出即完整描述了该任务对某个信号"发生时的处置策略"。
底层实现:从 pid 到 signal 表的完整链路
1. 任务选择:select_kthread_from_pid
命令的第一步是确定要分析的线程对象(pwndbg/commands/kcurrent.py):
- 若未传
pid,优先返回此前通过kcurrent --set设定的内核任务(get_kcurrent());若未设定,则取当前 CPU 上的任务并校验其task_struct指针位于内核地址空间; - 若传了
pid,则遍历pwndbg.commands.ktask.get_ktasks()返回的所有Ktask,在其中所有线程里匹配pid,找不到时打印警告ktask with pid {pid} not found。
选中后若thread.user_task为假(即task_struct->mm == 0,属于内核线程而非用户任务),命令会打印not user task并提前返回——因为只有用户任务才持有完整的信号处理上下文。该判断来自 pwndbg/commands/ktask.py:
@property def user_task(self) -> bool: return int(self.thread["mm"]) != 02. 读取信号表:Kthread.sighand生成器
任务线程对象由Kthread类封装(pwndbg/commands/ktask.py)。其sighand属性是一个生成器:
@property def sighand(self) -> Generator[tuple[int, int], None, None]: if not self.thread["sighand"].dereference().type.has_field("action"): return sighand = self.thread["sighand"]["action"] for i in range(sighand.type.array_len): action = sighand[i]["sa"] yield (int(action["sa_handler"]), int(action["sa_flags"]))即遍历sighand->action[]数组(长度_NSIG),逐个取出struct k_sigaction中内嵌struct sigaction sa的sa_handler与sa_flags。
3. 索引偏移:signal - 1
命令实现中有明确的注释与逻辑(pwndbg/commands/kcurrent.py):
# `sighand->action` is indexed by `signal - 1`, so entry `i` describes signal `i + 1` for i, (handler, flags) in enumerate(thread.sighand, start=1): if i not in m: continue由于内核中action[]数组以下标信号号 - 1存储,因此第i个槽位描述的是信号i+1。遍历时从 1 开始枚举,并跳过当前架构不支持的信号编号(if i not in m: continue)。
4. 信号编号映射表
信号名映射定义于 pwndbg/aglib/signal.py:
PER_ARCH_SIGNAL_MAPPINGS: dict[..., dict[int, SIGNALS]] = { "x86-64": COMMON_NUM_TO_SIGNAL_MAPPING, "aarch64": COMMON_NUM_TO_SIGNAL_MAPPING, }COMMON_NUM_TO_SIGNAL_MAPPING覆盖 1–31 号常规信号(如 1=SIGHUP、2=SIGINT、6=SIGABRT、8=SIGFPE、11=SIGSEGV、15=SIGTERM 等),并额外内置 12 个"共享"编号映射作为兜底(SHARED_NUM_TO_SIGNAL_MAPPING)。也就是说,当前ksighand的完整映射支持面向x86-64 与 aarch64两个架构,其他架构在命令执行时因映射表缺失会被跳过。
内核结构解析:sighand_struct 与 k_sigaction
ksighand之所以能直接按字段名读取action数组,依赖 pwndbg 的**类型信息恢复(typeinfo recovery)**机制:在无调试符号的内核上,pwndbg 会从内存中启发式重建内核结构体定义,其中就包含信号相关结构(pwndbg/aglib/kernel/ktask.py):
#define _NSIG 64 #define _NSIG_BPW (ptrsize 位数) #define _NSIG_WORDS (_NSIG / _NSIG_BPW) struct sigaction { __sighandler_t sa_handler; unsigned long sa_flags; #ifdef __ARCH_HAS_SA_RESTORER // x86-64 / aarch64 均定义 __sigrestore_t sa_restorer; #endif sigset_t sa_mask; /* mask last for extensibility */ }; struct k_sigaction { struct sigaction sa; }; struct sighand_struct { char _pad[off]; struct k_sigaction action[_NSIG]; };其中_pad偏移通过启发式扫描(寻找sighand内存中的双向链表指针)确定,__ARCH_HAS_SA_RESTORER仅在 x86-64/aarch64 上定义,与内核源码中__ARCH_HAS_SA_RESTORER的架构约定一致。同时,恢复出的task_struct中信号相关字段布局也与内核一致(pwndbg/aglib/kernel/ktask.py):
/* Signal handlers: */ struct signal_struct *signal; struct sighand_struct __rcu *sighand; sigset_t blocked; sigset_t real_blocked;整体恢复流程由recover_ktask_typeinfo()驱动(pwndbg/aglib/kernel/ktask.py),它依次推断mm、tasks、comm、pid、files、nsproxy等字段偏移后,最终把sighand_struct并入生成的task_struct定义中。这也解释了为什么该命令要求内核符号与分页开启——恢复流程需要init_mm、init_task等符号作为锚点。
运行前置条件
ksighand被四组装饰器约束(定义于 pwndbg/commands/init.py,挂载于 pwndbg/commands/kcurrent.py):
| 装饰器 | 校验内容 | 不满足时的表现 |
|---|---|---|
@OnlyWhenQemuKernel | 当前目标必须是 QEMU 内核调试(qemu.is_qemu_kernel()) | 报错:仅可在 QEMU 中调试 Linux 内核时运行 |
@OnlyWhenPagingEnabled | 内核分页已开启(paging_enabled()) | 报错:仅可在分页开启时运行 |
@OnlyWithKernelSymbols | 内核具有调试符号(has_debug_symbols()) | 报错并提示使用 vmlinux-to-elf 提取符号或自行编译内核 |
@WarnOnKernelConfigRandstruct | 无调试信息且内核开启CONFIG_RANDSTRUCT | 打印警告:输出可能不准确 |
因此该命令面向带符号的 QEMU Linux 内核调试会话;在普通用户态进程调试中无法使用。若目标内核启用了CONFIG_RANDSTRUCT(结构体字段随机化)且无调试信息,命令仍会尝试运行,但会提示结果可能不准确。
与其他内核命令的协作
ksighand属于 pwndbg 内核命令生态的一部分,与之最常搭配的命令有:
ktask:枚举所有内核任务及其线程(pwndbg/commands/ktask.py),输出中包含每个线程的 pid、cpu、uid/gid、[user task]标记——先用它定位目标 pid,再交给ksighand;kcurrent [--set pid]:查看或设定"当前内核任务"。--set会把指定任务设定为多个命令(kfile、kstack、knamespace、ksighand、kseccomp、pagewalk、vmmap)的默认作用对象(pwndbg/commands/kcurrent.py)。设定后直接执行ksighand即可免去每次传 pid;kseccomp:展示用户任务的 seccomp 过滤器,与信号表配合可完整刻画进程的系统调用与信号处置策略;kstack/kfile/knamespace:分别查看同一任务的栈、文件描述符与命名空间,共同构成任务级的取证视图。
它们共享同一套select_kthread_from_pid选择逻辑与Kthread封装,因此任务定位行为完全一致。
典型应用场景
在内核漏洞利用与逆向中,ksighand的典型价值包括:
- 确认目标进程是否安装自定义信号处理:例如某服务进程对
SIGSEGV设置了 handler(输出为具体地址而非SIG_DFL),则该地址指向的可能是其自身的崩溃恢复逻辑或沙箱兜底代码,值得进一步在用户态内存中分析; - 区分信号处置策略:快速区分
SIG_DFL/SIG_IGN/ 自定义 handler 三类处置,辅助判断进程对异常(如 0 地址解引用)的敏感度; - 沙箱/反调试审计:结合
sa_flags(如是否设置SA_SIGINFO、SA_RESTORER相关标志位)分析 handler 的调用约定与恢复机制,为 ROP/信号驱动的攻击路径设计提供依据; - 无符号内核取证:在仅有符号(甚至无调试信息、依赖类型恢复)的内核上,无需 VMLINUX DWARF 即可枚举任意用户任务的完整信号表。
局限与注意事项
- 仅对用户任务有效:内核线程(
mm == 0)会得到not user task提示,不会输出信号表; - 信号名映射仅覆盖x86-64 与 aarch64,其他架构下可能输出为空或仅显示部分信号;
- 输出依赖类型恢复的准确性:若内核开启
CONFIG_RANDSTRUCT且无调试信息,字段偏移推断可能失真,输出仅作参考; sa_handler若为自定义地址,pwndbg.chain.format只展示地址格式化结果,命令本身不反汇编 handler 内容——如需进一步分析,可结合nearpc或内存转储命令查看该地址处的指令。
小结
ksighand以极简的ksighand [pid]接口,把内核sighand_struct中 64 槽位的信号处理表完整呈现给调试者。其实现贯通了 pwndbg 的类型恢复、内核任务枚举、按架构的信号名映射三条核心链路,是 QEMU 内核调试场景下审计进程信号处置策略的实用工具。配合ktask定位线程、kcurrent --set固定目标,即可在几秒内获得目标进程的完整信号处置画像。
【免费下载链接】pwndbgExploit Development and Reverse Engineering with GDB & LLDB Made Easy项目地址: https://gitcode.com/GitHub_Trending/pw/pwndbg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考