42

我有一个正在建造的图书馆。当我运行以下任一对象时,我的所有对象都会连续编译和链接: ar rcs lib/libryftts.a $^

gcc -shared $^ -o lib/libryftts.so

在我的 Makefile 中。我也能够成功地将它们安装到/usr/local/lib 当我用 nm 测试文件时,所有功能都在那里。我的问题是,当我跑步gcc testing/test.c -lryftts -o test && file ./testgcc testing/test.c lib/libryftts.a -o test && file ./test 它说:

test: ELF 64-bit LSB shared object而不是test: ELF 64-bit LSB executable我所期望的。我究竟做错了什么?

4

2 回答 2

57

我究竟做错了什么?

没有。

听起来您的 GCC-pie默认配置为构建二进制文件。这些二进制文件实际上共享库(类型为ET_DYN),除了它们像普通可执行文件一样运行。

所以你应该只运行你的二进制文件,并且(如果它有效)不用担心它。

或者您可以将您的二进制文件与gcc -no-pie ...and 链接,它应该产生一个PIE类型ET_EXEC为 的不可执行文件,它file会说ELF 64-bit LSB executable.

于 2015-12-30T03:03:55.583 回答
30

file5.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_PIEELF 元数据,但由于提交9109a696f3289ba00eaa222fd432755ec4287e28的实现中存在错误,它实际上破坏了事情并将 GCC PIE 可执行文件显示为shared objects.

该错误已在 5.36 中的提交03084b161cf888b5286dbbcd964c31ccad4f64d9处修复。

该错误尤其存在于具有file5.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 选项是什么?

file5.36 行为细分

file在从源头研究如何工作之后。我们将得出结论:

  • 如果Elf32_Ehdr.e_type == ET_EXEC
    • 打印executable
  • 否则如果Elf32_Ehdr.e_type == ET_DYN
    • 如果DT_FLAGS_1存在动态部分条目
      • 如果DF_1_PIE设置在DT_FLAGS_1
        • 打印pie executable
      • 别的
        • 打印shared object
    • 别的
      • 如果文件可由用户、组或其他人执行
        • 打印pie executable
      • 别的
        • 打印shared object

这里有一些实验可以证实:

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 typeDF_1_PIE分别由以下决定:

readelf --file-header main.out | grep Type
readelf --dynamic     main.out | grep FLAGS_1

file5.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-bitfile

所以现在我们在那个文件中搜索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.cfunctiondodynamic中,如果存在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 提供信息。

于 2019-04-16T09:29:18.837 回答