The ELF Virus Writing HOWTO: i386-redhat-linux
Prev |  | Next

# 5. A closer look

| It looks worse than you can imagine! I can imagine some pretty bad things! That's why I said *worse*!
| Terry Pratchett, Moving Pictures

After emotions cooled down a bit we can examine the infected executable and compare it with the original.

Command: src/entry_point/segments.sh

    #!/bin/sh
    cd ${TMP}/one_step_closer/e1i1 \
    && ls -l sh_infected \
    && ${READELF} -l sh_infected

Output: out/i386-redhat-linux/entry_point/segments

    -rwxrwxr-x    1 alba     alba       545192 Aug 16 00:13 sh_infected

    Elf file type is EXEC (Executable file)
    Entry point 0x80c6420
    There are 6 program headers, starting at offset 52

    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      PHDR           0x000034 0x08048034 0x08048034 0x000c0 0x000c0 R E 0x4
      INTERP         0x0000f4 0x080480f4 0x080480f4 0x00013 0x00013 R   0x1
          [Requesting program interpreter: /lib/ld-linux.so.2]
      LOAD           0x000000 0x08048000 0x08048000 0x7f414 0x7f414 R E 0x1000
      LOAD           0x07f420 0x080c7420 0x080c7420 0x05934 0x09ad0 RW  0x1000
      DYNAMIC        0x084a0c 0x080cca0c 0x080cca0c 0x000d8 0x000d8 RW  0x4
      NOTE           0x000108 0x08048108 0x08048108 0x00020 0x00020 R   0x4

     Section to Segment mapping:
      Segment Sections...
       00
       01     .interp
       02     .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata
       03     .data .eh_frame .dynamic .ctors .dtors .got .bss
       04     .dynamic
       05     .note.ABI-tag

File size and code segment have grown as expected. Data segment and `DYNAMIC` segment moved accordingly:

> infected.file_size - sh.file_size = 545192 - 541096 = 4096 = 0x1000

> infected.`LOAD`[1].`Filesiz` - sh.`LOAD`[1].`Filesiz` = 0x7f414 - 0x7e414 = 0x1000

> infected.`LOAD`[2].`Offset` - sh.`LOAD`[2].`Offset` = 0x7f420 - 0x7e420 = 0x1000

> infected.`DYNAMIC`.`Offset` - sh.`DYNAMIC`.`Offset` = 0x84a0c - 0x83a0c = 0x1000

## 5.1. First scan

The big output of the scanner from Turn the pages contains the executable from last chapter. But for clarity we repeat the exercise.

Command: src/entry_point/scan_dist.sh

    #!/bin/sh
    echo	"/bin/bash
    	${TMP}/one_step_closer/e1i1/sh_infected" \
    | src/scanner/dist.pl

Output: out/i386-redhat-linux/entry_point/scan

    tmp/i386-redhat-linux/one_step_closer/e1i1/sh_infected virtaddr=0x080c7420 dist=0x0000000c
       2 files;    1 detected; min=0x0000000c; max=0x0000100c

This is like playing chess against oneself, and losing. Can't do much about it, though.

## 5.2. Looking around

The value of `Entry point` changed dramatically. In the original it is in the first part of the file:

> entry_point_ofs = 0x8059440 - 0x8048000 = 0x11440 = 70720 bytes.

The infected copy moved that to less than `4096` bytes from the end of the code segment.

> entry_point_ofs = 0x80c6420 - 0x8048000 = 0x7e420 = 517152 bytes.

> end_of_LOAD1 = 0x8048000 + 0x7f414 = 0x80c7414

> entry_point_distance_to_end = 0x80c7414 - 0x80c6420 = 0xff4 = 4084

This is another easy vulnerability to scanners. But the bad news ain't over. readelf(1) features another option, `-S`.

Command: src/entry_point/sections.sh

    #!/bin/sh
    ls -l /bin/bash
    ${READELF} -S /bin/bash

Output: out/i386-redhat-linux/entry_point/sections

    -rwxr-xr-x    1 root     root       541096 Apr 12 18:09 /bin/bash
    There are 23 section headers, starting at offset 0x83e10:

    Section Headers:
      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            00000000 000000 000000 00      0   0  0
      [ 1] .interp           PROGBITS        080480f4 0000f4 000013 00   A  0   0  1
      [ 2] .note.ABI-tag     NOTE            08048108 000108 000020 00   A  0   0  4
      [ 3] .hash             HASH            08048128 000128 002a20 04   A  4   0  4
      [ 4] .dynsym           DYNSYM          0804ab48 002b48 0067f0 10   A  5   1  4
      [ 5] .dynstr           STRTAB          08051338 009338 006538 00   A  0   0  1
      [ 6] .gnu.version      VERSYM          08057870 00f870 000cfe 02   A  4   0  2
      [ 7] .gnu.version_r    VERNEED         08058570 010570 000070 00   A  5   2  4
      [ 8] .rel.dyn          REL             080585e0 0105e0 000058 08   A  4   0  4
      [ 9] .rel.plt          REL             08058638 010638 0004a0 08   A  4   b  4
      [10] .init             PROGBITS        08058ad8 010ad8 000018 00  AX  0   0  4
      [11] .plt              PROGBITS        08058af0 010af0 000950 04  AX  0   0  4
      [12] .text             PROGBITS        08059440 011440 058680 00  AX  0   0 16
      [13] .fini             PROGBITS        080b1ac0 069ac0 00001e 00  AX  0   0  4
      [14] .rodata           PROGBITS        080b1ae0 069ae0 014934 00   A  0   0 32
      [15] .data             PROGBITS        080c7420 07e420 0055e8 00  WA  0   0 32
      [16] .eh_frame         PROGBITS        080cca08 083a08 000004 00  WA  0   0  4
      [17] .dynamic          DYNAMIC         080cca0c 083a0c 0000d8 08  WA  5   0  4
      [18] .ctors            PROGBITS        080ccae4 083ae4 000008 00  WA  0   0  4
      [19] .dtors            PROGBITS        080ccaec 083aec 000008 00  WA  0   0  4
      [20] .got              PROGBITS        080ccaf4 083af4 000260 04  WA  0   0  4
      [21] .bss              NOBITS          080ccd60 083d60 004190 00  WA  0   0 32
      [22] .shstrtab         STRTAB          00000000 083d60 0000b0 00      0   0  1
    Key to Flags:
      W (write), A (alloc), X (execute), M (merge), S (strings)
      I (info), L (link order), G (group), x (unknown)
      O (extra OS processing required) o (OS specific), p (processor specific)

The most interesting entry is `.text`. The start of the section, `0x8059440`, equals the entry point as reported by option `-l` in Bashful glance.

## 5.3. Second scan

The following scanner compares the start of section `.text` with the entry point. It's structure is similar to the script from Turn the pages.

Source: src/scanner/entry_point.pl

    #!/usr/bin/perl -w
    use strict;

    my $readelf = $ENV{'READELF'}
    || die "Environment variable READELF undefined.";

    my $min = 0xFFFFFFFF;
    my $max = 0;
    my $detected = 0;
    LOOP: while(my $filename = <>)
    {
      chomp $filename; $filename =~ s/^\s*//;
      next LOOP if ( ! -e $filename );
      open(ELF, "$readelf -Sl $filename 2>&1 |") || die "$1 ($filename)";

      my $entry_point;
      my $start_of_text;
      while(my $line = <ELF>)
      {
        chomp $line;
        if ($line =~ m/^Entry point 0x([0-9A-Fa-f]+)/)
        {
          $entry_point = hex($1);
        }
        elsif ($line =~ m/^\s*\[[\s\d]+\]\s+.text\s+PROGBITS\s+([0-9A-Fa-f]+)/)
        {
          $start_of_text = hex($1);
        }
      }
      close ELF;
      if (!defined($entry_point))
      {
        printf "%-46s has no entry point.\n", $filename;
      }
      elsif ($entry_point != $start_of_text)
      {
        $detected++;
        printf "%-46s ep=0x%08x sot=0x%08x\n",
          $filename, $entry_point, $start_of_text;
      }
    }
    printf "%4d files; %4d detected\n", $., $detected;

Once again we first test with typical places like `/bin`. Everything clean.

Output: out/i386-redhat-linux/scanner/entry_point_big

    1543 files;    0 detected

And then with all infected executables. Only a few are detected. Which means there is cure against this vulnerability (just read on).

Output: out/i386-redhat-linux/scanner/entry_point_small

    one_step_closer/e1i1/tcsh_infected ep=0x0808af70 sot=0x0804a1f0
    one_step_closer/e1i1/perl_infected ep=0x080f2260 sot=0x08059b20
    one_step_closer/e1i1/which_infected ep=0x0804b2c0 sot=0x08048b30
    one_step_closer/e1i1/sh_infected ep=0x080c6420 sot=0x08059440
    one_step_closer/e1i1/strip_sh_infected ep=0x080c6420 sot=0x08059440
      35 files;    5 detected

Prev | Home | Next
One step closer to the edge |  | The entry point