#!/usr/bin/perl -w
##################

##
# opcode_db.pl - Copyright (C) 2003 METASPLOIT.COM
##

use FindBin qw{$Bin};
use strict;

##
# This script must be installed into a specific directory structure
# to function properly. This structure is:
#
#  $BASE/opcode_db.pl
#  $BASE/data
#  $BASE/data/WinNT
#  $BASE/data/WinNT/3
#  $BASE/data/WinNT/6
#  $BASE/data/WinXP
#  $BASE/data/WinXP/0
#  $BASE/data/WinXP/1
#  $BASE/data/Win2000
#  $BASE/data/Win2000/0
#  $BASE/data/Win2000/1
#  $BASE/data/Win2000/2
#  $BASE/data/Win2000/3
#  $BASE/data/Win2000/4
#
# Each of the numerical directories contains the appropriate DLL and EXE
# files from each operating system and service pack. The function addresses
# and possible jmp/call locations will be pulled from each of those files and
# the output will be written to STDOUT as a SQL script. The STDERR output 
# should be saved and analyzed for potential problems. A complete run against
# the sample directory structure above on a 1Ghz PIII laptop took 15 minutes.
#
##





my $data_dir = "$Bin/data";

opendir(O, "$data_dir") || die "top opendir: $!";
while(defined(my $os = readdir(O)))
{
    next if ! -d "$data_dir/$os";
    next if $os =~ /\./;
    
    opendir(S, "$data_dir/$os") || die "sub opendir: $!";
    while (defined(my $sp = readdir(S)))
    {
        next if ! -d "$data_dir/$os/$sp";
        next if $sp =~ /\./;
        
        opendir(F, "$data_dir/$os/$sp") || die "int opendir: $!";
        while (defined(my $filename = readdir(F)))
        {
            next if ! -f "$data_dir/$os/$sp/$filename";
            
            print STDERR "[x] Working on $data_dir/$os/$sp/$filename...\n";
            DumpFuncs($os, $sp, "$data_dir/$os/$sp/$filename");
            DumpJumps($os, $sp, "$data_dir/$os/$sp/$filename");
        }
    }
}


sub DumpJumps 
{
    my %jmp =
    (
        "\xff\xd0" => "call eax",
        "\xff\xe0" => "jmp eax",
        "\xff\xd1" => "call ecx",
        "\xff\xe1" => "jmp ecx",
        "\xff\xd2" => "call edx",
        "\xff\xe2" => "jmp edx",
        "\xff\xd3" => "call ebx",
        "\xff\xe3" => "jmp ebx",
        "\xff\xd4" => "call esp",
        "\xff\xe4" => "jmp esp",
        "\xff\xd5" => "call ebp",
        "\xff\xe5" => "jmp ebp",
        "\xff\xd6" => "call esi",
        "\xff\xe6" => "jmp esi",
        "\xff\xd7" => "call edi",
        "\xff\xe7" => "jmp edi",
        "\x50\xc3" => "push eax",
        "\x53\xc3" => "push ebx",
        "\x51\xc3" => "push ecx",
        "\x52\xc3" => "push edx",
        "\x54\xc3" => "push esp",
        "\x55\xc3" => "push ebp",
        "\x56\xc3" => "push esi",
        "\x57\xc3" => "push edi",
    );

    my %types =
    (
        "push"  => 2,
        "call"  => 3,
        "jmp"   => 4,
    );
    

    my ($os, $sp, $target) = @_;
    my $data;
    
    my $filename = lc($target);
    $filename =~ s/.*\/(.*)/$1/g;
    
    my $pe = PEInfo->new($target);
    if (! $pe) 
    {
        print STDERR "[*] Failed to load $target...\n";
        return;
    }
    
    my $data = $pe->Raw();

    foreach my $opc (keys(%jmp))
    {
        my $lst = 0;
        my $idx = index($data,  $opc, $lst);
        while ($idx > 0)
        {
            my ($inst, $reg) = split(/\s+/, $jmp{$opc});
            my $type = $types{$inst};
            printf("INSERT INTO opcodes VALUES ('', '$os', '$sp', '$filename', '$type', '%.8x', '$reg');\n", $pe->OffsetToVirtual($idx));
            $lst = $idx + 1;
            $idx = index($data, $opc, $lst);
        }
    }
}


sub DumpFuncs 
{
    my ($os, $sp, $target) = @_;
    
    open(X, "objdump -x -C $target|") || die "objdump: $!";
    
    my $filename = lc($target);
    $filename =~ s/.*\/(.*)/$1/g;

    my %names = ();
    my %ords = ();
    my $mode = 0;
    my $base;
    
    while (my $line = <X>)
    {
        chomp($line);

        if ($mode == 0 && $line =~ m/ImageBase\s+(.*)/)
        {
            $mode = 1;
            $base = eval("0x$1");
            next;
        }


        if ($mode == 1 && $line =~ /Export Address Table -- Ordinal Base/ )
        {
            $mode = 2;
            next;
        }

        if ($mode == 2 && $line =~ /Ordinal\/Name Pointer/)
        {
            $mode = 3;
            next;
        }

        if ($mode == 3 && $line =~ /Sections:/)
        {
            $mode = 4;
        }  

        if ($mode == 2)
        {
            if ($line =~ m/\s+\[\s+([0-9]{1,4})\].*\]\s+(.*)\s+(Export|Forw).*/)
            {
                $ords{$1} = $2;
            }
        }

        if ($mode == 3)
        {
            if ($line =~ m/\s+\[\s+([0-9]{1,4})\]\s+(.*)/)
            {
                my $offset = $ords{$1};
                if ($offset)
                {
                    $names{$2} =  sprintf("%.8x", eval("0x" . $offset) + $base);
                }
            }   
        }
    }
    close (X);

    foreach my $name (keys(%names))
    {
       print "INSERT INTO opcodes VALUES ('', '$os', '$sp', '$filename', '1', '" . $names{$name} . "', '$name');\n";
    }

}

##
# Extract information from a PE image and make it accessible
# through an object-oriented interface.
##

package PEInfo;
use strict;

my $RAW;
my $LastErrorVal;
my %IMAGE_HDR;
my %OPT_IMAGE_HDR;
my %RVA;
my %SECTIONS;



sub new {
    my ($class, $args) = @_;
    my $object = bless {}, $class;
    return($object->LoadImage($args));
}

sub LastError {
    my $object = shift;
    if (@_) { $LastErrorVal = shift }
    return ($LastErrorVal);
}

sub Raw {
    my $object = shift;
    return $RAW;
}

sub ImageHeader {
    my $object = shift;
    my $name = shift;
    if (exists($IMAGE_HDR{$name}))
    {
        return($IMAGE_HDR{$name});
    }
    return undef;
}

sub ImageHeaders {
    my $object = shift;
    return keys(%IMAGE_HDR);
}

sub OptImageHeader {
    my $object = shift;
    my $name = shift;
    if (exists($OPT_IMAGE_HDR{$name}))
    {
        return($OPT_IMAGE_HDR{$name});
    }
    return undef;
}

sub OptImageHeaders {
    my $object = shift;
    return keys(%OPT_IMAGE_HDR);
}

sub Rva {
    my $object = shift;
    my $name = shift;
    if (exists($RVA{$name}))
    {
        return($RVA{$name});
    }
    return undef;
}

sub Rvas {
    my $object = shift;
    return keys(%RVA);
}

sub Section {
    my $object = shift;
    my $name = shift;
    if (exists($SECTIONS{$name}))
    {
        return($SECTIONS{$name});
    }
    return undef;
}

sub Sections {
    my $object = shift;
    return keys(%SECTIONS);
}

sub LoadImage {
    my ($object, $fn) = @_;
    my $data;   
    local *X;
    
    if (! open(X, "<$fn"))
    {
        $object->LastError("Could not open file: $!");
        return(undef);
    }
    
    while(<X>) { $data .= $_ }
    close(X);
    
    $RAW = $data;
    
    my $peo = $object->FindPEOffset(\$data);
    if (! $peo)
    {
        $object->LastError("Could not find PE header");
        return(undef);
    }
    
    $IMAGE_HDR{"MachineID"}               = unpack("S", substr($data, $peo + 4));
    $IMAGE_HDR{"NumberOfSections"}        = unpack("S", substr($data, $peo + 6));
    $IMAGE_HDR{"TimeDateStamp"}           = unpack("L", substr($data, $peo + 8));
    $IMAGE_HDR{"PointerToSymbolTable"}    = unpack("L", substr($data, $peo + 12));
    $IMAGE_HDR{"NumberOfSymbols"}         = unpack("L", substr($data, $peo + 16));
    $IMAGE_HDR{"SizeOfOptionalHeader"}    = unpack("S", substr($data, $peo + 20));
    $IMAGE_HDR{"Characteristics"}         = unpack("S", substr($data, $peo + 22));

    if ($IMAGE_HDR{"SizeOfOptionalHeader"} > 0)
    {
        my $opthdr = substr($data, $peo + 24, $IMAGE_HDR{"SizeOfOptionalHeader"});

        $OPT_IMAGE_HDR{"Magic"}               = unpack("S", substr($opthdr, 0));
        $OPT_IMAGE_HDR{"MajorLinker"}         = unpack("C", substr($opthdr, 2));
        $OPT_IMAGE_HDR{"MinorLinker"}         = unpack("C", substr($opthdr, 3));
        $OPT_IMAGE_HDR{"SizeOfCode"}          = unpack("L", substr($opthdr, 4));
        $OPT_IMAGE_HDR{"SizeOfInitialized"}   = unpack("L", substr($opthdr, 8));
        $OPT_IMAGE_HDR{"SizeOfUninitialized"} = unpack("L", substr($opthdr, 12));

        $OPT_IMAGE_HDR{"EntryPoint"}          = unpack("L", substr($opthdr, 16));
        $OPT_IMAGE_HDR{"BaseOfCode"}          = unpack("L", substr($opthdr, 20));
        $OPT_IMAGE_HDR{"BaseOfData"}          = unpack("L", substr($opthdr, 24));

        $OPT_IMAGE_HDR{"ImageBase"}           = unpack("L", substr($opthdr, 28));
        $OPT_IMAGE_HDR{"SectionAlign"}        = unpack("L", substr($opthdr, 32));
        $OPT_IMAGE_HDR{"FileAlign"}           = unpack("L", substr($opthdr, 36));

        $OPT_IMAGE_HDR{"MajorOS"}             = unpack("S", substr($opthdr, 38));
        $OPT_IMAGE_HDR{"MinorOS"}             = unpack("S", substr($opthdr, 40));
        $OPT_IMAGE_HDR{"MajorImage"}          = unpack("S", substr($opthdr, 42));
        $OPT_IMAGE_HDR{"MinorImage"}          = unpack("S", substr($opthdr, 44));
        $OPT_IMAGE_HDR{"MajorSub"}            = unpack("S", substr($opthdr, 46));
        $OPT_IMAGE_HDR{"MinorSub"}            = unpack("S", substr($opthdr, 48));

        $OPT_IMAGE_HDR{"Reserved"}            = unpack("L", substr($opthdr, 52));
        $OPT_IMAGE_HDR{"SizeOfImage"}         = unpack("L", substr($opthdr, 56));
        $OPT_IMAGE_HDR{"SizeOfHeaders"}       = unpack("L", substr($opthdr, 60));
        $OPT_IMAGE_HDR{"Checksum"}            = unpack("L", substr($opthdr, 64));
        $OPT_IMAGE_HDR{"Subsystem"}           = unpack("S", substr($opthdr, 68));
        $OPT_IMAGE_HDR{"DllCharacteristics"}  = unpack("S", substr($opthdr, 70));
        $OPT_IMAGE_HDR{"SizeOfStackReserve"}  = unpack("L", substr($opthdr, 72));
        $OPT_IMAGE_HDR{"SizeOfStackCommit"}   = unpack("L", substr($opthdr, 76));
        $OPT_IMAGE_HDR{"SizeOfHeapReserve"}   = unpack("L", substr($opthdr, 80));
        $OPT_IMAGE_HDR{"SizeOfHeapCommit"}    = unpack("L", substr($opthdr, 84));
        $OPT_IMAGE_HDR{"LoaderFlags"}         = unpack("L", substr($opthdr, 88));
        $OPT_IMAGE_HDR{"NumberOfRvaAndSizes"} = unpack("L", substr($opthdr, 92));

        my @RVAMAP =
        (
            "export",
            "import",
            "resource",
            "exception",
            "certificate",
            "basereloc",
            "debug",
            "archspec",
            "globalptr",
            "tls",
            "load_config",
            "boundimport",
            "importaddress",
            "delayimport",
            "comruntime",
            "none"
        );

        # parse the rva data
        my $rva_data = substr($opthdr, 96, $OPT_IMAGE_HDR{"NumberOfRvaAndSizes"} * 8 );
        for (my $x = 0; $x < $OPT_IMAGE_HDR{"NumberOfRvaAndSizes"}; $x++)
        {
            if (! $RVAMAP[$x]) { $RVAMAP[$x] = "unknown_$x" }
            $RVA{ $RVAMAP[$x] } =
                        [
                            unpack("L", substr($rva_data, ($x * 8))),
                            unpack("L", substr($rva_data, ($x * 8) + 4)),
                        ];
        }
    }
    
    # parse the section headers
    my $sec_begn = $peo + 24 + $IMAGE_HDR{"SizeOfOptionalHeader"};
    my $sec_data = substr($data, $sec_begn);
    
    for (my $x = 0; $x < $IMAGE_HDR{"NumberOfSections"}; $x++)
    {
        my $sec_head = $sec_begn + ($x * 40);
        my $sec_name = substr($data, $sec_head, 8);
        $sec_name =~ s/\x00//g;

        $SECTIONS{$sec_name} =
                    [
                       unpack("L", substr($data, $sec_head +  8)),
                       unpack("L", substr($data, $sec_head +  12)),
                       unpack("L", substr($data, $sec_head +  16)),
                       unpack("L", substr($data, $sec_head +  20)), 
                    ];
                    
        # delta to virtual from file offset inside this section
        $SECTIONS{$sec_name}->[4] = $SECTIONS{$sec_name}->[1] - $SECTIONS{$sec_name}->[3];
    }   
    
    #foreach (keys(%IMAGE_HDR)) { printf("%s\t0x%.8x\n", $_ , $IMAGE_HDR{$_}); }
    #foreach (keys(%OPT_IMAGE_HDR)) { printf("%s\t0x%.8x\n", $_ , $OPT_IMAGE_HDR{$_}); }
    #foreach (keys(%RVA)) { printf("%s\t0x%.8x [0x%.8x]\n", $_ , $RVA{$_}->[0], $RVA{$_}->[1] ); }
    #foreach (keys(%SECTIONS)) 
    #{ 
    #    printf("%s\t0x%.8x\t0x%.8x\t0x%.8x\t0x%.8x\t0x%.8x\n",
    #           $_ , $SECTIONS{$_}->[0], $SECTIONS{$_}->[1], $SECTIONS{$_}->[2], $SECTIONS{$_}->[3], $SECTIONS{$_}->[4]);
    #}
    
    return($object);    
}

sub OffsetToVirtual {
    my ($object, $offset) = @_;

    # if this image has no optional header and defined image base,
    # just return zero since we can't calculate the virtual
    if (! $OPT_IMAGE_HDR{"ImageBase"})
    {
        return(0);
    }

    foreach (keys(%SECTIONS)) 
    {
        if ($offset >= $SECTIONS{$_}->[3] && $offset < ($SECTIONS{$_}->[3] + $SECTIONS{$_}->[2]))
        {
            return($OPT_IMAGE_HDR{"ImageBase"} + $offset + $SECTIONS{$_}->[4]);
        }
    }   
        
    # not in any given section, return the offset + ImageBase    
    return($OPT_IMAGE_HDR{"ImageBase"} + $offset);
}

sub VirtualToOffset {
    my ($object, $virtual) = @_;
    if (! $virtual) { return(0) }
    
    foreach (keys(%SECTIONS)) 
    {
       if ($virtual > $SECTIONS{$_}->[1] && $virtual < ($SECTIONS{$_}->[0] + $SECTIONS{$_}->[1]))
       {
            return $virtual - $SECTIONS{$_}->[4];
       }
    }   
    return(0);
}

sub FindPEOffset {
    my ($object, $data_ref) = @_;
    my $peo = unpack("L", substr(${$data_ref}, 0x3c, 4));
    if (substr(${$data_ref}, 0, 2) ne "MZ"  || substr(${$data_ref}, $peo, 2) ne "PE") { return undef } 
    return($peo);
}
