1.フック技術概要
フック技術は、関数呼び出しをインターセプトするようテクノロジの利用者です。ユーザー統計関数は、新たな目的関数を注入し、いくつかの関数の呼び出しの数のためのフック技術によって達成することができます。Linuxプラットフォームでは、フックのユーザーとカーネル技術は、2つのレベルに分けることができ、それぞれのアナロジー異なるフック技術があります。本論文では、動的リンク技術のためのPLTのフックを説明しています。
例2のコード
第二に、我々は、main.cの機能は、関数strcmp関数my_strcmp、およびフック関数のフック効果を交換する実現書きます。コードの具体的な詳細
1の#include <stdio.hの> 2の#include <STDLIB.H> 3の#include <unistd.h> 4の#include < 文字列・H> 5の#include <inttypes.h> 6の#include <execinfo.h> 7#含めるは<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 チャー * S1と、CONST のchar *のS2) 22 { 23 リターン 0 。 24 } 25 26いるuintptr_tのget_base_addr(チャー *のlibnameに) 27 { 28 のFILE *のFP。 29 チャーライン[ 1024 ]。 30 // チャーBASE_ADDR [1024]。 31 uintptr_tをするBASE_ADDR = 0 。 32 33 であれば(NULL ==(FP =のfopen(" / PROC /自己/マップ"、" R " ))){ 34 にperror(" オープンERR " )。 35 リターン - 1 。 36 } 37 38 ながら(NULL!=関数fgets(線、はsizeof (ライン)、FP)){ 39 であれば(NULL!= はstrstr(ライン、libnameに)){ 40 のsscanf(ライン、" %" PRIxPTR " - %※のLX%* 4S 00000000 "、&BASE_ADDR)。 41 のprintf(" LINE2:%sは、BASE_ADDR:%" PRIxPTR " \ n " 、ライン、BASE_ADDR)。 42 ブレーク; 43 } 44 } 45 FCLOSE(FP)。 46 47 リターンBASE_ADDR。 48 } 49 50 空隙フック(){ 51 uintptr_tをするBASE_ADDR。 52 uintptr_tをaddrに、 53 //1 libpasswd.soのベースADDRを取得し 54 BASE_ADDR = get_base_addr(" libpasswd " )。 55 であれば(0 == BASE_ADDR)のリターン; 56 ADDR = BASE_ADDR + 0x201020 。 57 // 2.書き込みpermisson追加 58 MPROTECT((無効 *)PAGE_START(ADDR)を、PAGE_SIZE、PROT_READ | PROT_WRITE)。 59 // 3.交換する私たちのフックにfunc my_strcmp 60 *(無効 **)のaddr = my_strcmp。 61 // 4.キャッシュをクリア 62 __builtin ___ clear_cache((ボイド * )PAGE_START(ADDR)、 63 (ボイド * )PAGE_END(ADDR))。 64 } 65 66 のint main()の 67 { 68 フック()。 69 (check_is_authenticated " ABCDを" )。 70 リターン 0 ; 71 }
libpasswd.soは/ usr / lib /ディレクトリに置かれた後、次のコマンドは、main.cのをコンパイルします
実行した後、私たちは私たちが力の関数strcmpフック用libpasswd.so常に正しい結果を、表示します。
怎么样,是不是很神奇呀!下面我们就来看看plt hook到底是怎么实现的。
3. PLT hook原理
以上就是第一次调用strcmp函数的流程,可以看出开销还是不小的。在后续的调用中,首先还是跳转到strcmp的plt条目中,然后执行jmpq *0x200aaa(%rip)指令,不同的是这是strcmp对应的GOT条目已经被写入了strcmp的运行时地址,所以这条jmp指令直接将程序的执行跳转到strcmp函数中。那么是不是只要在strcmp对应的GOT条目中写入我们自己的函数的地址,就可以将控制跳转到自己实现的函数中了嘛。本着这样的思路我们来看看开始时的代码。
4. 代码分析
- 第一步我们要获取libpasswd在进程中的首地址,时刻记住我们拦截的是动态库中的函数,将来要替换的GOT[strcmp]也属于libpasswd。
-
第二步就是获取libpasswd中调用strcmp对应的GOT条目的地址,为什么是addr = base_addr + 0x201020呢?我们回到上文的libpasswd的plt段,可以看到strcmp的plt条目的第一条指令已经写出了相应的GOT条目的地址就是0x201020。又因为我们拦截的动态库的strcmp函数,所以必须加上libpasswd在main中的首地址。
3. 因为我们要写入目标进程的数据段,所以必须给相应的页增加写权限,这里使用mprotect函数来调整相应页的权限。
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 }