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