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