【问题标题】:Can ptrace tell if an x86 system call used the 64-bit or 32-bit ABI?ptrace 可以判断 x86 系统调用使用的是 64 位还是 32 位 ABI?
【发布时间】:2019-04-26 15:09:24
【问题描述】:

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

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

更复杂的是,64 位进程可以switch out of long mode 直接执行 32 位代码。他们也可以make 32-bit int $0x80 syscalls,当然,它使用 32 位系统调用号。我不“相信”我追踪的过程不使用这些技巧,所以我想正确地检测它们。而且我已经独立验证,至少在后一种情况下,ptrace 看到的是 32 位系统调用号和参数寄存器分配,而不是 64 位的。

我在内核源代码中四处寻找,发现arch/x86/include/asm/processor.h 中的TS_COMPAT 标志,每当64 位进程进行32 位系统调用时,它似乎是set。唯一的问题是我不知道如何从用户区访问这个标志,或者是否有可能。

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

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

我很茫然。也许我可以尝试使用其中一种技巧——此时我倾向于%cs/proc/<pid>/exec 方法——但我想要一些能够真正区分32 位和64 位系统调用的持久性。 在 x86-64 下使用 ptrace 的进程如何检测到其被跟踪者进行了系统调用,如何可靠地确定该系统调用是使用 32 位 (int $0x80) 还是 64 位 (syscall) 进行的ABI? 用户进程是否有其他方法可以获取有关它被授权 ptrace 的另一个进程的信息?

【问题讨论】:

  • 有一个提议的内核补丁可以添加一个PTRACE_GET_SYSCALL_INFO 请求来帮助解决这个问题。
  • @MarkPlotnick 这看起来很棒,感谢您指出!如果它被合并,我可以使用该请求作为旧解决方案的替代方案
  • 更新:PTRACE_GET_SYSCALL_INFO 在 Linux 5.3 中。

标签: linux x86 x86-64 system-calls ptrace


【解决方案1】:

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

更新:现在支持 per-syscall 检测 IDK 是哪个主线内核版本添加了该功能。我在内核版本 5.5 和 strace 版本 5.5 的 Arch Linux 上进行了测试。

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

mov eax, 4
int 0x80
mov eax, 60
syscall

给出这个踪迹: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 +++

strace 每次系统调用使用与以前不同的 ABI 位数时都会打印一条消息。请注意,关于 runs in 32 bit mode 的消息是完全错误的;它只是从 64 位模式使用 32 位 ABI。 “模式”有a specific technical meaning for x86-64,不是这个。


使用旧内核

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

但是对于像disallowing some system calls 这样的安全用例,这将容易受到竞争条件的影响:系统调用进程中的另一个线程可以在它们执行之后将syscall 字节重写为int 0x80,但在你可以偷看之前在他们那里ptrace


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

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

【讨论】:

  • 检查 %rip/%eip 的操作码我没有想到,但这很有意义!感谢您的洞察力。我很欣赏提到那个极端情况——我希望能够使用操作码扫描作为我的主要方法,但看起来我需要先检查进程的位数,然后再委托对 64 位进程进行操作码扫描.无论如何,感谢您的帮助!
  • 所以,要明确一点——您是说在 32 位进程的 vDSO 页面(但不是 64 位进程的页面)中,syscall 指令使用 32 位 ABI ?这是一个有趣的不一致。
  • @ameed:AMD 的 32 位模式 syscall 基本上是一条不同的指令,尽管它与 64 位 syscall 具有相同的助记符和相同的操作码。它显然不能在纯 32 位 CPU 上的传统模式下使用 R11d,因为该寄存器不存在。然而,内核端在兼容模式下与传统模式不同,IIRC Linux 甚至没有在传统模式下使用它,因为它设计得太糟糕而无法使用。但如果 sysenter 不可用,它将处于兼容模式。 Syscall or sysenter on 32 bits Linux?
  • @ameed:另请参阅What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code? 以获取 Linux 的 entry_64_compat.S(32 位 ABI 入口点到 64 位内核)和 entry_32.S(32 位 ABI 入口点到32 位内核)。特别是 github.com/torvalds/linux/blob/… 用于将 syscall 从 32 位进程转换为 64 位内核,其中解释了 32 位 Linux 内核禁用 32 位 syscall,因为它设计得太差了。
  • 我会看看这些。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-11
  • 2018-06-14
  • 2014-05-06
  • 1970-01-01
  • 2022-01-25
  • 1970-01-01
  • 2016-02-07
相关资源
最近更新 更多