我目前正在开发一个项目,该项目依赖于检测可执行文件属于哪个操作系统。
我只处理 ELF 可执行格式,所以我尝试使用
e_ident[EI_OSABI]
值,但没有给出健康的结果。
此外,
PT_INTERP
部分不符合作为解决方案的资格,因为它不提供有关共享库的信息(.so
),有时链接器的名称不包括内核的名称(示例:/lib/ld-musl-x86_64.so.1
。它不不包含内核名称,如 /lib64/ld-linux-x86-64.so.2
。)。
我认为第三种方法可以找到仅存在于一个内核上并始终添加到可执行文件中的系统调用。
如果我们举个例子来让它容易理解:
foo
的系统调用,并且每个 Linux 可执行文件和 Linux 共享库始终使用此系统调用bar
的系统调用,并且每个 FreeBSD 可执行文件和 FreeBSD 共享库始终使用此系统调用baz
的系统调用,并且每个 OpenBSD 可执行文件和 OpenBSD 共享库始终使用此系统调用如果我在可执行文件中找到
foo
、bar
、baz
系统调用之一,我可以检测到它的操作系统。
我的问题是:
foo
始终存在于 .dsym
部分)提前致谢!
不幸的是,系统调用没有命名,而是编号。例如,在 Linux x86-64 上,要调用
read(2)
,请使用系统调用 0,要调用 write(2)
,请使用系统调用 1。但是,在 FreeBSD 上,这些分别是系统调用 3 和 4。通常有一个标头 sys/syscall.h
提供值。
此外,您还可以高兴的是,系统调用可以根据体系结构而变化,至少在 Linux 上,并且某些体系结构具有系统调用,而其他体系结构则没有。例如,在 32 位 x86 上有 32 位和 64 位版本的
stat
,但由于 32 位版本(只能处理最大 2^31-1 字节的文件)已过时且无用,x86-64懒得去实现这样的事情,并且stat
系统调用在x86-64上始终是64位的。
此外,通常系统调用位于 libc 中,因为通常调用 C 函数,然后 libc 拥有关于系统调用号对应什么的所有知识。某些操作系统(例如 Linux)允许用户从其二进制文件直接进行系统调用;然而,其他的,比如 OpenBSD,不这样做,如果你尝试的话,内核会杀死你的进程。因此,在大多数情况下,可执行文件本身不包含任何实际的系统调用。
如果您的目标是检测二进制文件,您将需要多管齐下的方法,因为单一方法是不够的。首先,当 ELF 标头中的操作系统 ABI 不是 SysV 时,它通常是正确的。例如,这是检测 FreeBSD 的好方法。 (但是,您还应该使用
PT_INTERP
,它也将提供合适的上下文。确实,musl 的 ld.so 名称中不包含 linux
,但您知道如果它是 musl,那么它就是 Linux。
如果它是可执行文件,您可能还想查看 libc 值。有时
PT_INTERP
可能是通用的(例如,在 FreeBSD 上,它是非常有用的 /libexec/ld-elf.so.1
),但您也许能够区分操作系统与其 libc 版本。有些系统具有版本符号,因此查看版本符号可能会有所帮助。许多系统还有一个特殊的注释部分(例如,MirBSD 有 .note.miros.ident
)。
如果二进制文件是静态的,您将不会有 libc 或
PT_INTERP
,因此您可能需要更多查看。静态 Go 二进制文件有一个 .go.buildinfo
部分,其中包含您可以使用的 GOOS=
值(例如 GOOS=linux
)。
但是,幸运的是,在大多数情况下,
file
只需在二进制文件上运行即可为您提供此信息。然而,并非在所有情况下(MirBSD 就是一个很好的例子),所以如果你想处理所有 ELF 二进制文件,你真的必须退回到一些更复杂的探索。