固件系統(tǒng)中的二進制文件依賴于特定的系統(tǒng)環(huán)境執(zhí)行,針對固件的研究在沒有足夠的資金的支持下需要通過固件的模擬來執(zhí)行二進制文件程序。依賴于特定硬件環(huán)境的固件無法完整模擬,需要hook掉其中依賴于硬件的函數(shù)。
LD_PRELOAD的劫持
對于特定函數(shù)的劫持技術分為動態(tài)注入劫持和靜態(tài)注入劫持兩種。靜態(tài)注入指的是通過修改靜態(tài)二進制文件中的內(nèi)容來實現(xiàn)對特定函數(shù)的注入。動態(tài)注入則指的是在運行的過程中對特定的函數(shù)進行劫持,動態(tài)注入劫持一方面可以通過劫持PLT表或者GOT表來實現(xiàn),另一方面可以通過環(huán)境變量LD_PRELOAD來實現(xiàn)。
在《揭秘家用路由器0day漏洞挖掘》中作者針對D-link DIR-605L(FW_113)路由器中的Web應用程序boa,通過hook技術劫持 apmib_init()和apmib_get()函數(shù)修復boa對硬件的依賴,使得qemu-static-mips可以模擬執(zhí)行,在文中作者通過LD_PRELOAD環(huán)境變量實現(xiàn)對函數(shù)的劫持。網(wǎng)上針對LD_PRELOAD的劫持也有大量的描述。但是LD_PRELOAD仍舊不是普適的。
存在的問題
LD_PRELOAD環(huán)境變量的開關在編譯生成ulibc的時候指定,當關閉該選項的時候,無法使用LD_PRELOAD來預加載指定的動態(tài)鏈接庫文件。
固件的二進制文件中會將二進制文件中的Section信息去除掉,只保留下Segment的信息,使得無法通過patchelf來增加動態(tài)鏈接庫實現(xiàn)劫持。patchelf 支持對二進制文件的patch修改,或者添加執(zhí)行過程中的鏈接lib。
本文方案思路
在ELF文件中,存在DYNAMIC Segment,ld.so通過該段內(nèi)容來加載程序運行過程中需要的lib文件,圖1為在IDA中反編譯后查看的DYNAMIC段的內(nèi)容。
圖1 Elf32_Dyn結(jié)構體數(shù)組
DYNAMIC段介紹
dynamic 段開頭包含了由N個Elf32_Dyn組成的結(jié)構體,該結(jié)構體的D_tag代表了結(jié)構體的類型。d_un為通過union 聯(lián)合的指針d_ptr或者對應的結(jié)構體的值d_val。
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
}Elf32_Dyn;
d_tag字段保存了類型的定義參數(shù),詳見ELF(5)手冊。下面列出了動態(tài)鏈接器常用的比較重要的類型值
1-DT_NEEDED 該類型的數(shù)據(jù)結(jié)構保存了程序所需要的共享庫的名字相對字符串表的偏移量
2-DT_SYMTAB 動態(tài)符號表的地址,對應的節(jié)名為.dynsym
3-DT_HASH 符號散列表的名稱,對應的節(jié)名為.hash又稱為.gnu.hash
4-DT_STRTAB 符號字符串表的地址,對應的節(jié)名為 .dynstr
5-DT_PLTGOT 全局偏移表的地址
如上各個字段對應到 IDA 中顯示如下,d_tag字段代表了動態(tài)段的類型,當該值為1的時候。表示程序依賴的共享庫的名字,其對應的d_val表示了是要加載的lib的名稱在string table中的偏移。
共享庫文件名對應的結(jié)構體的類型值為0x01,如圖2所示,0x4001**-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 的相關結(jié)構體信息
Program Header結(jié)構體
展開該結(jié)構體,可以找到Dynamic段在文件中的偏移量0x140h
Program Header結(jié)構體
DT_NEEDED偽造
動態(tài)段由dynamic的結(jié)構體數(shù)組組成,dynamic的結(jié)構體定義如下:
在dynamic和elf_hash之間仍存在一段空余空余可填充空間。本文利用該段空間填充,實現(xiàn)特定lib的加載。
本文方案實驗與測試
利用dynamic和elf_hash之間的空余區(qū)域,在該區(qū)域偽造出新的dynamic的一個數(shù)組。如下圖,不修改二進制文件大小,偽造增添ibcjson.so,使得二進制文件加載 ibcjson.so。在ibcjson.so中編寫對應的劫持函數(shù)
思路:將原有的Elf32_Dyn數(shù)組元素依次后移,并在該數(shù)組的首部添加偽造的ibcjson.so,該lib的命名可以選用string table中的任一字符串即可。
核心移動代碼,將Elf32_Dyn中的元素依次后移一個,dynamic段 dynamic[0]元素作為要偽造填充的數(shù)據(jù),在本文的實驗中,將dynamic[0]中的value值加1。由于在MIPS下存在大小端兩種架構,在小端機器上的代碼解決大端架構的填充偽造時要注意大小端的轉(zhuǎn)換問題。
image.png
測試環(huán)境
TOTOLink N210RE中boa程序,劫持函數(shù)參考DIR605A,劫持apmib_init以及apmib_get
該固件的ld,關閉了LD_PRELOAD程序選項,未提供/etc/ld.preload
劫持函數(shù)代碼,采用了《揭秘家用路由器漏洞挖掘》提供的示例代碼如下,在原有的基礎上,增加printf來查看顯示是否劫持成功。
image.png
通過mips-linux-gcc進行編譯,這里注意mips-linux-gcc在編譯過程中的編譯文件的位置要位于參數(shù)之后(踩坑了!!!!)
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
Ubuntu下rand函數(shù)劫持測試
rand函數(shù)的頭文件是stdlib.h
編寫rand.c
image.png
編寫rand_hook.so
image.png
使用LD_PRELOAD測試,成功實現(xiàn)對該函數(shù)的劫持
使用patch,針對dynamic段元素添加偽造,測試rand_patch,依賴的庫文件增加了ibc.so.6,ibc.so.6需要通過export LD_LIBRARY_PATH導入ibc.so.6的文件路徑