#!/usr/bin/perl

# bindiff
# Author: nibble <.ds@gmail.com>
# License: GPL >= 2

# TODO: Diff data
# TODO: Fix delta bugs

use strict;

# Construct and execute radare command
sub radare_cmd {
	my ($bin, $dump, $dump_off, $cmd, $arch) = @_;
	my $radare_cmd = "";
	my $radare_cmd_true = "";
	my $radare_cmd_false = "";

	$radare_cmd = "echo \'".$cmd."\' | radare -nv -e \'scr.floodprot=false\' -e \'asm.arch = $arch\'";
	$radare_cmd .= " -f" if ($cmd !~ /@/);
	$radare_cmd_true = $radare_cmd." -e \'asm.offset = false\' $bin | awk \'{print \$1}\' > $dump";
	$radare_cmd_false = $radare_cmd." -e \'asm.offset = true\' $bin > $dump_off";
	system $radare_cmd_true;
	system $radare_cmd_false;
}

# Dump bin
sub dump_bin {
	my ($bin, $dump, $dump_off, $arch) = @_;
	my $cmd = "";

	foreach (split /[\n\r]+/, `rabin -S $bin`) {
		if (/^.*offset=(\w*).*size=(\w*).*privileges=...x.*$/) {
			$cmd.="pD $2 @ $1 &&";
		}
	}
	$cmd = "pD" if ($cmd eq "");
	&radare_cmd($bin, $dump, $dump_off, $cmd, $arch);
}

# Dump symbol
sub dump_sym {
	my ($sym, $bin, $dump, $dump_off, $arch) = @_;
	my $cmd = "";

	foreach (split /[\n\r]+/, `rabin -s $bin`) {
		if (/^.*offset=(\w+).*size=(\w+).*name=$sym$/i) {
			$cmd.="pD $2 @ $1 &&";
		}
	}
	&radare_cmd($bin, $dump, $dump_off, $cmd, $arch);
}

# Dump section
sub dump_scn {
	my ($scn, $bin, $dump, $dump_off, $arch) = @_;
	my $cmd = "";

	foreach (split /[\n\r]+/, `rabin -S $bin`) {
		if (/^.*offset=(\w+).*size=(\w+).*name=$scn$/) {
			$cmd.="pD $2 @ $1 &&";
		}
	}
	&radare_cmd($bin, $dump, $dump_off, $cmd, $arch);
}

# Print a range of lines from a file
sub print_lines {
	my ($file, $from, $to, $context, $prefix, $color) = @_;
	my $i = 1;
	my $ret = "";

	$context = 0 if ($context == -1);

	open FILE, "<$file";
	while ($ret = <FILE>) {	
		if (($i >= $from-$context && $i < $from && $prefix =~ /-/) ||
			($i > $to && $i <= $to + $context && $prefix =~ /\+/)) {
			print "  $ret";
		} elsif ($i >= $from && $i <= $to) {
			$ret = "$prefix $ret";
			if ($color) {
				$ret =~ s/\x1b\[.*m//;
				$ret =~ s/^\+/\x1b[32m+/;
				$ret =~ s/^-/\x1b[31m-/;
				$ret =~ s/$/\x1b[m/;
			}
			print $ret;
		} elsif ($i > $to + $context) {
			last;
		}
		$i++;
	}
	close FILE;
}

# Remove temporary files
sub rm_tmp {
	unlink @_;

	exit 0;
}

# Print usage
sub usage {
	print "Usage: $0 <file a> <file b> [options]\n",
		  "Options:\n",
		  "  -a arch     Force arch\n",
		  "  -c int      Number of context lines (-1 is full context)\n",
		  "  -C          Color output\n",
		  "  -s symbol   Diff specified symbol\n",
		  "  -S section  Diff specified section\n",
		  "  -h          This help\n";
	exit 1;
}

# Get args
my $a_bin = $ARGV[0];
my $b_bin = $ARGV[1];
my $arch = "intel";
my $symbol = "";
my $section = "";
my $context = 0;
my $color = 0;

$_ = `rabin -I $a_bin`;
if (defined $ENV{'ARCH'}) {
	$arch = $ENV{'ARCH'};
} elsif (/arch=(.*)/) {
	$arch = $1;
}

$color = $ENV{'COLOR'} if (defined $ENV{'COLOR'});

foreach (0 .. $#ARGV) {
    $arch = $ARGV[$_] if ($ARGV[$_-1] eq "-a"); 
    $context = $ARGV[$_] if ($ARGV[$_-1] eq "-c"); 
    $color = 1 if ($ARGV[$_] eq "-C");
    $symbol = $ARGV[$_] if ($ARGV[$_-1] eq "-s");
    $section = $ARGV[$_] if ($ARGV[$_-1] eq "-S");
	&usage if ($ARGV[$_] eq "-h");
} 

&usage unless (defined($a_bin) && defined($b_bin));

# Names for temporary files
my $a_dump="/tmp/.a.$$.".int rand(0xC0D3);
my $a_dump_off=$a_dump.".off";
my $b_dump="/tmp/.b.$$.".int rand(0xCAFE);
my $b_dump_off=$b_dump.".off";
my $diff="/tmp/.diff.$$.".int rand(0xBABE);

# Signal handling
$SIG{INT} = sub { &rm_tmp($a_dump, $a_dump_off, $b_dump, $b_dump_off, $diff) };

# Generate dumps
if ($symbol ne "") {
	&dump_sym($symbol, $a_bin, $a_dump, $a_dump_off, $arch);
	&dump_sym($symbol, $b_bin, $b_dump, $b_dump_off, $arch);
} elsif ($section ne "") {
	&dump_scn($section, $a_bin, $a_dump, $a_dump_off, $arch);
	&dump_scn($section, $b_bin, $b_dump, $b_dump_off, $arch);
} else {
	&dump_bin($a_bin, $a_dump, $a_dump_off, $arch);
	&dump_bin($b_bin, $b_dump, $b_dump_off, $arch);
}

# Diff dumps
system "diff -Hb $a_dump $b_dump | grep -E \'^(\\w|,)*\$\' > $diff";

my $line = "";
my $a_lines = 0;
my $b_lines = 0;
my $a_from = 0;
my $a_to = 0;
my $b_from = 0;
my $b_to = 0;
my $old_to = 0;

open DIFF, "<$diff";
while ($line = <DIFF>) {
	if ($line =~ /^(.*)c(.*)$/) {
		$a_lines = $1;
		$b_lines = $2;

		if ($a_lines =~ /(\d+),(\d+)/) {
			$a_from = $1;
			$a_to = $2;
		} else {
			$a_from = $a_lines;
			$a_to = $a_lines;
		}

		if ($b_lines =~ /(\d+),(\d+)/) {
			$b_from = $1;
			$b_to = $2;
		} else {
			$b_from = $b_lines;
			$b_to = $b_lines;
		}
	} elsif ($line =~ /(.*)a(.*)/) {
		$a_lines = $1;
		$b_lines = $2;

		$a_from = $a_lines + 1;
		$a_to = $a_lines;

		if ($b_lines =~ /(\d+),(\d+)/) {
			$b_from = $1;
			$b_to = $2;
		} else {
			$b_from = $b_lines;
			$b_to = $b_lines;
		}
	} elsif ($line =~ /(.*)d(.*)/) {
		$a_lines = $1;
		$b_lines = $2;

		if ($a_lines =~ /(\d+),(\d+)/) {
			$a_from = $1;
			$a_to = $2;
		} else {
			$a_from = $a_lines;
			$a_to = $a_lines;
		}

		$b_from = $b_lines + 1;
		$b_to = $b_lines;
	}

	if ($context == -1) {
		&print_lines($a_dump_off, $old_to + 1, $a_from - 1, $context, " ", $color);
		$old_to = $a_to;
	} 

	&print_lines($a_dump_off, $a_from, $a_to, $context, "-", $color);
	&print_lines($b_dump_off, $b_from, $b_to, $context, "+", $color);
	print "------------\n" unless ($context == -1);
}
&print_lines($a_dump_off, $a_to + 1, 1e12, $context, " ", $color) if ($context == -1);
close DIFF;

# Remove temporary files
&rm_tmp($a_dump, $a_dump_off, $b_dump, $b_dump_off, $diff);
