Show me the money
分类: C/C++
2010-07-21 19:45:22
下面是官方文档(Executable and Linkable Format (ELF))对于PLT的描述,有点晦涩。
1 .
When first creating the memory image of the program, the dynamic linker
sets the second and the third entries in the global offset table to special
values. Steps below explain more about
these values. 2 .
If the procedure linkage table is position-independent, the address 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. Con-sequently, the calling function is
responsible for setting the global offset table base register before calling
the procedure linkage table entry. 3 .
For illustration, assume the program calls name1, which transfers
control to the label .PLT1. 4 .
The first instruction jumps to the address in the global offset table
entry for name1. Initially, the global offset table holds the address of the
following pushl instruction, not the real address of name1. 5 .
Consequently, the program pushes a relocation offset (offset) on the
stack. The relocation offset is a
32-bit, non-negative byte offset into the relocation table. The designated relocation entry will have
type R_386_JMP_SLOT, and its offset will specify the global offset table
entry used in the previous jmp instruction.
The relocation entry also contains a symbol table index, thus telling
the dynamic linker what symbol is being referenced, name 6 .
After pushing the relocation offset, the program then jumps to .PLT0,
the first entry in the pro-cedure linkage table. The pushl instruction places the value of
the second global offset table entry (got_plus_4 or 4(%ebx)) on the stack,
thus giving the dynamic linker one word of identifying information. The program then jumps to the address in
the third global offset table entry 2-18 Portable Formats Specification,
Version 1.1 Tool Interface Standards
(TIS)ELF: Executable and Linkable Format (got_plus_8 or 8(%ebx)), which
transfers control to the dynamic linker. 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 name 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 |
下面我们通过一个例子来进行更直观的了解:
#include #include #include int main (int argc, char *argv[]) { srand(time(NULL)); printf("The
red quick fox "); fprintf(stdout,
"jumps over a lazy brown goat.\n"); return
0; } |
查看.plt段的内容:
Disassembly of section
.plt: 08048378
8048378: ff
35 804837e: ff
25 70 97 04 08 jmp
*0x8049770 8048384: 00
00 add
%al,(%eax) ... 08048388 8048388: ff
25 74 97 04 08 jmp
*0x8049774 804838e: 68
00 00 00 00 push
$0x0 8048393: e9
e0 ff ff ff jmp
8048378 <_init+0x30> 08048398 8048398: ff
25 78 97 04 08 jmp
*0x8049778 804839e: 68
08 00 00 00 push
$0x8 80483ae: 68
10 00 00 00 push
$0x10 80483b3: e 080483b8 <__libc_start_main@plt>: 80483b8: ff
25 80 97 04 08 jmp
*0x8049780 80483be: 68
18 00 00 00 push
$0x18 80483ce: 68
20 00 00 00 push
$0x20 80483d3: e 080483d8 80483d8: ff
25 88 97 04 08 jmp
*0x8049788 80483de: 68
28 00 00 00 push
$0x28 80483e3: e9
90 ff ff ff jmp
8048378 <_init+0x30> |
查看.got.plt段的内容:
Disassembly of section .got.plt: 08049768 <_GLOBAL_OFFSET_TABLE_>: 08049768:
08049690 00000000 00000000 0804838e 08049778:
0804839e 080483ae 080483be 080483ce 08049788:
080483de |
参照文档的说明,假设程序调用函数第一次执行srand@plt,那么将会发生
1. 从0x8049778处取出函数地址(等于0x0804839e),然后跳转到0x0804839e
2. 执行 push $0x8
3. 跳转到0x08048378
4. 执行pushl
0x
5. 从0x8049770处取出函数fix_plt地址(在程序初始化阶段,dynamic linker会在0x8049770处填入fix_plt函数的地址),然后跳转到函数fix_plt
6. fix_plt函数根据压入堆栈中的两个参数,定位到srand@plt对应的got表项(0x8049778),填入函数srand的绝对地址;然后跳转到srand去执行。
如果第二次执行函数srand@plt,从地址0x8049778取出的就已经是函数srand的地址了,那么就不会再执行以上的6个步骤了。
为了更直观的了解上述过程,我们修改一下测试程序:
#include
#include
#include
#include
static
void dump_got(void) { int i; extern unsigned int _GLOBAL_OFFSET_TABLE_[]; printf("_GLOBAL_OFFSET_TABLE_ at
%p\n", _GLOBAL_OFFSET_TABLE_); for(i=0; i<12; i++) printf(" %08x 0x%08x\n",
&_GLOBAL_OFFSET_TABLE_[i], _GLOBAL_OFFSET_TABLE_[i]); } static
void get_func_addr(const char *soname, const char *sym) { void *handle; handle = dlopen(soname, RTLD_NOW); if( handle != NULL ) { void *func = dlsym(handle, sym); if(func != NULL) printf("%s@%p\n", sym,
func); dlclose(handle); } } int
main (int argc, char *argv[]) { void *handle; dump_got(); srand(time(NULL)); printf("The red quick fox "); fprintf(stdout, "jumps over a lazy
brown goat.\n"); dump_got(); get_func_addr("libc.so.6",
"srand"); get_func_addr("libc.so.6",
"time"); return 0; } |
$gcc -fpic -Os study-plt.c –ldl
$./a.out
_GLOBAL_OFFSET_TABLE_ at
0x8049954 08049954 0x08049878 08049958 0x0070e658 08049960 0x08048426 08049964
0x08048436 08049968 0x08048446 08049970 0x 08049974 0x08048476 08049978 0x08048486 08049980 0x The red quick fox jumps
over a lazy brown goat. _GLOBAL_OFFSET_TABLE_ at
0x8049954 08049954 0x08049878 08049958 0x0070e658 08049960 0x 08049964 0x 08049968 0x08048446 08049970 0x 08049974 0x08048476 08049978 0x08048486 08049980
0x0079acc0 srand@0x time@0x79acc0 |
$objdump -R a.out
a.out: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 08049948 R_386_GLOB_DAT __gmon_start__ 08049950
R_386_GLOB_DAT stdout 08049960
R_386_JUMP_SLOT fputs 08049964
R_386_JUMP_SLOT srand 08049968
R_386_JUMP_SLOT __gmon_start__ 08049970
R_386_JUMP_SLOT __libc_start_main 08049974 R_386_JUMP_SLOT dlsym 08049978
R_386_JUMP_SLOT dlopen 08049980
R_386_JUMP_SLOT time |
从上面的运行结果我们可以清楚的看到,在函数调用前后,got表项所发生的变化。从而加深了我们对于ELF根据PLT进行重定位的理解。