elf有三种文件类型:可执行文件,共享目标文件(xx.so),可重定位文件(xx.o/xx.ko),本篇分析可重定位文件类型,也就是relocatable file.
代码:
ubuntu@ubuntu:~/work/hello$cat hello.c
#include
intfunc(void)
{
return 0;
}
staticint local_func(void)
{
return 0;
}
intglobal_func(void)
{
return 0;
}
intmain(void)
{
local_func();
global_func();
printf("hello world\n");
return 0;
}
编译:
ubuntu@ubuntu:~/work/hello$gcc -c hello.c -o hello.o
ubuntu@ubuntu:~/work/hello$file hello.o
hello.o:ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
反汇编:
objdump-S hello.o:
0000001e
1e: 55 push %ebp
1f: 89 e5 mov %esp,%ebp
21: 83 e4 f0 and $0xfffffff0,%esp
24: 83 ec 10 sub $0x10,%esp
27: e8 de ff ff ff call a
2c: e8 fc ff ff ff call 2d
31: c7 04 24 00 00 00 00 movl $0x0,(%esp)
38: e8 fc ff ff ff call 39
3d: b8 00 00 00 00 mov $0x0,%eax
42: c9 leave
43: c3 ret
上面的2d 39 都是当前指令的下一个指令地址,此时都是无意义的地址,需要在链接时重定位(relocatable),那么链接时是如何重定位的呢
查看section:
ubuntu@ubuntu:~/work/hello$readelf -S hello.o
Thereare 13 section headers, starting at offset 0x1a8:
SectionHeaders:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000044 00 AX 0 0 4
[ 2] .rel.text REL 00000000 0004c0 000018 08 11 1 4
[ 3] .data PROGBITS 00000000 000078 000000 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 000078 000000 00 WA 0 0 4
[ 5] .rodata PROGBITS 00000000 000078 00000c 00 A 0 0 1
[ 6] .comment PROGBITS 00000000 000084 00002b 01 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 00000000 0000af 000000 00 0 0 1
[ 8] .eh_frame PROGBITS 00000000 0000b0 000098 00 A 0 0 4
[ 9] .rel.eh_frame REL 00000000 0004d8 000020 08 11 8 4
[10] .shstrtab STRTAB 00000000 000148 00005f 00 0 0 1
[11] .symtab SYMTAB 00000000 0003b0 0000e0 10 12 10 4
[12] .strtab STRTAB 00000000 000490 00002f 00 0 0 1
我们需要关注第2,11,12这3个section
首先是重定位section:
ubuntu@ubuntu:~/work/hello$readelf -x 2 hello.o
Hex dumpof section '.rel.text':
0x00000000 2d000000 020b000034000000 01060000 -.......4.......
0x00000010 39000000 020d0000 9.......
这里可以发现上面所说的3个需要重定位的3个地址中的两个:2d 39都在这里(注意X86是小端),为什么a不在这里,因为它是local_func的跳转,local_func是用static定义的本地函数,a就是local_func的代码地址,它不需要重定位,因为它只在本地使用:
0000000a
a: 55 push %ebp
b: 89 e5 mov %esp,%ebp
d: b8 00 00 00 00 mov $0x0,%eax
12: 5d pop %ebp
13: c3
还回到刚才的.rel.text段,2d000000 020b0000和39000000 020d0000分别是个组合,对应elf的Elf32_Rel结构体:
typedefstruct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbolindex */
}Elf32_Rel;
#defineELF32_R_TYPE(val) ((val) &0xff)
r_info右移8位,就是一个索引值(再次强调注意这是小端):
2d000000 020b0000: 0b
39000000 020d0000: 0d
这个索引是用来索引另外一个段:.symtab
ubuntu@ubuntu:~/work/hello$readelf -x 11 hello.o
Hex dumpof section '.symtab':
0x00000000 00000000 00000000 00000000 00000000................
0x00000010 01000000 00000000 000000000400f1ff ................
0x00000020 00000000 00000000 0000000003000100 ................
0x00000030 00000000 00000000 0000000003000300 ................
0x00000040 00000000 00000000 0000000003000400 ................
0x00000050 09000000 0a000000 0a00000002000100 ................local_func
0x00000060 00000000 00000000 0000000003000500 ................
0x00000070 00000000 00000000 0000000003000700 ................
0x00000080 00000000 00000000 0000000003000800 ................
0x00000090 00000000 00000000 0000000003000600 ................
0x000000a0 14000000 00000000 0a00000012000100 ................
0x000000b0 1900000014000000 0a000000 12000100 ................ global_func
0x000000c0 25000000 1e000000 2600000012000100 %.......&.......
0x000000d0 2a00000000000000 00000000 10000000 *............... printf(put)
简单的说就是第b行和第d行。
这又是一个结构体:
typedefstruct
{
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
}Elf32_Sym;
以global_func举例,其中 st_value 对应代码段中的位置:
00000014
14: 55 push %ebp
15: 89 e5 mov %esp,%ebp
17: b8 00 00 00 00 mov $0x0,%eax
1c: 5d pop %ebp
1d: c3
st_size为代码长度,这不用多说。
st_info 是BIND类型,表明它是全局符号还是本地符号,还有其他类型我也搞不清楚
st_other表明符号类型,2表明是函数
st_shndx表明它属于哪个段
st_name 又是一个新section .strtab 的索引:
ubuntu@ubuntu:~/work/hello$readelf -x 12 hello.o
Hex dumpof section '.strtab':
0x00000000 0068656c 6c6f2e63 006c6f63616c5f66 .hello.c.local_f
0x00000010 756e6300 66756e63 00676c6f62616c5f unc.func.global_
0x00000020 66756e63 006d6169 6e007075 747300 func.main.puts.
这里索引了函数的符号。
这样global_func的函数代码起点,名字,类型等等就和最开始的引用点绑定起来了。在链接时,会将这些section和其他.o的section合并到一起,因此里面的值也会变化(加偏移),再通过这个绑定关系,修改掉引用处的值
另外个printf的 st_value为0,这是因为相对于hello.o来说它是个未定义符号,无法绑定其代码地址,不过链接时会为其设置一个plt地址,这又是elf另外两种格式的知识了(以前的相关总结在前公司内网里取不出来!)
可能有些写得不对,以后提高后再修改。
这里的st_value和st_shndx 最为关键,如果对其进行篡改,可以原本的符号篡改到elf的任意地址,详细见这个链接:
http://phrack.org/issues/68/11.html#article
:)