Segments

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

# 5. Segments

Well, my terminal's locked up, and I ain't got any Mail,
And I can't recall the last time that my program didn't fail;
I've got stacks in my structs, I've got arrays in my queues,
I've got the : Segmentation violation -- Core dumped blues.

ELF provides two parallel views of a file's contents. The linking view is defined by the section header table, an array of `Elf32_Shdr`. The execution view is defined by the program header table, an array of `Elf32_Phdr`.

Theoretically the ELF specification is quite liberal. Position, contents and order of sections and segments are not restricted. But in real life an operating system is to used to just one program loader, one linker and few compilers. This makes the work of virus writers easier. We can reverse engineer the de-facto standard, a tiny subset of what the ELF standard allows. On a typical system only a minority of programs violates this subset, so ignoring them does not lower chances of survival.

All headers necessary to execute a program are stuffed into the first page (0x1000 bytes on i386), probably to simplify the design of an OS' executable format detector. Dynamically linked executables require a few program headers more than static executables, but that's about all the variation there is. In any case the program header of the code segments is followed by the program header of the data segment. Static executables have the code segment at index 0, dynamically linked executables at index 2.

Let's get a bit more serious and examine the assembly program from The language of evil. A standalone executable built from assembler source is probably the most trivial example we can find.

## 5.1. objdump -fp

Command: pre/i386-redhat8.0-linux/segments/objdump.sh

    #!/bin/bash
    cd tmp/i386-redhat8.0-linux/evil_magic
    /bin/ls -Ll intel
    /usr/bin/objdump -fp intel

Output: out/i386-redhat8.0-linux/segments/objdump

    -rwxrwxr-x    1 alba     alba          416 Feb 15 23:48 intel

    intel:     file format elf32-i386
    architecture: i386, flags 0x00000102:
    EXEC_P, D_PAGED
    start address 0x08048080

    Program Header:
        LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
             filesz 0x00000097 memsz 0x00000097 flags r-x

## 5.2. readelf -l

`objdump`'s output is butt-ugly. On to `readelf`. The line starting with "There are 1 program headers" shows the value of `e_phnum` and `e_phoff`.

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

    #!/bin/bash
    cd tmp/i386-redhat8.0-linux/evil_magic
    /bin/ls -Ll intel
    /usr/bin/readelf -l intel

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

    -rwxrwxr-x    1 alba     alba          416 Feb 15 23:48 intel

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

    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      LOAD           0x000000 0x08048000 0x08048000 0x00097 0x00097 R E 0x1000

     Section to Segment mapping:
      Segment Sections...
       00     .text

## 5.3. Observations

Nice to see the entry point (0x8048080) again. Program layout is a simplified variation of Sort of an answer. The value of `FileSiz` includes ELF header and program header.

> Overhead:

> `overhead = Entry point - VirtAddr =`

> `0x8048080 - 0x8048000 = 0x80 = 128 bytes`

> Effective code size:

> `code size = FileSiz - overhead =`

> `0x97 - 0x80 = 0x17 = 23 bytes`

This matches with the disassembly listing. However, the ratio of file size to effective code deserves the title "Bloat", with capital B. Only 6 percent of the file actually do something useful!

> Bloat factor:

> `code size / file size = 23 / 416 = 0.055`

## 5.4. Segments of /bin/tcsh

Anyway, we see that even for trivial examples the code is surrounded by lots of other stuff. Let's zoom in on our target. [1].

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

    #!/bin/bash
    shell=$( /bin/sed 1q \
    	out/i386-redhat8.0-linux/scanner/segment_padding/infect )
    [ -x "${shell}" ] || exit 1
    /bin/ls -Ll ${shell}
    /usr/bin/readelf -l ${shell}

Output: out/i386-redhat8.0-linux/segments/sh/readelf

    -rwxr-xr-x    1 root     root       365432 Aug  8  2002 /bin/tcsh

    Elf file type is EXEC (Executable file)
    Entry point 0x804a130
    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 0x49358 0x49358 R E 0x1000
      LOAD           0x049360 0x08092360 0x08092360 0x03a30 0x33b40 RW  0x1000
      DYNAMIC        0x04caac 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     .eh_frame_hdr

Looks intimidating. But then the ELF specification says that only segments of type "LOAD" are considered for execution. Since the flags of the first one include "execute" but not "write" it must be the code segment. The other one has the "write" flags set, so it must be the data segment. There is one possible deviation: On sparc-sunos most executables built by Sun feature a data segment with "execute" flag.

`MemSiz` (0x33b40) is larger than `FileSiz` (0x3a30) in the data segment. Just like with mmap(2) excessive bytes are defined to be initialized with 0. The linker takes advantages of that by grouping all variables that should be initialized to zero at the end. Note that the last section of segment 3 (counting starts with 0) is called `.bss`, the traditional name for this kind of area.

The mapping for segment 2 looks even more complex. But I would guess that `.rodata` means "read-only data" and `.text` contains productive code, as opposed to the administrative stuff in the other sections.

Some executables of Red Hat 8.0 have an additional program header of type `GNU_EH_FRAME`.

## 5.5. Self modifying code

Previous examples in The language of evil used an `__attribute__` clause to put the code into section `.text`. Without that it would end up in section `.rodata`. Both are members of the code segment which is executable in it its entireness; in this regard that would make no difference. But what about putting the code a write enabled data segment? These settings can probably be changed by mprotect(2). [1] But what are the default settings?

Our minimal example is position independent and can be moved around freely. We need a slight modification to be able to call it like a function, though.

Output = Source: out/i386-redhat8.0-linux/evil_magic/func.inc

    const unsigned char in_code[]
    __attribute__ (( aligned(8), section(".text") )) =
    {
      0x53,                          /* 00000000: push ebx               */
      0x6A,0x04,                     /* 00000001: push byte +0x4         */
      0x58,                          /* 00000003: pop eax                */
      0x31,0xDB,                     /* 00000004: xor ebx,ebx            */
      0x43,                          /* 00000006: inc ebx                */
      0xB9,0x01,0x80,0x04,0x08,      /* 00000007: mov ecx,0x8048001      */
      0x6A,0x03,                     /* 0000000C: push byte +0x3         */
      0x5A,                          /* 0000000E: pop edx                */
      0xCD,0x80,                     /* 0000000F: int 0x80               */
      0x5B,                          /* 00000011: pop ebx                */
      0xC3                           /* 00000012: ret                    */
    }; /* 19 bytes (0x13) */

The program using this piece is platform independent. You will find it at Self modifying code (i). Below is the output. The

Output: out/i386-redhat8.0-linux/evil_magic/self_modify

    0x8048458 is code ... ELF sigill=0
    0x804981c is data ... ELF sigill=0
    0x8049838 is heap ... ELF sigill=0
    0xbffff8e0 is stack ... ELF sigill=0

### Notes

[1] | The matter is actually quite complex. Theo de Raadt himself describes the problems they had with making OpenBSD more secure at http://marc.theaimsgroup.com/?l=openbsd-tech&m=104391783312978&w=2

Prev | Home | Next
The language of evil |  | Sections