Useful tutorials:
- https://reverseengineering.stackexchange.com/a/1936
- https://cs.dartmouth.edu/~sergey/cs108/dyn-linking-with-gdb.txt
Show the dynamically linked libraries:
ldd /path/to/exe
Show unused dynamically linked libraries:
ldd -u /path/to/exe
List all calls to all linked libraries:
ltrace -c /path/to/exe
Filter it down to just a particular shared library:
ltrace -c -l 'libc.so.*' /path/to/exe
Start gdb:
gdb /path/to/exe
Start with a graphical-like interface:
gdb -tui /path/to/exe
That gives you a prompt:
(gdb) _
The underscore shows where you can type.
To exit:
(gdb) quit
Find the entry point:
(gdb) info file
See the address of a function (e.g., 'main'):
(gdb) info address main
See the args for a call:
(gdb) info args
See the stack frame:
(gdb) info frame
To run the program (in GDB):
(gdb) run
To run the program with arguments:
(gdb) set args arg1 arg2
(gdb) show args
(gdb) run
A shorter version:
(gdb) run arg1 arg2
To see the assembly:
(gdb) layout asm
To see the registers:
(gdb) layout regs
Set a breakpoint at a function:
(gdb) break main
Set a breakpoint at an address:
(gdb) break *0x400566
See the breakpoints:
(gdb) info breakpoints
Delete a breakpoint on a function:
(gdb) clear main
Delete a breakpoint on an address:
(gdb) clear *0x400566
To continue:
(gdb) continue
To step:
(gdb) step
To go to the next breakpoint:
(gdb) next
At assembly level, there are instruction-level step and next:
(gdb) stepi
(gdb) nexti
Useful information is here: https://ftp.gnu.org/old-gnu/Manuals/gdb/html_node/gdb_60.html
See the registers:
(gdb) info registers
GDB has names for four special registers.
- The register that contains the program counter is
$pc. On my machine, that's registerrip. - The register that contains the stack poitner is
$sp. On my machine, that's registerrsp. - The register tha contains the frame pointer is
$fp. On my machine, that's registerrbp. - The register that contains the processor status is
$ps. On my machine, that's the register labeledeflags.
The x command examines something in a program,
and the p command prints something. Info:
http://visualgdb.com/gdbreference/commands/x
Examine the instruction at the program counter:
(gdb) x/i $pc
Examine the next five instructions from the program counter:
(gdb) x/5i $pc
Disassemble a function:
(gdb) disass main
The GOT (Global Offset Table) is the actual table of offsets to the external libraries. The linker fills these in.
The PLT (Procedure Lookup Table) is a linking table. It has a stub for each external function call. It begins with code that triggers the linker to look up the address. Then it gets filled in with the actual address.
In the ELF file there are three important sections:
- .got: this is the table of addresses that are looked up by the linker.
- .plt: this has the code that calls exteral functions which is either code to trigger the linker, or it is code to jump to the resolved address.
- .plt.got: this is the linking table for the PLT. It contains an address back to the PLT first, then after an address is resolved, it contains the real address.
Compile the program:
make
Open the program with GDB:
gdb ./main
Look at the entry point and sections:
(gdb) info file
Note where the PLT and GOT tables start.
Show the disassembly:
(gdb) disass main
Find the line where puts@plt is called,
and set a breakpoint on that address:
(gdb) break *0x400542
Run the program:
(gdb) run
Examine the instruction we're looking at:
(gdb) x/i $pc
It should say something like this:
=> 0x40052 <main+11>: callq 0x400430 <puts@plt>
This says that we're an offset of 11 from the start of main,
and we're about to make a call to address 0x400430. This has
a name: it's <puts@plt>, meaning it's the puts code in
the PLT, at address 0x400430. Notice that this address is
inside the PLT table!
Step forward one instruction (this should take us
through the call, to 0x400430):
(gdb) si
Now examine the instruction we've jumped to:
(gdb) x/i $pc
We are at the spot in the PLT for puts. You should
see something like this:
=> 0x400430 <puts@plt>: jmpq *0x200be2(%rip) #0x601018
That says: jump to *0x200be2(%rip), which it computes
as 0x601018. Note: that's an address in the GOT table
(use info file again to confirm this).
The processor is not going to jump to 0x601018.
Rather, it's going to fetch the address to jump to
from 0x601018.
We can look at the instruction at that address:
(gdb) x/i 0x601018
On my machine, I see something like this:
0x601018: ss add $0x40,%al
We can see the value that this computes:
(gdb) x/x 0x601018
That should say something like this:
0x601018: 0x00400436
So the instruction at address 0x601018 will evaluate
to 0x00400436. Hence, the jmpq instruction is going
to jump to 0x400436.
Let's step through the jump:
(gdb) si
Examine the next 2 instructions:
(gdb) x/2i $pc
You should see something like this:
=> 0x400436 <puts@plt+6>: pushq $0x0
0x40043b <puts@plt+11>: jmpq 0x400420
What's 0x400420? The beginning of the PLT table.
Step through these two instructions:
(gdb) si
(gdb) si
Now we should be at 0x400420, the beginning of the PLT table.
Inspect the next instructions:
(gdb) x/2i $pc
I see something like this:
=> 0x400420: pushq 0x200be2(%rip) # 0x601008
0x400426: jmpq *0x200be4(%rip) # 0x601010
So we're going to push the value 0x200be2(%rip), which is
here computed as 0x601008, Then we jump to the value
that gets computed at *0x200be4(%rip), which is 0x601010.
This is going to take us into the system linker, to find the
real location of puts. Step through the next instructions
using si multiple times, until we get a return from the
linker. You'll see it go through the dynamic linker libraries,
e.g., dl-runtime.c and dl-lookup.c.
(gdb) si
(gdb) si
...
(gdb) si
Here's a program:
#include <unistd.h>
#include <stdio.h>
int main() {
char * args[] = {"/bin/ls", NULL};
printf("pid: %d\n", getpid());
}
Compile it and load it into GDB:
gcc -g --no-pie main.c -o main
gdb ./main
See the source code:
(gdb) list
Set a break point at main and run:
(gdb) break main
(gdb) r
Check the function's disassembly:
(gdb) disas main
Mine looks like this:
Dump of assembler code for function main:
0x00000000004005a7 <+0>: push %rbp
0x00000000004005a8 <+1>: mov %rsp,%rbp
0x00000000004005ab <+4>: sub $0x20,%rsp
=> 0x00000000004005af <+8>: mov %fs:0x28,%rax
0x00000000004005b8 <+17>: mov %rax,-0x8(%rbp)
0x00000000004005bc <+21>: xor %eax,%eax
0x00000000004005be <+23>: lea 0xcf(%rip),%rax # 0x400694
0x00000000004005c5 <+30>: mov %rax,-0x20(%rbp)
0x00000000004005c9 <+34>: movq $0x0,-0x18(%rbp)
0x00000000004005d1 <+42>: callq 0x400490 <getpid@plt>
0x00000000004005d6 <+47>: mov %eax,%esi
0x00000000004005d8 <+49>: lea 0xbd(%rip),%rdi # 0x40069c
0x00000000004005df <+56>: mov $0x0,%eax
0x00000000004005e4 <+61>: callq 0x4004b0 <printf@plt>
0x00000000004005e9 <+66>: mov $0x0,%eax
0x00000000004005ee <+71>: mov -0x8(%rbp),%rdx
0x00000000004005f2 <+75>: xor %fs:0x28,%rdx
0x00000000004005fb <+84>: je 0x400602 <main+91>
0x00000000004005fd <+86>: callq 0x4004a0 <__stack_chk_fail@plt>
0x0000000000400602 <+91>: leaveq
0x0000000000400603 <+92>: retq
Note the call instruction:
0x00000000004005d1 <+42>: callq 0x400490 <getpid@plt>
What's at 0x400490? It's in the PLT table.
You can see the tables:
(gdb) info file
Also, you can dump it and check it out:
objdump -s ./main > main.dump
less main.dump
Then you can search for Contents of section .plt.
So what's 0x400490 in the PLT table look like?
(gdb) disas 0x400490
I see this:
Dump of assembler code for function getpid@plt:
0x0000000000400490 <+0>: jmpq *0x200b82(%rip) # 0x601018
0x0000000000400496 <+6>: pushq $0x0
0x000000000040049b <+11>: jmpq 0x400480
That first jump goes to 0x601018. Where's that?
When I look at the sections again, I can see that this
is an address in the GOT table (specifically, it's in
the .got.plt section). Let's examine what it says:
(gdb) x/x 0x601018
That gives me this:
0x601018: 0x400496
This means the initial value stored in the GOT table for
0x601018 points to 0x400496. If I look in my sections,
I can see that 0x400496 is an address in the PLT table.
So, when the main function calls getpid at 0x4005d1, it
actually calls a stub routine in the PLT table at (0x400490).
There, in that stub, we first find an instruction to jump to
whatever value the GOT table has at 0x601018. The value it
has is 0x400496, which is again in the PLT table.
What's at 0x400496? It's the instruction right after
0x400490, the beginning of the PLT stub for getpid.
I.e., it's the middle line here:
Dump of assembler code for function getpid@plt:
0x0000000000400490 <+0>: jmpq *0x200b82(%rip) # 0x601018
0x0000000000400496 <+6>: pushq $0x0
0x000000000040049b <+11>: jmpq 0x400480
This pushes 0 onto the stack, and then jumps to 0x400480.
What's 0x400480? For me, this is the very first address
in the PLT table. What's there?
(gdb) disas 0x400480
It says there's no function there. So let's try to look at the instruction:
(gdb) x/i 0x400480
For me, it says:
0x400480: pushq 0x200b82(%rip) # 0x601008
So it pushes 0x601008 onto the stack.
What's the next instruction? Try each one, until we see a jump:
(gdb) x/i 0x400481
(gdb) x/i 0x400482
...
(gdb) x/i 0x40086
Finally, at 0x400486, I see a jump:
=> 0x400486: jmpq *0x200b84(%rip) # 0x601010
So the PLT was putting some stuff on the stack, and
doing some calculations. Now we're going to jump
to the value stored at 0x601010.
What's the value stored at 0x601010? I can see that this
is in the .got.plt section. What's the value?
(gdb) x/x 0x601010
I see this:
0x601010: 0xf7dec680
So we're going to jump there. What's at 0xf7dec680?
(gdb) x/x 0xf7dec680
It says I can't access that address:
0xf7dec680: Cannot access memory at address 0xf7dec680
We must be in the dynamic linker?
Step through some instructions:
(gdb) si
(gdb) si
...
It willl take a long time to step through
all the moves that the linker does. Instead,
set a breakpoint after the getpid function.
(gdb) list main
That shows:
1 #include <unistd.h>
2 #include <stdio.h>
3
4 int main() {
5 char * args[] = {"/bin/ls", NULL};
6 printf("pid: %d\n", getpid());
7 // sleep(60);
8 }
I can set a breakpoint on line 7:
(gdb) break 7
Now I can continue to that breakpoint:
(gdb) continue
That goes on, and prints out the PID, then stops:
Continuing.
pid: 13315
Breakpoint 2, main () at main.c:8
8 }
At this point, the dynamic linker should have
filled in the GOT table with the real address
of printf on my system. Let's check it.
Where was the PLT stub? It was 0x400490. What's
the value now?
(gdb) x/x 0x400490
Now I see this:
0x400490 <getpid@plt>: 0x0b8225ff
Hmm. That's the same value I saw before. I'm stuck.
Let's see what's mapped to that address. We know the
process ID is 13315, because the program printed it out.
But we could also look for it by searching for the PID
of the main program, because GDB is simply executing
that program:
ps aux | grep main
I can see it is the second item listed:
jt 13313 0.0 0.1 102604 41272 pts/0 S+ 09:44 0:00 gdb ./main
jt 13315 0.0 0.0 4508 848 pts/0 t 09:44 0:00 /path/to/main
Now that I know the PID, what's the memory map for that process:
less /proc/13315/maps
You can see the relation sections in the elf file:
readelf --relocs ./main
There we can see that getpid will be at 0x601018.
We can see the sections:
readelf --sections ./main