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

#
# constants
#
my $ENTITY_PREFIX = 'calc_';

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

#
# parse_od
#
sub parse_od()
{
  open(FILE, '<', 'out/evil_magic/od') || die "$!";
  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;
}

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

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

sub parse_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);

    # _size is set in parse_readelf()
    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
#
sub parse_readelf
{
  my $filename = shift;
  my $base = shift;

  $filename =~ /\/([^\/]*)$/;
  $base = $1 if (!defined($base));
  my $nr_load = 1;

  my $entry_point;

  open(FILE, '<', $filename) || die "Can't open $filename: $!";
  LOOP: 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_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_readelf_segment $filename, $base, 'DYNAMIC', 1;
    }
  }
  close FILE;
}

#
# main
#
$ENTITY{'base_addr'} = sprintf('0x%x', $BASE_ADDR);
parse_od();
parse_readelf('out/readelf/sh');
parse_readelf('out/readelf/evil_magic');
parse_readelf('out/entry_point/readelf', 'infected');

($ENTITY{'evil_magic_LOAD1_filesiz_D'} == 0x97)
|| die 'evil_magic_LOAD1_filesiz_D != 0x97';

set_num 'infected_size_delta',
  $ENTITY{'infected_size'} - $ENTITY{'sh_size'};
set_num 'infected_LOAD1_filesiz_delta',
  $ENTITY{'infected_LOAD1_filesiz_D'} - $ENTITY{'sh_LOAD1_filesiz_D'};
set_num 'infected_LOAD2_offset_delta',
  $ENTITY{'infected_LOAD2_offset_D'} - $ENTITY{'sh_LOAD2_offset_D'};
set_num 'infected_DYNAMIC1_offset_delta',
  $ENTITY{'infected_DYNAMIC1_offset_D'} - $ENTITY{'sh_DYNAMIC1_offset_D'};

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