checking if a binary compiled with “-static”

后端 未结 3 1716
独厮守ぢ
独厮守ぢ 2021-02-19 05:20

I have a binary file in linux. How can I check whether it has been compiled with \"-static\" or not?

3条回答
  •  一向
    一向 (楼主)
    2021-02-19 05:44

    Check if it has a program header of type INTERP

    At the lower level, an executable is static if it does not have a program header with type:

    Elf32_Phd.p_type == PT_INTERP
    

    This is mentioned in the System V ABI spec.

    Remember that program headers determine the ELF segments, including those of type PT_LOAD that will get loaded in to memory and be run.

    If that header is present, its contents are exactly the path of the dynamic loader.

    readelf check

    We can observe this with readelf. First compile a C hello world dynamically:

    gcc -o main.out main.c
    

    and then:

    readelf --program-headers --wide main.out
    

    outputs:

    Elf file type is DYN (Shared object file)
    Entry point 0x1050
    There are 11 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
      PHDR           0x000040 0x0000000000000040 0x0000000000000040 0x000268 0x000268 R   0x8
      INTERP         0x0002a8 0x00000000000002a8 0x00000000000002a8 0x00001c 0x00001c R   0x1
          [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
      LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x000560 0x000560 R   0x1000
      LOAD           0x001000 0x0000000000001000 0x0000000000001000 0x0001bd 0x0001bd R E 0x1000
      LOAD           0x002000 0x0000000000002000 0x0000000000002000 0x000150 0x000150 R   0x1000
      LOAD           0x002db8 0x0000000000003db8 0x0000000000003db8 0x000258 0x000260 RW  0x1000
      DYNAMIC        0x002dc8 0x0000000000003dc8 0x0000000000003dc8 0x0001f0 0x0001f0 RW  0x8
      NOTE           0x0002c4 0x00000000000002c4 0x00000000000002c4 0x000044 0x000044 R   0x4
      GNU_EH_FRAME   0x00200c 0x000000000000200c 0x000000000000200c 0x00003c 0x00003c R   0x4
      GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
      GNU_RELRO      0x002db8 0x0000000000003db8 0x0000000000003db8 0x000248 0x000248 R   0x1
    
     Section to Segment mapping:
      Segment Sections...
       00
       01     .interp
       02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
       03     .init .plt .plt.got .text .fini
       04     .rodata .eh_frame_hdr .eh_frame
       05     .init_array .fini_array .dynamic .got .data .bss
       06     .dynamic
       07     .note.ABI-tag .note.gnu.build-id
       08     .eh_frame_hdr
       09
       10     .init_array .fini_array .dynamic .got
    

    so note the INTERP header is there, and it is so important that readelf even gave a quick preview of its short 28 (0x1c) byte contents: /lib64/ld-linux-x86-64.so.2, which is the path to the dynamic loader (27 bytes long + 1 for \0).

    Note how this resides side by side with the other segments, including e.g. those that actually get loaded into memory such as: .text.

    We can then more directly extract those bytes without the preview with:

    readelf -x .interp main.out
    

    which gives:

    Hex dump of section '.interp':
      0x000002a8 2f6c6962 36342f6c 642d6c69 6e75782d /lib64/ld-linux-
      0x000002b8 7838362d 36342e73 6f2e3200          x86-64.so.2.
    

    as explained at: How can I examine contents of a data section of an ELF file on Linux?

    file source code

    file 5.36 source code comments at src/readelf.c claim that it also checks for PT_INTERP:

    /*
     * Look through the program headers of an executable image, searching
     * for a PT_INTERP section; if one is found, it's dynamically linked,
     * otherwise it's statically linked.
     */
    private int
    dophn_exec(struct magic_set *ms, int clazz, int swap, int fd, off_t off,
        int num, size_t size, off_t fsize, int sh_num, int *flags,
        uint16_t *notecount)
    {
        Elf32_Phdr ph32;
        Elf64_Phdr ph64;
        const char *linking_style = "statically";
    

    found with git grep statically from the message main.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped.

    However, this comment seems to be outdated compared to the code, which instead checks for PT_DYNAMIC:

        case PT_DYNAMIC:
            linking_style = "dynamically";
            doread = 1;
            break;
    

    I'm not sure why this is done, and I'm lazy to dig into git log now. In particular, this confused me a bit when I tried to make a statically linked PIE executable with --no-dynamic-linker as shown at: How to create a statically linked position independent executable ELF in Linux? which does not have PT_INTERP but does have PT_DYNAMIC, and which I do not expect to use the dynamic loader.

    I ended up doing a deeper source analysis for -fPIE at: Why does GCC create a shared object instead of an executable binary according to file? the answer is likely there as well.

    Linux kernel source code

    The Linux kernel 5.0 reads the ELF file during the exec system call at fs/binfmt_elf.c as explained at: How does kernel get an executable binary file running under linux?

    The kernel loops over the program headers at load_elf_binary

        for (i = 0; i < loc->elf_ex.e_phnum; i++) {
            if (elf_ppnt->p_type == PT_INTERP) {
                /* This is the program interpreter used for
                 * shared libraries - for now assume that this
                 * is an a.out format binary
                 */
    

    I haven't read the code fully, but I would expect then that it only uses the dynamic loader if INTERP is found, otherwise which path should it use?

    PT_DYNAMIC is not used in that file.

    Bonus: check if -pie was used

    I've explained that in detail at: Why does GCC create a shared object instead of an executable binary according to file?

提交回复
热议问题