elf magic revisited
to do more cool stuffs with ptrace(), elf sorcery is in order. or so i think. i’ve done some basic experiments (1 ,2, 3) with the elf abi some time ago, but that couldn’t prepare me for the things i wish to do recently. so today, i’ll be continuing my elf adventures by learning how the got (global offset table) and plt (procedure linkage table) mechanisms work.
see:
“global offset table and procedure linkage table” - the elf specification
i made a simple program so i can follow this paper too.
void thisfunction()
{
printf(\"hello world\n\");
}
int main(int argc, char **argv)
{
thisfunction();
return 0;
}
generated a disassembly dump of all sections too for reference.steph@heaven ~/git/null/c/ptrace $ gcc -g -o plt-got plt-got.c steph@heaven ~/git/null/c/ptrace $ objdump -D plt-got > plt-got.dasm
every instruction here, (unless otherwise noted) was done inside gdb. i started by disassembling <_start>. i followed the call function to the .plt section.
0x080482dc <_start +28>: call 0x8048298 <__libc_start_main @plt>i noticed that is is an absolute procedure linkage table. since it doesn’t use register ebx as offset.
steph@gdb $ disassemble 0x8048298 Dump of assembler code for function __libc_start_main@plt: 0x08048298 <__libc_start_main @plt+0>: jmp DWORD PTR ds:0x80495ac 0x0804829e <__libc_start_main @plt+6>: push 0x0 0x080482a3 <__libc_start_main @plt+11>: jmp 0x8048288 <_init +24>note that the first instruction is a jmp which i found quite frustrating since i just came from a call instruction. that instruction by the way instructs us to jmp at the memory address pointed to, by the memory address 0x80495ac. in other words, we get the value of 0x80495ac and jump to that value instead of using the memory address directly.
first, view the contents of memory address 0x80495ac
steph@gdb $ x/x 0x80495ac 0x80495ac <_global_offset_table_ +12>: 0x0804829ei noticed that the value 0x0804829e is actually just the very next instruction after the jmp. you can see for yourself in the above disassembly!
i quote from the paper i’m following:
“In the beginning, the GOT offsets points on the following push offsetin my case, my push instruction is in a different memory address - 0x0804829e.
0x804830e : look at the objdump trace above) . At the moment , the GOT
in our process is said to be “empty”, and is going to be filled as long
as the process calls remote functions (one entry is updated each time the
program calls this remote function *for the first time*) .”
i followed the jmp instruction next.
steph@gdb $ x/i 0x8048288 0x8048288 <_init +24>: push DWORD PTR ds:0x80495a4again, another pointer. let’s print it’s value shall we?
steph@gdb $ x/d 0x80495a4 0x80495a4 <_global_offset_table_ +4>: 0ok so now what? well, let’s try looking at the __libc_start_main@plt block.
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)we already know that the first pushl instruction pushed “zero” to the stack. now we determine what jmp *0x80495a8 is all about.
steph@gdb $ x/d 0x80495a8 0x80495a8 <_global_offset_table_ +8>: 0the same.
and with the help of gdb, i constructed the got table
print out the values of every index of the global offset table
steph@gdb $ x 0x80495a0 0x80495a0 <_global_offset_table_>: 0x080494d4 steph@gdb $ x 0x80495a0+4 0x80495a4 <_global_offset_table_ +4>: 0x00000000 steph@gdb $ x 0x80495a0+8 0x80495a8 <_global_offset_table_ +8>: 0x00000000 steph@gdb $ x 0x80495a0+12 0x80495ac <_global_offset_table_ +12>: 0x0804829e steph@gdb $ x 0x80495a0+16 0x80495b0 <_global_offset_table_ +16>: 0x080482aeand construct the array
_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)based on this array, i quote from the paper i’m reading. it makes sense now doesn’t it?
We can see that the third first entries in the GOT have special values :- The [0] entry contains the offset for the .dynamic section of
this object, it’s used by the dynamic linker to know some
preferences and some very useful informations . Look at the
ELF reference for further details . Some stuff are also explained
in the dynamic linking related chapter of this tfile .- The [1] one is the link_map structure offset associated with
this object, it’s an internal structure in ld.so describing
a lot of interresting stuffs, I wont go in depth with it in this
paper .- The [2] entry contains the runtime process fixup function offset
(pointing in the dynamic linking code zone). This pointer is used by
the first entry of the plt which is called when you want to launch
a remote (undefined) function for the first time .The 2nd and 3rd entries are set to NULL at the beginning and are filled by
the dynamic linker before the process code segment starting function takes
control . These are filled in elf_machine_runtime_setup() in
sysdeps/i386/dl-machine.h .
these, on the other hand make up the plt (procedure linkage table) entry.
08048298 <__libc_start_main @plt>: 8048298: ff 25 ac 95 04 08 jmp *0x80495ac 804829e: 68 00 00 00 00 push $0x0 80482a3: e9 e0 ff ff ff jmp 8048288 <_init +0x18> 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>
so as a summary, the got (global descriptor table) deals with address calculations to absolute locations. and the plt (procedure linkage table) “maps” function calls to absolute locations. whatever that means.
*giggles*
well, that was quite fun! (smirk) it took me many hours of staring at seemingly “garbage” outputs of objdump and gdb. and the paper i’m following didn’t really make much sense at first too. it makes me wonder if i even got the basic concepts correct.
in my next post i’ll try running the program and see if there are changes. in the got/plt section.
