elf magic again
the rundown basicly consists of the following:
1) make a shared library
2) make a program that calls it
3) inspect the shared object, like what happened before.
4) inspect the main program, like what happened before.
first comes the shared library (stephy.so) steppings. (i just love the word step!)
/** stephy. c - shared elf object file example
*
* gcc -fpic -shared -o stephy.so stephy.c -lc
*
* notes:
*
* -fpic
* implies poisition independent addressing
* -c
* compile, don't link
*
* -shared
* produce a shared object that can be linked
* with other objects to form an executable.
*
* -lc
* link with the libc.a library
*
* 04/22/06 stephanie
*/
#include <stdio.h>
void banner(char *sentence)
{
printf("%s\n", sentence);
}
the first thing i noticed from its disassembly is that it doesn’t have a PT_INTERP section. so a position independent, elf shared object file relies on another program to call the dynamic linker and finish its own relocations for itself to be integrated in the calling program. as with an executable elf object, a shared elf object also has a base address which is the lowest virtual address associated with the memory image of the program’s object file.
i focused on three parts of this shared object file. sections plt and got-plt and finally, void banner’s disassembly listing.
this is how the relevant section .plt looks like:
00000448 <printf@plt-0x10>:
448: ff b3 04 00 00 00 push DWORD PTR [ebx+4]
44e: ff a3 08 00 00 00 jmp DWORD PTR [ebx+8]
454: 00 00 add BYTE PTR [eax],al
...
00000458 <printf@plt>:
458: ff a3 0c 00 00 00 jmp DWORD PTR [ebx+12]
45e: 68 00 00 00 00 push 0x0
463: e9 e0 ff ff ff jmp 448 <_init+0x18>
we are examining a position independent object file. as one may have noticed, it has a different addressing mode over absolute procedure linkage table entries since ebx is added to an offset number. more on this as we go along.
then, verifying that the shared object’s global offset table entry zero still points to it’s dynamic section is sufficient i guess. the disassembly tells me that stephy.so’s got-plt table is located at address 0x16bc.
steph@gdb $ x/x 0x16bc 0x16bc <__JCR_LIST__+208>: 0x000015f0
i verfied that address 0x15f0 is indeed the shared object’s .dynamic section by using objdump.
steph@heaven ~/git/null/c/symbol-resolution $ objdump -d --section=.dynamic stephy.so stephy.so: file format elf32-i386 Disassembly of section .dynamic: 000015f0 < .dynamic>: — snip –
i moved further and tried to understand how the function void banner inside stephy.so is implemented, including how it refers to it’s own procedure linkage entry table entries, e.g. the local printf() function in the object file itself.
first comes the function disassembly.
steph@gdb $ disas banner Dump of assembler code for function banner: 0x00000554: push ebp 0x00000555 : mov ebp,esp 0x00000557 : push ebx 0x00000558 : sub esp,0x4 0x0000055b : call 0x560 0x00000560 : pop ebx 0x00000561 : add ebx,0x115c 0x00000567 : sub esp,0x8 0x0000056a : push DWORD PTR [ebp+8] 0x0000056d : lea eax,[ebx-0x10e9] 0x00000573 : push eax 0x00000574 : call 0x458 0x00000579 : add esp,0x10 0x0000057c : mov ebx,DWORD PTR [ebp-4] 0x0000057f : leave 0x00000580 : ret 0x00000581 : nop 0x00000582 : nop 0x00000583 : nop End of assembler dump.
ahh, the first two instructions are familiar, - function prolog. saves the current frame pointer before assigning a new one. the thing that puzzled me was the instructions starting from banner+7 up to the call to printf@plt. here is how i understood it.
remember, we are taking a look at position independent code here, so these are just techniques to make ebx point to the right location - e.g. the global offset table before call printf@plt. i quote from the elf specification:
2. If the procedure linkage table is position-independent, the addressand if you still remember, ebx is added to an offest in the procedure linkage entry illustation from before. this is how the flexibility of position independence works. of course, we still need to prove that ebx indeed points to the global offset table right? so lets step into the assembly listing in finer detail now, step by step instruction.
of the global offset table must reside in %ebx. Each shared object
file in the process image has its own procedure linkage table, and
control transfers to a procedure linkage table entry only from
within the same object file. Consequently, the calling function is
responsible for setting the global offset table base register
before calling the procedure linkage table entry.
0x00000554: push ebp 0x00000555 : mov ebp,esp
function prolog you guys. save the current frame pointer and make the stack pointer, the start address of the new frame.
0x00000557: push ebx 0x00000558 : sub esp,0x4
save the old value of ebx. as to why it subs the esp by 4 bytes, i don’t know.
0x0000055b: call 0x560 0x00000560 : pop ebx
ok. so these two instructions are pairs that are really important. the program needs to find the address of the global offset table relative to it’s address. the call instruction pushes the eip on the stack which happens to be the address of the pop ebx instruction after it. then the pop ebx instruction, retrieves the address that the call instruction pushed at the stack. ebx now therefore has the address holds it’s own address.
0x00000561: add ebx,0x115c
add the value 0x115c (4444 decimal) to the current address. why? well, let’s do some math. also, for this example, we will be using the absolute address of pop ebx which is 0x00000560. 0x0560 + 0x115c = 0x16bc
steph@gdb $ x/x 0x16bc 0x16bc <__jcr_list__ +208>: 0x000015f0
and if we take a look at address 0x16bc we would notice that it’s actually the first entry (entry zero) of the global offset table! how do we know? coz we already know the address of the our global offset table (from above).
ok, so ebx, how has the address of the global offset table that will evetually be used when the first call to printf@plt resolves. we move to the next set of instructions now.
0x00000567: sub esp,0x8 0x0000056a : push DWORD PTR [ebp+8]
allocate space at the stack again and save the value of ebp+8 on the stack. more on this in a while.
0x0000056d: lea eax,[ebx-0x10e9] 0x00000573 : push eax 0x00000574 : call 0x458
again, some math, ebx = 0x16bc, which is consequently, the address of our global offset table. why subtract by 0x10e9? well, to get the address of section .rodata. if we take a look at void banner’s source, we see that it calls printf with two arguments, a “%s” string and a character pointer *sentence. then at last, it calls the procedure linkage table entry for printf which now works like a charm because we already have the address of the got in register ebx.
let’s just print the contents of address 0x10e9 for fun. we have our equation: 0x16bc - 0x1039 = 0x05d3.
steph@gdb $ x/s 0x05d3 0x5d3 <_fini +27>: “%s\n”
ok! “%s\n” it is! the first argument to the printf() call inside void banner(). we move on.
0x00000579: add esp,0x10 0x0000057c : mov ebx,DWORD PTR [ebp-4]
the banner now does some cleaning and restores ebx’s former value.
and finally, the function epilog.
0x0000057f: leave 0x00000580 : ret 0x00000581 : nop 0x00000582 : nop 0x00000583 : nop
other than the added ebx register inside the plt definitions, the flow is the same as absolute procedure linkage entry. well, that’s it.
