# https://github.com/CoreSecurity/impacket/blob/master/examples/secretsdump.py
# https://github.com/byt3bl33d3r/CrackMapExec

from samba.credentials import Credentials, DONT_USE_KERBEROS
from samba import gensec, Ldb, drs_utils
from samba.param import LoadParm
from samba.dcerpc import drsuapi, misc, drsblobs, security
from binascii import unhexlify, hexlify
import hashlib
from Crypto.Cipher import ARC4, DES
from struct import pack
import samba.dcerpc.security
import Crypto.Cipher.ARC4
from samba.ndr import ndr_pack, ndr_unpack, ndr_print
import binascii

from samba.samdb import SamDB
from samba.auth import system_session
import ldb


# rpcclient  -U administrator%Univention.98  10.200.7.132
# setuserinfo win1 18 univention

samname = "Administrator"
acct_pass = "Univention.98"
domain = "w2k12"
user = "win4"
#user = "Administrator"
server = "10.200.7.132"

lp = LoadParm()

repl_creds = Credentials()
repl_creds.guess(lp)
repl_creds.set_kerberos_state(DONT_USE_KERBEROS)
repl_creds.set_username(samname)
repl_creds.set_password(acct_pass)


binding_options = "seal,print"
drs, drsuapi_handle, bind_supported_extensions = drs_utils.drsuapi_connect(server, lp, repl_creds)




def decrypt(key, data, rid):
	salt = data[0:16]
	check_sum = data[16:]
	md5 = hashlib.new('md5')
	md5.update(key)
	md5.update(salt)
	finalMD5 = md5.digest()
	cipher = ARC4.new(finalMD5)
	plainText = cipher.decrypt(data[16:])
	hash = removeDESLayer(plainText[4:], rid)
	return hexlify(hash)

def transformKey(InputKey):
	# Section 2.2.11.1.2 Encrypting a 64-Bit Block with a 7-Byte Key
	OutputKey = []
	OutputKey.append( chr(ord(InputKey[0]) >> 0x01) )
	OutputKey.append( chr(((ord(InputKey[0])&0x01)<<6) | (ord(InputKey[1])>>2)) )
	OutputKey.append( chr(((ord(InputKey[1])&0x03)<<5) | (ord(InputKey[2])>>3)) )
	OutputKey.append( chr(((ord(InputKey[2])&0x07)<<4) | (ord(InputKey[3])>>4)) )
	OutputKey.append( chr(((ord(InputKey[3])&0x0F)<<3) | (ord(InputKey[4])>>5)) )
	OutputKey.append( chr(((ord(InputKey[4])&0x1F)<<2) | (ord(InputKey[5])>>6)) )
	OutputKey.append( chr(((ord(InputKey[5])&0x3F)<<1) | (ord(InputKey[6])>>7)) )
	OutputKey.append( chr(ord(InputKey[6]) & 0x7F) )
	
	for i in range(8):
	    OutputKey[i] = chr((ord(OutputKey[i]) << 1) & 0xfe)
	
	return "".join(OutputKey)

def deriveKey(baseKey):
	# 2.2.11.1.3 Deriving Key1 and Key2 from a Little-Endian, Unsigned Integer Key
	# Let I be the little-endian, unsigned integer.
	# Let I[X] be the Xth byte of I, where I is interpreted as a zero-base-index array of bytes.
	# Note that because I is in little-endian byte order, I[0] is the least significant byte.
	# Key1 is a concatenation of the following values: I[0], I[1], I[2], I[3], I[0], I[1], I[2].
	# Key2 is a concatenation of the following values: I[3], I[0], I[1], I[2], I[3], I[0], I[1]
	key = pack('<L',baseKey)
	key1 = key[0] + key[1] + key[2] + key[3] + key[0] + key[1] + key[2]
	key2 = key[3] + key[0] + key[1] + key[2] + key[3] + key[0] + key[1]
	return transformKey(key1),transformKey(key2)

def removeDESLayer(cryptedHash, rid):
	Key1,Key2 = deriveKey(rid)
	Crypt1 = DES.new(Key1, DES.MODE_ECB)
	Crypt2 = DES.new(Key2, DES.MODE_ECB)
	decryptedHash = Crypt1.decrypt(cryptedHash[:8]) + Crypt2.decrypt(cryptedHash[8:])
	return decryptedHash

dcinfo = drsuapi.DsGetDCInfoRequest1()
dcinfo.level = 1
dcinfo.domain_name = domain
i, o = drs.DsGetDomainControllerInfo(drsuapi_handle, 1, dcinfo)
computer_dn = o.array[0].computer_dn

req = drsuapi.DsNameRequest1()
names = drsuapi.DsNameString()
names.str = '%s\%s' % (domain, user)
req.format_offered = drsuapi.DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT
req.format_desired = drsuapi.DRSUAPI_DS_NAME_FORMAT_FQDN_1779
req.count = 1
req.names = [names]
i, o = drs.DsCrackNames(drsuapi_handle, 1, req)
user_dn = o.array[0].result_name

req = drsuapi.DsNameRequest1()
names = drsuapi.DsNameString()
names.str = computer_dn
req.format_offered = drsuapi.DRSUAPI_DS_NAME_FORMAT_FQDN_1779
req.format_desired = drsuapi.DRSUAPI_DS_NAME_FORMAT_GUID
req.count = 1
req.names = [names]
i, o = drs.DsCrackNames(drsuapi_handle, 1, req)
source_dsa_guid = o.array[0].result_name
guid = source_dsa_guid.replace('{', '').replace('}', '').encode('utf8')

#guid = "e121f416-5309-4b2a-83ee-2f04593951d5"
#user_dn = "CN=win1,CN=Users,DC=w2k12,DC=test"

req8 = drsuapi.DsGetNCChangesRequest8()
req8.destination_dsa_guid = misc.GUID(guid)
req8.source_dsa_invocation_id = misc.GUID(guid)
req8.naming_context = drsuapi.DsReplicaObjectIdentifier()
req8.naming_context.dn = user_dn
req8.replica_flags = 0
req8.max_object_count = 402
req8.max_ndr_size = 402116
req8.extended_op = drsuapi.DRSUAPI_EXOP_REPL_SECRET
req8.fsmo_info = 0

while True:
	(level, ctr) = drs.DsGetNCChanges(drsuapi_handle, 8, req8)
	rid = None
	unicode_blob = None
	if ctr.first_object is None:
		break
	for i in ctr.first_object.object.attribute_ctr.attributes:
		if str(i.attid) == "589970":
		# DRSUAPI_ATTID_objectSid
			if i.value_ctr.values:
				for j in i.value_ctr.values:
					sid = ndr_unpack(security.dom_sid, j.blob)
					_tmp, rid = sid.split()
		if str(i.attid) == "589914":
		# DRSUAPI_ATTID_unicodePwd
			if i.value_ctr.values:
				for j in i.value_ctr.values:
					unicode_blob = j.blob
	if rid and unicode_blob:
		nt_hash = decrypt(drs.user_session_key, unicode_blob, rid)
		print nt_hash

	if ctr.more_data == 0:
		break
