xqemu/scripts/windpl/windpl.pl
2013-08-20 23:34:49 +10:00

1948 lines
No EOL
66 KiB
Perl
Executable file

#!/usr/bin/env perl
# win/xbox KD client
#
# Copyright (C) 2007 SecureWorks, Inc.
# Copyright (C) 2013 espes
#
# This program is free software subject to the terms of the GNU General
# Public License. You can use, copy, redistribute and/or modify the
# program under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version. You should have received a copy of
# the GNU General Public License along with this program. If not,
# please see http://www.gnu.org/licenses/ for a copy of the GNU General
# Public License.
#
# The program is subject to a disclaimer of warranty and a limitation of
# liability, as disclosed below.
#
# Disclaimer of Warranty.
#
# THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
# APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
# HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
# WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
# OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU
# ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, CORRECTION OR
# RECOVERY FROM DATA LOSS OR DATA ERRORS.
#
# Limitation of Liability.
#
# IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
# WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
# CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
# INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
# ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
# NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES
# SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE
# WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
use IO::Select;
use IO::Socket;
use File::stat;
use Fcntl ':mode';
use strict;
$| = 1;
my $dev = $ARGV[0];
$dev ||= "/dev/ttyS0";
$SIG{'ALRM'} = sub { die "timeout" };
my $timeout = 10; # max time to wait on packet
my $running = 1;
my $pcontext; # global process context
my %kernelcontext;
$kernelcontext{'peb'} = 0;
$kernelcontext{'pid'} = 0;
$kernelcontext{'eprocess'} = 0;
$kernelcontext{'dtb'} = 0;
my %processcontext;
$processcontext{'peb'} = 0;
$processcontext{'pid'} = 0;
$processcontext{'eprocess'} = 0;
$processcontext{'dtb'} = 0;
$pcontext = \%kernelcontext;
my $version;
my $kernelbase;
my $nextpid = 1;
my %breakpoints;
my $curbp = 1;
my $controlspace;
my $controlspacesent;
my $isserial;
my $serial;
my $socket;
my $ds = stat($dev) || die "$!";
if (S_ISCHR($ds->mode)) {
require Device::SerialPort;
$serial = tie( *FH, 'Device::SerialPort', "$dev" ) || die "Can't tie: $!";
$serial->baudrate(115200);
$serial->parity("none");
$serial->databits(8);
$serial->stopbits(1);
$serial->handshake("none");
$serial->write_settings || die "failed writing settings";
FH->blocking(0);
} elsif (S_ISSOCK($ds->mode)) {
$socket = IO::Socket::UNIX->new(
Type => SOCK_STREAM,
Peer => $dev
) || die "Can't create socket $!";
} else {
die "$dev not a character device or a socket\n";
}
sub writeDev {
my $data = shift;
if ($serial) {
$serial->write($data);
} else {
$socket->syswrite($data);
}
}
sub readLoop {
my $wanted = shift;
my $total = 0;
my $count;
my $outbuf;
while ( $total < $wanted ) {
my $buf;
if ($serial) {
( $count, $buf ) = $serial->read(1);
} else {
#FH->blocking(1);
$count = $socket->sysread($buf, 1);
if ($count == 0) {
die "eof";
}
# printf "readLoop $count %x\n", ord($buf);
#FH->blocking(0);
}
if ($count) {
$total += $count;
$outbuf .= $buf;
}
}
return $outbuf;
}
sub hexformat {
my $buf = shift;
my $len = length($buf);
return unless $len;
my $b = "0000 ";
my $c = 0;
for ( split( //, $buf ) ) {
$c++;
$b .= sprintf( "%02x ", ord );
unless ( $c % 16 ) {
if ( $c < $len ) {
$b .= sprintf( "\n%04x ", $c );
}
else {
$b .= sprintf( "\n", $c );
}
}
}
$b .= "\n" unless substr( $b, -1, 1 ) eq "\n";
return $b;
}
sub cksum {
my $sum;
for ( split( //, shift ) ) {
$sum += ord;
}
return $sum;
}
my $PACKET_LEADER = 0x30303030;
my $CONTROL_PACKET_LEADER = 0x69696969;
my $PACKET_TYPE_UNUSED = 0;
my $PACKET_TYPE_KD_STATE_CHANGE32 = 1;
my $PACKET_TYPE_KD_STATE_MANIPULATE = 2;
my $PACKET_TYPE_KD_DEBUG_IO = 3;
my $PACKET_TYPE_KD_ACKNOWLEDGE = 4;
my $PACKET_TYPE_KD_RESEND = 5;
my $PACKET_TYPE_KD_RESET = 6;
my $PACKET_TYPE_KD_STATE_CHANGE64 = 7;
my $PACKET_TYPE_MAX = 8;
sub handlePacket {
my $quiet = shift;
my ($ptype, $buf) = getPacket($quiet);
if (!$buf) {
return;
}
if ($ptype == $PACKET_TYPE_KD_STATE_MANIPULATE) {
handleStateManipulate($buf, $quiet);
} elsif ($ptype == $PACKET_TYPE_KD_DEBUG_IO) {
handleDebugIO($buf, $quiet);
} elsif ($ptype == $PACKET_TYPE_KD_STATE_CHANGE64) {
handleStateChange($buf, $quiet);
}
return ($ptype, $buf);
}
sub getPacket {
my $payload;
my $quiet = shift;
my $ptype;
my $buf = readLoop(4);
my $plh = unpack( "I", $buf );
if ( $plh == $PACKET_LEADER || $plh == $CONTROL_PACKET_LEADER ) {
printf "Got packet leader: %08x\n", $plh unless $quiet;
$buf = readLoop(2);
$ptype = unpack( "S", $buf );
print "Packet type: $ptype\n" unless $quiet;
$buf = readLoop(2);
my $bc = unpack( "S", $buf );
print "Byte count: $bc\n" unless $quiet;
$buf = readLoop(4);
my $pid = unpack( "I", $buf );
$nextpid = $pid;
printf "Packet ID: %08x\n", $pid unless $quiet;
$buf = readLoop(4);
my $ck = unpack( "I", $buf );
printf "Checksum: %08x\n", $ck unless $quiet;
if ($bc) {
$payload = readLoop($bc);
}
# send ack if it's a non-control packet
if ( $plh == 0x30303030 ) {
# packet trailer
my $trail = readLoop(1);
# print hexformat($trail) unless $quiet;
if ( $trail eq "\xaa" ) {
# print "sending Ack\n";# unless $quiet;
sendAck();
}
}
}
return ($ptype, $payload);
}
# PACKET_TYPE_KD_DEBUG_IO apis
# DBGKD_DEBUG_IO
my $DbgKdPrintStringApi = 0x00003230;
my $DbgKdGetStringApi = 0x00003231;
sub handleDebugIO {
my $buf = shift;
my $quiet = shift;
my $apiNumber = unpack("I", substr($buf, 0, 4));
if ($apiNumber == $DbgKdPrintStringApi) {
print "DBG PRINT STRING: ".substr($buf, 0x10);
}
}
# PACKET_TYPE_KD_STATE_CHANGE states
# X86_NT5_DBGKD_WAIT_STATE_CHANGE64
my $DbgKdExceptionStateChange = 0x00003030;
my $DbgKdLoadSymbolsStateChange = 0x00003031;
sub handleStateChange {
my $buf = shift;
my $quiet = shift;
my $newState = unpack("I", substr($buf, 0, 4));
printf "State Change: %08x\n", $newState unless $quiet;
if ($newState == $DbgKdExceptionStateChange) {
my %exceptions = (
0xc0000005 => "EXCEPTION_ACCESS_VIOLATION",
0xc000008c => "EXCEPTION_ARRAY_BOUNDS_EXCEEDED",
0x80000003 => "EXCEPTION_BREAKPOINT",
0x80000002 => "EXCEPTION_DATATYPE_MISALIGNMENT",
0xc000008d => "EXCEPTION_FLT_DENORMAL_OPERAND",
0xc000008e => "EXCEPTION_FLT_DIVIDE_BY_ZERO",
0xc000008f => "EXCEPTION_FLT_INEXACT_RESULT",
0xc0000030 => "EXCEPTION_FLT_INVALID_OPERATION",
0xc0000091 => "EXCEPTION_FLT_OVERFLOW",
0xc0000032 => "EXCEPTION_FLT_STACK_CHECK",
0xc0000033 => "EXCEPTION_FLT_UNDERFLOW",
0x80000001 => "EXCEPTION_GUARD_PAGE",
0xc000001d => "EXCEPTION_ILLEGAL_INSTRUCTION",
0xc0000006 => "EXCEPTION_IN_PAGE_ERROR",
0xc0000094 => "EXCEPTION_INT_DIVIDE_BY_ZERO",
0xc0000035 => "EXCEPTION_INT_OVERFLOW",
0xc00000fd => "EXCEPTION_STACK_OVERFLOW"
);
# DBGKM_EXCEPTION64
my $ex = substr($buf, 32);
my $code = unpack("I", substr($ex,0,4));
my $flags = unpack("I", substr($ex,4,4));
my $record = unpack("I", substr($ex,8,4));
my $address = unpack("I", substr($ex,16,4));
my $parameters = unpack("I", substr($ex,24,4));
if ( $exceptions{$code} ) {
printf "*** %s ", $exceptions{$code};
}
else {
printf "*** Exception %08x ", $code;
}
printf "at %08x\n", $address;
printf "Exception flags = %08x\n", $flags;
printf "Exception record = %08x\n", $record;
printf "Exception address = %08x\n", $address;
printf "Number parameters = %08x\n", $parameters;
$running = 0;
# my @v = getVersionInfo();
# $version = $v[0];
# $kernelbase = $v[2];
} elsif ($newState == $DbgKdLoadSymbolsStateChange) {
#DBGKD_LOAD_SYMBOLS64
my $file = substr($buf, 0x3b8);
chop $file;
print "Load Symbols for $file\n";
#nothing to do...
sendDbgKdContinue2();
}
}
# PACKET_TYPE_KD_STATE_MANIPULATE api numbers
# DBGKD_MANIPULATE_STATE64
my $DbgKdReadVirtualMemoryApi = 0x00003130;
my $DbgKdWriteVirtualMemoryApi = 0x00003131;
my $DbgKdGetContextApi = 0x00003132;
my $DbgKdSetContextApi = 0x00003133;
my $DbgKdWriteBreakPointApi = 0x00003134;
my $DbgKdRestoreBreakPointApi = 0x00003135;
my $DbgKdContinueApi = 0x00003136;
my $DbgKdReadControlSpaceApi = 0x00003137;
my $DbgKdWriteControlSpaceApi = 0x00003138;
my $DbgKdReadIoSpaceApi = 0x00003139;
my $DbgKdWriteIoSpaceApi = 0x0000313A;
my $DbgKdRebootApi = 0x0000313B;
my $DbgKdContinueApi2 = 0x0000313C;
my $DbgKdReadPhysicalMemoryApi = 0x0000313D;
my $DbgKdWritePhysicalMemoryApi = 0x0000313E;
my $DbgKdSetSpecialCallApi = 0x00003140;
my $DbgKdClearSpecialCallsApi = 0x00003141;
my $DbgKdSetInternalBreakPointApi = 0x00003142;
my $DbgKdGetInternalBreakPointApi = 0x00003143;
my $DbgKdReadIoSpaceExtendedApi = 0x00003144;
my $DbgKdWriteIoSpaceExtendedApi = 0x00003145;
my $DbgKdGetVersionApi = 0x00003146;
my $DbgKdWriteBreakPointExApi = 0x00003147;
my $DbgKdRestoreBreakPointExApi = 0x00003148;
my $DbgKdCauseBugCheckApi = 0x00003149;
my $DbgKdSwitchProcessor = 0x00003150;
my $DbgKdPageInApi = 0x00003151;
my $DbgKdReadMachineSpecificRegister = 0x00003152;
my $DbgKdWriteMachineSpecificRegister = 0x00003153;
my $DbgKdSearchMemoryApi = 0x00003156;
my $DbgKdGetBusDataApi = 0x00003157;
my $DbgKdSetBusDataApi = 0x00003158;
my $DbgKdCheckLowMemoryApi = 0x00003159;
sub handleStateManipulate {
my $buf = shift;
my $quiet = shift;
my $apiNumber = unpack("I", substr($buf, 0, 4));
printf "State Manipulate: %08x\n", $apiNumber;
if ( $apiNumber == $DbgKdWriteBreakPointApi ) {
my $bp = sprintf( "%08x", unpack( "I", substr( $buf, 16, 4 ) ) );
my $handle = unpack( "I", substr( $buf, 20, 4 ) );
print "Breakpoint $handle set at $bp\n";
$breakpoints{$bp} = $handle;
}
elsif ( $apiNumber == $DbgKdRestoreBreakPointApi ) {
my $handle = unpack( "I", substr( $buf, 16, 4 ) );
print "Breakpoint $handle cleared\n";
}
elsif ( $apiNumber == $DbgKdGetVersionApi ) {
my $version = substr( $buf, 16 );
print "VERS: ", hexformat($version) unless $quiet;
}
elsif ( $apiNumber == $DbgKdReadVirtualMemoryApi ) {
my $vmem = substr( $buf, 56 );
print "VMEM:\n", hexasc($vmem) unless $quiet;
}
elsif ( $apiNumber == $DbgKdReadPhysicalMemoryApi ) {
my $pmem = substr( $buf, 56 );
print "PMEM:\n", hexasc($pmem) unless $quiet;
}
elsif ( $apiNumber == $DbgKdReadControlSpaceApi ) {
$controlspace = substr( $buf, 56 );
print "CNTL: ", hexformat($controlspace) unless $quiet;
}
else {
print "UNKN: ", hexasc($buf) unless $quiet;
}
}
sub packetHeader {
my $d = shift;
my $header = "\x30\x30\x30\x30" . # packet leader
"\x02\x00" . # packet type PACKET_TYPE_KD_STATE_MANIPULATE
pack( "S", length($d) ) . # sizeof data
pack( "I", $nextpid ) . # packet id
pack( "I", cksum($d) ); # checksum of data
return $header;
}
sub sendAck {
my $ack =
"\x69\x69\x69\x69\x04\x00\x00\x00\x00\x00\x80\x80\x00\x00\x00\x00";
substr( $ack, 8, 4 ) = pack( "I", $nextpid );
#print hexformat($ack);
writeDev($ack);
}
sub sendReset {
my $rst =
"\x69\x69\x69\x69\x06\x00\x00\x00\x00\x00\x80\x80\x00\x00\x00\x00";
# print "Sending reset packet\n";
# print hexformat($rst);
writeDev($rst);
}
sub getContext {
my %context;
sendDbgKdGetContext();
my $buf = waitStateManipulate($DbgKdGetContextApi);
if ( length($buf) > 204 ) {
my $ctx = substr( $buf, 56 );
#print "CTXT: ", hexformat($context);
$context{'ContextFlags'} = unpack( "I", substr( $ctx, 0, 4 ) );
$context{'DR0'} = unpack( "I", substr( $ctx, 4, 4 ) );
$context{'DR1'} = unpack( "I", substr( $ctx, 8, 4 ) );
$context{'DR2'} = unpack( "I", substr( $ctx, 12, 4 ) );
$context{'DR3'} = unpack( "I", substr( $ctx, 16, 4 ) );
$context{'DR6'} = unpack( "I", substr( $ctx, 20, 4 ) );
$context{'DR7'} = unpack( "I", substr( $ctx, 24, 4 ) );
$context{'fp.ControlWord'} = unpack( "I", substr( $ctx, 28, 4 ) );
$context{'fp.StatusWord'} = unpack( "I", substr( $ctx, 32, 4 ) );
$context{'fp.TagWord'} = unpack( "I", substr( $ctx, 36, 4 ) );
$context{'fp.ErrorOffset'} = unpack( "I", substr( $ctx, 40, 4 ) );
$context{'fp.ErrorSelector'} = unpack( "I", substr( $ctx, 44, 4 ) );
$context{'fp.DataOffset'} = unpack( "I", substr( $ctx, 48, 4 ) );
$context{'fp.DataSelector'} = unpack( "I", substr( $ctx, 52, 4 ) );
$context{'fp.RegisterArea'} = substr( $ctx, 56, 80 );
$context{'fp.Cr0NpxState'} = unpack( "I", substr( $ctx, 136, 4 ) );
$context{'GS'} = unpack( "I", substr( $ctx, 140, 4 ) );
$context{'FS'} = unpack( "I", substr( $ctx, 144, 4 ) );
$context{'ES'} = unpack( "I", substr( $ctx, 148, 4 ) );
$context{'DS'} = unpack( "I", substr( $ctx, 152, 4 ) );
$context{'EDI'} = unpack( "I", substr( $ctx, 156, 4 ) );
$context{'ESI'} = unpack( "I", substr( $ctx, 160, 4 ) );
$context{'EBX'} = unpack( "I", substr( $ctx, 164, 4 ) );
$context{'EDX'} = unpack( "I", substr( $ctx, 168, 4 ) );
$context{'ECX'} = unpack( "I", substr( $ctx, 172, 4 ) );
$context{'EAX'} = unpack( "I", substr( $ctx, 176, 4 ) );
$context{'EBP'} = unpack( "I", substr( $ctx, 180, 4 ) );
$context{'EIP'} = unpack( "I", substr( $ctx, 184, 4 ) );
$context{'CS'} = unpack( "I", substr( $ctx, 188, 4 ) );
$context{'Eflags'} = unpack( "I", substr( $ctx, 192, 4 ) );
$context{'ESP'} = unpack( "I", substr( $ctx, 196, 4 ) );
$context{'SS'} = unpack( "I", substr( $ctx, 200, 4 ) );
$context{'leftovers'} = substr( $ctx, 204 );
return %context;
}
}
sub setContext {
my %context = @_;
my $ctx = pack( "I", $context{'ContextFlags'} );
$ctx .= pack( "I", $context{'DR0'} );
$ctx .= pack( "I", $context{'DR1'} );
$ctx .= pack( "I", $context{'DR2'} );
$ctx .= pack( "I", $context{'DR3'} );
$ctx .= pack( "I", $context{'DR6'} );
$ctx .= pack( "I", $context{'DR7'} );
$ctx .= pack( "I", $context{'fp.ControlWord'} );
$ctx .= pack( "I", $context{'fp.StatusWord'} );
$ctx .= pack( "I", $context{'fp.TagWord'} );
$ctx .= pack( "I", $context{'fp.ErrorOffset'} );
$ctx .= pack( "I", $context{'fp.ErrorSelector'} );
$ctx .= pack( "I", $context{'fp.DataOffset'} );
$ctx .= pack( "I", $context{'fp.DataSelector'} );
$ctx .= $context{'fp.RegisterArea'};
$ctx .= pack( "I", $context{'fp.Cr0NpxState'} );
$ctx .= pack( "I", $context{'GS'} );
$ctx .= pack( "I", $context{'FS'} );
$ctx .= pack( "I", $context{'ES'} );
$ctx .= pack( "I", $context{'DS'} );
$ctx .= pack( "I", $context{'EDI'} );
$ctx .= pack( "I", $context{'ESI'} );
$ctx .= pack( "I", $context{'EBX'} );
$ctx .= pack( "I", $context{'EDX'} );
$ctx .= pack( "I", $context{'ECX'} );
$ctx .= pack( "I", $context{'EAX'} );
$ctx .= pack( "I", $context{'EBP'} );
$ctx .= pack( "I", $context{'EIP'} );
$ctx .= pack( "I", $context{'CS'} );
$ctx .= pack( "I", $context{'Eflags'} );
$ctx .= pack( "I", $context{'ESP'} );
$ctx .= pack( "I", $context{'SS'} );
$ctx .= $context{'leftovers'};
sendDbgKdSetContext($ctx);
waitStateManipulate($DbgKdSetContextApi);
}
sub getVersionInfo {
print "getVersionInfo\n";
# os version, protocol version, kernel base, module list, debugger data
sendDbgKdGetVersion();
my $buf = waitStateManipulate($DbgKdGetVersionApi);
if ( length($buf) > 32 ) {
my $v = substr( $buf, 16 );
my $osv = sprintf "%d.%d", unpack( "S", substr( $v, 4, 2 ) ),
unpack( "S", substr( $v, 6, 2 ) );
my $pv = unpack( "S", substr( $v, 8, 2 ) );
my $machinetype = unpack( "S", substr( $v, 12, 2 ) );
my $kernbase = unpack( "I", substr( $v, 16, 4 ) );
my $modlist = unpack( "I", substr( $v, 24, 4 ) );
my $ddata = unpack( "I", substr( $v, 32, 4 ) );
if ( $pv < 5 ) {
printf "Debug protocol version %d not supported\n", $pv;
exit;
}
if ( $machinetype && ( $machinetype != 0x2d ) ) {
printf "Processor architecture %04x not supported\n", $machinetype;
exit;
}
printf "Windows version = %s\n", $osv;
printf "Protocol version = %d\n", $pv;
printf "Kernel base = %08x\n", $kernbase;
printf "Module list = %08x\n", $modlist;
printf "Debugger data = %08x\n", $ddata;
return ( $osv, $pv, $kernbase, $modlist, $ddata );
}
return ( "0.0", 0, 0, 0, 0 );
}
sub printVersionData {
my @v = getVersionInfo();
printf "Windows version = %s\n", $v[0];
printf "Protocol version = %d\n", $v[1];
printf "Kernel base = %08x\n", $v[2];
printf "Module list = %08x\n", $v[3];
printf "Debugger data = %08x\n", $v[4];
}
sub getKernelModules {
my $save = $pcontext;
$pcontext = \%kernelcontext; # this procedure is kernel context only
my %modules;
my @v = getVersionInfo();
my $flink = readDword( $v[3] );
my @modlist = walkList($flink);
for my $mod (@modlist) {
#printf "module at %08x\n", $mod;
my $buf = readVirtualMemory( $mod, 0x34 );
if ( length($buf) == 0x34 ) {
my $base = unpack( "I", substr( $buf, 0x18, 4 ) );
next if $base == 0;
my $entry = unpack( "I", substr( $buf, 0x1c, 4 ) );
my $size = unpack( "I", substr( $buf, 0x20, 4 ) );
my $path = substr( $buf, 0x24, 8 );
my $name = substr( $buf, 0x2c, 8 );
$modules{$base}{'name'} = unicodeStructToAscii($name);
$modules{$base}{'path'} = unicodeStructToAscii($path);
$modules{$base}{'size'} = $size;
$modules{$base}{'entry'} = $entry;
}
}
$pcontext = $save;
return %modules;
}
sub unicodeStructToAscii {
my $struct = shift;
return if length($struct) != 8;
my $len = unpack( "S", substr( $struct, 0, 2 ) );
my $vaddr = unpack( "I", substr( $struct, 4, 4 ) );
my $buf = readVirtualMemory( $vaddr, $len );
if ( length($buf) == $len ) {
$buf =~ s/\x00//g; # ok not really Unicode to Ascii
return $buf;
}
}
sub hexasc {
my $buf = shift;
my $len = length($buf);
return unless $len;
my $count = 0;
my $ascii;
my $out = "0000 ";
for ( split( //, $buf ) ) {
my $c = ord;
$out .= sprintf( "%02x ", $c );
if ( ( $c > 0x1f ) && ( $c < 0x7f ) ) {
$ascii .= $_;
}
else {
$ascii .= ".";
}
unless ( ++$count % 16 ) {
if ( $count < $len ) {
$out .= sprintf( " %s\n%04x ", $ascii, $count );
}
else {
$out .= sprintf( " %s\n", $ascii );
}
$ascii = "";
}
}
if ($ascii) {
my $padding = 48 - ( ( $count % 16 ) * 3 );
$out .= " " x $padding;
$out .= " $ascii\n";
}
return $out;
}
sub sendManipulateStatePacket {
my $d = shift;
my $h = packetHeader($d);
#print "SEND: ", hexformat($h),
# hexformat($d);
writeDev($h);
writeDev($d);
writeDev("\xaa");
}
sub sendDbgKdContinue2 {
#print "Sending DbgKdContinue2Api packet\n";
my $d = "\x00" x 56;
substr( $d, 0, 4 ) = pack( "I", $DbgKdContinueApi2 );
substr( $d, 8, 4 ) = pack( "I", 0x00010001 );
substr( $d, 16, 4 ) = pack( "I", 0x00010001 );
substr( $d, 24, 4 ) = pack( "I", 0x400 ); # TraceFlag
substr( $d, 28, 4 ) = pack( "I", 0x01 ); # Dr7
sendManipulateStatePacket($d);
}
sub sendDbgKdGetVersion {
#print "Sending DbgKdGetVersionApi packet\n";
my $d = "\x00" x 56;
substr( $d, 0, 4 ) = pack( "I", $DbgKdGetVersionApi );
sendManipulateStatePacket($d);
}
sub sendDbgKdWriteBreakPoint {
my $bp = hex(shift);
#print "Sending DbgKdWriteBreakPointApi packet\n";
my $d = "\x00" x 56;
substr( $d, 0, 4 ) = pack( "I", $DbgKdWriteBreakPointApi );
substr( $d, 16, 4 ) = pack( "I", $bp );
substr( $d, 20, 4 ) = pack( "I", $curbp++ );
sendManipulateStatePacket($d);
}
sub sendDbgKdRestoreBreakPoint {
my $bp = shift;
if ( defined( $breakpoints{$bp} ) ) {
#print "Sending DbgKdRestoreBreakPointApi packet\n";
my $d = "\x00" x 56;
substr( $d, 0, 4 ) = pack( "I", $DbgKdRestoreBreakPointApi );
substr( $d, 16, 4 ) = pack( "I", $breakpoints{$bp} );
sendManipulateStatePacket($d);
delete( $breakpoints{$bp} );
}
else {
print "Breakpoint not set at $bp\n";
}
}
sub sendDbgKdReadControlSpace {
#print "Sending DbgKdReadControlSpaceApi packet\n";
my $d = "\x00" x 56;
substr( $d, 0, 4 ) = pack( "I", 0x3137 );
substr( $d, 16, 4 ) = pack( "I", 0x02cc );
substr( $d, 24, 4 ) = pack( "I", 84 );
sendManipulateStatePacket($d);
}
sub sendDbgKdWriteControlSpace {
#print "Sending DbgKdWriteControlSpaceApi packet\n";
my $d = "\x00" x 56;
substr( $d, 0, 4 ) = pack( "I", 0x3138 );
substr( $d, 16, 4 ) = pack( "I", 0x02cc );
substr( $d, 24, 4 ) = pack( "I", 84 );
$d .= $controlspace;
sendManipulateStatePacket($d);
$controlspacesent = 1;
}
sub sendDbgKdGetContext {
#print "Sending DbgKdGetContextApi packet\n";
my $d = "\x00" x 56;
substr( $d, 0, 4 ) = pack( "I", $DbgKdGetContextApi );
sendManipulateStatePacket($d);
}
sub sendDbgKdSetContext {
my $ctx = shift;
#print "Sending DbgKdSetContextApi packet\n";
my $d = "\x00" x 56;
substr( $d, 0, 4 ) = pack( "I", $DbgKdSetContextApi );
substr( $d, 16, 4 ) = substr( $ctx, 0, 4 );
$d .= $ctx;
sendManipulateStatePacket($d);
}
sub sendDbgKdReadPhysicalMemory {
my $addr = shift;
my $readlen = shift;
#print "Sending DbgKdReadPhysicalMemoryApi packet\n";
my $d = "\x00" x 56;
substr( $d, 0, 4 ) = pack( "I", $DbgKdReadPhysicalMemoryApi );
substr( $d, 16, 4 ) = pack( "I", $addr );
substr( $d, 24, 4 ) = pack( "I", $readlen );
sendManipulateStatePacket($d);
}
sub sendDbgKdReadVirtualMemory {
my $vaddr = shift;
my $readlen = shift;
#print "Sending DbgKdReadVirtualMemoryApi packet\n";
my $d = "\x00" x 56;
substr( $d, 0, 4 ) = pack( "I", $DbgKdReadVirtualMemoryApi );
substr( $d, 16, 4 ) = pack( "I", $vaddr );
substr( $d, 24, 4 ) = pack( "I", $readlen );
sendManipulateStatePacket($d);
}
sub sendDbgKdWriteVirtualMemory {
my $vaddr = shift;
my $data = shift;
my $writelen = length($data);
#print "Sending DbgKdWriteVirtualMemoryApi packet\n";
my $d = "\x00" x 56;
substr( $d, 0, 4 ) = pack( "I", $DbgKdWriteVirtualMemoryApi );
substr( $d, 16, 4 ) = pack( "I", $vaddr );
substr( $d, 24, 4 ) = pack( "I", $writelen );
$d .= $data;
sendManipulateStatePacket($d);
}
sub readDword {
my $addr = shift;
#print "Reading dword at %08x\n", $addr;
my $buf = readVirtualMemory( $addr, 4 );
if ( length($buf) == 4 ) {
return unpack( "I", $buf );
}
return "failed";
}
sub readPhysicalMemory {
my $addr = shift;
my $len = shift;
my $chunksize = 0x800; # max to request in one packet
my $out;
while ( $len > 0 ) {
if ( $len < $chunksize ) {
sendDbgKdReadPhysicalMemory( $addr, $len );
my $buf = waitStateManipulate($DbgKdReadPhysicalMemoryApi);
if ( length($buf) > 56 ) {
$out .= substr( $buf, 56 );
}
$len = 0;
}
else {
sendDbgKdReadPhysicalMemory( $addr, $chunksize );
my $buf = waitStateManipulate($DbgKdReadPhysicalMemoryApi);
if ( length($buf) > 56 ) {
$out .= substr( $buf, 56 );
}
$len -= $chunksize;
$addr += $chunksize;
}
}
return $out;
}
sub writePhysicalMemory {
my $addr = shift;
my $buf = shift;
my $len = length($buf);
my $chunksize = 0x800; # max to send in one packet
my $offset = 0;
while ( $len > 0 ) {
if ( $len < $chunksize ) {
sendDbgKdWritePhysicalMemory( $addr, $buf );
waitStateManipulate(0x313e);
$len = 0;
}
else {
sendDbgKdWritePhysicalMemory( $addr,
substr( $buf, $offset, $chunksize ) );
waitStateManipulate(0x313e);
$len -= $chunksize;
$offset += $chunksize;
$addr += $chunksize;
}
}
return;
}
sub writeVirtualMemory {
my $addr = shift;
my $buf = shift;
my $len = length($buf);
return unless $addr && $len;
my $chunksize = 0x800; # max to send in one packet
my $offset = 0;
if ( $pcontext->{'pid'} == 0 ) {
while ( $len > 0 ) {
if ( $len < $chunksize ) {
sendDbgKdWriteVirtualMemory( $addr, $buf );
waitStateManipulate($DbgKdWriteVirtualMemoryApi);
$len = 0;
}
else {
sendDbgKdWriteVirtualMemory( $addr,
substr( $buf, $offset, $chunksize ) );
waitStateManipulate($DbgKdWriteVirtualMemoryApi);
$len -= $chunksize;
$offset += $chunksize;
$addr += $chunksize;
}
}
}
else {
my $distance_to_page_boundary = 0x1000 - ( $addr & 0xfff );
if ( $distance_to_page_boundary > $len ) {
my $physaddr = logical2physical($addr);
writePhysicalMemory( $physaddr, $buf );
return;
}
else {
my $physaddr = logical2physical($addr);
$buf =
writePhysicalMemory( $physaddr,
substr( $buf, 0, $distance_to_page_boundary ) );
$addr += $distance_to_page_boundary;
$offset += $distance_to_page_boundary;
my $remainder = $len - $distance_to_page_boundary;
while ( $remainder > 0 ) {
if ( $remainder < 0x1000 ) {
my $physaddr = logical2physical($addr);
writePhysicalMemory( $physaddr,
substr( $buf, $offset, $remainder ) );
$remainder = 0;
}
else {
my $physaddr = logical2physical($addr);
writePhysicalMemory( $physaddr,
substr( $buf, $offset, 0x1000 ) );
$addr += 0x1000;
$offset += 0x1000;
$remainder -= 0x1000;
}
}
return;
}
}
}
sub readVirtualMemory {
my $addr = shift;
my $len = shift;
return unless $addr && $len;
my $chunksize = 0x800; # max to request in one packet
my $out;
my $buf;
if ( $pcontext->{'pid'} == 0 ) {
while ( $len > 0 ) {
if ( $len < $chunksize ) {
sendDbgKdReadVirtualMemory( $addr, $len );
$buf = waitStateManipulate($DbgKdReadVirtualMemoryApi);
if ( length($buf) > 56 ) {
$out .= substr( $buf, 56 );
}
$len = 0;
}
else {
sendDbgKdReadVirtualMemory( $addr, $chunksize );
$buf = waitStateManipulate($DbgKdReadVirtualMemoryApi);
if ( length($buf) > 56 ) {
$out .= substr( $buf, 56 );
}
$len -= $chunksize;
$addr += $chunksize;
}
}
return $out;
}
else {
my $distance_to_page_boundary = 0x1000 - ( $addr & 0xfff );
if ( $distance_to_page_boundary > $len ) {
my $physaddr = logical2physical($addr);
return readPhysicalMemory( $physaddr, $len );
}
else {
my $physaddr = logical2physical($addr);
$buf = readPhysicalMemory( $physaddr, $distance_to_page_boundary );
$addr += $distance_to_page_boundary;
my $remainder = $len - $distance_to_page_boundary;
while ( $remainder > 0 ) {
if ( $remainder < 0x1000 ) {
my $physaddr = logical2physical($addr);
$buf .= readPhysicalMemory( $physaddr, $remainder );
$remainder = 0;
}
else {
my $physaddr = logical2physical($addr);
$buf .= readPhysicalMemory( $physaddr, 0x1000 );
$addr += 0x1000;
$remainder -= 0x1000;
}
}
return $buf;
}
}
}
sub sendDbgKdReboot {
print "Sending DbgKdRebootApi packet\n";
my $d = "\x00" x 56;
substr( $d, 0, 4 ) = pack( "I", 0x313b );
sendManipulateStatePacket($d);
}
sub waitStateManipulate {
return if $running;
my $wanted = shift;
my $quiet = shift;
my $ptype;
my $buf;
alarm($timeout);
eval {
while (1) {
($ptype, $buf) = handlePacket(1);
if ($ptype == $PACKET_TYPE_KD_STATE_MANIPULATE) {
my $api = unpack( "I", substr( $buf, 0, 4 ) );
if ($api == $wanted) {
last;
}
}
}
};
alarm(0);
if ($@) {
if ( $@ !~ /timeout/ ) {
die "Fatal: $@\n";
}
else {
printf "Timeout waiting for %04x packet reply\n", $wanted;
}
}
return $buf;
}
sub getPspCidTable {
my $pspcidtable = 0;
my $save = $pcontext;
$pcontext = \%kernelcontext; # this procedure is kernel context only
sendDbgKdGetVersion();
my $buf = waitStateManipulate($DbgKdGetVersionApi);
my $pddata = unpack( "I", substr( $buf, 48, 4 ) );
if ($pddata) {
#printf "Pointer to debugger data struct is at %08x\n", $pddata;
my $ddata = readDword($pddata);
if ( $ddata ne "failed" ) {
#printf "debugger data struct is at %08x\n", $ddata;
$pspcidtable = readDword( $ddata + 88 );
if ( $pspcidtable ne "failed" ) {
#printf "PspCidTable is %08x\n", $pspcidtable;
$pcontext = $save;
return $pspcidtable;
}
}
}
$pcontext = $save;
return 0;
}
sub getEprocess {
my $pid = shift;
my $j = ( $pid >> 18 ) & 0xff;
my $k = ( $pid >> 10 ) & 0xff;
my $l = ( $pid >> 2 ) & 0xff;
my $save = $pcontext;
$pcontext = \%kernelcontext; # this procedure is kernel context only
#print "Finding eprocess[$j][$k][$l]\n";
my $pspcidtable = getPspCidTable();
if ($pspcidtable) {
my $subtable;
if ( $version >= 6.0 ) {
$subtable = readDword($pspcidtable);
}
else {
my $table;
my $ptable = readDword($pspcidtable);
if ( $ptable ne "failed" ) {
#printf "ptable: %08x\n", $ptable;
$table = readDword( $ptable + 8 );
}
if ( $table ne "failed" ) {
#printf "table: %08x\n", $table;
$subtable = readDword( $table + ( $j * 4 ) );
}
}
if ( ($subtable) && ( $subtable ne "failed" ) ) {
#printf "subtable: %08x\n", $subtable;
my $subsubtable = readDword( $subtable + ( $k * 4 ) );
if ( $subsubtable ne "failed" ) {
#printf "subsubtable: %08x\n", $subsubtable;
my $entry = readDword( $subsubtable + ( $l * 8 ) );
if ( $entry ne "failed" ) {
if ( $version < 6 ) {
$entry |= 0x80000000; # lock bit
}
else {
$entry &= 0xfffffffe; # lock bit
}
#printf "eprocess of pid 0x%x starts at %08x\n", $pid, $entry;
$pcontext = $save;
return $entry;
}
}
}
}
$pcontext = $save;
return 0;
}
sub getProcessList {
my $ep;
my %prochash;
my ( $listoffset, $pidoffset, $nameoffset, $timeoffset );
my ( $threadoffset, $peboffset, $dtboffset );
my $save = $pcontext;
$pcontext = \%kernelcontext; # this procedure is kernel context only
if ( $version >= 6.0 ) {
# xp, vista
$ep = getEprocess(4);
$listoffset = 0x88;
$pidoffset = 0x84;
$nameoffset = 0x174;
$timeoffset = 0x70;
$threadoffset = 0x1b0;
$peboffset = 0x1b0;
$dtboffset = 0x18;
}
else {
# win2k
$ep = getEprocess(8);
$listoffset = 0xa0;
$pidoffset = 0x9c;
$nameoffset = 0x1fc;
$timeoffset = 0x88;
$threadoffset = 0x1a4;
$peboffset = 0x1b0;
$dtboffset = 0x18;
}
#printf "System ep: %08x\n", $ep;
unless ($ep) { $pcontext = $save; return }
my @procs = walkList( $ep + $listoffset, $listoffset );
for my $eproc (@procs) {
my $e = readVirtualMemory( $eproc, 0x21c );
if ( length($e) == 0x21c ) {
#print hexformat($e);
my $name = substr( $e, $nameoffset, 16 );
$name =~ s/\x00//g;
my $pid = unpack( "I", substr( $e, $pidoffset, 4 ) );
next unless ($pid) && ( $pid < 0xffff );
my $dtb = unpack( "I", substr( $e, $dtboffset, 4 ) );
my $peb = unpack( "I", substr( $e, $peboffset, 4 ) );
my $created = ft2Time( substr( $e, $timeoffset, 8 ) );
my @threads =
walkList( unpack( "I", substr( $e, 0x50, 4 ) ), $threadoffset );
if (@threads) {
$prochash{$eproc}{'pid'} = $pid;
$prochash{$eproc}{'name'} = $name;
$prochash{$eproc}{'created'} = $created;
$prochash{$eproc}{'dtb'} = $dtb;
$prochash{$eproc}{'peb'} = $peb;
@{ $prochash{$eproc}{'threads'} } = @threads;
}
}
}
$pcontext = $save;
return %prochash;
}
sub ft2Time {
my $ft = shift;
return 0 unless length($ft) == 8;
my $ch = 0x019db1de;
my $cl = 0xd53e8000;
my $lo = unpack( "I", substr( $ft, 0, 4 ) );
my $hi = unpack( "I", substr( $ft, 4, 4 ) );
return 0 if ( $hi < $ch ) || ( ( $hi == $ch ) && ( $lo < $cl ) );
return ( ( ( ( $hi * 0x10000 ) * 0x10000 ) + $lo ) -
( ( ( $ch * 0x10000 ) * 0x10000 ) + $cl ) ) / 10000000;
}
sub walkList {
my @ret;
my $flink = shift; # address of LIST_ENTRY in struct
my $offset = shift; # offset to LIST_ENTRY from beginning of struct
my $top = $flink;
while ( $flink != 0 ) {
push( @ret, $flink - $offset );
$flink = readDword($flink);
last if ( $flink == $top ) || ( $flink eq "failed" );
}
return @ret;
}
sub injectSUSShellcode {
my $title = shift;
my $message = shift;
my $userbase = 0x7ffe0800;
my $ring0base = 0xffdf0800;
my $save = $pcontext;
$pcontext = \%kernelcontext; # this procedure is kernel context only
my $messageboxa = getProcAddress( "user32.dll", "MessageBoxA" );
my $sc =
"\x6a\x00\x68\x00\x00\x00\x00\x68\x00\x00\x00\x00"
. "\x6A\x00\xE8\x00\x00\x00\x00\xc3$title\x00$message\x00";
substr( $sc, 3, 4 ) = pack( "I", $userbase + 20 );
substr( $sc, 8, 4 ) = pack( "I", $userbase + 21 + length($title) );
substr( $sc, 15, 4 ) = pack( "I", $messageboxa - ( $userbase + 19 ) );
writeVirtualMemory( $ring0base, $sc );
printf "Shellcode injected at %08x (%08x)\n", $ring0base, $userbase;
$pcontext = $save;
}
sub insertApc {
print "Searching for thread in explorer.exe\n";
my %procs = getProcessList();
my $thread;
for ( sort keys %procs ) {
my $n = lc( $procs{$_}{'name'} );
print "Found $n\n";
if ( $n eq "explorer.exe" ) {
#printf "Found explorer.exe\n";
$thread = shift( @{ $procs{$_}{'threads'} } );
last;
}
}
unless ($thread) {
print "Failed to find thread\n";
return;
}
printf "Using thread object at %08x\n", $thread;
my $kernelret = findRet();
my $shellcode = 0x7ffe0800;
my $apc = "\x00" x 48;
my $putme = 0xffdf0900;
my $save = $pcontext;
$pcontext = \%kernelcontext; # this procedure is kernel context only
substr( $apc, 0, 2 ) = pack( "S", 0x12 ); # type = Apc object
substr( $apc, 2, 2 ) = pack( "S", 0x30 ); # size of object = 48 bytes
substr( $apc, 8, 4 ) = pack( "I", $thread ); # ethread ptr
substr( $apc, 20, 4 ) = pack( "I", $kernelret ); # ret command in kernel
substr( $apc, 28, 4 ) = pack( "I", $shellcode ); # shellcode vaddr
substr( $apc, 36, 4 ) = pack( "I", $putme + 0x50 ); # system arg 1
substr( $apc, 40, 4 ) = pack( "I", $putme + 0x54 ); # system arg 2
substr( $apc, 46, 1 ) = "\x01"; # Apc mode = user mode
substr( $apc, 47, 1 ) = "\x01"; # Inserted = true (well, it will be)
printf "Built APC object for thread at %08x\n", $thread;
#print hexformat($apc);
printf "Inserting into APC list at %08x\n", $thread + 0x3c;
my $oldflink = readDword( $thread + 0x3c );
printf "Replacing old Apc flink: %08x\n", $oldflink;
if ( $oldflink ne "failed" ) {
substr( $apc, 12, 4 ) = pack( "I", $oldflink ); # flink
substr( $apc, 16, 4 ) = pack( "I", $thread + 0x3c ); # blink
# write APC object to SharedUserSpace
writeVirtualMemory( $putme, $apc );
# insert our APC into the list
writeVirtualMemory( $thread + 0x3c, pack( "I", $putme + 12 ) );
# set UserApcPending = TRUE
writeVirtualMemory( $thread + 0x4a, "\x01" );
printf "Inserted APC into thread at %08x\n", $thread;
}
else {
print "Failed to insert APC\n";
}
$pcontext = $save;
}
sub parsePE {
# Some PE parsing code borrowed from Metasploit PE module
my %pe_hdr;
my $crap;
my $base = shift;
my $data = readVirtualMemory( $base, 0x800 );
$data .= readVirtualMemory( $base + 0x800, 0x800 );
#printf "Read %d bytes of PE header at %08x\n", length($data),$base;
return unless length($data) == 0x1000;
return unless substr( $data, 0, 2 ) eq "MZ";
my $peo = unpack( "I", substr( $data, 0x3c, 4 ) );
return unless substr( $data, $peo, 2 ) eq "PE";
$pe_hdr{"MachineID"} = unpack( "S", substr( $data, $peo + 4 ) );
$pe_hdr{"NumberOfSections"} = unpack( "S", substr( $data, $peo + 6 ) );
$pe_hdr{"TimeDateStamp"} = unpack( "L", substr( $data, $peo + 8 ) );
$pe_hdr{"PointerToSymbolTable"} = unpack( "L", substr( $data, $peo + 12 ) );
$pe_hdr{"NumberOfSymbols"} = unpack( "L", substr( $data, $peo + 16 ) );
$pe_hdr{"SizeOfOptionalHeader"} = unpack( "S", substr( $data, $peo + 20 ) );
$pe_hdr{"Characteristics"} = unpack( "S", substr( $data, $peo + 22 ) );
if ( $pe_hdr{"SizeOfOptionalHeader"} < 224 ) {
return 0;
}
my $opthdr = substr( $data, $peo + 24, $pe_hdr{"SizeOfOptionalHeader"} );
$pe_hdr{"Magic "} = unpack( "S", substr( $opthdr, 0 ) );
$pe_hdr{"MajorLinker"} = unpack( "C", substr( $opthdr, 2 ) );
$pe_hdr{"MinorLinker"} = unpack( "C", substr( $opthdr, 3 ) );
$pe_hdr{"SizeOfCode"} = unpack( "L", substr( $opthdr, 4 ) );
$pe_hdr{"SizeOfInitialized"} = unpack( "L", substr( $opthdr, 8 ) );
$pe_hdr{"SizeOfUninitialized"} = unpack( "L", substr( $opthdr, 12 ) );
$pe_hdr{"EntryPoint"} = unpack( "L", substr( $opthdr, 16 ) );
$pe_hdr{"BaseOfCode"} = unpack( "L", substr( $opthdr, 20 ) );
$pe_hdr{"BaseOfData"} = unpack( "L", substr( $opthdr, 24 ) );
$pe_hdr{"ImageBase"} = unpack( "L", substr( $opthdr, 28 ) );
$pe_hdr{"SectionAlign"} = unpack( "L", substr( $opthdr, 32 ) );
$pe_hdr{"FileAlign"} = unpack( "L", substr( $opthdr, 36 ) );
$pe_hdr{"MajorOS"} = unpack( "S", substr( $opthdr, 38 ) );
$pe_hdr{"MinorOS"} = unpack( "S", substr( $opthdr, 40 ) );
$pe_hdr{"MajorImage"} = unpack( "S", substr( $opthdr, 42 ) );
$pe_hdr{"MinorImage"} = unpack( "S", substr( $opthdr, 44 ) );
$pe_hdr{"MajorSub"} = unpack( "S", substr( $opthdr, 46 ) );
$pe_hdr{"MinorSub"} = unpack( "S", substr( $opthdr, 48 ) );
$pe_hdr{"Reserved"} = unpack( "L", substr( $opthdr, 52 ) );
$pe_hdr{"SizeOfImage"} = unpack( "L", substr( $opthdr, 56 ) );
$pe_hdr{"SizeOfHeaders"} = unpack( "L", substr( $opthdr, 60 ) );
$pe_hdr{"Checksum"} = unpack( "L", substr( $opthdr, 64 ) );
$pe_hdr{"Subsystem"} = unpack( "S", substr( $opthdr, 68 ) );
$pe_hdr{"DllCharacteristics"} = unpack( "S", substr( $opthdr, 70 ) );
$pe_hdr{"SizeOfStackReserve"} = unpack( "L", substr( $opthdr, 72 ) );
$pe_hdr{"SizeOfStackCommit"} = unpack( "L", substr( $opthdr, 76 ) );
$pe_hdr{"SizeOfHeapReserve"} = unpack( "L", substr( $opthdr, 80 ) );
$pe_hdr{"SizeOfHeapCommit"} = unpack( "L", substr( $opthdr, 84 ) );
$pe_hdr{"LoaderFlags"} = unpack( "L", substr( $opthdr, 88 ) );
$pe_hdr{"NumberOfRvaAndSizes"} = unpack( "L", substr( $opthdr, 92 ) );
my @RVAMAP = qw(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, $pe_hdr{"NumberOfRvaAndSizes"} * 8 );
my %RVA;
for ( my $x = 0 ; $x < $pe_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 + $pe_hdr{"SizeOfOptionalHeader"};
my $sec_data = substr( $data, $sec_begn );
for ( my $x = 0 ; $x < $pe_hdr{"NumberOfSections"} ; $x++ ) {
my $sec_head = $sec_begn + ( $x * 40 );
my $sec_name = substr( $data, $sec_head, 8 );
$sec_name =~ s/\x00//g;
if ( $sec_name eq "" ) { $sec_name = ".sec$x" }
#my $sec_name = ".sec$x";
my $vsize = unpack( "L", substr( $data, $sec_head + 8 ) );
my $voffset = unpack( "L", substr( $data, $sec_head + 12 ) );
my $rsize = unpack( "L", substr( $data, $sec_head + 16 ) );
my $roffset = unpack( "L", substr( $data, $sec_head + 20 ) );
my $type;
if ( $voffset == $pe_hdr{"BaseOfCode"} ) { $type = "CODE" }
elsif ( $voffset == $pe_hdr{"BaseOfData"} ) { $type = "DATA" }
else { $type = "UNKNOWN" }
}
$pe_hdr{'import'} = $RVA{'import'}->[0];
$pe_hdr{'export'} = $RVA{'export'}->[0];
$pe_hdr{'importsize'} = $RVA{'import'}->[1];
$pe_hdr{'exportsize'} = $RVA{'export'}->[1];
return %pe_hdr;
}
sub getImports {
my $base = shift; # base address of module
my $ioffset = shift; # offset to import table
my $size = shift; # size of import table
my $crap;
my $imports = readVirtualMemory( $base + $ioffset, $size );
for ( my $i = 0 ; $i < $size ; $i += 20 ) {
last if substr( $imports, $i, 20 ) eq "\x00" x 20;
my $rvaILT = unpack( "L", substr( $imports, $i, 4 ) );
my $timestamp = unpack( "L", substr( $imports, $i + 4, 4 ) );
my $forwarderchain = unpack( "L", substr( $imports, $i + 8, 4 ) );
my $rvaModuleName = unpack( "L", substr( $imports, $i + 12, 4 ) );
my $rvaIAT = unpack( "L", substr( $imports, $i + 16, 4 ) );
my $modname = readVirtualMemory( $base + $rvaModuleName );
$modname =~ s/\x00.*//;
if ($rvaILT) {
my $count = 0;
my $ibuf = readVirtualMemory( $base + $rvaILT, 4 );
IGRAB: while ( $ibuf ne "\x00\x00\x00\x00" ) {
my $importthunkRVA = unpack( "L", $ibuf );
last IGRAB if $importthunkRVA == 0;
if ( $importthunkRVA & 0x8000000 ) {
printf "ORD: 0x%x\n", $importthunkRVA & ~0x80000000;
}
else {
my $importname =
readVirtualMemory( $importthunkRVA & ~0x80000000, 255 );
$importname = substr( $importname, 2 );
( $importname, $crap ) = split( /\x00/, $importname );
my $thunk = $base + $rvaIAT + ( $count * 4 );
my ( $mod, $suff ) = split( /\./, lc($modname) );
printf "%s (0x%x)\n", $importname, $thunk;
}
$count++;
$ibuf =
readVirtualMemory( $base + ( $rvaILT + $count * 4 ), 4 );
} # end while
} # end if rvaILT
else {
my $count = 0;
my $ibuf = readVirtualMemory( $base + $rvaIAT, 4 );
IGRAB: while ( $ibuf ne "\x00\x00\x00\x00" ) {
my $importthunkRVA = unpack( "L", $ibuf );
last IGRAB if $importthunkRVA == 0;
my $importname =
readVirtualMemory( $base + $importthunkRVA, 255 );
$importname = substr( $importname, 2 );
( $importname, $crap ) = split( /\x00/, $importname );
my $thunk = $base + $rvaIAT + ( $count * 4 );
my ( $mod, $suff ) = split( /\./, lc($modname) );
printf "%s (0x%x)\n", $importname, $thunk;
$count++;
$ibuf = readVirtualMemory( $base + $rvaIAT, 4 );
} # end while
} # end if rvaILT
} # end if import module
}
sub locateExportNameInTable {
my $procname = shift;
my $base = shift;
my $eoffset = shift;
my $size = shift;
my %exp = getExports( $base, $eoffset, $size );
for ( keys %exp ) {
if ( $exp{$_} eq $procname ) {
return $_;
}
}
}
sub getExports {
my $base = shift;
my $eoffset = shift;
my $size = shift;
my %exportlist;
return unless $base && $eoffset && $size;
my $exports = readVirtualMemory( $base + $eoffset, $size );
my $ebase = unpack( "I", substr( $exports, 16, 4 ) );
my $enumfuncs = unpack( "I", substr( $exports, 20, 4 ) );
my $enumnames = unpack( "I", substr( $exports, 24, 4 ) );
my $EATrva = unpack( "I", substr( $exports, 28, 4 ) );
my $ENTrva = unpack( "I", substr( $exports, 32, 4 ) );
my $EOTrva = unpack( "I", substr( $exports, 36, 4 ) );
my ( @exportnames, @exportordinals, @exportfunctions );
# get ascii name table boundaries
my $nbegin = readDword( $base + $ENTrva );
my $nend = readDword( $base + $ENTrva + ( ( $enumnames - 1 ) * 4 ) );
my $lastname = readVirtualMemory( $nend + $base, 255 );
my $term = index( $lastname, "\x00" );
$nend += $term;
my $namebuf = readVirtualMemory( $nbegin + $base, $nend - $nbegin );
#print hexasc($namebuf);
my $nametable = readVirtualMemory( $ENTrva + $base, $enumnames * 4 );
my $functable = readVirtualMemory( $EATrva + $base, $enumfuncs * 4 );
my $ordtable = readVirtualMemory( $EOTrva + $base, $enumfuncs * 2 );
for ( 0 .. $enumnames - 1 ) {
my $n = unpack( "L", substr( $nametable, $_ * 4, 4 ) );
if ( $n >= $nbegin ) {
my $ename = substr( $namebuf, $n - $nbegin, 255 );
$ename =~ s/\x00.*//g;
push( @exportnames, $ename );
#printf "Adding name index %d (begins at %08x: raw %08x) %s\n",
#$_, $n, $n-$nbegin, $ename;
}
}
for ( 0 .. $enumfuncs - 1 ) {
my $eord = unpack( "S", substr( $ordtable, $_ * 2, 2 ) );
push( @exportordinals, $eord );
}
for ( 0 .. $enumfuncs - 1 ) {
my $eaddr = unpack( "L", substr( $functable, $_ * 4, 4 ) );
push( @exportfunctions, $eaddr );
}
for my $o ( 0 .. $#exportnames ) {
my $name = $exportnames[$o];
my $ord = $exportordinals[$o];
my $addr = $exportfunctions[$ord];
$name ||= $ord;
$exportlist{ $addr + $base } = $name;
}
return %exportlist;
}
sub findRet {
my $hp;
my $pos;
my $save = $pcontext;
$pcontext = \%kernelcontext; # this procedure is kernel context only
for ( 0 .. 100 ) {
$hp = $_;
my $buf =
readVirtualMemory( $kernelbase + 0x1000 + ( $hp * 0x800 ), 0x800 );
$pos = index( $buf, "\xc3" );
last unless $pos == -1;
}
my $ret = $kernelbase + 0x1000 + ( $hp * 0x800 ) + $pos;
printf "Found RETN instruction at %08x", $ret;
$pcontext = $save;
return $ret;
}
sub logical2physical {
my $logical = shift; # a virtual address in a process
my $pdb = $pcontext->{'dtb'};
return unless $pdb;
my $offset = $logical & 0xfff; # save byte offset
my $pde = ( $logical >> 22 ) & 0x3ff;
my $pte = ( $logical >> 12 ) & 0x3ff;
my $buf = readPhysicalMemory( $pdb + ( $pde * 4 ), 4 );
my $valid = unpack( "I", $buf ) & 0x1;
if ($valid) {
my $ptb = unpack( "I", $buf ) & 0xfffff000;
#printf "Seeking to PTB %08x + PTE %03x * 4 = %08x\n", $ptb, $pte, $ptb + ($pte * 4);
$buf = readPhysicalMemory( $ptb + ( $pte * 4 ), 4 );
$valid = unpack( "I", $buf ) & 0x1;
if ($valid) {
my $phys = unpack( "I", $buf ) & 0xfffff000;
return ( $phys | $offset ); #restore byte offset
}
}
printf "Invalid PTE found for va %08x: %08x\n", $logical,
unpack( "I", $buf );
return 0;
}
sub listExports {
my $base = shift;
my %pe = parsePE($base);
if ( $pe{'export'} && $pe{'exportsize'} ) {
printf "Exports found in PE file at %08x:\n", $base;
my %exp = getExports( $base, $pe{'export'}, $pe{'exportsize'} );
for ( sort keys %exp ) {
printf "%08x:%s\n", $_, $exp{$_};
}
}
else {
print "No export table found\n";
}
}
sub getProcAddress {
my $module = shift;
my $procname = shift;
my $save = $pcontext;
my $addr;
# get eprocess list, start with bottom process
my %procs = getProcessList();
for ( sort keys %procs ) {
my $dtb = $procs{$_}{'dtb'};
my $peb = $procs{$_}{'peb'};
my $pid = $procs{$_}{'pid'};
my $eprocess = $_;
next unless $peb;
printf "Searching for %s in modules of pid %x (eprocess is %08x)\n",
$procname, $pid, $eprocess;
# set process context
$processcontext{'dtb'} = $dtb;
$processcontext{'pid'} = $pid;
$processcontext{'peb'} = $peb;
$processcontext{'eprocess'} = $eprocess;
$pcontext = \%processcontext;
my %modules = getUserModules();
for ( sort keys %modules ) {
if ( ( $modules{$_}{'name'} =~ /^$module$/i )
|| ( $modules{$_}{'name'} =~ /^$module\.dll/i ) )
{
printf "Found instance of %s at %08x\n", $module, $_;
my %pe = parsePE($_);
$addr =
locateExportNameInTable( $procname, $_, $pe{'export'},
$pe{'exportsize'} );
goto DONEGOTPROC;
}
}
}
DONEGOTPROC:
# back to original process context
$pcontext = $save;
return $addr;
}
sub getUserModules {
my %modules;
# read PEB into buf
my $peb = $pcontext->{'peb'};
my $pebdata = readVirtualMemory( $peb, 0x300 );
next unless length($pebdata) == 0x300;
# get module list
my $mptr = unpack( "I", substr( $pebdata, 0x0c, 4 ) );
my $modulelist = readDword( $mptr + 0x14, 4 );
my @modlist = walkList($modulelist);
for my $mod (@modlist) {
my $buf = readVirtualMemory( $mod, 0x34 );
if ( length($buf) == 0x34 ) {
my $base = unpack( "I", substr( $buf, 0x10, 4 ) );
next if $base == 0;
my $entry = unpack( "I", substr( $buf, 0x14, 4 ) );
my $size = unpack( "I", substr( $buf, 0x18, 4 ) );
my $path = substr( $buf, 0x1c, 8 );
my $name = substr( $buf, 0x24, 8 );
$modules{$base}{'name'} = unicodeStructToAscii($name);
$modules{$base}{'path'} = unicodeStructToAscii($path);
$modules{$base}{'size'} = $size;
$modules{$base}{'entry'} = $entry;
}
}
return %modules;
}
my $s = IO::Select->new();
$s->add( \*STDIN );
if ($serial) {
$s->add( \*FH );
} elsif ($socket) {
$s->add($socket);
}
my @ready;
sendReset();
while ( @ready = $s->can_read ) {
for my $fh (@ready) {
if ( $fh == \*STDIN ) {
my $line = <$fh>;
if ( $line =~ /break/ ) {
print "Sending break...\n";
writeDev("b");
}
elsif ( $running == 1 ) {
print "Kernel is busy (send break command)\n";
}
elsif ( $line =~ /processcontext ([0-9A-Fa-f]+)/ ) {
my $pid = hex($1);
if ( $pid == 0 ) {
$pcontext = \%kernelcontext;
print "Process context is kernel\n";
}
else {
my $eproc = getEprocess($pid);
my $dtb = readDword( $eproc + 0x18 );
my $peb = readDword( $eproc + 0x1b0 );
if ($peb) {
$processcontext{'eprocess'} = $eproc;
$processcontext{'dtb'} = $dtb;
$processcontext{'peb'} = $peb;
$processcontext{'pid'} = $pid;
$pcontext = \%processcontext;
printf "Implicit process is now %x\n", $pid;
}
else {
print "Invalid PID (PEB not found in eprocess)\n";
}
}
}
elsif ( $line =~ /getprocaddress (\S+) (\S+)/ ) {
my $dll = $1;
my $export = $2;
my $addr = getProcAddress( $dll, $export );
if ($addr) {
printf "%s!%s:%08x\n", $dll, $export, $addr;
}
else {
printf "%s!%s not found\n", $dll, $export;
}
}
elsif ( $line =~ /listexports ([0-9A-Fa-f]+)/ ) {
listExports( hex($1) );
}
elsif ( $line =~ /^logical2physical ([0-9A-Fa-f]+)/ ) {
printf "%08x -> %08x\n", hex($1), logical2physical( hex($1) );
}
elsif ( $line =~ /^parsepe ([0-9A-Fa-f]+)/ ) {
my %PE = parsePE( hex($1) );
my $compiled = localtime( $PE{"TimeDateStamp"} );
print "Compiled on $compiled\n";
}
elsif ( $line =~
/^writevirtualmemory ([0-9A-Fa-f]+) [0-9A-Fa-f][0-9A-Fa-f]/ )
{
chomp($line);
my ( $c, $addr, @bytes ) = split( /\s+/, $line );
sendDbgKdWriteVirtualMemory( hex($addr),
join( "", map { chr(hex) } @bytes ) );
}
elsif ( $line =~ /^(?:messagebox|mb)\s+(.*)\|(.*)/ ) {
my $title = $1;
my $message = $2;
injectSUSShellcode( $title, $message );
insertApc();
}
elsif ( $line =~ /^processlist|^listprocess/ ) {
print "Walking process list...\n";
my %procs = getProcessList();
for ( reverse sort keys %procs ) {
my $c = localtime( $procs{$_}{'created'} );
printf "%04x %s\n", $procs{$_}{'pid'}, $procs{$_}{'name'};
printf
"Eprocess: %08x DTB: %08x PEB: %08x Created: %s\n", $_,
$procs{$_}{'dtb'}, $procs{$_}{'peb'}, $c;
print "Threads: ";
print join( " ",
map { sprintf "%08x", $_ } @{ $procs{$_}{'threads'} } );
print "\n\n";
}
}
elsif ( $line =~ /^module|^listmodules/ ) {
my %modules;
if ( $pcontext->{'pid'} == 0 ) {
%modules = getKernelModules();
}
else {
%modules = getUserModules();
}
for ( sort keys %modules ) {
printf "%s\tPath:%s\n", $modules{$_}{'name'},
$modules{$_}{'path'};
printf "base:%08x " . "size:%08x entry:%08x\n\n", $_,
$modules{$_}{'size'}, $modules{$_}{'entry'};
}
}
elsif ( $line =~ /^findprocessbyname (\S+)/ ) {
my $name = $1;
my %procs = getProcessList();
PROCFIND:
for ( sort keys %procs ) {
my $c = localtime( $procs{$_}{'created'} );
my $n = $procs{$_}{'name'};
if ( lc($name) eq lc($n) ) {
printf "%04x %s\n", $procs{$_}{'pid'},
$procs{$_}{'name'};
printf
"Eprocess: %08x DTB: %08x PEB: %08x Created: %s\n",
$_, $procs{$_}{'dtb'}, $procs{$_}{'peb'}, $c;
print "Threads: ";
print join( " ",
map { sprintf "%08x", $_ }
@{ $procs{$_}{'threads'} } );
print "\n";
last PROCFIND;
}
}
}
elsif ( $line =~ /^eprocess ([0-9A-Fa-f]+)/ ) {
my $ep = getEprocess( hex($1) );
sendDbgKdReadVirtualMemory( $ep, 648 );
my $buf = waitStateManipulate($DbgKdReadVirtualMemoryApi);
if ( length($buf) > 56 ) {
my $eproc = substr( $buf, 56 );
if ( length($eproc) > 0x20c ) {
my $name;
if ( $version > 5 ) {
$name = substr( $eproc, 0x174, 16 );
}
else {
$name = substr( $eproc, 0x1fc, 16 );
}
$name =~ s/\x00//g;
print "Process name is $name\n";
my $next = unpack( "I", substr( $eproc, 0xa0, 4 ) );
}
}
}
elsif ( $line =~ /^bp ([0-9A-Fa-f]+)/ ) {
sendDbgKdWriteBreakPoint($1);
}
elsif ( $line =~ /^bc ([0-9A-Fa-f]+)/ ) {
sendDbgKdRestoreBreakPoint($1);
}
elsif ( $line =~ /^bl/ ) {
print "Breakpoints:\n", join( "\n", sort keys %breakpoints ),
"\n";
}
elsif ( $line =~ /^continue/ ) {
sendDbgKdContinue2();
$running = 1;
}
elsif ( $line =~ /^getpspcidtable/ ) {
getPspCidTable();
}
elsif ( $line =~ /^(autocontinue|g)$/ ) {
# get/set context to update EIP before continuing
my %context = getContext();
$context{'EIP'}++;
setContext(%context);
sendDbgKdContinue2();
$running = 1;
}
elsif ( $line =~ /^version/ ) {
printVersionData();
}
elsif ( $line =~ /^readcontrolspace/ ) {
sendDbgKdReadControlSpace();
}
elsif ( $line =~ /^writecontrolspace/ ) {
if ($controlspace) {
sendDbgKdWriteControlSpace();
}
else {
print "Haven't gotten control space yet!\n";
}
}
elsif ( $line =~ /^r (.*)=(.*)/ ) {
my $reg = $1;
my $val = $2;
my %context = getContext();
if ( length($reg) < 4 ) {
$reg = uc($reg);
}
if ( exists $context{$reg} ) {
if ( $reg eq "fp.RegisterArea" ) {
printf "Not supported yet.\n";
}
elsif ( $reg eq "leftovers" ) {
printf "Not supported.\n";
}
else {
$context{$reg} = hex($val);
setContext(%context);
%context = getContext();
printf "New value of %s is %08x\n", $reg,
$context{$reg};
}
}
else {
print "Register $reg unknown\n";
}
}
elsif ( $line =~ /^r (.*)/ ) {
my $reg = $1;
my %context = getContext();
if ( length($reg) < 4 ) {
$reg = uc($reg);
}
if ( exists $context{$reg} ) {
if ( $reg eq "fp.RegisterArea" ) {
printf "%s = \n%s\n", $reg, hexprint($reg);
}
else {
printf "%s = %08x\n", $reg, $context{$reg};
}
}
else {
print "Register $reg unknown\n";
}
}
elsif ( $line =~ /^r$|^getcontext/ ) {
my %context = getContext();
for ( sort keys %context ) {
if ( ( $_ ne "fp.RegisterArea" )
&& ( $_ ne "leftovers" ) )
{
printf "%s=%08x\n", $_, $context{$_};
}
}
# print "\n";
}
elsif ( $line =~ /^dw ([0-9A-Fa-f]+)/ ) {
printf "%08x: %08x\n", hex($1), readDword( hex($1) );
}
elsif ( $line =~ /^(?:readvirtualmem|d) ([0-9A-Fa-f]+)/ ) {
my $vaddr = $1;
my $readlen;
if ( $line =~
/(?:readvirtualmem|d) ([0-9A-Fa-f]+) ([0-9A-Fa-f]+)/ )
{
$readlen = hex($2);
}
$readlen ||= 4;
my $buf = readVirtualMemory( hex($vaddr), $readlen );
print hexasc($buf);
}
elsif ( $line =~ /^(?:readphysicalmem|dp) ([0-9A-Fa-f]+)/ ) {
my $addr = $1;
my $readlen;
if ( $line =~
/(?:readphysicalmem|dp) ([0-9A-Fa-f]+) ([0-9A-Fa-f]+)/ )
{
$readlen = hex($2);
}
$readlen ||= 4;
sendDbgKdReadPhysicalMemory( hex($addr), $readlen );
}
elsif ( $line =~ /^reboot/ ) {
sendDbgKdReboot();
}
elsif ( $line =~ /^quit|^exit/ ) {
untie *FH;
exit;
}
elsif ( $line =~ /^reset/ ) {
sendReset();
}
}
elsif ( $fh == \*FH || $fh == $socket ) {
handlePacket(0);
}
print "\n\n";
}
}