Search code examples
clinuxexecutableelf

Add interpreter to shared object without touching the code


I'm following this post, in which it is clearly shown how to add an interpreter to a shared object.

Now I'm trying to do the same but without add the line

const char interp_path[] __attribute__((section(".interp"))) = "/lib64/ld-linux-x86-64.so.2";

i.e. I would like the code to be

#include <stdio.h>
#include <stdlib.h>
 
void print_version()
{
    printf("Library version 1.0\n");

    exit(0);
}

compiled with

gcc -shared -fPIC -Wl,--entry=print_version -o lib.so lib.c

and to add an interpreter to my lib.so.

WHAT I TRIED

  • I tried to use patchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 lib.so. This results in the following error:
patchelf: cannot find section '.interp'. The input file is most likely statically linked

  • then I tried to add the section. Noticing that in a standard executable I had the A flag
  [ 1] .interp           PROGBITS        0000000000000318 000318 00001c 00   A  0   0  1

I tried

echo -n /lib64/ld-linux-x86-64.so.2 > tempfile
objcopy --add-section .interp=tempfile --set-section-flags .interp=alloc,readonly lib.so libi.so

which complains telling me that objcopy: libi.so: warning: allocated section `.interp' not in segment. Despite with readelf I get the correct header section and patchelf does not complain any more, I cannot get my libi.so as I want.

Can you help me?


Solution

  • As I said in the comment,

    Simply adding the .interp section is not enough and the interpreter will not be found by the loader, because it is not part of a loadable segment, thus is not loaded on memory. Along with the section, you also have to add/update a program header and make it of PT_INTERP type and set its values according to the interpreter string's attributes (which is now located in the .interp section).

    Unfortunately I didn't find any useful tool for this purpose when I was writing the comment... You can use libraries like LIEF to add your custom PT_INTERP program header (I'm not sure if this library supports it though).

    If you want to write if from scratch, it will not be conceptually difficult. I assume that your code is Position-Independent (As you said, your target is a shared object). You have to append the new PT_INTERP program header to the end of the program header table and then update the following headers accordingly‌ (You can extract header information using libbfd):

    ELF Header

    Add the sizeof(Elf64_Phdr) or sizeof(Elf32_Phdr) to e_entry and e_shoff, because the Entry Point and the base of section header table will be shifted after appending the new phdr. Also add 1 to e_phnum (Which stores the number of program headers).

    Section headers

    Because the section and section header's data is usually placed after the program header table, the shift affects the correct offsets for the sections' positions; thus you also have to add the sizeof(Elf64_Phdr) or sizeof(Elf32_Phdr) to the sh_offset field of each of the section headers.

    Program headers

    Because the sections' data has shifted, you also have to update the p_offset field for all of the program headers that use offsets after the program header table. This means you have to skip program headers like PT_PHDR, whose data is located before the program header table and are not affected by the shift.

    The custom program header

    To create the program header itself, you can use the program header structure available in elf.h (Elf64_Phdr or Elf32_Phdr). Initialize the structure in this way:

    p_type   = 3       // Set p_type to PT_INTERP
    p_flags  = PF_R    // Set permission for .interp to Readable
    p_offset = (Your .interp offset)
    p_vaddr  = (Your .interp virtual address)
    p_paddr  = (Your .interp virtual address)
    p_filesz = 28     // strlen(Interpreter string) + 1
    p_memsz  = 28
    p_align  = 1
    

    You can then write the initialized structure on a file and then inject it into the end of the program header (As I said above).