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

#
# constants
#
my $ENTITY_PREFIX = 'calc.';

#
# global variables
#
my %ENTITY;
my $BASE_ADDR = 0x08048000;

#
# parse_od
#
sub parse_od($)
{
  my $filename = shift;

  open(FILE, '<', $filename) || die "Can't open $filename: $!";
  LOOP: while(<FILE>)
  {
    chomp;
    my @number = split / /;
    my $start = hex($number[1]);

    ($start == 0x08048080) || die "$start != 0x08048080";
    $ENTITY{'evil.magic.start'} = '0x08048080';

    my $ofs = $start - $BASE_ADDR;
    ($ofs == 0x80) || die "$ofs != 0x80";
    $ENTITY{'evil.magic.ofs.D'} = $ofs;
    $ENTITY{'evil.magic.ofs.X'} = sprintf('0x%x', $ofs);

    last LOOP;
  }
  close FILE;
}

#
# set_num
#
sub set_num($$)
{
  my $base = shift;
  my $num = shift;

  $ENTITY{$base . '.D'} = $num;
  $ENTITY{$base . '.X'} = sprintf('0x%x', $num);
}

#
# parse_one_readelf_segment
#
sub parse_one_readelf_segment($$$$)
{
  my $filename = shift;
  my $base = shift;
  my $segment_name = shift;
  my $nr_load = shift;

  my @number = split / +/;
  my $virtaddr = hex($number[3]);
  my $filesiz = hex($number[5]);
  my $end = $virtaddr + $filesiz;

  # flags may contain spaces, e.g. "R E"
  # this will confuse the hell out of split(), so we use substr instead
  my $align = hex(substr($_, 68));
  if ($segment_name eq 'LOAD')
  {
    ($align == 4096) || die "$align != 4096 ($filename)";
  }

  # 'evil.magic.ofs.D' is set by parse_od()
  my $ofs = $ENTITY{$base . '.ofs.D'};
  if (defined($ofs))
  {
    my $codesize = $filesiz - $ofs;
    set_num($base . '.codesize', $codesize);

    my $bloat = int($codesize) / int($ENTITY{$base . '.size'});
    $ENTITY{$base . '.bloat'} = sprintf("%.3f", $bloat);
    $ENTITY{$base . '.bloat.percent'} = sprintf("%.0f", $bloat * 100);
  }
  my $prev_base = $base . '.' . $segment_name . ($nr_load - 1);
  my $prev_end = $ENTITY{$prev_base . '.end.D'};
  $base .=  '.' . $segment_name . $nr_load;

  if (defined($prev_end))
  {
    my $diff = $virtaddr - $prev_end;
    set_num($base . '.diff', $diff);

    my $diff_align = $diff - $align;
    if ($diff_align < 0)
    {
      print "<!-- $diff_align &lt; 0 ($filename) -->\n";
    }
    else
    {
      set_num($base . '.diff.align', $diff_align);
    }
  }
  elsif ($nr_load == 1 && $segment_name eq 'LOAD')
  {
    ($virtaddr == $BASE_ADDR) || die "$virtaddr != $BASE_ADDR ($filename)";
  }
  set_num($base . '.offset', hex($number[2]));
  set_num($base . '.virtaddr', $virtaddr);
  set_num($base . '.filesiz', $filesiz);
  set_num($base . '.end', $end);
  set_num($base . '.align', $align);
}

#
# parse_readelf_segments
#
sub parse_readelf_segments($$)
{
  my $filename = shift;
  my $base = shift;

  my $nr_load = 1;
  my $entry_point;

  open(FILE, '<', $filename) || die "Can't open $filename: $!";
  while(<FILE>)
  {
    chomp;
    my @number = split / +/;
    if (m/^-rwx/)
    {
      $ENTITY{$base . '.size'} = $number[4];
    }
    elsif (m/^Entry point /)
    {
      $entry_point = hex($number[2]);
      set_num $base . '.entry.point', $entry_point;
      set_num $base . '.entry.point.start.ofs', $entry_point - $BASE_ADDR;
    }
    elsif (m/^\s*LOAD\s*/)
    {
      parse_one_readelf_segment $filename, $base, 'LOAD', $nr_load;
      if ($nr_load++ == 1)
      {
	set_num $base . '.entry.point.dist.to.end',
	  $ENTITY{$base . '.LOAD1.end.D'} - $entry_point;
      }
    }
    elsif (m/^\s*DYNAMIC\s*/)
    {
      parse_one_readelf_segment $filename, $base, 'DYNAMIC', 1;
    }
    elsif (m/^\s*NOTE\s*/)
    {
      parse_one_readelf_segment $filename, $base, 'NOTE', 1;
    }
  }
  close FILE;
}

#
# parse_readelf_sections
#
sub parse_readelf_sections($;$)
{
  my $filename = shift;
  my $base = shift;

  open(FILE, '<', $filename) || die "Can't open $filename: $!";
  LOOP: while(<FILE>)
  {
    if (m/^  \[[ 0-9]+\] \.text /)
    {
      my @word = split;
      set_num $base . '.text.addr', hex($word[3]);
      set_num $base . '.text.off', hex($word[4]);
      set_num $base . '.text.size', hex($word[5]);
      last LOOP;
    }
  }
  close FILE;
}

#
# parse_ndisasm
#
sub parse_ndisasm($$)
{
  my $filename = shift;
  my $base = shift;
  my $nr_dword = 0;

  open(FILE, '<', $filename) || die "Can't open $filename: $!";
  LOOP: while(<FILE>)
  {
    my @number = split;
    if (m/\bpush dword 0x/)
    {
      set_num $base . '.ndisasm.dword' . $nr_dword++, hex($number[4]);
    }
    elsif (m/\bcall\b/)
    {
      set_num $base . '.ndisasm.call.addr', hex($number[3]);
      last LOOP;
    }
  }
  close FILE;
}

#
# parse_gdb
#
sub parse_gdb($$)
{
  my $filename = shift;
  my $base = shift;
  my $nr_dword = 0;
  my $nr_call = 0;

  open(FILE, '<', $filename) || die "Can't open $filename: $!";
  LOOP: while(<FILE>)
  {
    my @word = split;
    if (m/\bpush\s+0x/)
    {
      set_num $base . '.gdb.dword' . $nr_dword++, hex($word[3]);
    }
    elsif (m/\bcall\b/)
    {
      my $prefix = $base . '.gdb.call' . $nr_call++;
      set_num $prefix . '.addr', hex($word[3]);
      my $name = $word[4];
      $name =~ s/[<>]//g;
      $ENTITY{$prefix . '.name'} = $name;
    }
  }
  close FILE;
}

#
# parse_credits
#
sub parse_credits($)
{
  my $filename = shift;
  open(FILE, '<', $filename) || die "Can't open $filename: $!";
  my @credits = <FILE>;
  close FILE;

  my $line = join(', ', sort(@credits));
  $line =~ s/\s*\n//g;
  $ENTITY{'credits'} = $line;
}

#
# parse_ls
#
sub parse_ls($$)
{
  my $filename = shift;
  my $base = shift;

  open(FILE, '-|', 'ls -l ' . $filename) || die "Can't ls $filename: $!";
  LOOP: while(<FILE>)
  {
    chomp;
    my @number = split / +/;
    if (m/^-rw/)
    {
      $ENTITY{$base . '.size'} = $number[4];
    }
  }
  close FILE;
}

#
# main
#
$ENTITY{'base.addr'} = sprintf('0x%x', $BASE_ADDR);
parse_od 'out/evil_magic/od';
parse_readelf_segments 'out/readelf/segments', 'sh';
parse_readelf_segments 'out/readelf/evil_magic', 'evil.magic';
parse_readelf_segments 'out/entry_point/segments', 'infone';
parse_ndisasm 'out/entry_point/sh.ndisasm', 'sh';
parse_ndisasm 'out/entry_point/two.ndisasm', 'inftwo';
parse_ndisasm 'out/entry_point/three.ndisasm', 'infthree';
parse_gdb 'out/entry_point/sh.gdb', 'sh';
parse_credits 'CREDITS';
parse_ls 'tmp/one_step_closer/infection', 'infection';
parse_readelf_segments 'out/additional_cs/segments', 'additional.cs';
parse_readelf_sections 'out/entry_point/sections', 'sh';

($ENTITY{'evil.magic.LOAD1.filesiz.D'} == 0x97)
|| die 'evil.magic.LOAD1.filesiz.D != 0x97';

set_num 'infone.size.delta',
  $ENTITY{'infone.size'} - $ENTITY{'sh.size'};
set_num 'infone.LOAD1.filesiz.delta',
  $ENTITY{'infone.LOAD1.filesiz.D'} - $ENTITY{'sh.LOAD1.filesiz.D'};
set_num 'infone.LOAD2.offset.delta',
  $ENTITY{'infone.LOAD2.offset.D'} - $ENTITY{'sh.LOAD2.offset.D'};
set_num 'infone.DYNAMIC1.offset.delta',
  $ENTITY{'infone.DYNAMIC1.offset.D'} - $ENTITY{'sh.DYNAMIC1.offset.D'};
set_num 'additional.cs.size.delta',
  $ENTITY{'additional.cs.size'} - $ENTITY{'sh.size'};

($ENTITY{'sh.gdb.call0.name'} eq '__libc_start_main')
|| die $ENTITY{'sh.gdb.call0.name'} . ' != __libc_start_main';

($ENTITY{'infection.size'} <= $ENTITY{'infone.NOTE1.filesiz.D'})
|| die $ENTITY{'infection.size'} . ' > ' . $ENTITY{'infone.NOTE1.filesiz.D'};

($ENTITY{'sh.entry.point.D'} == $ENTITY{'sh.text.addr.D'})
|| die $ENTITY{'sh.entry.point.D'} . ' != ' . $ENTITY{'sh.text.addr.D'};

for my $name(keys(%ENTITY))
{
  printf "<!ENTITY %s%s \"%s\">\n", $ENTITY_PREFIX, $name, $ENTITY{$name};
}
