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。
备注:内容仅供参考。
