小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

鏈接(2)

 quasiceo 2017-12-18

鏈接(2)——?jiǎng)討B(tài)鏈接匯編探秘

原創(chuàng) 2015年06月05日 12:27:47

        關(guān)于動(dòng)態(tài)鏈接原理性文章有很多,在此本人盡量以深入淺出和少量的篇幅將問(wèn)題闡述清楚,拋開(kāi)無(wú)關(guān)的擴(kuò)展。

 

一、linux加載可執(zhí)行文件時(shí)的存儲(chǔ)器映像

 

下面圖片所描述的??臻g,是linux加載應(yīng)用程序時(shí)所生成的:

 

 

        首先,如果不更改內(nèi)核,那么linux系統(tǒng)加載程序(包括內(nèi)核里的子進(jìn)程)都是從0x08048000地址開(kāi)始的。當(dāng)加載器運(yùn)行時(shí),先對(duì)應(yīng)用程序中的可執(zhí)行文件進(jìn)行解析,將代碼段和數(shù)據(jù)段按照4kB對(duì)齊的方式,從0x08048000開(kāi)始往高地址放。緊接著就是存放堆,堆的增長(zhǎng)方向會(huì)按照箭頭所指往高地址增長(zhǎng)。

        但是堆的空間是有限的,它只能增長(zhǎng)到0x3fffffff,從0x40000000開(kāi)始,需要存放動(dòng)態(tài)庫(kù),這個(gè)動(dòng)態(tài)庫(kù)的加載,仍然屬于加載器的工作范疇,有多少庫(kù)就加載多少庫(kù),但同樣我們也看到,動(dòng)態(tài)庫(kù)的加載量也是有限的,它和用戶棧的增長(zhǎng)方向相反,過(guò)多的庫(kù)會(huì)壓制用戶棧的增長(zhǎng),而用戶棧的無(wú)限擴(kuò)展也會(huì)影響到動(dòng)態(tài)庫(kù)的調(diào)用數(shù)量。

        用戶棧是從0xbfffffff開(kāi)始的,那么從0xc0000000開(kāi)始向上,保留的就是內(nèi)核代碼,用于對(duì)所加載程序的控制。

        由于C語(yǔ)言的指針訪問(wèn)時(shí)不受程序和操作系統(tǒng)限制的,理論上我們可以訪問(wèn)到空間內(nèi)任意的地址,當(dāng)然,內(nèi)核本身有預(yù)警機(jī)制,當(dāng)你的指針試圖修改代碼段、共享庫(kù)段甚至是內(nèi)核段時(shí),很可能就會(huì)出現(xiàn)著名的“segmentation fault (core dumped)   ”段錯(cuò)誤,這是操作系統(tǒng)的自我保護(hù)機(jī)制。

        現(xiàn)在就有個(gè)問(wèn)題可以思考下,我們都知道動(dòng)態(tài)鏈接庫(kù)是在程序運(yùn)行時(shí)才由加載器提供的,我們同樣還知道,鏈接器在生成可執(zhí)行文件時(shí),已經(jīng)把函數(shù)跳轉(zhuǎn)的邏輯地址寫(xiě)死了,那請(qǐng)問(wèn)調(diào)用動(dòng)態(tài)函數(shù)(比如printf)時(shí),由于未執(zhí)行即未加載,匯編代碼是如何解釋printf的跳轉(zhuǎn)地址呢?程序在運(yùn)行時(shí),以什么依據(jù)到上圖中的共享庫(kù)區(qū)域?qū)ふ易约合胍暮瘮?shù)地址呢?

 

二、位置無(wú)關(guān)代碼PIC

        動(dòng)態(tài)庫(kù)存在的一個(gè)主要目的就是,允許多個(gè)正在運(yùn)行的進(jìn)程來(lái)共享相同的庫(kù)代碼,從而節(jié)約寶貴的存儲(chǔ)資源。那么庫(kù)代碼本身在硬盤(pán)的哪個(gè)位置并不重要,只要事先已被編譯,任何進(jìn)程隨時(shí)都可以把它需要的,庫(kù)代碼移花接木到共享庫(kù)映射區(qū)域中。

        我們就拿最簡(jiǎn)單的printf函數(shù)來(lái)說(shuō),它是屬于libc.so中的庫(kù)函數(shù),于是我們寫(xiě)個(gè)最簡(jiǎn)單的調(diào)用函數(shù):

#include <stdio.h>
void main(void)
{
        printf("haha!\n");
        return;
}

 

        直接gcc -O2編譯出來(lái)a.out,于是進(jìn)行反編譯:objdump -D a.out:

        真是夠長(zhǎng)的,先看main函數(shù)部分的printf調(diào)用:

08048368 <main>:
 8048368:    55                       push   %ebp
 8048369:    89 e5                    mov    %esp,%ebp
 804836b:    83 ec 08                 sub    $0x8,%esp
 804836e:    83 e4 f0                 and    $0xfffffff0,%esp
 8048371:    83 ec 1c                 sub    $0x1c,%esp
 8048374:    68 60 84 04 08           push   $0x8048460
 8048379:    e8 22 ff ff ff           call   80482a0 <puts@plt>
 804837e:    c9                       leave 
 804837f:    c3                       ret   

 

        call語(yǔ)句讓我們?nèi)フ?x80482a0地址,好吧,那么我們?nèi)フ艺野l(fā)現(xiàn):

080482a0 <puts@plt>:
 80482a0:       ff 25 58 95 04 08       jmp    *0x8049558
 80482a6:       68 00 00 00 00          push   $0x0
 80482ab:       e9 e0 ff ff ff          jmp    8048290 <_init+0x18>

        好吧,又要跳轉(zhuǎn)到0x8049558,于是我們走著:

反匯編 .got 節(jié):
 
08049548 <.got>:
 8049548:       00 00                   add    %al,(%eax)
        ...
反匯編 .got.plt 節(jié):

0804954c <_GLOBAL_OFFSET_TABLE_>:      
 804954c:       80 94 04 08 00 00 00    adcb   $0x0,0x8(%esp,%eax,1)
 8049553:       00
 8049554:       00 00                   add    %al,(%eax)
 8049556:       00 00                   add    %al,(%eax)
 8049558:       a6                      cmpsb  %es:(%edi),%ds:(%esi)
 8049559:       82                      (bad) 
 804955a:       04 08                   add    $0x8,%al
 804955c:       b6 82                   mov    $0x82,%dh
 804955e:       04 08                   add    $0x8,%al

        我去……8049558對(duì)應(yīng)的cmpsb語(yǔ)句是,是什么sb東西???接下來(lái)是不是要去百度cmpsb關(guān)鍵字?省了吧,.got節(jié)、.got.plt節(jié)明顯被objdump曲解了。先了解下got和plt到底是什么東西。

        .got叫做全局偏移量表(global offset table),而plt是過(guò)程鏈接表(procedure linkage table)。在got表中有g(shù)ot[0]~got[n]的n個(gè)全局量的偏移地址,它符合下表所描述的結(jié)構(gòu)特性:

地址 表目 內(nèi)容 描述
  GOT[0]   .dynamic節(jié)地址
  GOT[1]   鏈接器標(biāo)識(shí)信息
  GOT[2]   動(dòng)態(tài)鏈接器入口點(diǎn)
  GOT[3]   printf函數(shù)調(diào)用push地址

 

        接著分析,我們先看804954c,它的內(nèi)容80 94 04 08,是不是看起來(lái)很眼熟?對(duì)了,倒過(guò)來(lái)就是地址0x8049480,到這個(gè)地址去看看:

反匯編 .dynamic 節(jié):

08049480 <_DYNAMIC>:
 8049480:       01 00                   add    %eax,(%eax)
 8049482:       00 00                   add    %al,(%eax)
 8049484:       24 00                   and    $0x0,%al
 8049486:       00 00                   add    %al,(%eax)
 8049488:       0c 00                   or     $0x0,%al
 804948a:       00 00                   add    %al,(%eax)

        是不是剛好為上表中說(shuō)的.dynamic節(jié)起始地址?!也就是說(shuō)這個(gè)是GOT[0]里存的內(nèi)容,也就是說(shuō)它的長(zhǎng)度是4字節(jié)。依次推下去,如果我們想獲得所關(guān)心的GOT[3]的地址,只需在GOT數(shù)組上跳3個(gè)步進(jìn),用0x804954c加個(gè)3*4字節(jié)即可,于是得到0x8049558,那么GOT[3]里的內(nèi)容拼起來(lái)就是0x80482a6!什么?這個(gè)值怎么得出來(lái)的?是啊,我添加這幾個(gè)字的幾分鐘之前也納悶,自己去年寫(xiě)的東西,當(dāng)初是怎么想出來(lái)的?結(jié)果稍微觀察了下發(fā)現(xiàn),既然GOT數(shù)組是按4字節(jié)對(duì)齊(別問(wèn)我為什么,有本事找glibc開(kāi)發(fā)者問(wèn)去?。?,那么你可以從上表中804955a到8049558地址里面存的值倒著拼回來(lái)不就是0x80482a6了么?唉,看不懂的人一定是大笨蛋!大啊大……笨……egg……

繼續(xù)找這個(gè)地址:

08048290 <puts@plt-0x10>:              
 8048290:       ff 35 50 95 04 08       pushl  0x8049550
 8048296:       ff 25 54 95 04 08       jmp    *0x8049554
 804829c:       00 00                   add    %al,(%eax)
        ...    
 
080482a0 <puts@plt>:                   
 80482a0:       ff 25 58 95 04 08       jmp    *0x8049558
 80482a6:       68 00 00 00 00          push   $0x0
 80482ab:       e9 e0 ff ff ff          jmp    8048290 <_init+0x18>

        壓入0,這個(gè)是首個(gè)被調(diào)用的外部函數(shù)所以標(biāo)識(shí)為0,接下來(lái)跳轉(zhuǎn)到8048290,也就是<puts@plt-0x10>: 的部分,壓入0x8049550,這是GOT[1]的地址也就是鏈接器的標(biāo)識(shí)信息。繼續(xù)jmp到0x8049554,這是GOT[2]也就是動(dòng)態(tài)鏈接器入口點(diǎn),這兩個(gè)跳轉(zhuǎn)都是跳到內(nèi)核中執(zhí)行相應(yīng)的代碼,最后動(dòng)態(tài)鏈接器會(huì)通過(guò)一系列變態(tài)運(yùn)算,將printf的地址定位出來(lái),假設(shè)是0x41111111,并用此地址值覆蓋GOT[3]里的值,并把控制傳遞給printf。

        當(dāng)下次再調(diào)用printf時(shí),main函數(shù)執(zhí)行call 80482a0 <puts@plt>時(shí),會(huì)繼續(xù)執(zhí)行 80482a0:       ff 25 58 95 04 08       jmp    *0x8049558跳轉(zhuǎn)到GOT[3],但此時(shí)GOT[3]中的0x80482a6已經(jīng)被0x41111111覆蓋,因此程序直接跳轉(zhuǎn)到0x41111111也就是printf庫(kù)函數(shù)地址去執(zhí)行!

        我之所以敢隨便編一個(gè)0x41111111,首先因?yàn)槲腋鶕?jù)第一部分所描述的程序進(jìn)程得知?jiǎng)討B(tài)庫(kù)地址從0x40000000開(kāi)始,而具體映射到哪個(gè)值,只有進(jìn)程運(yùn)行后才曉得,所以怎么編都沒(méi)人敢說(shuō)我錯(cuò),哈哈!
       

        回顧上面對(duì)動(dòng)態(tài)函數(shù)的分析,我們發(fā)現(xiàn)這是ELF編譯系統(tǒng)一個(gè)很有趣的技術(shù),他被稱為延遲綁定(lazy binding),很奇怪為啥不是懶人綁定呢O(∩_∩)O~,意思就是說(shuō),printf的地址綁定不發(fā)生在鏈接器做鏈接時(shí),而是延遲到程序被執(zhí)行,動(dòng)態(tài)鏈接器加載動(dòng)態(tài)庫(kù)后,程序第一次執(zhí)行動(dòng)態(tài)函數(shù)時(shí),才完成函數(shù)地址綁定。

 

 

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多