二進制固件函數劫持術-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段的內容。

圖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挖掘技術》
《二進制分析實戰》