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	0x82b0

OK,确实在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!

转载请注明出处:生活秀

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

时间: 2024-10-11 10:47:49

Android漫游记(3)---重定位之GOT & PLT & R_ARM_JUMP_SLOT的相关文章

了解动态链接(六)—— 重定位表

柳条青青,南风熏熏,幻化奇峰瑶岛,一天的黄云白云,那边麦浪中间,有农妇笑语殷殷.问后园豌豆肥否,问杨梅可有鸟来偷:好几天不下雨了,玫瑰花还未曾红透:梅夫人今天进城去,且看她有新闻无有.—— 徐志摩·夏日田间即景 无论是可执行文件还是 so,只要它依赖于其他 so(.dynsym 动态符号表中有导入符号存在),那么在编译链接阶段,这些符号的地址未知,所以只能在动态链接阶段对其进行地址重定位. 注意:以 PIC 编译的 so,虽然称“地址无关代码”,但也需要重定位.因为对于 PIC 的 so 来说,

ELF Format 笔记(十)—— 重定位(relocation)

ilocker:关注 Android 安全(新手) QQ: 2597294287 重定位就是把符号引用与符号定义链接起来的过程,这也是 android linker 的主要工作之一. 当程序中调用一个函数时,相关的 call 指令必须在执行期将控制流转到正确的目标地址.所以,so 文件中必须包含一些重定位相关的信息,linker 据此完成重定位的工作. 这些重定位信息保存在一系列的重定位项中,重定位项的结构如下: 这些重定位项位于 .rel.plt section 中. r_offset:对于可

cordova开发中,android端利用百度sdk定位。

原文在此,感谢作者http://snoopyxdy.blog.163.com/blog/static/601174402014420872345/ 近期的一个phonegap项目把我做的焦头烂额,最让人蛋疼的就是安卓4.1.x对html5的定位获取经纬度有个无比巨大的坑,一般我们利用如下代码进行html5的定位 navigator.geolocation.getCurrentPosition(function(pos){               alert(JSON.stringify(po

关于重定位代码的浅显理解

首先需要朱有鹏老师,这是在学习了朱老师的课程之后的一点理解,代码是根据朱老师的源码学习之后编写的. 根据反汇编代码  d0024010:     e24f0018       sub      r0, pc, #24 可以看出通过adr汇编伪指令将加载地址写入r0寄存器,此时因为adr指令采用相对寻址的寻址方式所以adr实际写入r0寄存器的地址为程序的加载地址而非反汇编代码所指示的0xd0024000,通过ldr伪指令将所需重定位的地址写入r1寄存器,此时r1所存的地址为0xd0024064,该

TI C66x DSP 系统events及其应用 - 5.9(IST重定位)

RESET中断的获取包必须位于0地址处,然后其他中断的获取包可以位于任何256字(1k字节边界)对齐的程序空间.IST的位置由ISTP寄存器中的ISTB字段决定. IST重新定位举例: 1,将IST重新定位到800h: 将地址0h~200h的源IST拷贝到800h~A00h中: 将800h写到ISTP寄存器中: MVK 800h,B2 MVC B2,ISTP 则ISTP = 800h = 1000 0000 0000b 2,ISTP引导CPU重新定位的IST中去顶相应的ISFP(指令获取包) 假

ELF格式的重定位原理分析

前面有篇文章分析了ELF格式,也只是让我们对目标文件有了一个大概的了解,并没有说明一个十分重要的问题:重定位,今天重新看了下重定位的资料,终于弄懂了重定位的过程,下面来做一个分析. 我们将使用下面两个源代码中的文件a.c和b.c展开分析: //a.c extern int shared; int main() { int a=100; swap(&a,&shared); } //b.c int shared=1; void swap(int *a,int *b) { *a^=*b^=*a^

S5pv210裸机实验——SDRAM重定位

一:为什么需要进行重定位 我们在设计一个程序时,会给这个程序指定一个运行地址(链接地址).就是说我们在编译程序时其实心里是知道我们程序将来被运行时的地址(运行地址)的,而且必须给编译器链接器指定这个地址(链接地址)才行.最后得到的二进制程序理论上是和你指定的运行地址有关的,将来这个程序被执行时必须放在当时编译链接时给定的那个地址(链接地址)下才行,否则不能运行(就叫位置有关代码).但是有个别特别的指令他可以跟指定的地址(链接地址)没有关系,也就是说这些代码实际运行时不管放在哪里都能正常运行. 二

总结:代码重定位

什么是重定位?为什么要代码重定位? 要弄清楚上面的这两个问题,首先要理解下面这几个概念 一.编码 (1)位置无关编码:PIC,可执行程序运行时与代码在内存中的地址无关,代码中没有使用绝对地址,而是使用的相对地址.(例如:B.BL.MOV等指令) (2)位置有关编码:可执行程序运行时与代码在内存中的地址有关系.(例如:LDR PC, =MAIN等指令) 二.地址 (1)链接地址:程序编译链接时指定的地址(使用makefile或者链接脚本可以指定链接地址) (2)运行地址:程序在内存中实际运行的地址

也谈PE重定位表

最近研究脱壳,遇到了dll,所以不可避免的需要修复重定位表.以前研究过也脱过不少壳,但都是exe从来没有手工修过重定位表,于是搜索之,恩有这么一篇:<PE重定位表学习手记>,有刚巧手上有看雪段钢等编著的<加密与解密>,一并阅读. <PE重定位表学习手记>中说到: 每个块的首部是如下定义: typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock; } IMAGE_BAS