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!
轉載請注明出處:生活秀