自己動手寫病毒—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

   



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