mirror of
https://github.com/samba-team/samba.git
synced 2024-12-27 03:21:53 +03:00
24fe85041f
Signed-off-by: Joe Guo <joeg@catalyst.net.nz> Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abartlet@samba.org>
414 lines
16 KiB
Python
Executable File
414 lines
16 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# Copyright Stefan Metzmacher 2011-2012
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it 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.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# This is useful to sync passwords from an AD domain.
|
|
#
|
|
# $
|
|
# $ source4/scripting/devel/repl_cleartext_pwd.py \
|
|
# -Uadministrator%A1b2C3d4 \
|
|
# 172.31.9.219 DC=bla,DC=base /tmp/cookie cleartext_utf8 131085 displayName
|
|
# # starting at usn[0]
|
|
# dn: CN=Test User1,CN=Users,DC=bla,DC=base
|
|
# cleartext_utf8: A1b2C3d4
|
|
# displayName:: VABlAHMAdAAgAFUAcwBlAHIAMQA=
|
|
#
|
|
# # up to usn[16449]
|
|
# $
|
|
# $ source4/scripting/devel/repl_cleartext_pwd.py \
|
|
# -Uadministrator%A1b2C3d4
|
|
# 172.31.9.219 DC=bla,DC=base cookie_file cleartext_utf8 131085 displayName
|
|
# # starting at usn[16449]
|
|
# # up to usn[16449]
|
|
# $
|
|
#
|
|
|
|
from __future__ import print_function
|
|
import sys
|
|
|
|
# Find right direction when running from source tree
|
|
sys.path.insert(0, "bin/python")
|
|
|
|
import samba.getopt as options
|
|
from optparse import OptionParser
|
|
|
|
from samba.dcerpc import drsuapi, drsblobs, misc
|
|
from samba.ndr import ndr_pack, ndr_unpack, ndr_print
|
|
|
|
import binascii
|
|
import hashlib
|
|
import Crypto.Cipher.ARC4
|
|
import struct
|
|
import os
|
|
|
|
from ldif import LDIFWriter
|
|
|
|
|
|
class globals:
|
|
def __init__(self):
|
|
self.global_objs = {}
|
|
self.ldif = LDIFWriter(sys.stdout)
|
|
|
|
def add_attr(self, dn, attname, vals):
|
|
if dn not in self.global_objs:
|
|
self.global_objs[dn] = {}
|
|
self.global_objs[dn][attname] = vals
|
|
|
|
def print_all(self):
|
|
for dn, obj in self.global_objs.items():
|
|
self.ldif.unparse(dn, obj)
|
|
continue
|
|
self.global_objs = {}
|
|
|
|
|
|
def attid_equal(a1, a2):
|
|
return (a1 & 0xffffffff) == (a2 & 0xffffffff)
|
|
|
|
|
|
########### main code ###########
|
|
if __name__ == "__main__":
|
|
parser = OptionParser("repl_cleartext_pwd.py [options] server dn cookie_file clear_utf8_name [attid attname attmode] [clear_utf16_name")
|
|
sambaopts = options.SambaOptions(parser)
|
|
credopts = options.CredentialsOptions(parser)
|
|
parser.add_option_group(credopts)
|
|
|
|
(opts, args) = parser.parse_args()
|
|
|
|
if len(args) == 4:
|
|
pass
|
|
elif len(args) == 7:
|
|
pass
|
|
elif len(args) >= 8:
|
|
pass
|
|
else:
|
|
parser.error("more arguments required - given=%d" % (len(args)))
|
|
|
|
server = args[0]
|
|
dn = args[1]
|
|
cookie_file = args[2]
|
|
if len(cookie_file) == 0:
|
|
cookie_file = None
|
|
clear_utf8_name = args[3]
|
|
if len(args) >= 7:
|
|
try:
|
|
attid = int(args[4], 16)
|
|
except Exception:
|
|
attid = int(args[4])
|
|
attname = args[5]
|
|
attmode = args[6]
|
|
if attmode not in ["raw", "utf8"]:
|
|
parser.error("attmode should be 'raw' or 'utf8'")
|
|
else:
|
|
attid = -1
|
|
attname = None
|
|
attmode = "raw"
|
|
if len(args) >= 8:
|
|
clear_utf16_name = args[7]
|
|
else:
|
|
clear_utf16_name = None
|
|
|
|
lp = sambaopts.get_loadparm()
|
|
creds = credopts.get_credentials(lp)
|
|
|
|
if not creds.authentication_requested():
|
|
parser.error("You must supply credentials")
|
|
|
|
gls = globals()
|
|
try:
|
|
f = open(cookie_file, 'r')
|
|
store_blob = f.read()
|
|
f.close()
|
|
|
|
store_hdr = store_blob[0:28]
|
|
(store_version,
|
|
store_dn_len, store_dn_ofs,
|
|
store_hwm_len, store_hwm_ofs,
|
|
store_utdv_len, store_utdv_ofs) = \
|
|
struct.unpack("<LLLLLLL", store_hdr)
|
|
|
|
store_dn = store_blob[store_dn_ofs:store_dn_ofs + store_dn_len]
|
|
store_hwm_blob = store_blob[store_hwm_ofs:store_hwm_ofs + store_hwm_len]
|
|
store_utdv_blob = store_blob[store_utdv_ofs:store_utdv_ofs + store_utdv_len]
|
|
|
|
store_hwm = ndr_unpack(drsuapi.DsReplicaHighWaterMark, store_hwm_blob)
|
|
store_utdv = ndr_unpack(drsblobs.replUpToDateVectorBlob, store_utdv_blob)
|
|
|
|
assert store_dn == dn
|
|
# print "%s" % ndr_print(store_hwm)
|
|
# print "%s" % ndr_print(store_utdv)
|
|
except Exception:
|
|
store_dn = dn
|
|
store_hwm = drsuapi.DsReplicaHighWaterMark()
|
|
store_hwm.tmp_highest_usn = 0
|
|
store_hwm.reserved_usn = 0
|
|
store_hwm.highest_usn = 0
|
|
store_utdv = None
|
|
|
|
binding_str = "ncacn_ip_tcp:%s[spnego,seal]" % server
|
|
|
|
drs_conn = drsuapi.drsuapi(binding_str, lp, creds)
|
|
|
|
bind_info = drsuapi.DsBindInfoCtr()
|
|
bind_info.length = 28
|
|
bind_info.info = drsuapi.DsBindInfo28()
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_BASE
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7
|
|
bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT
|
|
(info, drs_handle) = drs_conn.DsBind(misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID), bind_info)
|
|
|
|
null_guid = misc.GUID()
|
|
|
|
naming_context = drsuapi.DsReplicaObjectIdentifier()
|
|
naming_context.dn = dn
|
|
highwatermark = store_hwm
|
|
uptodateness_vector = None
|
|
if store_utdv is not None:
|
|
uptodateness_vector = drsuapi.DsReplicaCursorCtrEx()
|
|
if store_utdv.version == 1:
|
|
uptodateness_vector.cursors = store_utdv.cursors
|
|
elif store_utdv.version == 2:
|
|
cursors = []
|
|
for i in range(0, store_utdv.ctr.count):
|
|
cursor = drsuapi.DsReplicaCursor()
|
|
cursor.source_dsa_invocation_id = store_utdv.ctr.cursors[i].source_dsa_invocation_id
|
|
cursor.highest_usn = store_utdv.ctr.cursors[i].highest_usn
|
|
cursors.append(cursor)
|
|
uptodateness_vector.cursors = cursors
|
|
|
|
req8 = drsuapi.DsGetNCChangesRequest8()
|
|
|
|
req8.destination_dsa_guid = null_guid
|
|
req8.source_dsa_invocation_id = null_guid
|
|
req8.naming_context = naming_context
|
|
req8.highwatermark = highwatermark
|
|
req8.uptodateness_vector = uptodateness_vector
|
|
req8.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
|
|
drsuapi.DRSUAPI_DRS_PER_SYNC |
|
|
drsuapi.DRSUAPI_DRS_GET_ANC |
|
|
drsuapi.DRSUAPI_DRS_NEVER_SYNCED |
|
|
drsuapi.DRSUAPI_DRS_WRIT_REP)
|
|
req8.max_object_count = 402
|
|
req8.max_ndr_size = 402116
|
|
req8.extended_op = 0
|
|
req8.fsmo_info = 0
|
|
req8.partial_attribute_set = None
|
|
req8.partial_attribute_set_ex = None
|
|
req8.mapping_ctr.num_mappings = 0
|
|
req8.mapping_ctr.mappings = None
|
|
|
|
user_session_key = drs_conn.user_session_key
|
|
|
|
print("# starting at usn[%d]" % (highwatermark.highest_usn))
|
|
|
|
while True:
|
|
(level, ctr) = drs_conn.DsGetNCChanges(drs_handle, 8, req8)
|
|
if ctr.first_object is None and ctr.object_count != 0:
|
|
raise RuntimeError("DsGetNCChanges: NULL first_object with object_count=%u" % (ctr.object_count))
|
|
|
|
obj_item = ctr.first_object
|
|
while obj_item is not None:
|
|
obj = obj_item.object
|
|
|
|
if obj.identifier is None:
|
|
obj_item = obj_item.next_object
|
|
continue
|
|
|
|
# print '%s' % obj.identifier.dn
|
|
|
|
is_deleted = False
|
|
for i in range(0, obj.attribute_ctr.num_attributes):
|
|
attr = obj.attribute_ctr.attributes[i]
|
|
if attid_equal(attr.attid, drsuapi.DRSUAPI_ATTID_isDeleted):
|
|
is_deleted = True
|
|
if is_deleted:
|
|
obj_item = obj_item.next_object
|
|
continue
|
|
|
|
spl_crypt = None
|
|
attvals = None
|
|
for i in range(0, obj.attribute_ctr.num_attributes):
|
|
attr = obj.attribute_ctr.attributes[i]
|
|
if attid_equal(attr.attid, attid):
|
|
attvals = []
|
|
for j in range(0, attr.value_ctr.num_values):
|
|
assert attr.value_ctr.values[j].blob is not None
|
|
val_raw = attr.value_ctr.values[j].blob
|
|
val = None
|
|
if attmode == "utf8":
|
|
val_unicode = unicode(val_raw, 'utf-16-le')
|
|
val = val_unicode.encode('utf-8')
|
|
elif attmode == "raw":
|
|
val = val_raw
|
|
else:
|
|
assert False, "attmode[%s]" % attmode
|
|
attvals.append(val)
|
|
if not attid_equal(attr.attid, drsuapi.DRSUAPI_ATTID_supplementalCredentials):
|
|
continue
|
|
assert attr.value_ctr.num_values <= 1
|
|
if attr.value_ctr.num_values == 0:
|
|
break
|
|
assert attr.value_ctr.values[0].blob is not None
|
|
spl_crypt = attr.value_ctr.values[0].blob
|
|
|
|
if spl_crypt is None:
|
|
obj_item = obj_item.next_object
|
|
continue
|
|
|
|
assert len(spl_crypt) >= 20
|
|
confounder = spl_crypt[0:16]
|
|
enc_buffer = spl_crypt[16:]
|
|
|
|
m5 = hashlib.md5()
|
|
m5.update(user_session_key)
|
|
m5.update(confounder)
|
|
enc_key = m5.digest()
|
|
|
|
rc4 = Crypto.Cipher.ARC4.new(enc_key)
|
|
plain_buffer = rc4.decrypt(enc_buffer)
|
|
|
|
(crc32_v) = struct.unpack("<L", plain_buffer[0:4])
|
|
attr_val = plain_buffer[4:]
|
|
crc32_c = binascii.crc32(attr_val) & 0xffffffff
|
|
assert int(crc32_v[0]) == int(crc32_c), "CRC32 0x%08X != 0x%08X" % (crc32_v[0], crc32_c)
|
|
|
|
spl = ndr_unpack(drsblobs.supplementalCredentialsBlob, attr_val)
|
|
|
|
# print '%s' % ndr_print(spl)
|
|
|
|
cleartext_hex = None
|
|
|
|
for i in range(0, spl.sub.num_packages):
|
|
pkg = spl.sub.packages[i]
|
|
if pkg.name != "Primary:CLEARTEXT":
|
|
continue
|
|
cleartext_hex = pkg.data
|
|
|
|
if cleartext_hex is not None:
|
|
cleartext_utf16 = binascii.a2b_hex(cleartext_hex)
|
|
if clear_utf16_name is not None:
|
|
gls.add_attr(obj.identifier.dn, clear_utf16_name, [cleartext_utf16])
|
|
try:
|
|
cleartext_unicode = unicode(cleartext_utf16, 'utf-16-le')
|
|
cleartext_utf8 = cleartext_unicode.encode('utf-8')
|
|
gls.add_attr(obj.identifier.dn, clear_utf8_name, [cleartext_utf8])
|
|
except Exception:
|
|
pass
|
|
|
|
if attvals is not None:
|
|
gls.add_attr(obj.identifier.dn, attname, attvals)
|
|
|
|
krb5_old_hex = None
|
|
|
|
for i in range(0, spl.sub.num_packages):
|
|
pkg = spl.sub.packages[i]
|
|
if pkg.name != "Primary:Kerberos":
|
|
continue
|
|
krb5_old_hex = pkg.data
|
|
|
|
if krb5_old_hex is not None:
|
|
krb5_old_raw = binascii.a2b_hex(krb5_old_hex)
|
|
krb5_old = ndr_unpack(drsblobs.package_PrimaryKerberosBlob, krb5_old_raw, allow_remaining=True)
|
|
|
|
# print '%s' % ndr_print(krb5_old)
|
|
|
|
krb5_new_hex = None
|
|
|
|
for i in range(0, spl.sub.num_packages):
|
|
pkg = spl.sub.packages[i]
|
|
if pkg.name != "Primary:Kerberos-Newer-Keys":
|
|
continue
|
|
krb5_new_hex = pkg.data
|
|
|
|
if krb5_new_hex is not None:
|
|
krb5_new_raw = binascii.a2b_hex(krb5_new_hex)
|
|
krb5_new = ndr_unpack(drsblobs.package_PrimaryKerberosBlob, krb5_new_raw, allow_remaining=True)
|
|
|
|
# print '%s' % ndr_print(krb5_new)
|
|
|
|
obj_item = obj_item.next_object
|
|
|
|
gls.print_all()
|
|
|
|
if ctr.more_data == 0:
|
|
store_hwm = ctr.new_highwatermark
|
|
|
|
store_utdv = drsblobs.replUpToDateVectorBlob()
|
|
store_utdv.version = ctr.uptodateness_vector.version
|
|
store_utdv_ctr = store_utdv.ctr
|
|
store_utdv_ctr.count = ctr.uptodateness_vector.count
|
|
store_utdv_ctr.cursors = ctr.uptodateness_vector.cursors
|
|
store_utdv.ctr = store_utdv_ctr
|
|
|
|
# print "%s" % ndr_print(store_hwm)
|
|
# print "%s" % ndr_print(store_utdv)
|
|
|
|
store_hwm_blob = ndr_pack(store_hwm)
|
|
store_utdv_blob = ndr_pack(store_utdv)
|
|
|
|
#
|
|
# uint32_t version '1'
|
|
# uint32_t dn_str_len
|
|
# uint32_t dn_str_ofs
|
|
# uint32_t hwm_blob_len
|
|
# uint32_t hwm_blob_ofs
|
|
# uint32_t utdv_blob_len
|
|
# uint32_t utdv_blob_ofs
|
|
store_hdr_len = 7 * 4
|
|
dn_ofs = store_hdr_len
|
|
hwm_ofs = dn_ofs + len(dn)
|
|
utdv_ofs = hwm_ofs + len(store_hwm_blob)
|
|
store_blob = struct.pack("<LLLLLLL", 1,
|
|
len(dn), dn_ofs,
|
|
len(store_hwm_blob), hwm_ofs,
|
|
len(store_utdv_blob), utdv_ofs) + \
|
|
dn + store_hwm_blob + store_utdv_blob
|
|
|
|
tmp_file = "%s.tmp" % cookie_file
|
|
f = open(tmp_file, 'wb')
|
|
f.write(store_blob)
|
|
f.close()
|
|
os.rename(tmp_file, cookie_file)
|
|
|
|
print("# up to usn[%d]" % (ctr.new_highwatermark.highest_usn))
|
|
break
|
|
print("# up to tmp_usn[%d]" % (ctr.new_highwatermark.highest_usn))
|
|
req8.highwatermark = ctr.new_highwatermark
|