ptrace 能否判断 x86 系统调用使用的是 64 位还是 32 位 ABI?

ame*_*eed 7 linux x86 ptrace x86-64 system-calls

我正在尝试使用 ptrace 来跟踪由单独进程发出的所有系统调用,无论是 32 位 (IA-32) 还是 64 位 (x86-64)。我的跟踪器将在启用 IA-32 仿真的 64 位 x86 安装上运行,但理想情况下能够跟踪 64 位和 32 位应用程序,包括 64 位应用程序是否分叉并执行 32 位进程.

问题是,由于 32 位和 64 位系统调用号不同,我需要知道进程是 32 位还是 64 位以确定它使用哪个系统调用,即使我有系统调用号。似乎有不完美的方法,例如检查/proc/<pid>/exec或(如 strace 那样)寄存器结构的大小,但没有什么可靠的。

更复杂的是,64 位进程可以从长模式切换到直接执行 32 位代码。他们还可以进行 32 位int $0x80系统调用,当然,使用 32 位系统调用号。我不“相信”我跟踪的进程不会使用这些技巧,所以我想正确检测它们。而且我已经独立验证,至少在后一种情况下,ptrace 看到的是 32 位系统调用号和参数寄存器分配,而不是 64 位的。

我在内核源代码中找到了 中的TS_COMPAT标志arch/x86/include/asm/processor.h,每当 64 位进程进行 32 位系统调用时,该标志似乎都会设置。唯一的问题是我不知道如何从用户空间访问这个标志,或者是否有可能。

我还考虑过阅读%cs并将其与$0x23or进行比较$0x33,受到这种在运行过程中切换位数的方法的启发。但这仅检测 32 位进程,不一定是来自 64 位进程的32 位系统调用(那些使用int $0x80)。它也很脆弱,因为它依赖于未记录的内核行为。

最后,我注意到 x86 架构在扩展功能启用寄存器 MSR 中有一点长模式。但是 ptrace 无法从被跟踪者读取 MSR,我觉得从我的跟踪器中读取它是不够的,因为我的跟踪器总是以长模式运行。

我不知所措。或许我可以尝试使用其中一种 hacks——在这一点上我倾向于%cs/proc/<pid>/exec方法——但我想要一些持久的东西,它实际上可以区分 32 位和 64 位系统调用。在 x86-64 下使用 ptrace 的进程如何能够可靠地确定该系统调用是使用 32 位 ( int $0x80) 还是 64 位 ( syscall) ABI 进行的?用户进程是否有其他方法可以获取有关它被授权进行 ptrace 的另一个进程的信息?

Pet*_*des 5

有趣的是,我没有意识到没有一种明显更智能的方法strace可以用来int 0x80从 64 位进程中正确解码。(这正在处理中,请参阅此答案以获取建议的内核补丁链接以添加PTRACE_GET_SYSCALL_INFO到 ptrace straceAPI。4.26 已经在补丁内核上支持它。)

更新:现在支持每个系统调用检测 IDK,哪个主线内核版本添加了该功能。我在 Arch Linux 内核版本 5.5 和strace版本 5.5上进行了测试。

例如,这个 NASM 源组装成一个静态可执行文件:

mov eax, 4
int 0x80
mov eax, 60
syscall
Run Code Online (Sandbox Code Playgroud)

给出了这个痕迹: nasm -felf64 foo.asm && ld foo.o && strace ./a.out

execve("./foo", ["./foo"], 0x7ffcdc233180 /* 51 vars */) = 0
strace: [ Process PID=1262249 runs in 32 bit mode. ]
write(0, NULL, 0)                       = 0
strace: [ Process PID=1262249 runs in 64 bit mode. ]
exit(0)                                 = ?
+++ exited with 0 +++
Run Code Online (Sandbox Code Playgroud)

strace每次系统调用使用与以前不同的 ABI 位时打印一条消息。请注意,关于在 32 位模式下运行的消息是完全错误的;它只是使用 64 位模式下的 32 位 ABI。“模式”对于 x86-64具有特定的技术含义,而这并非如此。


使用较旧的内核

作为一种解决方法,我认为您可以在 RIP 处反汇编代码并检查它是否是syscall指令 ( 0F 05),因为ptrace它确实让您读取目标进程的内存。

但是对于像禁止某些系统调用这样的安全用例,这很容易受到竞争条件的影响:系统调用进程中的另一个线程可以在syscall字节int 0x80执行之后重写它们,但在您可以使用ptrace.


只有当进程在 64 位模式下运行时才需要这样做,否则只有 32 位 ABI 可用。如果不是,则无需检查。(vdso 页面可能会syscall在支持它但不支持的 AMD CPU 上使用 32 位模式sysenter。首先不检查 32 位进程可以避免这种极端情况。)我想你是说你有一种可靠的方法来检测至少。

(我没有直接使用 ptrace API,只是类似的工具strace使用它。所以我希望这个答案有意义。)