Android漫游记(3)---重定位之GOT & PLT & R_ARM_JUMP_SLOT

来源:互联网 发布:mac版本老装不了xcode 编辑:程序博客网 时间:2024/06/12 21:31

    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 mainDump of assembler code for function main:   0x00008378 <+0>:push{r3, r4, r11, lr}   0x0000837c <+4>:addr11, sp, #12=> 0x00008380 <+8>:ldrr4, [pc, #124]; 0x8404 <main+140>,当前PC寄存器所指位置   0x00008384 <+12>:addr4, pc, r4   0x00008388 <+16>:ldrr3, [pc, #120]; 0x8408 <main+144>   0x0000838c <+20>:addr3, pc, r3   0x00008390 <+24>:movr0, r3   0x00008394 <+28>:bl0x82b0 ;跳转到puts调用!!!<==============   0x00008398 <+32>:ldrr3, [pc, #108]; 0x840c <main+148>   0x0000839c <+36>:ldrr3, [r4, r3]   0x000083a0 <+40>:ldrr3, [r3, #4]   0x000083a4 <+44>:subr2, r3, #1   0x000083a8 <+48>:ldrr3, [pc, #92]; 0x840c <main+148>   0x000083ac <+52>:ldrr3, [r4, r3]   0x000083b0 <+56>:strr2, [r3, #4]   0x000083b4 <+60>:ldrr3, [pc, #80]; 0x840c <main+148>   0x000083b8 <+64>:ldrr3, [r4, r3]   0x000083bc <+68>:ldrr3, [r3, #4]   0x000083c0 <+72>:cmpr3, #0   0x000083c4 <+76>:bge0x83dc <main+100>   0x000083c8 <+80>:ldrr3, [pc, #60]; 0x840c <main+148>   0x000083cc <+84>:ldrr3, [r4, r3]   0x000083d0 <+88>:movr0, r3   0x000083d4 <+92>:bl0x82bc   0x000083d8 <+96>:b0x83f8 <main+128>   0x000083dc <+100>:ldrr3, [pc, #40]; 0x840c <main+148>   0x000083e0 <+104>:ldrr3, [r4, r3]   0x000083e4 <+108>:ldrr3, [r3]   0x000083e8 <+112>:addr2, r3, #1   0x000083ec <+116>:ldrr3, [pc, #24]; 0x840c <main+148>   0x000083f0 <+120>:ldrr3, [r4, r3]   0x000083f4 <+124>:strr2, [r3]   0x000083f8 <+128>:movr3, #0   0x000083fc <+132>:movr0, r3   0x00008400 <+136>:pop{r3, r4, r11, pc}   0x00008404 <+140>:andeqr1, r0, r8, asr r12   0x00008408 <+144>:andeqr0, r0, r12, lsr #1   0x0000840c <+148>:; <UNDEFINED> instruction: 0xfffffffcEnd of assembler dump.
注意我上面的红色部分注释,那个BL指令就是跳转到puts(当然下面会看到,实际上是先跳转到了plt区)。我们执行b *0x8394,在BL指令处下断点,然后c命令继续执行。

然后我们执行x/i $pc查看当前指令是不是执行到我们的断点:

> display/i $pc> x/i $pc=> 0x8394 <main+28>:bl0x82b0
OK,确实在0x8394断下,到这里,我们停止继续执行,来检查下几个重要信息。

首先我们看下0x00008394 <+28>: bl0x82b0这行汇编,它的意思是带链接跳转到0x82b0执行,那么0x82b0究竟有什么指令呢?

执行disas 0x82b0,0x82c0,输出如下:

> disas 0x82b0,0x82c0Dump of assembler code from 0x82b0 to 0x82c0:   0x000082b0:addr12, pc, #0   0x000082b4:addr12, r12, #4096; 0x1000   0x000082b8:ldrpc, [r12, #3392]!; 0xd40 跳转到libc.so puts入口 <span style="font-family: Arial, Helvetica, sans-serif;"><==============</span>   0x000082bc:addr12, pc, #0End of assembler dump.
前面的两条指令我们忽略过去(主要是计算.got偏移),我们直接执行到0x82b8,即上标的红色指令。

简单解释指令含义:讲r12+0xd40所指向的内存字加载到PC寄存器,实际上就是跳转到那个地址。

我们看看r12+0xd40此时的值是什么,执行p/x $r12+0xd40:

1: x/i $pc=> 0x82b8:ldrpc, [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!微笑

转载请注明出处:生活秀


0 0