行业动态

防御吧作为15年知名老牌域名服务商,CNNIC和CANN双认证域名注册商,已经
持续为500多万个域名提供服务,包括智能DNS/自由转移/隐私保护等服务!
二进制固件函数劫持术-DYNAMIC
2022-06-10 13:02:55 【

固件系统中的二进制文件依赖于特定的系统环境执行,针对固件的研究在没有足够的资金的支持下需要通过固件的模拟来执行二进制文件程序。依赖于特定硬件环境的固件无法完整模拟,需要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所示,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 的相关结构体信息




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下存在大小端两种架构,在小端机器上的代码解决大端架构的填充伪造时要注意大小端的转换问题。


image.png


测试环境


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



该固件的ld,关闭了LD_PRELOAD程序选项,未提供/etc/ld.preload


劫持函数代码,采用了《揭秘家用路由器漏洞挖掘》提供的示例代码如下,在原有的基础上,增加printf来查看显示是否劫持成功。


image.png


通过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






Ubuntu下rand函数劫持测试


rand函数的头文件是stdlib.h


编写rand.c


image.png


编写rand_hook.so


image.png


使用LD_PRELOAD测试,成功实现对该函数的劫持




使用patch,针对dynamic段元素添加伪造,测试rand_patch,依赖的库文件增加了ibc.so.6,ibc.so.6需要通过export LD_LIBRARY_PATH导入ibc.so.6的文件路径


】【打印关闭】 【返回顶部
分享到QQ空间
分享到: 
上一篇Windows Follina 零日漏洞现在被.. 下一篇服务器日常使用中的四种维护方案

立足首都,辐射全球,防御吧专注云防御及云计算服务15年!

联系我们

服务热线:13051179500 18910191973
企业QQ:1245940436
技术支持:010-56159998
E-Mail:xihedata.com
Copyright ? 2003-2016 fangyuba. 防御吧(完美解决防御与加速) 版权所有 增值许可:京B2-20140042号
售前咨询
公司总机:18910191973
24小时电话:010-56159998
投诉电话:18910191973
值班售后/技术支持
售后服务/财务
备案专员
紧急电话:18610088800