One can search the brain with a microscope and not find the mind, and can search the stars with a telescope and not find God. | |
J. Gustav White |
After finding an exploitable peculiarity you need to verify its existence in a typical population of target executables. Some peculiarities can be used only once, i.e. filling the segment gap on i386 removes the gap. This makes the following scripts kind of dual-use technology.
Scanner scripts read a list of file names from stdin, one file name per line. Human readable output is written to stdout, with the convention that clean targets create no output. Since the scripts just parse the output of another tool performance is generally bad. Use of file(1) as a fast file-type is the best we can do to lower noise and duration to acceptable regions.
The following driver script are configurable. First arguments is the output file. Second argument is the file name of the scanner. Third argument is required postfix of target file names. A quoted empty string ("") accepts all files in target directories. The other typical value is "_infected" since this postfix is used by all examples in this document. All other arguments are directory names passed on to find(1).
Source: src/scanner/driver.sh
#!/bin/sh
dst=$1; shift
scanner=$1; shift
postfix=$1; shift
flags='-p'
[ `basename ${scanner}` = 'entry_point.pl' ] && flags='-fh'
. ./config-${HOSTNAME}.sh
type="ELF ${TEVWH_ELF_ADDR_SIZE}-bit ${TEVWH_BYTE_ORDER}SB executable"
# On Solaris 'find' has no -print0 and 'xargs' has neither -0 nor -r
${TEVWH_PATH_FIND} "$@" -type f -name "*${postfix}" \
| ${TEVWH_PATH_XARGS} ${TEVWH_PATH_FILE} \
| ${TEVWH_PATH_SED} -ne "s/:[[:space:]]*${type}.*//p" \
| ${TEVWH_PATH_TEE} ${dst}.files \
| ${TEVWH_PATH_XARGS} ${TEVWH_PATH_OBJDUMP} ${flags} \
| ${scanner} 2>&1 \
| ${TEVWH_PATH_SED} "s#^${TEVWH_TMP}/##" \
| ${TEVWH_PATH_TEE} ${dst}.full \
| ${TEVWH_PATH_TAIL} \
> ${dst} |
The input is expected to be the output of objdump -p (for multiple files). Input is parsed for lines starting with the word LOAD. The end of the segment is calculated and stored for the next line. The distance from the start of a LOAD segment to the end of the previous one is than compared with two values. See Segment padding infection (i) for an overview of _SC_PAGESIZE and Alignment. Actual values for a specific platform are at Variables & packages.
Output is at Segment padding infection. Its meaning is ambigious. We can positively tell whether the gap exists or not. But we cannot say whether the gap never existed or is indeed occupied by an infection. On platforms other than i386 alignment is larger than page size. It is possible that a small infection taking just one page affects a single target more than once and still leaves a gap larger than the segment alignment. These cases go undetected by this script.
Anyway, det_page is the number of files detected to have a gap smaller equal one page (no further infection possible). det_align is the number of files where the gap is smaller than segment alignment (probably infected). Obviously these criteria overlap.
Source: src/scanner/dist.pl
#!/usr/bin/perl -w
use strict;
my $tmp = $ENV{'TEVWH_TMP'} || die "TEVWH_TMP undefined.";
my $pagesize = $ENV{'TEVWH_PAGESIZE'} || die "TEVWH_PAGESIZE undefined.";
$pagesize = hex($pagesize);
my $align = $ENV{'TEVWH_ELF_ALIGN'} || die "TEVWH_ELF_ALIGN undefined.";
$align = hex($align);
my $min = 0xFFFFFFFF; my $max = 0; my $det_page = 0; my $det_align = 0;
my $nr_files = 0; my $filename;
while(<>)
{
if (m#^(/[^:\s]+):#) { $nr_files++; $filename = $1; next; }
if (m#^$tmp/([^:\s]+):#) { $nr_files++; $filename = $1; next; }
if (m#^Program Header:#)
{
my $nrLoad = 0; my $end = 0;
while(<>)
{
last if (m#^\s*$#);
next if (!m/^\s*LOAD\s+/);
$nrLoad++;
my @number = split /\s+/;
my $addr = hex($number[5]);
$_ = <>;
@number = split /\s+/, $_;
my $filesiz = hex($number[2]);
if ($end != 0)
{
my $dist = $addr - $end;
my $print = 0;
if ($dist <= $pagesize) { $det_page++; $print = 1; }
if ($dist < $align) { $det_align++; $print = 1; }
printf "%-52s addr=%08x dist=%08x\n", $filename, $addr, $dist
if ($print);
$max = $dist if ($dist > $max);
$min = $dist if ($dist < $min);
}
$end = $addr + $filesiz;
}
printf("%-52s has %d LOAD segments.\n", $filename, $nrLoad)
if ($nrLoad != 2);
}
}
printf "files=%04d; det_page=%04d; det_align=%04d; min=0x%08x; max=0x%08x\n",
$nr_files, $det_page, $det_align, $min, $max; |
For each file the start of section .text should equal the entry point. See Sections for background and output.
Source: src/scanner/entry_point.pl
#!/usr/bin/perl -w
use strict;
my $tmp = $ENV{'TEVWH_TMP'} || die "TEVWH_TMP undefined.";
my $min = 0xFFFFFFFF; my $max = 0; my $detected = 0;
my $nr_files = 0; my $filename; my $entry_point;
while(<>)
{
if (m#^(/[^:\s]+):#) { $nr_files++; $filename = $1; next; }
if (m#^$tmp/([^:\s]+):#) { $nr_files++; $filename = $1; next; }
if (m#start address 0x([0-9A-Fa-f]+)#) { $entry_point = hex($1); next; }
if (m#^Idx Name#)
{
if (!defined($entry_point))
{
printf "%-64s has no entry point.\n", $filename;
next;
}
my $start_of_text;
while(<>)
{
if (m/^\s*\d+\s+.text\s+[0-9A-Fa-f]+\s+([0-9A-Fa-f]+)/)
{
$start_of_text = hex($1);
last;
}
}
if ($entry_point != $start_of_text)
{
$detected++;
printf "%-64s ep=0x%08x sot=0x%08x\n",
$filename, $entry_point, $start_of_text;
}
}
}
printf "files=%04d; detected=%04d\n", $nr_files, $detected; |