If the execveat系统调用是用来启动一个新进程的,我们首先输入文件系统/exec.c在内核源代码中进入 SYSCALL_DEFINEx(execveat..) 函数。
然后这个函数调用这些函数:
The 搜索二进制处理程序迭代各种二进制处理程序。在 64 位 Linux 内核中,将有一个用于 64 位 ELF 的处理程序和一个用于 32 位 ELF 的处理程序。两个处理程序最终都是从同一源构建的文件系统/binfmt_elf.c。然而,32 位处理程序是通过构建的文件系统/compat_binfmt_elf.c它重新定义了之前的一些宏包括源文件binfmt_elf.c itself.
Inside binfmt_elf.c, elf_check_arch叫做。这是一个定义在的宏拱门/x86/include/asm/elf.h64 位处理程序与 32 位处理程序中的定义不同。对于 64 位,它与EM_X86_64(62 - 在 include/uapi/ilnux/elf-em.h 中定义)。对于 32 位,它与EM_386 (3) or EM_486(6)(在同一文件中定义)。如果比较失败,二进制处理程序就会放弃,因此我们最终只有一个处理程序负责 ELF 解析和执行 - 取决于 ELF 是 64 位还是 32 位。
因此,在 64 位 Linux 中解析 32 位 ELVE 与 64 位 ELVE 的所有差异都应该在该文件中找到文件系统/compat_binfmt_elf.c.
主要线索似乎是兼容启动线程. 启动线程被重新定义为兼容启动线程。该函数定义位于拱门/x86/内核/process_64.c. 兼容启动线程然后打电话启动线程公共有了这些论点:
start_thread_common(regs, new_ip, new_sp,
test_thread_flag(TIF_X32)
? __USER_CS : __USER32_CS,
__USER_DS, __USER_DS);
而正常的启动线程函数调用启动线程公共有了这些论点:
start_thread_common(regs, new_ip, new_sp,
__USER_CS, __USER_DS, 0);
在这里我们已经看到依赖于架构的代码做了一些事情CS64 位 ELVE 与 32 位 ELVE 的情况有所不同。
然后我们有 __USER_CS 和 __USER32_CS 的定义拱门/x86/include/asm/segment.h:
#define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS*8 + 3)
#define __USER32_CS (GDT_ENTRY_DEFAULT_USER32_CS*8 + 3)
and:
#define GDT_ENTRY_DEFAULT_USER_CS 6
#define GDT_ENTRY_DEFAULT_USER32_CS 4
So __USER_CS
是 6*8 + 3 = 51 = 0x33
And __USER32_CS
是 4*8 + 3 = 35 = 0x23
这些数字与这些示例中用于 CS 的数字相匹配:
- 用于在进程中间从 64 位模式切换到 32 位模式 https://stackoverflow.com/a/48855022/5744809
- 用于在进程中间从 32 位模式切换到 64 位模式 https://stackoverflow.com/a/48855906/5744809
由于CPU不是运行在实模式下,段寄存器填充的并不是段本身,而是一个16位选择器:
来自维基百科(保护模式 https://en.wikipedia.org/wiki/Protected_mode):
在保护模式下,segment_part 被 16 位选择器取代,其中 13 位高位(位 3 至位 15)包含描述符表内条目的索引。下一位(位 2)指定该操作是与 GDT 还是与 LDT 一起使用。选择器的最低两位(位 1 和位 0)组合起来定义请求的权限,其中 0 和 3 的值分别表示最高和最低权限。
当 CS 值为 0x23 时,位 1 和 0 是3,意思是“最低权限”。位 2 是0, 意义GDT,位 3 到位 15 是 4,这意味着我们得到index 4来自全局描述符表(GDT)。
这就是迄今为止我能够挖掘的距离。