Search code examples
linuxelfbinutils

Linux ELF files: Which byte will differ for static and dynamic ELF programs?


I'm working with linux elf files.

I want to detect, if the given elf program is statically linked (full static link, ldd says "not a dynamic executable") or dynamically linked. The ELF is for embedded Linux, so I can't just run it or use ldd utility.

I want to do this entirely in my program, by reading and checking some bytes. I want not to depend on file utility or on libelf, binutils, etc.

Which bytes will be different?


Solution

  • How about using ldd.c from μClibc? It should be fairly easy to strip out any unwanted dependencies / checks if you want. I think this is a smarter approach than trying to figure out all the corner cases from reading man 5 elf, though FWIW it looks to be just checking for a PT_INTERP program header as you suspect in the comments.

    Update: There's a few more checks. I've tried to extract the relevant parts, but I can't be sure if I've missed anything so check for yourself. The code checks 32-bit and 64-bit x86 ELF files. It assumes a little-endian architecture.

    #include <stdio.h>
    #include <stdlib.h>
    #include <stddef.h>
    #include <ctype.h>
    #include <inttypes.h>
    
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    #include <unistd.h>
    
    #include <elf.h>
    
    int main(int argc, char* argv[])
    {
        const char* fname = argv[0];
        if (argc >= 2) fname = argv[1];
    
        int fd;
        struct stat st;
        void *mapping;
    
        if ((fd = open(fname, O_RDONLY)) == -1) {
            perror(fname);
            return 1;
        }
    
        if (fstat(fd, &st)) {
            perror("fstat");
            close(fd);
            return 1;
        }
    
        if ((mapping = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
            perror("mmap");
            close(fd);
            return 1;
        }
        const Elf32_Ehdr* eh = mapping;
    
        if (st.st_size < (off_t)sizeof(Elf32_Ehdr) ||
            eh->e_ident[EI_MAG0] != ELFMAG0 || 
            eh->e_ident[EI_MAG1] != ELFMAG1 || 
            eh->e_ident[EI_MAG2] != ELFMAG2 || 
            eh->e_ident[EI_MAG3] != ELFMAG3 ||
            eh->e_ident[EI_VERSION] != EV_CURRENT) {
            printf("Not a valid ELF file\n");
            return 0;
        }
    
        if (eh->e_type != ET_EXEC && eh->e_type != ET_DYN) {
            printf("Not executable or shared object\n");
            return 0;
        }
    
        int is_dynamic = 0;
    
        // change as appropriate, but remember that byteswapping might be needed in some cases
        if (eh->e_ident[EI_CLASS] == ELFCLASS32 && eh->e_ident[EI_DATA] == ELFDATA2LSB && eh->e_machine == EM_386) {
            uint16_t ph_cnt;
            for (ph_cnt = 0; ph_cnt < eh->e_phnum; ph_cnt++) {
                const Elf32_Phdr* ph = (const Elf32_Phdr*)((const uint8_t*)mapping + eh->e_phoff + ph_cnt * eh->e_phentsize);
                if (ph->p_type == PT_DYNAMIC || ph->p_type == PT_INTERP) {
                    is_dynamic = 1;
                }
            }
        } else if (eh->e_ident[EI_CLASS] == ELFCLASS64 && eh->e_ident[EI_DATA] == ELFDATA2LSB && eh->e_machine == EM_X86_64) {
            const Elf64_Ehdr* eh = mapping;
            uint16_t ph_cnt;
            for (ph_cnt = 0; ph_cnt < eh->e_phnum; ph_cnt++) {
                const Elf64_Phdr* ph = (const Elf64_Phdr*)((const uint8_t*)mapping + eh->e_phoff + ph_cnt * eh->e_phentsize);
                if (ph->p_type == PT_DYNAMIC || ph->p_type == PT_INTERP) {
                    is_dynamic = 1;
                }
            }
        } else {
            printf("Unsupported architecture\n");
            return 0;
        }
    
        munmap(mapping, st.st_size);
        close(fd);
        printf("%s: %sdynamic\n", fname, is_dynamic?"":"not ");
        return 0;
    }