file
5.36说的很清楚
file
如果可执行文件是 PIE,5.36 实际上会清楚地打印出来,如下所示:https ://unix.stackexchange.com/questions/89211/how-to-test-whether-a-linux-binary-was-compiled-as-位置无关代码/435038#435038
例如,一个 PIE 可执行文件显示为:
main.out:ELF 64 位 LSB pie 可执行文件,x86-64,版本 1 (SYSV),动态链接,未剥离
和非 PIE 的:
main.out:ELF 64 位 LSB 可执行文件,x86-64,版本 1 (SYSV),静态链接,未剥离
该功能是在 5.33 中引入的,但它只是做了一个简单的chmod +x
检查。在此之前,它只是shared object
为 PIE 打印的。
在 5.34 中,它旨在开始检查更专业的DF_1_PIE
ELF 元数据,但由于提交9109a696f3289ba00eaa222fd432755ec4287e28的实现中存在错误,它实际上破坏了事情并将 GCC PIE 可执行文件显示为shared objects
.
该错误已在 5.36 中的提交03084b161cf888b5286dbbcd964c31ccad4f64d9处修复。
该错误尤其存在于具有file
5.34 的 Ubuntu 18.10 中。
ld -pie
由于巧合,它在链接汇编代码时不会表现出来。
源代码细分显示在file
此答案的“5.36 源代码分析”部分。
Linux 内核 5.0 判断是否可以使用 ASLR 基于ET_DYN
file
“混乱”的根本原因是PIE 可执行文件和共享库都是位置独立的,并且可以放置在随机的内存位置中。
在fs/binfmt_elf.c内核只接受这两种类型的 ELF 文件:
/* First of all, some simple consistency checks */
if (interp_elf_ex->e_type != ET_EXEC &&
interp_elf_ex->e_type != ET_DYN)
goto out;
然后,只有 forET_DYN
才将其设置load_bias
为不为零的值。然后load_bias
是什么决定了 ELF 偏移量:如何在 Linux 中确定 PIE 可执行文件的文本部分的地址?
/*
* If we are loading ET_EXEC or we have already performed
* the ET_DYN load_addr calculations, proceed normally.
*/
if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
elf_flags |= elf_fixed;
} else if (loc->elf_ex.e_type == ET_DYN) {
/*
* This logic is run once for the first LOAD Program
* Header for ET_DYN binaries to calculate the
* randomization (load_bias) for all the LOAD
* Program Headers, and to calculate the entire
* size of the ELF mapping (total_size). (Note that
* load_addr_set is set to true later once the
* initial mapping is performed.)
*
* There are effectively two types of ET_DYN
* binaries: programs (i.e. PIE: ET_DYN with INTERP)
* and loaders (ET_DYN without INTERP, since they
* _are_ the ELF interpreter). The loaders must
* be loaded away from programs since the program
* may otherwise collide with the loader (especially
* for ET_EXEC which does not have a randomized
* position). For example to handle invocations of
* "./ld.so someprog" to test out a new version of
* the loader, the subsequent program that the
* loader loads must avoid the loader itself, so
* they cannot share the same load range. Sufficient
* room for the brk must be allocated with the
* loader as well, since brk must be available with
* the loader.
*
* Therefore, programs are loaded offset from
* ELF_ET_DYN_BASE and loaders are loaded into the
* independently randomized mmap region (0 load_bias
* without MAP_FIXED).
*/
if (elf_interpreter) {
load_bias = ELF_ET_DYN_BASE;
if (current->flags & PF_RANDOMIZE)
load_bias += arch_mmap_rnd();
elf_flags |= elf_fixed;
} else
load_bias = 0;
我在实验上证实了这一点:gcc 和 ld 中与位置无关的可执行文件的 -fPIE 选项是什么?
file
5.36 行为细分
file
在从源头研究如何工作之后。我们将得出结论:
- 如果
Elf32_Ehdr.e_type == ET_EXEC
- 否则如果
Elf32_Ehdr.e_type == ET_DYN
- 如果
DT_FLAGS_1
存在动态部分条目
- 如果
DF_1_PIE
设置在DT_FLAGS_1
:
- 别的
- 别的
这里有一些实验可以证实:
Executable generation ELF type DT_FLAGS_1 DF_1_PIE chdmod +x file 5.36
--------------------------- -------- ---------- -------- -------------- --------------
gcc -fpie -pie ET_DYN y y y pie executable
gcc -fno-pie -no-pie ET_EXEC n n y executable
gcc -shared ET_DYN n n y pie executable
gcc -shared ET_DYN n n n shared object
ld ET_EXEC n n y executable
ld -pie --dynamic-linker ET_DYN y y y pie executable
ld -pie --no-dynamic-linker ET_DYN y y y pie executable
在 Ubuntu 18.10、GCC 8.2.0、Binutils 2.31.1 中测试。
每种实验的完整测试示例在以下位置进行了描述:
ELF type
并DF_1_PIE
分别由以下决定:
readelf --file-header main.out | grep Type
readelf --dynamic main.out | grep FLAGS_1
file
5.36 源码分析
要分析的关键文件是magic/Magdir/elf。
这种神奇的格式仅根据固定位置的字节值确定文件类型。
格式本身记录在:
man 5 magic
因此,此时您需要准备好以下文档:
在文件末尾,我们看到:
0 string \177ELF ELF
!:strength *2
>4 byte 0 invalid class
>4 byte 1 32-bit
>4 byte 2 64-bit
>5 byte 0 invalid byte order
>5 byte 1 LSB
>>0 use elf-le
>5 byte 2 MSB
>>0 use \^elf-le
\177ELF
是每个 ELF 文件开头的 4 个魔术字节。\177
是 的八进制数0x7F
。
然后通过与Elf32_Ehdr
标准中的 struct 比较,我们看到第 4 个字节(第 5 个字节,魔术标识符之后的第一个字节)确定了 ELF 类:
e_ident[EI_CLASSELFCLASS]
它的一些可能值是:
ELFCLASS32 1
ELFCLASS64 2
那么在file
源代码中,我们有:
1 32-bit
2 64-bit
和是输出到标准输出32-bit
的字符串!64-bit
file
所以现在我们在那个文件中搜索shared object
,我们被引导到:
0 name elf-le
>16 leshort 0 no file type,
!:mime application/octet-stream
>16 leshort 1 relocatable,
!:mime application/x-object
>16 leshort 2 executable,
!:mime application/x-executable
>16 leshort 3 ${x?pie executable:shared object},
所以这elf-le
是某种标识符,包含在代码的前一部分中。
字节 16 正是 ELF 类型:
Elf32_Ehdr.e_type
它的一些价值是:
ET_EXEC 2
ET_DYN 3
因此,ET_EXEC
总是打印为executable
.
ET_DYN
但是有两种可能性,具体取决于${x
:
pie executable
shared object
${x
问:文件是否可由用户、组或其他人执行?如果是,则显示pie executable
,否则shared object
。
这种扩展是在varexpand
函数中完成的src/softmagic.c
:
static int
varexpand(struct magic_set *ms, char *buf, size_t len, const char *str)
{
[...]
case 'x':
if (ms->mode & 0111) {
ptr = t;
l = et - t;
} else {
ptr = e;
l = ee - e;
}
break;
然而,还有一个技巧!在src/readelf.c
functiondodynamic
中,如果存在DT_FLAGS_1
动态部分 ( PT_DYNAMIC
) 的标志条目,则其中的权限st->mode
会被DF_1_PIE
标志的存在或不存在覆盖:
case DT_FLAGS_1:
if (xdh_val & DF_1_PIE)
ms->mode |= 0111;
else
ms->mode &= ~0111;
break;
5.34 的 bug 是初始代码写成:
if (xdh_val == DF_1_PIE)
这意味着如果设置了另一个标志,GCC 在默认情况下会这样做,则可DF_1_NOW
执行文件显示为shared object
.
ELF 标准中DT_FLAGS_1
没有描述 flags 条目,因此它必须是 Binutils 扩展。
该标志在 Linux 内核 5.0 或 glibc 2.27 中没有用处,所以我似乎纯粹是为了表明文件是否为 PIE 提供信息。