elf之relocatable文件学习总结

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:55push%ebp
1f:89 e5mov%esp,%ebp
21:83 e4 f0and$0xfffffff0,%esp
24:83 ec 10sub$0x10,%esp
27:e8 de ff ff ffcalla
2c:e8 fc ff ff ffcall2d
31:c7 04 24 00 00 00 00movl$0x0,(%esp)
38:e8 fc ff ff ffcall39
3d:b8 00 00 00 00mov$0x0,%eax
42:c9leave
43:c3ret

上面的2d 39都是当前指令的下一个指令地址,此时都是无意义的地址,需要在链接时重定位(relocatable),那么链接时是如何重定位的呢

查看section:
ubuntu@ubuntu:~/work/hello$readelf -S hello.o
Thereare 13 section headers, starting at offset 0x1a8:

SectionHeaders:
[Nr] NameTypeAddrOffSizeES Flg Lk Inf Al
[ 0]NULL00000000 000000 000000 00000
[ 1] .textPROGBITS00000000 000034 000044 00AX 004
[ 2] .rel.textREL00000000 0004c0 000018 081114
[ 3] .dataPROGBITS00000000 000078 000000 00WA 004
[ 4] .bssNOBITS00000000 000078 000000 00WA 004
[ 5] .rodataPROGBITS00000000 000078 00000c 00A 001
[ 6] .commentPROGBITS00000000 000084 00002b 01MS 001
[ 7] .note.GNU-stackPROGBITS00000000 0000af 000000 00001
[ 8] .eh_framePROGBITS00000000 0000b0 000098 00A 004
[ 9] .rel.eh_frameREL00000000 0004d8 000020 081184
[10] .shstrtabSTRTAB00000000 000148 00005f 00001
[11] .symtabSYMTAB00000000 0003b0 0000e0 1012 104
[12] .strtabSTRTAB00000000 000490 00002f 00001

我们需要关注第2,11,12这3个section
首先是重定位section:
ubuntu@ubuntu:~/work/hello$readelf -x 2hello.o

Hex dumpof section '.rel.text':
0x00000000 2d000000 020b000034000000 01060000 -.......4.......
0x00000010 39000000 020d00009.......
这里可以发现上面所说的3个需要重定位的3个地址中的两个:2d39都在这里(注意X86是小端),为什么a不在这里,因为它是local_func的跳转,local_func是用static定义的本地函数,a就是local_func的代码地址,它不需要重定位,因为它只在本地使用:
0000000a:
a:55push%ebp
b:89 e5mov%esp,%ebp
d:b8 00 00 00 00mov$0x0,%eax
12:5dpop%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 11hello.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 charst_info; /* Symbol type and binding */
unsigned charst_other; /* Symbol visibility */
Elf32_Sectionst_shndx; /* Section index */
}Elf32_Sym;
以global_func举例,其中 st_value 对应代码段中的位置:
00000014:
14:55push%ebp
15:89 e5mov%esp,%ebp
17:b8 00 00 00 00mov$0x0,%eax
1c:5dpop%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 747300func.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
【elf之relocatable文件学习总结】:)

    推荐阅读