20

Given a position-independent, statically-linked, stripped binary, there does not appear to be a way in GDB to set a breakpoint at the entry point without disabling ASLR.

  • break start and similar functions do not work, because there is no symbolic information
  • set stop-on-solib-events 1 does not work as the binary is not dynamically linked
  • break *0xdeadbeef for the entry point does not work, as the entry point is unresolved until the binary starts
  • catch load does not work, as it does not load any libraries
  • start does not work, as main is not defined and no libraries are loaded

Without patching the binary, what mechanism can I use to break at the first instruction executed?

Possible?

Since a now-deleted response to the question said that a PIE statically-linked binary is impossible, a trivial example is the linker itself.

It is statically linked.

$ ldd /lib/x86_64-linux-gnu/ld-2.19.so
    statically linked

It is executable.

$ strace /lib/x86_64-linux-gnu/ld-2.19.so
execve("/lib/x86_64-linux-gnu/ld-2.19.so", ["/lib/x86_64-linux-gnu/ld-2.19.so"], [/* 96 vars */]) = 0
brk(0)                                  = 0x7ff787b3d000
writev(2, [{"Usage: ld.so [OPTION]... EXECUTA"..., 1373}], 1Usage: ld.so [OPTION]... EXECUTABLE-FILE [ARGS-FOR-PROGRAM...]

It is position-independent.

$ readelf -h /lib/x86_64-linux-gnu/ld-2.19.so | grep DYN
  Type:                              DYN (Shared object file)

Solutions

It looks like this can be done with Python by utilizing some of the events made available: http://asciinema.org/a/19078

However, I'd like a native-GDB solution.

A successful solution will break at _start in ld.so when executed directly without disabling ASLR. It should look something like this:

sh $ strip -s /lib/x86_64-linux-gnu/ld-2.19.so -o ld.so
sh $ gdb ./ld.so
(gdb) $ set disable-randomization off
(gdb) $ <your magic commands>
(gdb) $ x/i $pc
=> 0x7f9ba515d2d0:     mov    rdi,rsp
(gdb) $ info proc map
process 10432
Mapped address spaces:

        Start Addr           End Addr       Size     Offset objfile
    0x7f9ba515c000     0x7f9ba517f000    0x23000        0x0 /lib/x86_64-linux-gnu/ld-2.19.so 
    0x7f9ba537e000     0x7f9ba5380000     0x2000    0x22000 /lib/x86_64- linux-gnu/ld-2.19.so
    0x7f9ba5380000     0x7f9ba5381000     0x1000        0x0 
    0x7fffc34c7000     0x7fffc38ca000   0x403000        0x0 [stack]
    0x7fffc398b000     0x7fffc398d000     0x2000        0x0 [vdso]
0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]
4

4 Answers 4

24

UPDATE: GDB 8.1 has a starti command, as mentioned below by /u/ruslan

Setting a breakpoint on an unmapped address before starting the target process does this, effectively. It's not correct functionality, but rather a side-effect of the failure to set the breakpoint.

(gdb) break *0
Breakpoint 1 at 0x0
(gdb) r
Starting program: /home/user/ld.so 
Error in re-setting breakpoint 1: Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x0

Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x0

(gdb) x/i $pc
=> 0x7faae3a25cd0:      mov    rdi,rsp
0
12

Starting with GDB 8.1, there's a special command for this: starti. Example GDB session:

$ gdb /bin/true
Reading symbols from /bin/true...(no debugging symbols found)...done.
(gdb) starti
Starting program: /bin/true 

Program stopped.
0xf7fdd800 in _start () from /lib/ld-linux.so.2
(gdb) x/5i $pc
=> 0xf7fdd800 <_start>: mov    eax,esp
   0xf7fdd802 <_start+2>:       call   0xf7fe2160 <_dl_start>
   0xf7fdd807 <_dl_start_user>: mov    edi,eax
   0xf7fdd809 <_dl_start_user+2>:       call   0xf7fdd7f0
   0xf7fdd80e <_dl_start_user+7>:       add    ebx,0x1f7e6
3

You can define gdb function to break on first argument of libc_star_main. The first si/ni is to load libc itself. Put it in your .gdbinit file.

define bmain
    si
    ni
    b __libc_start_main
    c
    b *($rdi)
    c
end
0

Just stumbled across this and thought I would add a bit.

If you want to stop at the entry point of a stripped, dynamically-linked program, I recommend the following procedure.

  1. Start GDB with the file.
  2. Start the program with starti so that the loader maps it into memory. (By default GDB turns off ASLR for your program, but this method will work either way.)
  3. Use info file to get the address of the entry point. This will show virtual addresses after start (that's why we used starti first).
  4. Find the entry point and set a breakpoint at that address.
  5. Run cont to get to the breakpoint.
$ gdb /usr/bin/ls
(gdb) starti
(gdb) info file
...
Local exec file:
    `/usr/bin/ls', file type elf64-x86-64.
    Entry point: 0x55555555a7d0
...
(gdb) b *0x55555555a7d0
(gdb) cont
...
Breakpoint 1, 0x000055555555a7d0 in ?? ()
(gdb) x/i $rip
=> 0x55555555a7d0:  endbr64 

Now, if you want main on Linux, step ahead through the disassembly until you find out how rdi is being set (assuming the usual crt0.o has been linked to start the C runtime).

(gdb) 
   0x55555555a7f1:  lea    rdi,[rip+0xffffffffffffe5f8]        # 0x555555558df0

Ah ha! We can find main (even without any symbols) at 0x555555558df0.

(gdb) b *0x555555558df0
(gdb) cont

Now you are at main. Leaving this here in case someone else comes along with the same question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.