Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android漫游記(3)---重定位之GOT & PLT & R_ARM_JUMP_SLOT

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

編輯:關於Android編程

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 
#include 

/**
 * 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系統級調試的一大神器!)。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+08nT2s7Sw8fSqrLpv7TWuMHu1rTQ0LXEz7i92qOs0vK0y9Do0qq9+NDQu+Ox4Ly2tffK1KOsvfjI62dkYrrzo6zK5MjrcmVtb3RlIHRhcmdldCA6cG9ydLrzo6y9+MjrR0RCzOHKvrf7o6zO0sPHyuTI62Rpc2FzIG1haW7AtLLpv7RtYWluuq/K/bXEt7S747HgtPrC66O6PC9wPgo8cD48L3A+CjxwcmUgY2xhc3M9"brush:java;">> 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 ,當前PC寄存器所指位置 0x00008384 <+12>: add r4, pc, r4 0x00008388 <+16>: ldr r3, [pc, #120] ; 0x8408 0x0000838c <+20>: add r3, pc, r3 0x00008390 <+24>: mov r0, r3 0x00008394 <+28>: bl 0x82b0 ;跳轉到puts調用!!!<============== 0x00008398 <+32>: ldr r3, [pc, #108] ; 0x840c 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 0x000083ac <+52>: ldr r3, [r4, r3] 0x000083b0 <+56>: str r2, [r3, #4] 0x000083b4 <+60>: ldr r3, [pc, #80] ; 0x840c 0x000083b8 <+64>: ldr r3, [r4, r3] 0x000083bc <+68>: ldr r3, [r3, #4] 0x000083c0 <+72>: cmp r3, #0 0x000083c4 <+76>: bge 0x83dc 0x000083c8 <+80>: ldr r3, [pc, #60] ; 0x840c 0x000083cc <+84>: ldr r3, [r4, r3] 0x000083d0 <+88>: mov r0, r3 0x000083d4 <+92>: bl 0x82bc 0x000083d8 <+96>: b 0x83f8 0x000083dc <+100>: ldr r3, [pc, #40] ; 0x840c 0x000083e0 <+104>: ldr r3, [r4, r3] 0x000083e4 <+108>: ldr r3, [r3] 0x000083e8 <+112>: add r2, r3, #1 0x000083ec <+116>: ldr r3, [pc, #24] ; 0x840c 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>: ; instruction: 0xfffffffc End of assembler dump. 注意我上面的紅色部分注釋,那個BL指令就是跳轉到puts(當然下面會看到,實際上是先跳轉到了plt區)。我們執行b *0x8394,在BL指令處下斷點,然後c命令繼續執行。

然後我們執行x/i $pc查看當前指令是不是執行到我們的斷點:

> display/i $pc
> x/i $pc
=> 0x8394 :	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入口 <==============
   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!微笑

轉載請注明出處:生活秀

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved