How to create a statically linked position independent executable ELF in Linux?
Asked Answered
G

1

5

I have a working position independent Linux freestanding x86_64 hello world:

main.S

.text
.global _start
_start:
asm_main_after_prologue:
    /* Write */
    mov $1, %rax    /* syscall number */
    mov $1, %rdi    /* stdout */
    lea msg(%rip), %rsi  /* buffer */
    mov $len, %rdx  /* len */
    syscall

    /* Exit */
    mov $60, %rax   /* syscall number */
    mov $0, %rdi    /* exit status */
    syscall
msg:
    .ascii "hello\n"
len = . - msg

which I can assemble and run with:

as -o main.o main.S
ld -o main.out main.o
./main.out

Since it is position independent due to the RIP relative load, now I wanted to link it as a PIE and see it get loaded at random addresses every time to have some fun.

First I tried:

ld -pie -o main.out main.o

but then running it fails with:

-bash: ./main.out: No such file or directory

and readelf -Wa says that a weird interpreter /lib/ld64.so.1 was used instead of the regular one /lib64/ld-linux-x86-64.so.2 for some reason.

I then learnt that his is actually the recommended System V AMD64 ABI interpreter name at 5.2.1 "Program Interpreter".

In any case, I then try to force matters with:

ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -o main.out main.o

and now it works: I get hello and the executable gets loaded to a different address every time according to GDB.

Finally, as a final step, I wanted to also make that executable be statically linked to make things even more minimal, and possibly get rid of the explicit -dynamic-linker.

That's what I could not do, and this is why I'm asking here.

If I try either of:

ld -static -pie -o main.out main.o
ld -static -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -o main.out main.o

-static does not seem to make any difference: I still get dynamic executables.

After quickly glancing at the kernel 5.0 source code in fs/binfmt_elf.c I saw this interesting comment:

         * 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

so I guess when I achieve what I want, I will have a valid interpreter, and I'm so going to use my own minimal hello world as the interpreter of another program.

One thing I might try later on is see how some libc implementation compiles its loader and copy it.

Related question: Compile position-independent executable with statically linked library on 64 bit machine but that mentions an external library, so hopefully this is more minimal and answerable.

Tested in Ubuntu 18.10.

Gambill answered 13/4, 2019 at 10:32 Comment(1)
Related: What's the difference between "statically linked" and "not a dynamic executable" from Linux ldd? re: static-PIE vs. a regular PIE that uses an ELF interpreter but has no shared libs.Walz
S
7

You want to add --no-dynamic-linker to your link command:

$ ld main.o -o main.out -pie --no-dynamic-linker

$ file main.out
main.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, not stripped

$ ./main.out
hello

so I guess when I achieve what I want, I will have a valid interpreter, and I'm so going to use my own minimal hello world as the interpreter of another program.

I am not sure I understood what you are saying correctly. If you meant that main.out would have itself as its interpreter, that's wrong.

P.S. GLIBC-2.27 added support for -static-pie, so you no longer have to resort to assembly to get a statically linked PIE binary. But you'll have to use very recent GCC and GLIBC.

Septima answered 13/4, 2019 at 15:12 Comment(5)
Awesome, thanks! I wonder why file says it is dynamically linked, it seems to check for PT_DYNAMIC instead of PT_INTERP stackoverflow.com/questions/8040631/… By "use my own minimal hello world as the interpreter of another program" I meant something like take a normal C hello world and compile it pointing to my own gcc -Wl,--dynamic-linker= to see what args Linux kernel passes to linker. It worked when I use the asm hello world, but segfaulted when I tried -static-pie on a C hello world (Ubuntu 18.10 has it).Gambill
@CiroSantilli新疆改造中心996ICU六四事件"to see what args ..." the answer is: none (see https://mcmap.net/q/14728/-jump-to-entry-point-of-elf-from-loader). I also reproduced the crash when -static-pie ld.so is used as an interpreter, but have not understood why it's crashing. Worth a separate question.Septima
@CiroSantilli新疆改造中心996ICU六四事件 Ok, I figured out the crash. It's complicated :-(Septima
Cool! I was about to ask the question but got distracted with other stuff. Is it a bug or just behavior? Should I ask a question so you can share findings?Gambill
@CiroSantilli新疆改造中心996ICU六四事件 stackoverflow.com/questions/55679589/…Septima

© 2022 - 2024 — McMap. All rights reserved.