PAYATHON 2026

Linux 内核 ROP 调试中 RBP 与 panic 日志为何不一致?

数据小许

结论

日志中的 RBP=0x3039 与函数执行过程一致:prepare_kernel_cred 的序言包含 mov rbp, rdi,而函数入口处的 RDI 是 0x3039。panic 日志记录的是异常发生时的寄存器状态,不是 GDB 停在函数入口时的快照。日志显示,执行已进入 prepare_kernel_cred,并在之后调用 get_task_cred 时发生页错误。

问题出在参数上:0x3039 不是空值,也不是有效的 task_struct *。该内核版本支持调用 prepare_kernel_cred(NULL),但传入非零哨兵值后,函数会把它当作任务指针处理,最终访问无效地址。

如何解读日志

调试器显示的指令序列是:

push rbp
mov rbp, rdi

如果 GDB 此时停在 push rbp 执行前,显示的就是执行前状态。panic 日志记录的则是异常发生时的状态:

RDI: 0000000000003039
RBP: 0000000000003039
RIP: 0010:get_task_cred+0x1/0x40
CR2: 0000000000003779

异常发生时,RDI 和 RBP 都是 0x3039,指令流也已从 prepare_kernel_cred 进入 get_task_cred。这表明 mov rbp, rdi 已经执行,寄存器值的变化有对应的指令。

CR2 记录触发页错误的线性地址。结合 RDI=0x3039 和调用栈,可以判断非空参数被当作任务指针使用,导致了无效访问。仅凭这段日志无法确定哪个字段访问产生了 0x3779;要确认地址的计算过程,需要查看这份内核构建对应的反汇编和指令。

为什么 ni 后看到更深处的异常

ni 是 GDB 的 nexti 命令。它通常执行一条机器指令;遇到 call 时会越过被调用函数。从 push rbp 执行前开始单步,正常情况下应停在下一条指令附近,不会直接跑到 get_task_cred。

panic 日志表明来宾内核确实继续执行到了函数内部,但单凭这段日志无法解释 GDB 为什么没有在预期位置停下。调试器显示的入口快照和异常日志可能对应不同的执行时刻。还需要确认单步命令作用于当前目标、目标在命令后重新停住,并且日志来自同一 CPU 和同一调试会话。异常发生后,可以先比较当前 $rip 和预期的下一条指令,再检查目标是否仍处于停止状态,以判断单步是否按预期完成。

处理方向

  • 不要用 0x3039 代替空指针。这个调用的参数应符合 prepare_kernel_cred(NULL) 的预期;非零哨兵值会被当作指针处理。
  • 调用前核对传入参数,并分别记录函数入口和单步停止后的 RIP、RDI、RBP,以区分执行前状态和 panic 时的状态。
  • 如果从函数入口执行一次 ni 后仍然直接 panic,检查 GDB 是否在正确的 QEMU 目标上单步,以及 panic 是否来自同一 CPU、同一轮执行。
  • 使用与运行内核匹配的符号和反汇编,确认 get_task_cred 附近实际执行的访存指令。不同构建的指令布局可能不同,不能只根据函数偏移推断具体字段。

额外的 ret 没有改变现象,这与日志显示的参数问题一致:调整栈对齐不会把无效的非空指针变成 NULL。

备注:内容仅供参考。