二進制固件函數劫持術-DYNAMIC

 背景介紹

  固件系統中的二進制文件依賴於特定的系統環境執行,針對固件的研究在沒有足夠的資金的支持下需要通過固件的模擬來執行二進制文件程序。依賴於特定硬件環境的固件無法完整模擬,需要hook掉其中依賴於硬件的函數。

 LD_PRELOAD的劫持

 對於特定函數的劫持技術分爲動態注入劫持和靜態注入劫持兩種。靜態注入指的是通過修改靜態二進制文件中的內容來實現對特定函數的注入。動態注入則指的是在運行的過程中對特定的函數進行劫持,動態注入劫持一方面可以通過劫持PLT表或者GOT表來實現,另一方面可以通過環境變量LD_PRELOAD來實現。

 在《揭祕家用路由器0day漏洞挖掘》中作者針對D-link DIR-605L(FW_113)路由器中的Web應用程序boa,通過hook技術劫持 apmib_init()和apmib_get()函數修復boa對硬件的依賴,使得qemu-static-mips可以模擬執行,在文中作者通過LD_PRELOAD環境變量實現對函數的劫持。網上針對LD_PRELOAD的劫持也有大量的描述。但是LD_PRELOAD仍舊不是普適的。

 存在的問題

 LD_PRELOAD環境變量的開關在編譯生成ulibc的時候指定,當關閉該選項的時候,無法使用LD_PRELOAD來預加載指定的動態鏈接庫文件。

 固件的二進制文件中會將二進制文件中的Section信息去除掉,只保留下Segment的信息,使得無法通過patchelf來增加動態鏈接庫實現劫持。patchelf 支持對二進制文件的patch修改,或者添加執行過程中的鏈接lib。

 本文方案思路

 ELF文件中,存在DYNAMIC Segment,ld.so通過該段內容來加載程序運行過程中需要的lib文件,圖1爲在IDA中反編譯後查看的DYNAMIC段的內容。

二進制固件函數劫持術-DYNAMIC932.png

1 Elf32_Dyn結構體數組

 DYNAMIC段介紹

 dynamic 段開頭包含了由N個Elf32_Dyn組成的結構體,該結構體的D_tag代表了結構體的類型。d_un爲通過union 聯合的指針d_ptr或者對應的結構體的值d_val。

typedef struct {

    Elf32_Sword d_tag;

    union {

        Elf32_Word d_val;

        Elf32_Addr d_ptr;

    } d_un;

}Elf32_Dyn;

  d_tag字段保存了類型的定義參數,詳見ELF(5)手冊。下面列出了動態鏈接器常用的比較重要的類型值

 1-DT_NEEDED 該類型的數據結構保存了程序所需要的共享庫的名字相對字符串表的偏移量

 2-DT_SYMTAB 動態符號表的地址,對應的節名爲.dynsym

 3-DT_HASH 符號散列表的名稱,對應的節名爲.hash又稱爲.gnu.hash

 4-DT_STRTAB 符號字符串表的地址,對應的節名爲 .dynstr

 5-DT_PLTGOT 全局偏移表的地址

 如上各個字段對應到 IDA 中顯示如下,d_tag字段代表了動態段的類型,當該值爲1的時候。表示程序依賴的共享庫的名字,其對應的d_val表示了是要加載的lib的名稱在string table中的偏移。

 共享庫文件名對應的結構體的類型值爲0x01,如圖2所示,0x4001C4-0x4001E4保存有5個二進制文件所依賴的共享庫名字,其D_VAL的值是指向string table的偏移量。

二進制固件函數劫持術-DYNAMIC1754.png

2 DT_NEEDED 共享庫依賴圖

 DYNAMIC段的定位

 從二進制文件頭起始定位DYNAMIC段的流程如下:

 ELF_HEADER->Program Header -> DYNAMIC Program

 ELF_HEADER中保存有Program Header的偏移

二進制固件函數劫持術-DYNAMIC2033.png

3 ELF Header

 Program Header中保存有Dynamic Segment 的相關結構體信息

二進制固件函數劫持術-DYNAMIC2233.png

4 Program Header

 展開該結構體,可以找到Dynamic段在文件中的偏移量0x140h

二進制固件函數劫持術-DYNAMIC2429.png

Program Header結構體

 DT_NEEDED僞造

 動態段由dynamic的結構體數組組成,dynamic的結構體定義如下:

二進制固件函數劫持術-DYNAMIC2639.png

 在dynamic和elf_hash之間仍存在一段空餘空餘可填充空間。本文利用該段空間填充,實現特定lib的加載。

二進制固件函數劫持術-DYNAMIC2839.png

 

 【----幫助網安學習,以下所有學習資料免費領!加vx:yj009991,備註 “博客園” 獲取!】

 ① 網安學習成長路徑思維導圖
 ② 60+網安經典常用工具包
 ③ 100+SRC漏洞分析報告
 ④ 150+網安攻防實戰技術電子書
 ⑤ 最權威CISSP 認證考試指南+題庫
 ⑥ 超1800頁CTF實戰技巧手冊
 ⑦ 最新網安大廠面試題合集(含答案)
 ⑧ APP客戶端安全檢測指南(安卓+IOS)

 

 本文方案實驗與測試

 利用dynamic和elf_hash之間的空餘區域,在該區域僞造出新的dynamic的一個數組。如下圖,不修改二進制文件大小,僞造增添ibcjson.so,使得二進制文件加載 ibcjson.so。在ibcjson.so中編寫對應的劫持函數。

二進制固件函數劫持術-DYNAMIC3114.png

 思路: 將原有的Elf32_Dyn數組元素依次後移,並在該數組的首部添加僞造的ibcjson.so,該lib的命名可以選用string table中的任一字符串即可。

二進制固件函數劫持術-DYNAMIC3341.png

 核心移動代碼,將Elf32_Dyn中的元素依次後移一個,dynamic段 dynamic[0]元素作爲要僞造填充的數據,在本文的實驗中,將dynamic[0]中的value值加1。由於在MIPS下存在大小端兩種架構,在小端機器上的代碼解決大端架構的填充僞造時要注意大小端的轉換問題。

void move_dynamic(char* buf){

    int x = 0;

    Elf32_Dyn* dyn = (Elf32_Dyn *)buf;

    while(1){

        if(dyn[x].d_tag == 0 && dyn[x].d_un.d_ptr == 0){

            break;

        }

        x++;

        if(x>100) {

            printf("Error break\n");

            break;

        }

    }

    while(x--){

        //printf("the index x is %x\n",x);

        mem_cpy(&dyn[x],&dyn[x+1],8);

    }

    dyn[x+1].d_un.d_val = b2l(l2b(dyn[x+1].d_un.d_val) + 1);

}

 測試環境

  TOTOLink N210RE中boa程序,劫持函數參考DIR605A,劫持apmib_init以及apmib_get

  該固件的ld,關閉了LD_PRELOAD程序選項,未提供/etc/ld.preload

  劫持函數代碼,採用了《揭祕家用路由器漏洞挖掘》提供的示例代碼如下,在原有的基礎上,增加printf來查看顯示是否劫持成功。

#include<stdio.h>

#define MIB_HW_VER 0x250

#define MIB_IP_ADDR 170

#define MIB_CAPTCHA 0x2C1

int apmib_init(void){

        printf("helllo");

        return 1;

}

int fork(void){

        return 0;

}

void apmib_get(int code,int *value){

        switch(code){

                case MIB_HW_VER:

                        *value = 1;

                        break;

                case MIB_IP_ADDR:

                        *value = 1;

                        break;

                case MIB_CAPTCHA:

                        *value = 1;

                        break;

        }

        return;

}

 通過mips-linux-gcc進行編譯,這裏注意mips-linux-gcc在編譯過程中的編譯文件的位置要位於參數之後(踩坑了!!!!)

 mips-linux-gcc -Wall -fPIC -shared apmib.c -o ibcjson.so

 通過如下代碼,給原有的boa二進制文件添加一個dynamic

#include<stdio.h>

#include <stdio.h>

#include <stdlib.h>

 

#include "elf.h"

#define PATCH "boa_patch"

int b2l(int be)

{

    return ((be >> 24) &0xff )

        | ((be >> 8) & 0xFF00)

        | ((be << 8) & 0xFF0000)

        | ((be << 24));    

}

char* buf = NULL;

int l2b(int le) {

 

    return (le & 0xff) << 24

            | (le & 0xff00) << 8

            | (le & 0xff0000) >> 8

            | (le >> 24) & 0xff;

}

static char *_get_interp(char *buf)

{

   int x;

 

   // Check for the existence of a dynamic loader

   Elf_Ehdr *hdr = (Elf_Ehdr *)buf;

   Elf_Phdr *phdr = (Elf_Phdr * )(buf + l2b(hdr->e_phoff));   

   printf("the phdr address is: 0x%x 0x%x 0x%x 0x%x\n",phdr,l2b(hdr->e_phoff),buf,sizeof(hdr->e_phoff));

   for(x = 0; x < hdr->e_phnum; x++){

      if(l2b(phdr[x].p_type) == PT_DYNAMIC){

         // There is a dynamic loader present, so load it

         return buf + l2b(phdr[x].p_offset);

      }

   }

 

   return NULL;

}

int mem_cpy(char* src,char* dst,int len){

    printf("the src addr is %x , %x\n",src-buf,dst-buf);

    for(int x=0;x<len;x++){

        dst[x]=src[x];

    }

    return 0;

}

/*

1 2 3 4

temp = 2

strcpy(1,2)

1 2

strcpy()

 

*/

void move_dynamic(char* buf){

    int x = 0;

    Elf32_Dyn* dyn = (Elf32_Dyn *)buf;

    //Elf32_Dyn tmp_dyn;

    //mem_cpy()

    while(1){

        if(dyn[x].d_tag == 0 && dyn[x].d_un.d_ptr == 0){

            printf("the x is %d\n",x);

            break;

        }

        x++;

        if(x>100) {

            printf("Error break\n");

            break;

        }

    }

    while(x--){

        //printf("the index x is %x\n",x);

        mem_cpy(&dyn[x],&dyn[x+1],8);

    }

    dyn[x+1].d_un.d_val = b2l(l2b(dyn[x+1].d_un.d_val) + 1);

    //FILE* fw = fopen("")

}

void analyse(char* buf){

    char* phdr_address = NULL;

    phdr_address = _get_interp(buf);

    printf("phdr address:  0x%x\n",phdr_address-buf);

    move_dynamic(phdr_address);

 

}

void save_binary(char* buf,int size){

    FILE* fw = fopen(PATCH,"wb");

    fwrite(buf,size,1,fw);

    fclose(fw);

}

int main(int argc,char *argv[],char* envp[]){

    if(argc < 2){

        printf("not enough argc\n");

    }

    FILE* fp = fopen(argv[1],"rb");

 

    fseek(fp,0,SEEK_END);

    int size = ftell(fp);

    fseek(fp, 0L, SEEK_SET);

    buf = malloc(size);

    fread(buf,size,1,fp);

    analyse(buf);

    save_binary(buf,size);

    free(buf);

    return 0;

 

}

 Makefile如下

all: elf.h analyse_ph.c

gcc analyse_ph.c -m32 -g3 -o analyse

./analyse boa_real_n210

 

 針對N210RE 的測試截圖如下,通過export LD_LIBRARY_PATH使得程序加載ibcjson.so,成功劫持boa,輸出helllo

二進制固件函數劫持術-DYNAMIC7544.png

 Ubuntu下rand函數劫持測試

 rand函數的頭文件是stdlib.h

 編寫rand.c

#include<stdio.h>

#include<stdlib.h>

int main(){

    int a = 0;

    a = rand()%100;

    printf("the a is %d\n",a);

    return 0;

}

//gcc -m32 rand.c -o rand

 

 編寫rand_hook.so

#include<stdio.h>

int rand(){

    printf("hook !\n");

    return 100;

}

//gcc -fPIC -Wall -shared -m32 rand_hook.c -o rand_hook.so

 使用LD_PRELOAD測試,成功實現對該函數的劫持

二進制固件函數劫持術-DYNAMIC8065.png

 使用patch,針對dynamic段元素添加僞造,測試rand_patch,依賴的庫文件增加了ibc.so.6,ibc.so.6需要通過export LD_LIBRARY_PATH導入ibc.so.6的文件路徑

二進制固件函數劫持術-DYNAMIC8314.png

 實現對rand的劫持

二進制固件函數劫持術-DYNAMIC8468.png

 總結

 本文通過研究二進制文件中的dynamic段,通過修改二進制文件增加依賴共享庫,可以解決在模擬固件的過程時,固件缺少節信息且固件函數無法通過LD_PRELOAD劫持的問題。該方案仍有不足之處,對於ld加載共享庫的依賴順序、共享庫劫持的底層原理尚未深入探究。

 參考

 《揭祕家用路由器0day挖掘技術》

 《二進制分析實戰》

 更多靶場實驗練習、網安學習資料,請點擊這裏>>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章