ARM ELF的函数重定位与x86是一致的,但由于汇编指令不同,再鼓捣一遍。
示例代码:
#include <stdio.h>
#include <stdlib.h>
int main () {
puts ("Hello world");
sleep (1);
FILE *fp = fopen ("1.c", "r");
fclose (fp);
exit (0);
}
通过 readelf -r 可以查看ELF中所有需要重定位的函数,我们以fopen()函数为例,分析其重定位过程。
$ arm-linux-androideabi-readelf -r elf_2
Relocation section '.rel.plt' at offset 0x278 contains 7 entries:
Offset Info Type Sym.Value Sym. Name
00009ff4 00000516 R_ARM_JUMP_SLOT 00000000 fopen
首先main()函数中,通过 bl 82f4
调用fopen(),82f4是一个16进制表示的地址,位于.plt节。
$ arm-linux-androideabi-objdump -d elf_2
000083c8 <main>:
...
8404: ebffffba bl 82f4 <fopen@plt>
Disassembly of section .plt:
000082f4 <fopen@plt>:
82f4: e28fc600 add ip, pc, #0, 12 @由于ARM三级流水,PC = 0x82f4 + 0x8
82f8: e28cca01 add ip, ip, #4096
82fc: e5bcfcf8 ldr pc, [ip, #3320]! @ip + 0xcf8 = 0x9ff4
以上三条指令执行完,从0x9ff4位置取值给pc,完成间接寻址的跳转。看一下0x9ff4处内容:
(gdb) p/x *0x9ff4
$1 = 0x82b0
程序跳转到0x82b0位置:
Disassembly of section .plt:
000082b0 <__libc_init@plt-0x14>:
82b0: e52de004 push {lr} ; (str lr, [sp, #-4]!)
82b4: e59fe004 ldr lr, [pc, #4] ; 82c0 <__libc_init@plt-0x4>
82b8: e08fe00e add lr, pc, lr
82bc: e5bef008 ldr pc, [lr, #8]!
82c0: 00001d18 andeq r1, r0, r8, lsl sp
可以看到,这是.plt节的开始位置,IDA帮助我们做了一些显示的优化,所以其汇编结果与objdump看到的不同,它假装替我们完成了GOT的重定位过程,实际并非如此:
@ida的显示结果
.got:00009FF4 fopen_ptr DCD __imp_fopen
下面解析一下.plt节开头的这几条指令:
@ 1. stack <- lr
@ 2. lr <- 0x1d18
@ 3. lr <- 0x82c0 + 0x1d18 = 0x9fd8
@ 4. pc <- [0x9fd8 + 0x8], lr <- 0x9fd8 + 0x8 = 0x9fe0
发现程序最终从0x9fe0地址处取值,并间接寻址将其作为地址跳转过去执行。使用gdb发现,此处静态值为0x0。显然这块地址内容,要由程序运行时动态补充的,否则这条指令将产生0地址访问异常。
(gdb) p/x *0x82c0
$1 = 0x1d18
(gdb) p/x *0x9fe0
$2 = 0x0
(gdb)
原来GOT的前3项,是为系统预留的(GOT[0][1][2]),其中GOT[1]中是ELF中所有动态库构成的链表的指针,GOT[2]是_dl_runtime_resolve函数指针。这个函数将具体完成函数的重定向过程,并将结果反馈到GOT表中。
因此上面静态分析时,GOT这3个表项是没有值的,它们由加载器动态填充。
以前画的x86的图,同样适应 ARM: