in the name of zero

April 14, 2006

elf magic revisited

Filed under: hermetic studies

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   0x80482a8

0x08048383 <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
shows a jmp instruction to a push instruction (push 0x8) and finally, a jmp to 0x8048288. 0x8 therefore is the relocation offset of printf in the relocation table. consequently, the last jmp instruction transfered control to the .PLT0 code that is outlined below.

steph@gdb $ x/2i 0x8048288
0x8048288 <_init +24>:   push   DWORD PTR ds:0x80495a4
0x804828e <_init +30>:   jmp    DWORD PTR ds:0x80495a8

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/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>:   0xb7ea0290
what’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()

0x08048398 
: call 0x8048370 <thisfunction>
it has no procedure linkage entry table.

a blessed holy friday to all.

Comments »

The URI to TrackBack this entry is: http://gnurbs.blogsome.com/2006/04/14/elf-magic-revisited-2/trackback/

No comments yet.

RSS feed for comments on this post.

Leave a comment

Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>


Get free blog up and running in minutes with Blogsome | Theme designs available here