elf magic revisited
in part 1 of this post, i took a look at an elf binary’s global offset table - procedure linkage table under gdb i learned their basic components and structure. although, i have yet to see them in action. i’ll start by showing the initial values of the global offset table again (program still hasn’t been executed)
_GLOBAL_OFFSET_TABLE_[0] = 0x080494d4 (address of _DYNAMIC) _GLOBAL_OFFSET_TABLE_[1] = 0x00000000 (NULL) _GLOBAL_OFFSET_TABLE_[2] = 0x00000000 (NULL) _GLOBAL_OFFSET_TABLE_[3] = 0x0804829e (offset to push instruction of __libc_start_main@plt) _GLOBAL_OFFSET_TABLE_[4] = 0x080482ae (offset to push instruction of printf@plt)so the first entry (index 0) of the global offset table holds the address of a binary’s _DYNAMIC section. and it allows a program (especially the dynamic linker) to find its own dynamic structure without having yet processed its relocation entries. it contains instructions for the dynamic linker to initialize itself without relying on another program to relocate its memory image.
the second entry (index 1) is set to null at the beginning and it holds the address filled by the dynamic linker with it’s own internal link_map structure (struct link_map in /usr/include/link.h)
the third entry (index 2) is also set to null at the beginning and it is used by the dynamic linker when a function is called for the first time. subsequent calls to a function will not anymore pass this instruction and instead, jump directly to the address of the function.
the remaining entires (indices 3,4) are address to pushl instruction which is a 32 bit non-negative byte offset into the relocation table.
this is an example entry in the procedure linkage table.
080482a8 <printf @plt>: 80482a8: ff 25 b0 95 04 08 jmp *0x80495b0 80482ae: 68 08 00 00 00 push $0x8 80482b3: e9 d0 ff ff ff jmp 8048288 <_init +0x18>
after pushing the relocation offset, it jumps to the first entry - .PLT0 (notice the jmp 8048288 <_init +0x18>) instruction in the plt figure above).
this is an example of a .PLT0 figure.
Disassembly of section .plt: 08048288 <__libc_start_main @plt-0x10>: 8048288: ff 35 a4 95 04 08 pushl 0x80495a4 804828e: ff 25 a8 95 04 08 jmp *0x80495a8 8048294: 00 00 add %al,(%eax) …
let’s follow the steps outlined above in another aspect now. please refer to the c function here.
load the program in gdb and set a breakpoint in thisfunction(). (note that this function has a printf() statement in it)
steph@heaven ~/git/null/c/ptrace $ gdb plt-got Using host libthread_db library "/lib/libthread_db.so.1". steph@gdb $ b thisfunction Breakpoint 1 at 0x8048376: file plt-got.c, line 5. steph@gdb $ r Starting program: /home/steph/git/null/c/ptrace/plt-got Breakpoint 1, thisfunction () at plt-got.c:5 5 printf(\"hello world\n\");
diassemble thisfunction and inspect the call to printf() closely.
steph@gdb $ disassemble thisfunction+14 Dump of assembler code for function thisfunction: 0x08048370 <thisfunction +0>: push ebp 0x08048371 <thisfunction +1>: mov ebp,esp 0x08048373 <thisfunction +3>: sub esp,0x8 0x08048376 <thisfunction +6>: sub esp,0xc 0x08048379 <thisfunction +9>: push 0x80484ac 0x0804837e <thisfunction +14>: call 0x80482a80x08048383 <thisfunction +19>: add esp,0x10 0x08048386 <thisfunction +22>: leave 0x08048387 <thisfunction +23>: ret
the function printf() has not been called yet before, the disassembly of the instruction at 0x80482a8 the first pushl instruction saves the value of the second value of the global offset table at the stack then jmps to the address of the third entry of the global address table. how do we verify? by simply printing out the values of DWORD PTR ds:0x80495a4 and DWORD PTR ds:0x80495a8 respectively. notice the +4 and +8 offset values.
steph@gdb $ x/2i 0x8048288
0x8048288 <_init +24>: push DWORD PTR ds:0x80495a4
0x804828e <_init +30>: jmp DWORD PTR ds:0x80495a8
steph@gdb $ x/x 0x80495a4
0x80495a4 <_global_offset_table_ +4>: 0xb7f27490
steph@gdb $ x/x 0x80495a8
0x80495a8 <_global_offset_table_ +8>: 0xb7f1d2c0
the last instruction (jmp) transfer control now to the dynamic linker which. i quote from the elf specification now.
7. When the dynamic linker receives control, it unwinds the stack,
looks at the designated relocation entry, finds the symbol’s value,
stores the “real'’ address for name1 in its global offset table
entry, and transfers control to the desired destination.
how do we know that 0xb7f1d2c0 refers to the dynamic linker now? we take a look at a process’s memory map at proc.
steph@heaven ~ $ cat /proc/`pidof plt-got`/maps 08048000-08049000 r-xp 00000000 03:01 4928 /home/steph/git/null/c/ptrace/plt-got 08049000-0804a000 rw-p 00000000 03:01 4928 /home/steph/git/null/c/ptrace/plt-got b7def000-b7df0000 rw-p b7def000 00:00 0 b7df0000-b7f00000 r-xp 00000000 03:08 29605 /lib/libc-2.3.4.so b7f00000-b7f01000 ---p 00110000 03:08 29605 /lib/libc-2.3.4.so b7f01000-b7f02000 r--p 00110000 03:08 29605 /lib/libc-2.3.4.so b7f02000-b7f05000 rw-p 00111000 03:08 29605 /lib/libc-2.3.4.so b7f05000-b7f07000 rw-p b7f05000 00:00 0 b7f13000-b7f26000 r-xp 00000000 03:08 29606 /lib/ld-2.3.4.so <= this one! b7f26000-b7f28000 rw-p 00012000 03:08 29606 /lib/ld-2.3.4.so bfd11000-bfd26000 rw-p bfd11000 00:00 0 [stack] ffffe000-fffff000 —p 00000000 00:00 0 [vdso]the file /lib/ld-2.3.4.so is the dynamic linker shared object file.
now that the introductory details are out of our way, i restarted the program and tried stepping thru the printf() function and inspect the global offset table and procedure linkage table once more.
steph@gdb $ step hello world 6 }follow the jmp to printf@plt again.
steph@gdb $ disassemble 0x80482a8 Dump of assembler code for function printf@plt: 0x080482a8 <printf @plt+0>: jmp DWORD PTR ds:0x80495b0 0x080482ae <printf @plt+6>: push 0x8 0x080482b3 <printf @plt+11>: jmp 0x8048288 <_init +24>inspect the first instruction.
steph@gdb $ x/x 0x80495b0 0x80495b0 <_global_offset_table_ +16>: 0xb7ea0290what’s this? it now immediately jumps to _GLOBAL_OFFSET_TABLE_+16 instead of going back to the push instruction. now i quote from the elf specification again.
8. Subsequent executions of the procedure linkage table entry will
transfer directly to name1, without calling the dynamic linker a
second time. That is, the jmp instruction at .PLT1 will transfer to
name1, instead of “falling through'’ to the pushl instruction.
but wait, we still have to make sense of what the memory address at offset +16 (0xb7ea0290) has right?
i checked the memory map again.
steph@heaven ~/git/null/c/ptrace $ cat /proc/`pidof plt-got`/maps 08048000-08049000 r-xp 00000000 03:01 4928 /home/steph/git/null/c/ptrace/plt-got 08049000-0804a000 rw-p 00000000 03:01 4928 /home/steph/git/null/c/ptrace/plt-got b7e5c000-b7e5d000 rw-p b7e5c000 00:00 0 b7e5d000-b7f6d000 r-xp 00000000 03:08 29605 /lib/libc-2.3.4.so <= this one! b7f6d000-b7f6e000 —p 00110000 03:08 29605 /lib/libc-2.3.4.so b7f6e000-b7f6f000 r–p 00110000 03:08 29605 /lib/libc-2.3.4.so b7f6f000-b7f72000 rw-p 00111000 03:08 29605 /lib/libc-2.3.4.so b7f72000-b7f74000 rw-p b7f72000 00:00 0 b7f7f000-b7f80000 rw-p b7f7f000 00:00 0 b7f80000-b7f93000 r-xp 00000000 03:08 29606 /lib/ld-2.3.4.so b7f93000-b7f95000 rw-p 00012000 03:08 29606 /lib/ld-2.3.4.so bf87d000-bf893000 rw-p bf87d000 00:00 0 [stack] ffffe000-fffff000 —p 00000000 00:00 0 [vdso]libc is in that memory range afterall. so that proves that there is indeed, no subsequent relocation pushes and calls to .PLT0 for every subsequent dynamic function call after the first one.
but what about static functions like thisfunction()? well, they have absolute address during runtime inside the program core and this is already known so they are just called directly. example:
main function calling thisfunction()
0x08048398it has no procedure linkage entry table.: call 0x8048370 <thisfunction>
a blessed holy friday to all.
