<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

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

    VSole2022-06-01 15:55:34

    背景介紹

    固件系統中的二進制文件依賴于特定的系統環境執行,針對固件的研究在沒有足夠的資金的支持下需要通過固件的模擬來執行二進制文件程序。依賴于特定硬件環境的固件無法完整模擬,需要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段的內容。

    圖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的偏移量。

    圖2 DT_NEEDED 共享庫依賴圖

    DYNAMIC段的定位

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

    ELF_HEADER->Program Header -> DYNAMIC Program

    ELF_HEADER中保存有Program Header的偏移

    圖3 ELF Header

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

    Program Header結構體

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

    Program Header結構體

    DT_NEEDED偽造

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

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

    本文方案實驗與測試

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

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

    核心移動代碼,將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");            break;        }    }    while(x--){        //printf("the index x is %x",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#define MIB_HW_VER 0x250#define MIB_IP_ADDR 170#define MIB_CAPTCHA 0x2C1int 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#include #include 
    #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",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",src-buf,dst-buf);    for(int x=0;x        dst[x]=src[x];    }    return 0;}/*1 2 3 4
    temp = 2strcpy(1,2)1 2strcpy()
    */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",x);            break;        }        x++;        if(x>100) {            printf("Error break");            break;        }    }    while(x--){        //printf("the index x is %x",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",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");    }    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

    Ubuntu下rand函數劫持測試

    rand函數的頭文件是stdlib.h

    編寫rand.c 

    #include#includeint main(){    int a = 0;    a = rand()%100;    printf("the a is %d",a);    return 0;}//gcc -m32 rand.c -o rand
    

    編寫rand_hook.so

    #includeint rand(){    printf("hook !");    return 100;}//gcc -fPIC -Wall -shared -m32 rand_hook.c -o rand_hook.so
    

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

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

    實現對rand的劫持

    總結

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

    參考

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

    《二進制分析實戰》

    printf二進制
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    上一篇文章介紹了xorstr的原理和最小化驗證概念的代碼,這篇文章來看下這種已經被廣泛應用于各惡意樣本以及安全組件中的技術如何還原,如果還沒看上篇建議先看下了解其實現后再看本篇文章。
    依賴于特定硬件環境的固件無法完整模擬,需要hook掉其中依賴于硬件的函數。LD_PRELOAD的劫持對于特定函數的劫持技術分為動態注入劫持和靜態注入劫持兩種。網上針對LD_PRELOAD的劫持也有大量的描述
    有sleep反調試,把sleep nop掉。這里讀取了名稱為cod的資源,用resource hacker把資源復制下來。然后向下執行,這里是一個對cod資源進行解密的地方。這里要注意的是如果檢測到調試器,那么byte_7FF6DA64F000[3]將會被賦值為36。所以要把這個if語句通過修改ZF標志位的方式來繞過反調試。cod資源解密腳本如下:arr = [0x18, 0x57, 0x68, 0x64]. 用ida打開,看到有花指令。
    二進制里面,每一位只要大于等于?則都要向高位進一。為了方便表示,還衍生出了二進制的子類,比如八進制,十六進制等,主要是二進制向這些進制轉換較為容易,而計算機平時又都處理二進制數據,因此就出現了這些常見的進制計數。信息存儲大多數計算機使用的都是8位的塊,或者叫字節,字節是作為計算機可尋址的最小單位。一般來說我們并不習慣于將一個字節寫成八位二進制的數,而是會寫成兩位十六進制的數。
    結果分析Hook前Hook后,我們的彈窗本該是hello的但是hook后,程序流程被我們修改了。760D34B2 55 push ebp760D34B3 8BEC mov ebp,esp通過這兩條指令,函數就可以在堆棧中為局部變量分配存儲空間,并在函數執行過程中保存和恢復現場。這樣做的好處是可以避免局部變量和其他函數之間的沖突,同時也可以提高函數的可讀性和可維護性。
    Binutils一組二進制程序處理工具,包括:addr2line、ar、objcopy、objdump、as、ld、ldd、readelf、size等。靜態庫的代碼在編譯過程中已經被載入可執行程序,因此體積較大。C語言標準僅僅定義了C標準庫函數原型,并沒有提供實現。C運行時庫又常簡稱為C運行庫。與C語言類似,C++也定義了自己的標準,同時提供相關支持庫,稱為C++運行時庫。準備工作由于GCC工具鏈主要是在Linux環境中進行使用,因此本文也將以Linux系統作為工作環境。
    一個最簡單的linux kernel rootkit就是一個linux kernel module。
    文中使用的示例代碼可以從 這里 獲取。的功能是在終端打印出hello這6個字符(包括結尾的?編譯它們分別生成libtest.so和?存在嚴重的內存泄露問題,每調用一次say_hello函數,就會泄露1024字節的內存。
    紅隊人員拿到一臺主機權限后首先會考慮將該機器作為一個持久化的據點,種植一個具備持久化的后門,就需要用到權限維持
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类