| 1 |
#!/usr/bin/perl |
|---|
| 2 |
############### |
|---|
| 3 |
|
|---|
| 4 |
## |
|---|
| 5 |
# Name: msfelfscan |
|---|
| 6 |
# Author: Richard Johnson <rjohnson [at] uninformed.org> |
|---|
| 7 |
# Version: $Revision$ |
|---|
| 8 |
# Description: Search ELF files for given opcodes |
|---|
| 9 |
# License: |
|---|
| 10 |
# |
|---|
| 11 |
# This file is part of the Metasploit Exploit Framework |
|---|
| 12 |
# and is subject to the same licenses and copyrights as |
|---|
| 13 |
# the rest of this package. |
|---|
| 14 |
# |
|---|
| 15 |
## |
|---|
| 16 |
|
|---|
| 17 |
require 5.6.0; |
|---|
| 18 |
|
|---|
| 19 |
use FindBin qw{$RealBin}; |
|---|
| 20 |
use lib "$RealBin/lib"; |
|---|
| 21 |
use Getopt::Std; |
|---|
| 22 |
use strict; |
|---|
| 23 |
|
|---|
| 24 |
use Pex::ELFInfo; |
|---|
| 25 |
use Pex::Nasm::Ndisasm; |
|---|
| 26 |
use Pex; |
|---|
| 27 |
|
|---|
| 28 |
use Msf::ColPrint; |
|---|
| 29 |
use Msf::TextUI; |
|---|
| 30 |
|
|---|
| 31 |
no utf8; |
|---|
| 32 |
no locale; |
|---|
| 33 |
|
|---|
| 34 |
Msf::UI::ActiveStateSucks(); |
|---|
| 35 |
Msf::UI::BrokenUTF8(); |
|---|
| 36 |
|
|---|
| 37 |
my $VERSION = '$Revision$'; |
|---|
| 38 |
|
|---|
| 39 |
my %opts = (); |
|---|
| 40 |
my %jmps = |
|---|
| 41 |
( |
|---|
| 42 |
"\xff\xd0" => ["eax", "call"], |
|---|
| 43 |
"\xff\xe0" => ["eax", "jmp" ], |
|---|
| 44 |
"\xff\xd1" => ["ecx", "call"], |
|---|
| 45 |
"\xff\xe1" => ["ecx", "jmp" ], |
|---|
| 46 |
"\xff\xd2" => ["edx", "call"], |
|---|
| 47 |
"\xff\xe2" => ["edx", "jmp" ], |
|---|
| 48 |
"\xff\xd3" => ["ebx", "call"], |
|---|
| 49 |
"\xff\xe3" => ["ebx", "jmp" ], |
|---|
| 50 |
"\xff\xe4" => ["esp", "jmp" ], |
|---|
| 51 |
"\xff\xd5" => ["ebp", "call"], |
|---|
| 52 |
"\xff\xe5" => ["ebp", "jmp" ], |
|---|
| 53 |
"\xff\xd6" => ["esi", "call"], |
|---|
| 54 |
"\xff\xe6" => ["esi", "jmp" ], |
|---|
| 55 |
"\xff\xd7" => ["edi", "call"], |
|---|
| 56 |
"\xff\xe7" => ["edi", "jmp" ], |
|---|
| 57 |
|
|---|
| 58 |
"\x50\xc3" => ["eax", "push"], |
|---|
| 59 |
"\x53\xc3" => ["ebx", "push"], |
|---|
| 60 |
"\x51\xc3" => ["ecx", "push"], |
|---|
| 61 |
"\x52\xc3" => ["edx", "push"], |
|---|
| 62 |
"\x54\xc3" => ["esp", "push"], |
|---|
| 63 |
"\x55\xc3" => ["ebp", "push"], |
|---|
| 64 |
"\x56\xc3" => ["esi", "push"], |
|---|
| 65 |
"\x57\xc3" => ["edi", "push"], |
|---|
| 66 |
); |
|---|
| 67 |
|
|---|
| 68 |
my %pops = |
|---|
| 69 |
( |
|---|
| 70 |
"eax" => "\x58", |
|---|
| 71 |
"ebx" => "\x5b", |
|---|
| 72 |
"ecx" => "\x59", |
|---|
| 73 |
"edx" => "\x5a", |
|---|
| 74 |
"esi" => "\x5e", |
|---|
| 75 |
"edi" => "\x5f", |
|---|
| 76 |
"ebp" => "\x5d", |
|---|
| 77 |
); |
|---|
| 78 |
|
|---|
| 79 |
|
|---|
| 80 |
getopts("f:d:j:sx:a:B:A:I:nhvED", \%opts); |
|---|
| 81 |
Usage() if($opts{'h'}); |
|---|
| 82 |
Version() if($opts{'v'}); |
|---|
| 83 |
|
|---|
| 84 |
if ($opts{'h'} || |
|---|
| 85 |
(! defined($opts{'f'}) && ! defined($opts{'d'})) || |
|---|
| 86 |
(! defined($opts{'j'}) && |
|---|
| 87 |
! defined($opts{'x'}) && |
|---|
| 88 |
! defined($opts{'a'}) && |
|---|
| 89 |
! defined($opts{'D'}) && |
|---|
| 90 |
! $opts{'s'}) |
|---|
| 91 |
) |
|---|
| 92 |
{ |
|---|
| 93 |
Usage(); |
|---|
| 94 |
exit(0); |
|---|
| 95 |
} |
|---|
| 96 |
|
|---|
| 97 |
my $func; |
|---|
| 98 |
my $args = { }; |
|---|
| 99 |
|
|---|
| 100 |
if(exists($opts{'s'})) { |
|---|
| 101 |
$func = \&popPopRet; |
|---|
| 102 |
} |
|---|
| 103 |
elsif(exists($opts{'j'})) { |
|---|
| 104 |
$func = \&jmpReg; |
|---|
| 105 |
$args->{'reg'} = $opts{'j'}; |
|---|
| 106 |
} |
|---|
| 107 |
elsif(exists($opts{'x'})) { |
|---|
| 108 |
$func = \®ex; |
|---|
| 109 |
$args->{'regex'} = $opts{'x'}; |
|---|
| 110 |
} |
|---|
| 111 |
elsif(exists($opts{'a'})) { |
|---|
| 112 |
$func = \&address; |
|---|
| 113 |
$args->{'address'} = hex($opts{'a'}); |
|---|
| 114 |
} |
|---|
| 115 |
elsif(exists($opts{'D'})) { |
|---|
| 116 |
$func = \&dumpinfo; |
|---|
| 117 |
$args->{'dumpinfo'} = hex($opts{'D'}); |
|---|
| 118 |
} |
|---|
| 119 |
|
|---|
| 120 |
$args->{'before'} = $opts{'B'} if(exists($opts{'B'})); |
|---|
| 121 |
$args->{'after'} = $opts{'A'} if(exists($opts{'A'})); |
|---|
| 122 |
|
|---|
| 123 |
if($opts{'f'}) { |
|---|
| 124 |
|
|---|
| 125 |
my $filename = $opts{'f'}; |
|---|
| 126 |
my $elf = Pex::ELFInfo->new('File' => $filename, 'Debug' => $opts{'E'}); |
|---|
| 127 |
if (! $elf) |
|---|
| 128 |
{ |
|---|
| 129 |
print STDERR "$0: could not load ELF image from file.\n"; |
|---|
| 130 |
exit(0); |
|---|
| 131 |
} |
|---|
| 132 |
if ($opts{'I'}) { $elf->ImageBase($opts{'I'}) } |
|---|
| 133 |
&{$func}($elf, $args); |
|---|
| 134 |
} |
|---|
| 135 |
|
|---|
| 136 |
sub dumpinfo { |
|---|
| 137 |
my $elf = shift; |
|---|
| 138 |
my $args = shift; |
|---|
| 139 |
my $col; |
|---|
| 140 |
my @Ehdr = $elf->ElfHeaders; |
|---|
| 141 |
my @Phdr = $elf->ProgramHeaders; |
|---|
| 142 |
|
|---|
| 143 |
print "\n\n[ ELF Header ]\n\n"; |
|---|
| 144 |
$col = Msf::ColPrint->new(4, 4); |
|---|
| 145 |
foreach my $hdr (@Ehdr) { |
|---|
| 146 |
$col->AddRow($hdr, sprintf("0x%.8x",$elf->ElfHeader($hdr))); |
|---|
| 147 |
} |
|---|
| 148 |
print $col->GetOutput; |
|---|
| 149 |
|
|---|
| 150 |
print "\n\n[ Program Headers ]\n\n"; |
|---|
| 151 |
my $e_phnum = $elf->ElfHeader("e_phnum"); |
|---|
| 152 |
for(my $i = 0; $i < $e_phnum; $i++) |
|---|
| 153 |
{ |
|---|
| 154 |
$col = Msf::ColPrint->new(4, 4); |
|---|
| 155 |
foreach my $hdr (@Phdr) { |
|---|
| 156 |
$col->AddRow($hdr, sprintf("0x%.8x",$elf->ProgramHeader($i, $hdr))); |
|---|
| 157 |
} |
|---|
| 158 |
print $col->GetOutput; |
|---|
| 159 |
printf("\n----\n"); |
|---|
| 160 |
} |
|---|
| 161 |
|
|---|
| 162 |
} |
|---|
| 163 |
|
|---|
| 164 |
|
|---|
| 165 |
# Scan for pop/pop/ret addresses |
|---|
| 166 |
sub popPopRet |
|---|
| 167 |
{ |
|---|
| 168 |
my $elf = shift; |
|---|
| 169 |
my $data = $elf->Raw; |
|---|
| 170 |
my $args = shift; |
|---|
| 171 |
foreach my $rA (keys(%pops)) |
|---|
| 172 |
{ |
|---|
| 173 |
foreach my $rB (keys(%pops)) |
|---|
| 174 |
{ |
|---|
| 175 |
my $opc = $pops{$rA} . $pops{$rB} . "\xc3"; |
|---|
| 176 |
my $lst = 0; |
|---|
| 177 |
my $idx = index($data, $opc, $lst); |
|---|
| 178 |
while ($idx > 0) |
|---|
| 179 |
{ |
|---|
| 180 |
printf("0x%.8x $rA $rB ret\n", $elf->OffsetToVirtual($idx)); |
|---|
| 181 |
$lst = $idx + 1; |
|---|
| 182 |
$idx = index($data, $opc, $lst); |
|---|
| 183 |
} |
|---|
| 184 |
} |
|---|
| 185 |
} |
|---|
| 186 |
} |
|---|
| 187 |
|
|---|
| 188 |
# Scan for jmp/call/push,ret addresses |
|---|
| 189 |
sub jmpReg |
|---|
| 190 |
{ |
|---|
| 191 |
my $elf = shift; |
|---|
| 192 |
my $data = $elf->Raw; |
|---|
| 193 |
my $args = shift; |
|---|
| 194 |
my $reg = $args->{'reg'}; |
|---|
| 195 |
foreach my $opc (keys(%jmps)) |
|---|
| 196 |
{ |
|---|
| 197 |
next if ($reg && lc($reg) ne $jmps{$opc}->[0]); |
|---|
| 198 |
|
|---|
| 199 |
my $lst = 0; |
|---|
| 200 |
my $idx = index($data, $opc, $lst); |
|---|
| 201 |
while ($idx > 0) |
|---|
| 202 |
{ |
|---|
| 203 |
my ($reg, $typ) = @{$jmps{$opc}}; |
|---|
| 204 |
printf("0x%.8x $typ $reg\n", $elf->OffsetToVirtual($idx)); |
|---|
| 205 |
$lst = $idx + 1; |
|---|
| 206 |
$idx = index($data, $opc, $lst); |
|---|
| 207 |
} |
|---|
| 208 |
} |
|---|
| 209 |
} |
|---|
| 210 |
|
|---|
| 211 |
# Regex |
|---|
| 212 |
sub regex { |
|---|
| 213 |
my $elf = shift; |
|---|
| 214 |
my $data = $elf->Raw; |
|---|
| 215 |
my $args = shift; |
|---|
| 216 |
my $regex = $args->{'regex'}; |
|---|
| 217 |
$regex .= '.' x $args->{'after'} if($args->{'after'}); |
|---|
| 218 |
$regex = ('.' x $args->{'before'}) . $regex if($args->{'before'}); |
|---|
| 219 |
|
|---|
| 220 |
while($data =~ m/($regex)/g) { |
|---|
| 221 |
my $found = $1; |
|---|
| 222 |
my $index = pos($data) - length($found); |
|---|
| 223 |
printf("0x%.8x %s\n", $elf->OffsetToVirtual($index), hexOutput($found)); |
|---|
| 224 |
} |
|---|
| 225 |
} |
|---|
| 226 |
|
|---|
| 227 |
sub address { |
|---|
| 228 |
my $elf = shift; |
|---|
| 229 |
my $data = $elf->Raw; |
|---|
| 230 |
my $args = shift; |
|---|
| 231 |
|
|---|
| 232 |
my $address = $args->{'address'} - $args->{'before'}; |
|---|
| 233 |
my $length = $args->{'before'} + $args->{'after'}; |
|---|
| 234 |
$length = 1 if(!$length); |
|---|
| 235 |
my $index = $elf->VirtualToOffset($address); |
|---|
| 236 |
my $found = substr($data, $index, $length); |
|---|
| 237 |
return if(!defined($index) || length($found) == 0); |
|---|
| 238 |
printf("0x%.8x %s\n", $address, hexOutput($found)); |
|---|
| 239 |
} |
|---|
| 240 |
|
|---|
| 241 |
sub hexOutput { |
|---|
| 242 |
my $data = shift; |
|---|
| 243 |
my $string = unpack('H*', $data); |
|---|
| 244 |
if($opts{'n'}) { |
|---|
| 245 |
# my $tempString = $string; |
|---|
| 246 |
# $tempString =~ s/(..)/\\x$1/g; |
|---|
| 247 |
$string .= "\n--- ndisasm output ---\n"; |
|---|
| 248 |
# $string .= `echo -ne "$tempString" | ndisasm -u /dev/stdin`; |
|---|
| 249 |
$string .= Pex::Nasm::Ndisasm->DisasData($data); |
|---|
| 250 |
$string .= "--- ndisasm output ---"; |
|---|
| 251 |
} |
|---|
| 252 |
return($string); |
|---|
| 253 |
} |
|---|
| 254 |
|
|---|
| 255 |
|
|---|
| 256 |
sub Usage |
|---|
| 257 |
{ |
|---|
| 258 |
print STDERR |
|---|
| 259 |
qq{ Usage: $0 <input> <mode> <options> |
|---|
| 260 |
Inputs: |
|---|
| 261 |
-f <file> Read in ELF file |
|---|
| 262 |
Modes: |
|---|
| 263 |
-j <reg> Search for jump equivalent instructions |
|---|
| 264 |
-s Search for pop+pop+ret combinations |
|---|
| 265 |
-x <regex> Search for regex match |
|---|
| 266 |
-a <address> Show code at specified virtual address |
|---|
| 267 |
Options: |
|---|
| 268 |
-A <count> Number of bytes to show after match |
|---|
| 269 |
-B <count> Number of bytes to show before match |
|---|
| 270 |
-I address Specify an alternate base load address |
|---|
| 271 |
-n Print disassembly of matched data |
|---|
| 272 |
}; |
|---|
| 273 |
exit(0); |
|---|
| 274 |
|
|---|
| 275 |
} |
|---|
| 276 |
sub Version { |
|---|
| 277 |
my $ver = Pex::Utils::Rev2Ver($VERSION); |
|---|
| 278 |
print STDERR qq{ |
|---|
| 279 |
Msfelfscan Version: $ver |
|---|
| 280 |
|
|---|
| 281 |
}; |
|---|
| 282 |
exit(0); |
|---|
| 283 |
} |
|---|
| 284 |
|
|---|