自己动手写病毒—ELF文件病毒

author:luojiafei

blog:  http://blog.csdn.net/luojiafei

e-mail:    [email protected]

data:  2012/1/5                       


                         自己动手写病毒—ELF文件病毒

    对病毒的兴趣由来已久,所以在空闲的时间来很多时候都是逛逛技术网站,在不经意间发现了国外有一篇关于ELF文件病毒的文章,借着之前为一个课程任务而编写操作系统的期间而积累下来的经验,便花了几天时间去研究了一下ELF文件病毒 并且写下了一个名为helloworld(这个名字够俗啊!),其实就是感染每一个ELF可执行文件,当用户运行被感染的文件时,就会运行一个我预先用qt写好的标题为”helloworld”的对话框,可见这是一个相对友好的病毒,仅仅是为学习而写出来的。下面便是正题了,通过这篇文章我们能学到:

    1.elf文件格式

    2.自己编写Linux的系统调用,加深对linux内核的认识。

    3.病毒原理和逆向工程分析,我们会用到EDB(即linux下的ollydgb)来动态调试病毒.

 

1.ELF文件格式

    a.ELF文件类型

       ELF文件主要分为三种类型:

ñ 可重定位文件(Relocatable File)包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。

ñ 可执行文件(Executable File) 包含适合于执行的一个程序,此文件规定了exec() 如何创建一个程序的进程映像。

ñ 共享目标文件(Shared Object File) 包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,生成另外一个目标文件。其次,动态链接器(Dynamic Linker)可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像。

 

    b.ELF文件的数据表示

        ELF文件头结构及相关常数被定义在”/usr/include/elf.h”里。ELF目标文件中的所有数据结构都     遵从自然大小和对齐规则。如果 必要,数据结构可以包含显式的补齐,例如为了确保4字节对象按       4字节边界对齐。数据对齐同样适用于文件内部。下面为ELF中常用的数据格式:

名称

大小

对齐

描述

Elf32_Addr

4

4

无符号程序地址

Elf32_Half

2

2

无符号短整型

Elf32_Off

4

4

无符号偏移地址

Elf32_Sword

4

4

有符号整型

Elf32_Word

4

4

有符号整型

        ELF除了32位版还有64位版本,数据类型的名称和大小也相应地变化(Elf64_Addr…)。

 

    c.ELF的链接视图和执行视图

        目标文件既要参与程序链接又要参与程序执行。出于方便性和效率考虑,目标文件格式提供了两种      并行视图,分别反映了这些活动的不同需求。

        这两个视图并不冲突,在执行视图中的”Segment”是由链接视图中的多个权限和性质相仿         的”Section”组成的。

        下面我们便来一边看例子一边学习。例如一个test程序,

            #include<stdio.h>

            intmain()

            {

                printf("this is test\n");

                return 0;

            }

        我们用readelf -e test查看会出现4个部分,分别为ELF Header, Section Headers,    ProgramHeaders 和 Section to Segment mapping.

    (1)ELF Header:

在usr/include/elf.h中我们可以看到关于ELF Header的结构体Elf32_Ehdr

 

/* The ELF file header.  This appears at the start of every ELFfile.  */

 

#define EI_NIDENT (16)

 

typedef struct

{

 unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */

 Elf32_Half    e_type;         /* Object file type */

 Elf32_Half    e_machine;      /* Architecture */

 Elf32_Word    e_version;      /*Object file version */

 Elf32_Addr    e_entry;        /* Entry point virtual address */

 Elf32_Off e_phoff;        /* Program header table file offset */

 Elf32_Off e_shoff;        /* Section header table file offset */

 Elf32_Word    e_flags;        /* Processor-specific flags */

 Elf32_Half    e_ehsize;       /* ELF header size in bytes */

 Elf32_Half    e_phentsize;        /* Program header table entry size */

 Elf32_Half    e_phnum;        /* Program header table entry count */

 Elf32_Half    e_shentsize;        /* Section header table entry size */

  Elf32_Half    e_shnum;        /*Section header table entry count */

 Elf32_Half    e_shstrndx;     /* Section header string table index */

} Elf32_Ehdr;

 

test中ELF_Header输出:

 Magic:   7f 45 4c 46 01 01 01 0000 00 00 00 00 00 00 00

 Class:                            ELF32

 Data:                             2's complement, little endian

 Version:                          1 (current)

 OS/ABI:                           UNIX - System V

  ABIVersion:                       0

 Type:                             EXEC (Executable file)

 Machine:                          Intel 80386

 Version:                          0x1

 Entry point address:              0x8048300

 Start of program headers:         52 (bytes into file)

 Start of section headers:         4440 (bytes into file)

 Flags:                            0x0

 Size of this header:              52 (bytes)

 Size of program headers:          32 (bytes)

 Number of program headers:        8

 Size of section headers:          40 (bytes)

 Number of section headers:        29

 Section header string table index: 26

        ELF魔数    我们可以从前面readelf的输出看到,最前面的“Magic”的16个字节刚好是对应”Elf32_Ehdr”的e_ident这个成员。这16个字节被ELF标准规定用来标识ELF文件的平台属性,比如ELF字长(32位/64位),字节序,ELF文件版本,

 

 

       最开始的4个字节是所有ELF文件都必须相同的标识码,分别为0x7F,0X45,0X4C,0x46,第一个字节对应ASCII字符里面的DEL控制符,后面3个字节刚好是ELF这3个字母的ASCII码。这4个字节又被称为ELF文件的魔数,几乎所有的可执行文件格式的最开始的几个字节都是魔数。比如a.out格式最开始两个字节为0x01,0x07;PE/COFF文件最开始两个字节为0x4d,0x5a, 即ASCII字符MZ。这个魔数用来确认文件的类型,操作系统在加载可执行文件的时候会确认魔数是否正确,如果不正确会拒绝加载。接下来的一个字节是用来标识ELF的文件类型的,0x01表示32位的,0x02表示是64位的;第6个字是字节序,规定该ELF文件文件是大端还是小端的。第7个字节规定ELF文件的主版本号,一般是1,因为ELF标准自1.2版本以后就再也没有更新了。后面的9个字节ELF标准没有定义,一般填0,有些平台会使用这9个字节作为扩展标志。

 

       文件类型   e_type成员表示ELF文件类型,即前面提到过的3种ELF文件类型,每个文件类型对应一个常量。系统通过这个常量来判断ELF的真正文件类型,而不是通过文件的扩展名。

       机器类型  ELF文件格式被设计成可以在多个平台下使用。这并不表示同一个ELF文件可以在不同的平台下使用(就像java的字节码文件那样),而是不同平台下的ELF文件都遵循同一套ELF标准。e_machine成员就表示该ELF文件的平台属性。

    版本 e_version 这个成员决定文件的版本。

   

    入口地址 e_entry表示程序的入口地址,文件test的入口地址为0x8048300。

   

    e_phoff Program header table 在文件中的偏移量(以字节计数),这里是52.

   

    e_shoff Section header table 在文件中的偏移量(以字节计数)。这里是4440

 

    e_flags 对IA32而言,此项为0.

 

    e_ehsize ELF header 大小(以字节计数)。这里是52.

 

    e_phentsize Program header table 中每一个条目(一个Program header)的大小。这里是32

   

    e_phnum  Programheader table 中有多少个条目,这里有8个。

 

    e_shentsize Section header table 中每一个条目(一个Secion header)的大小,这里是40.  

 

    e_shnum Section header table 中有多少个条目,这里是29.

 

 

    (2)Section Headers:

   在usr/include/elf.h中Section Headers对应着结构体Elf32_Shdr.

 

/*Section header.  */

 

typedefstruct

{

  Elf32_Word  sh_name;      /* Section name (string tbl index) */

  Elf32_Word  sh_type;      /* Section type */

  Elf32_Word  sh_flags;     /* Section flags */

  Elf32_Addr  sh_addr;      /* Section virtual addr at execution */

  Elf32_Off   sh_offset;    /* Section file offset */

  Elf32_Word  sh_size;      /* Section size in bytes */

  Elf32_Word  sh_link;      /* Link to another section */

  Elf32_Word  sh_info;      /* Additional section information */

  Elf32_Word  sh_addralign;     /* Section alignment */

  Elf32_Word  sh_entsize;       /* Entry size if section holds table */

}Elf32_Shdr;

 

test中的对应输出:

  [Nr] Name              Type            Addr     Off   Size   ES Flg Lk Inf Al

  [ 0]                   NULL            00000000 000000 000000 00      0  0  0

  [ 1] .interp           PROGBITS        08048134 000134 000013 00   A 0   0  1

  [ 2] .note.ABI-tag     NOTE            08048148 000148 000020 00   A 0   0  4

  [ 3] .note.gnu.build-i NOTE            08048168 000168 000024 00   A  0   0  4

  [ 4] .gnu.hash         GNU_HASH        0804818c 00018c 000020 04   A 5   0  4

  [ 5] .dynsym           DYNSYM          080481ac 0001ac 000050 10   A 6   1  4

  [ 6] .dynstr           STRTAB          080481fc 0001fc 00004a 00   A 0   0  1

  [ 7] .gnu.version      VERSYM          08048246 000246 00000a 02   A 5   0  2

  [ 8] .gnu.version_r    VERNEED         08048250 000250 000020 00   A 6   1  4

  [ 9] .rel.dyn          REL             08048270 000270 000008 08   A 5   0  4

  [10] .rel.plt          REL             08048278 000278 000018 08   A 5  12  4

  [11] .init             PROGBITS        08048290 000290 000030 00  AX 0   0  4

  [12] .plt              PROGBITS        080482c0 0002c0 000040 04  AX 0   0  4

  [13] .text             PROGBITS        08048300 000300 00016c 00  AX 0   0 16

  [14] .fini             PROGBITS        0804846c 00046c 00001c 00  AX 0   0  4

  [15] .rodata           PROGBITS        08048488 000488 000015 00   A 0   0  4

  [16] .eh_frame         PROGBITS        080484a0 0004a0 000004 00   A 0   0  4

  [17] .ctors            PROGBITS        08049f14 000f14 000008 00  WA 0   0  4

  [18] .dtors            PROGBITS        08049f1c 000f1c 000008 00  WA 0   0  4

  [19] .jcr              PROGBITS        08049f24 000f24 000004 00  WA 0   0  4

  [20] .dynamic          DYNAMIC         08049f28 000f28 0000c8 08  WA 6   0  4

  [21] .got              PROGBITS        08049ff0 000ff0 000004 04  WA 0   0  4

  [22] .got.plt          PROGBITS        08049ff4 000ff4 000018 04  WA 0   0  4

  [23] .data             PROGBITS        0804a00c 00100c 000008 00  WA 0   0  4

  [24] .bss              NOBITS          0804a014 001014 000008 00  WA 0   0  4

  [25] .comment          PROGBITS        00000000 001014 000054 01  MS 0   0  1

  [26] .shstrtab         STRTAB          00000000 001068 0000ee 00      0  0  1

  [27] .symtab           SYMTAB          00000000 0015e0 000400 10     28 44  4

  [28] .strtab           STRTAB          00000000 0019e0 0001f7 00      0  0  1

Key toFlags:

  W (write), A (alloc), X (execute), M (merge),S (strings)

  I (info), L (link order), G (group), T (TLS),E (exclude), x (unknown)

  O (extra OS processing required) o (OSspecific), p (processor specific)

 

    sh_name 段名是一个字符串,他位于一个叫做“.shstrtab”的字符串表。sh_name是段名字符串在”.shstrtab”中的偏移。

 

    sh_type 段的类型,

 

 

 

    sh_flags  段的标志位表示该段在进程虚拟地址空间中的属性,比如是否可写,是否可执行等。

    段的链接信息(sh_link,sh_info) 如果段的类型是与链接相关的(不论是动态或静态链接),比如重定位,符号表等,那么sh_link和sh_info这两个成员的意义是

    段地址对齐 sh_addralign在程序的运行的性能方面有着重要的作用。

 

    项的长度 sh_entsize,因为有些段包含了一些固定大小的项,如符号表。

 

 

(3)ProgramHeaders:

   test中对应的输出:

ProgramHeaders:

  Type          Offset   VirtAddr   PhysAddr  FileSiz MemSiz  Flg Align

  PHDR          0x000034 0x08048034 0x08048034 0x00100 0x00100 R E 0x4

  INTERP        0x000134 0x08048134 0x08048134 0x00013 0x00013 R   0x1

      [Requesting program interpreter:/lib/ld-linux.so.2]

  LOAD          0x000000 0x08048000 0x08048000 0x004a4 0x004a4 R E 0x1000

  LOAD          0x000f14 0x08049f14 0x08049f14 0x00100 0x00108 RW  0x1000

  DYNAMIC       0x000f28 0x08049f28 0x08049f28 0x000c8 0x000c8 RW  0x4

  NOTE          0x000148 0x08048148 0x08048148 0x00044 0x00044 R   0x4

  GNU_STACK     0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

  GNU_RELRO     0x000f14 0x08049f14 0x08049f14 0x000ec 0x000ec R   0x1

 

    结构体:

/*Program segment header.  */

 

typedefstruct

{

  Elf32_Word  p_type;           /* Segment type */

  Elf32_Off   p_offset;     /* Segment file offset */

  Elf32_Addr  p_vaddr;      /* Segment virtual address */

  Elf32_Addr  p_paddr;      /* Segment physical address */

  Elf32_Word  p_filesz;     /* Segment size in file */

  Elf32_Word  p_memsz;      /* Segment size in memory */

  Elf32_Word  p_flags;      /* Segment flags */

  Elf32_Word  p_align;      /* Segment alignment */

}Elf32_Phdr;

 

 

    p_type 当前Program header所描述的段的类型,只有LOAD才会被加载。

 

    p_offset 段的第一个字节在文件中的偏移。

 

    p_vaddr 段的第一个字节在内存中的虚拟地址。

 

    p_paddr 在物理地址定位相关的系统中,此项是为物理地址保留。

 

    p_filesz 段在文件中的长度。

 

    p_memsz 段在内存中的长度。

 

    p_flags 与段相关的标志。

 

    p_align 就是段对齐。

 

 

(4)Sectionto Segment mapping:

test中的输出:

  Segment Sections...

   00    

   01    .interp

   02    .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr.gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata.eh_frame

   03    .ctors .dtors .jcr .dynamic .got .got.plt .data .bss

   04    .dynamic

   05     .note.ABI-tag .note.gnu.build-id

   06    

   07    .ctors .dtors .jcr .dynamic .got

 

 

2.病毒原理

   无论在linux或者windows页的单位为4K,而在绝大多数的情况下可执行文件并不会填满最后一个页面而且还会留下很大的空间,于是我们便可以把病毒写进这些空间,并把入口地址指向我们的病毒,当执行完病毒后再执行原来的代码,所以我们病毒的瓶颈就是设法让它变得很小,我这里是1.6K左右。

    另外我们在病毒程序中的系统调用可以通过修改与链接相关的段或直接用内联汇编写系统调用,而前者比较麻烦,于是我就采取了后者。具体怎么做,我们可以参考linux源代码中的include/unistd.h,这里就举个简单的例子。

假设我们要用fork系统调用,

#define__syscall0(type,name) \

staticinline type name(void) \

{ \

long__res; \

__asm__volatile ("int $0x80" \

    : "=a" (__res) \

    : "0" (__NR_##name)); \

    return (type) __res; \

}

 

__syscall0(int,fork);

 

用法:

    int pid = fork()

 

 

3.实验

   前提:一定要弄懂elf格式和有足够的耐心。

   工具vim,gcc,gdb,EDB

   



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章