PLT hook筆記

1. hook技術概述   

     hook技術是一種攔截用戶函數調用的技術。經過hook技術能夠實現統計用戶對某些函數的調用次數,對函數注入新的功能的目標。在Linux平臺,Hook技術能夠分紅用戶和內核兩個層面,每一個類比中都存在不一樣的hook技術。本文主要介紹針對動態連接技術的PLT hook。html

2. 代碼實例

    首先咱們先用一個實例來向你們展現一下PLT hook的效果。代碼的功能是驗證用戶在命令行輸入的密碼,hook的目標是strcmp函數,經過將strcmp函數的返回值置爲0,達到不管用戶輸入任何密碼,即便是錯誤的,都返回驗證經過的提示。 
    先編寫passwd.c。該代碼的做用是調用strcmp函數並判斷用戶輸入的密碼是否正確,並打印相應的提示。文件中只有一個函數就是check_is_authenticated。

  其次編寫咱們的main.c 該函數實現了將要替換strcmp函數的my_strcmp,和使hook生效的hook函數。代碼的具體細節linux

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <string.h>
 5 #include <inttypes.h>
 6 #include <execinfo.h>
 7 #include <sys/user.h>
 8 #include <sys/mman.h>
 9 #include "passwd.h"
10 
11 #define PAGE_SHIFT  12
12 #define PAGE_SIZE  (_AC(1,UL) << PAGE_SHIFT)
13 #define PAGE_MASK  (~(PAGE_SIZE-1))
14 
15 #define __AC(X,Y) (X##Y)
16 #define _AC(X,Y) __AC(X,Y)
17 
18 #define PAGE_START(addr) ((addr) & PAGE_MASK)
19 #define PAGE_END(addr)   (PAGE_START(addr) + PAGE_SIZE)
20 
21 int my_strcmp(const char *s1, const char *s2) 
22 {
23     return 0;
24 }
25 
26 uintptr_t get_base_addr(char *libname) 
27 {
28     FILE *fp;
29     char line[1024];
30     //char base_addr[1024];
31     uintptr_t base_addr = 0;
32 
33     if (NULL == (fp = fopen("/proc/self/maps", "r"))) {
34         perror("open err");
35         return -1;
36     }
37 
38     while (NULL != fgets(line, sizeof(line), fp)) {
39         if (NULL != strstr(line, libname)) {
40             sscanf(line, "%"PRIxPTR"-%*lx %*4s 00000000", &base_addr);
41             printf("line2:%s, base_addr:%"PRIxPTR"\n", line, base_addr);
42             break;
43         } 
44     }
45     fclose(fp);
46 
47     return base_addr;
48 }
49 
50 void hook() {
51     uintptr_t base_addr;
52     uintptr_t addr;
53     //1. get the base addr of libpasswd.so
54     base_addr = get_base_addr("libpasswd");
55     if (0 == base_addr) return;
56     addr = base_addr + 0x201020; 
57     //2. add the write permisson
58     mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ|PROT_WRITE);
59     //3. replace our hook func my_strcmp
60     *(void **)addr = my_strcmp; 
61     //4. clear the cache
62     __builtin___clear_cache((void *)PAGE_START(addr),
63             (void *)PAGE_END(addr));
64 }
65 
66 int main() 
67 {
68     hook();
69     check_is_authenticated("abcd");
70     return 0;
71 }
  接下來咱們將passwd.c編譯成動態庫libpasswd.so,main.c調用libpasswd.so中的check_is_authenticated函數。咱們是用下列gcc命令將passwd.c編譯爲動態庫,其中-fpic是生成位置無關代碼,-shared表示咱們生成的是一個動態庫,不須要main函數的參與。

  在將libpasswd.so放置到/usr/lib/目錄中以後,使用如下命令編譯main.c緩存

  運行以後咱們 能夠看到驗證結果始終是正確的,說明咱們對於libpasswd.so中的strcmp函數的Hook已經生效。函數

怎麼樣,是否是很神奇呀!下面咱們就來看看plt hook究竟是怎麼實現的。post

3. PLT hook原理

  說了這麼久,PLT hook究竟是怎麼實現的呢?到底什麼是PLT呢?下面我就帶你們瞭解如下到底什麼是PLT。
    在瞭解PLT以前咱們須要先了解下什麼是共享庫和動態連接技術。靜態連接技術,是以一種將多個可連接目標文件連接爲一個獨立的可執行文件的過程。在連接的過程當中,鏈接器會將靜態庫中的函數完整的複製到可執行文件的文本段中。在一個運行較多進程的系統中,這種連接方式對於內存消耗是不可小覷的。以下圖所示是兩種不一樣的鏈接方式生成的可執行文件的大小,能夠看到兩者相差極大。爲了解決這個問題,共享庫誕生了, 共享庫是一個目標模塊或者說目標文件,在運行或者加載時,能夠加載到內存的任意位置,並和內存中的程序連接起來,這個過程是一個叫作動態連接器的組件完成的。在完成連接的過程當中,連接器僅僅複製一些重定位和符號表信息到可執行文件中,大大減少了可執行文件的大小。

 

   PLT(Procedure Linkage Table)全程過程連接表,主要用於協助程序完成延遲加載的功能,假設程序調用一個動態庫中的函數,由於動態庫能夠被加載到內存中的任意位置,所以咱們沒法去預測這個函數的運行時地址。正常的作法是爲該函數調用生成一個重定位記錄,而後動態連接器在程序加載的時候去解析它。可是這並不符合位置無關代碼的作法,由於須要連接器修改調用模塊的文本段。GNU使用延遲加載的技術去解決這個問題,把對函數地址的解析延遲到了對於函數的實際調用的時刻。使用這種技術,在第一次函數調用時的開銷較大,可是在以後的調用中只會花費一條指令和一個間接的內存引用。
   延遲調用的完成須要PLT和GOT(Global Offset Table)全局偏移量表協做完成。若是一個目標模塊調用任何在共享庫中的函數,那麼它就有本身的GOT和PLT。GOT是數據段的一部分,PLT是代碼段的一部分。下面咱們來看看PLT和GOT表中的內容:
  PLT。PLT表中的每一項是一個16字節代碼。PLT[0]是一個特殊條目,它跳轉到動態連接器中。PLT[1]是系統啓動函數的條目。從第三項開始是用戶調用的動態庫函數的條目。GOT。GOT中每一個表項是8字節的地址類型數據。和PLT相似,GOT[0]和GOT[1]也是特殊條目,包含動態連接器在解析函數地址時會使用的信息。GOT[2]是動態連接器在ld-linux.so模塊中的入口點。其他的每個條目對應一個被調用的函數,其地址在須要時進行解析。每一個條目都有一個對應的PLT條目。下面咱們仍是以一個實例來解釋一下延遲調用的過程。
  下列代碼是一個動態庫libpasswd.so中調用strcmp函數的過程,從下列代碼,咱們能夠看到對於strcmp函數的調用,實際上調用的是PLT中的strcmp條目。strcmp條目其實是三條彙編代碼,讓咱們這些彙編語句都作了什麼。在560行,使用jmpq指令跳轉到GOT中strcmp對應的條目,GOT初始時都指向它對應的PLT條目的第二條指令,在這裏也就是"pushq $0x0"。在把strcmp的ID(0x0)壓入棧中後,使用jmpq指令跳轉到PLT[0], 前面說過PLT[0]存儲的是動態連接器相關的條目,先使用pushq命令將GOT[1]中存儲的動態鏈接器的一個參數壓棧("pushq 0x200ab2(%rip)")。值得注意的是這裏的0x200ab2是一個常量,這裏用到了位置無關代碼(PIC)的相關知識,再也不贅述。而後使用jmpq指令,經過GOT[2]中存儲的動態連接器的地址間接跳轉到動態連接器中,這裏要注意代碼中"*"的意義,"*"表示獲取地址對應的內存,相似指針變量的解引用運算符。在跳轉到動態連接器以後,鏈接器經過咱們剛剛壓入棧中的兩個值(被調用函數的ID, 和GOT[1])來肯定strcmp函數的運行時地址,並用這個地址重寫strcmp對應的GOT條目,在將控制傳遞給strcmp函數。

  以上就是第一次調用strcmp函數的流程,能夠看出開銷仍是不小的。在後續的調用中,首先仍是跳轉到strcmp的plt條目中,而後執行jmpq *0x200aaa(%rip)指令,不一樣的是這是strcmp對應的GOT條目已經被寫入了strcmp的運行時地址,因此這條jmp指令直接將程序的執行跳轉到strcmp函數中。那麼是否是隻要在strcmp對應的GOT條目中寫入咱們本身的函數的地址,就能夠將控制跳轉到本身實現的函數中了嘛。本着這樣的思路咱們來看看開始時的代碼。ui

4. 代碼分析

代碼分紅兩部分,一個是被hook的動態庫libpasswd.so和調用被hook動態庫的文件main.c。這裏要強調的是咱們攔截的是動態庫中的函數。下面我來說解一下核心的hook函數。
  1.  第一步咱們要獲取libpasswd在進程中的首地址,時刻記住咱們攔截的是動態庫中的函數,未來要替換的GOT[strcmp]也屬於libpasswd。
  2.  第二步就是獲取libpasswd中調用strcmp對應的GOT條目的地址,爲何是addr = base_addr + 0x201020呢?咱們回到上文的libpasswd的plt段,能夠看到strcmp的plt條目的第一條指令已經寫出了相應的GOT條目的地址就是0x201020。又由於咱們攔截的動態庫的strcmp函數,因此必須加上libpasswd在main中的首地址。spa

 

    3.  由於咱們要寫入目標進程的數據段,因此必須給相應的頁增長寫權限,這裏使用mprotect函數來調整相應頁的權限。命令行

         4.  第四步就是將相應的GOT條目的內容替換爲咱們的函數的地址。這一句值得說道說道,*(void **)addr = my_strcmp;你們以爲這句和 (void *)addr = my_strcmp有什麼區別呢?畢竟對*(void **) = (void *)看上去也是正確的啊!起初我也有這樣的疑惑,可是仔細想一想,其實並非這樣,咱們要記住GOT表中的每一項都存儲的是一個地址,同時addr存儲的是相應的GOT條目的地址,所以咱們必須先將addr強轉爲一個指向指針類型的指針。再使用星號解引用這樣獲取的就是GOT條目中存儲的內容。這裏須要理解的是指針類型其實和int char double是同樣的也是一種數據類型。將上面的代碼轉換爲如下形式也許更好理解:*( (void *) *addr) = my_strcmp。
 
 1 void hook() {
 2     uintptr_t base_addr;
 3     uintptr_t addr;
 4     //1. get the base addr of libpasswd.so
 5     base_addr = get_base_addr("libpasswd");
 6     if (0 == base_addr) return;
 7     addr = base_addr + 0x201020; 
 8     //2. add the write permisson
 9     mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ|PROT_WRITE);
10     //3. replace our hook func my_strcmp
11     *(void **)addr = my_strcmp; 
12     //4. clear the cache
13     __builtin___clear_cache((void *)PAGE_START(addr),
14             (void *)PAGE_END(addr));
15 }
        5. 最後一步咱們須要清除硬件緩存,由於GOT表項的內容極可能已經繞過內存被緩存到了硬件緩存中,可能會致使hook失敗。
  以上就是我瞭解到的PLT hook的所有內容,這裏咱們還只是hook本身的進程中調用的動態庫的函數。對於其餘進程的函數由於涉及到讀取/proc/map文件和修改頁權限,所以必需要root權限纔可以運行。具體如何實如今下一篇文章中,咱們再進行探討。

參考

3. 深刻理解計算機系統 本文關於連接知識的來源
相關文章
相關標籤/搜索