首页 > 代码库 > Android漫游记(3)---重定位之GOT & PLT & R_ARM_JUMP_SLOT
Android漫游记(3)---重定位之GOT & PLT & R_ARM_JUMP_SLOT
Android系统的动态链接工具是/system/bin/linker(一般的Linux系统是ld.so),虽然名字不同,但是基本的动态链接过程是类似的。需要注意的一点是,Linux一般是Lazy,即所谓的“懒”加载方式,但是Android系统有点区别,是非Lazy方式,即所有的重定位操作,在进程首次执行以前已经全部完成。这大概也是Android应用首次启动比较慢的原因之一吧!
关于Android系统的PLT和GOT可以写上一篇高考作为,在这里就不提概念性的东西了,网上有一篇博文:http://www.codeproject.com/Articles/70302/Redirecting-functions-in-shared-ELF-libraries ,这篇文章里是基于I386架构的,和Arm有所不同,但原理是一样的。如果有同学对下面的内容感兴趣,可以先去看看,把基本的东西先搞清楚,这样可能会顺利些,当然,最好了解些arm汇编~~~
还是老习惯,我会直接写个小Demo也演示Android系统的Linker是如何处理重定位的,当然,ELF重定位的类型非常多,我们这里重点关注:R_ARM_JUMP_SLOT(386架构上叫R_386_JUMP_SLOT,很像,不是么)。该类型重定位一般是针对外部函数引用的,即你要引用一个外部定义的函数符号,linker在加载你的应用的时候,就需要应用此种类型的重定位,来完成符号到真实函数的链接!
举例:比如你的应用需要打印一段文本到中断,你可能会调用libc.so这个C库的puts函数。这就是外部引用,那么在你的应用里就必定针对puts调用,存在一个R_ARM_JUMP_SLOT重定位信息。
先上一段非常小的程序:
/* * PLT&GOT Resolver * Created on: 2014-6 * Author: Chris.Z */ #include <stdio.h> #include <stdlib.h> /** * define the local method * */ void local_method_call() { printf("[+]I'm local method.\n"); } int main() { //printf("[+]PLT&GOT Resolver...\n"); //local_method_call(); //call puts to check r_arm_jump_slot puts("[+]call the method of puts from libc.\n"); getchar(); return 0; }这个程序没干其他事,就是调用了puts输出一段文本,这里的puts定义在libc.so库中。麻雀虽小,五脏俱全,我们验证下linker是怎么实现对于puts的“动态链接”的。
首先用readelf看下程序生成的二进制文件的elf内容(截取部分):
注意上图中的箭头所指,正是我们所说的R_ARM_JUMP_SLOT重定位,位于.rel.plt区。注意这行信息中的第一列Offset,值是0x9ff8,先记录下来,后面会用到。
我们实际动态运行下上面的小程序,同时用GDB附加到进程进行远程动态调试(GDB实在是Android系统级调试的一大神器!)。
由于我们要查看指令执行的细节,因此需要进行汇编级调试,进入gdb后,输入remote target :port后,进入GDB提示符,我们输入disas main来查看main函数的反汇编代码:
> disas main Dump of assembler code for function main: 0x00008378 <+0>: push {r3, r4, r11, lr} 0x0000837c <+4>: add r11, sp, #12 => 0x00008380 <+8>: ldr r4, [pc, #124] ; 0x8404 <main+140>,当前PC寄存器所指位置 0x00008384 <+12>: add r4, pc, r4 0x00008388 <+16>: ldr r3, [pc, #120] ; 0x8408 <main+144> 0x0000838c <+20>: add r3, pc, r3 0x00008390 <+24>: mov r0, r3 0x00008394 <+28>: bl 0x82b0 ;跳转到puts调用!!!<============== 0x00008398 <+32>: ldr r3, [pc, #108] ; 0x840c <main+148> 0x0000839c <+36>: ldr r3, [r4, r3] 0x000083a0 <+40>: ldr r3, [r3, #4] 0x000083a4 <+44>: sub r2, r3, #1 0x000083a8 <+48>: ldr r3, [pc, #92] ; 0x840c <main+148> 0x000083ac <+52>: ldr r3, [r4, r3] 0x000083b0 <+56>: str r2, [r3, #4] 0x000083b4 <+60>: ldr r3, [pc, #80] ; 0x840c <main+148> 0x000083b8 <+64>: ldr r3, [r4, r3] 0x000083bc <+68>: ldr r3, [r3, #4] 0x000083c0 <+72>: cmp r3, #0 0x000083c4 <+76>: bge 0x83dc <main+100> 0x000083c8 <+80>: ldr r3, [pc, #60] ; 0x840c <main+148> 0x000083cc <+84>: ldr r3, [r4, r3] 0x000083d0 <+88>: mov r0, r3 0x000083d4 <+92>: bl 0x82bc 0x000083d8 <+96>: b 0x83f8 <main+128> 0x000083dc <+100>: ldr r3, [pc, #40] ; 0x840c <main+148> 0x000083e0 <+104>: ldr r3, [r4, r3] 0x000083e4 <+108>: ldr r3, [r3] 0x000083e8 <+112>: add r2, r3, #1 0x000083ec <+116>: ldr r3, [pc, #24] ; 0x840c <main+148> 0x000083f0 <+120>: ldr r3, [r4, r3] 0x000083f4 <+124>: str r2, [r3] 0x000083f8 <+128>: mov r3, #0 0x000083fc <+132>: mov r0, r3 0x00008400 <+136>: pop {r3, r4, r11, pc} 0x00008404 <+140>: andeq r1, r0, r8, asr r12 0x00008408 <+144>: andeq r0, r0, r12, lsr #1 0x0000840c <+148>: ; <UNDEFINED> instruction: 0xfffffffc End of assembler dump.注意我上面的红色部分注释,那个BL指令就是跳转到puts(当然下面会看到,实际上是先跳转到了plt区)。我们执行b *0x8394,在BL指令处下断点,然后c命令继续执行。
然后我们执行x/i $pc查看当前指令是不是执行到我们的断点:
> display/i $pc > x/i $pc => 0x8394 <main+28>: bl 0x82b0OK,确实在0x8394断下,到这里,我们停止继续执行,来检查下几个重要信息。
首先我们看下0x00008394 <+28>: bl0x82b0这行汇编,它的意思是带链接跳转到0x82b0执行,那么0x82b0究竟有什么指令呢?
执行disas 0x82b0,0x82c0,输出如下:
> disas 0x82b0,0x82c0 Dump of assembler code from 0x82b0 to 0x82c0: 0x000082b0: add r12, pc, #0 0x000082b4: add r12, r12, #4096 ; 0x1000 0x000082b8: ldr pc, [r12, #3392]! ; 0xd40 跳转到libc.so puts入口 <span style="font-family: Arial, Helvetica, sans-serif;"><==============</span> 0x000082bc: add r12, pc, #0 End of assembler dump.前面的两条指令我们忽略过去(主要是计算.got偏移),我们直接执行到0x82b8,即上标的红色指令。
简单解释指令含义:讲r12+0xd40所指向的内存字加载到PC寄存器,实际上就是跳转到那个地址。
我们看看r12+0xd40此时的值是什么,执行p/x $r12+0xd40:
1: x/i $pc => 0x82b8: ldr pc, [r12, #3392]! ; 0xd40 > p/x $r12+0xd40 $1 = 0x9ff8看到了吗,这个0x9ff8正是我们上面记录的那个地址!
我们在执行info symbol 0x9ff8命令:
> info symbol 0x9ff8 _GLOBAL_OFFSET_TABLE_ + 20 in section .got
到这里,我们做个简单小结:
elf文件的.rel.plt区里,针对R_ARM_JUMP_SLOT类型的重定位,其offset的内容就是.got区该符号的地址,即位于GOT表基址偏移20个字节(本例中的GOT表基址为0x9fe4)。
这里要说明的是,可执行和.so动态链接库的计算方式有所差别,.so需要加上加载时的模块基址。
下面,我们在看看.got的0x9ff8里是什么内容:
执行p/x *0x9ff8:
> p/x *0x9ff8 $2 = 0x4011a7dc puts真实的入口地址<====================我们在对照下进程的maps:
正是libc.so所在的地址,实际上就是puts的函数入口地址!好了,今天就写到这里,Enjoy IT!
转载请注明出处:生活秀