mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
967 lines
28 KiB
Python
967 lines
28 KiB
Python
# Dispatch for various request types.
|
|
#
|
|
# Copyright (C) Catalyst IT Ltd. 2017
|
|
#
|
|
# 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/>.
|
|
#
|
|
import os
|
|
import ctypes
|
|
import random
|
|
|
|
from samba.net import Net
|
|
from samba.dcerpc import security, drsuapi, nbt, lsa, netlogon, ntlmssp
|
|
from samba.dcerpc.netlogon import netr_WorkstationInformation
|
|
from samba.dcerpc.security import dom_sid
|
|
from samba.netbios import Node
|
|
from samba.ndr import ndr_pack
|
|
from samba.credentials import (
|
|
CLI_CRED_NTLMv2_AUTH,
|
|
MUST_USE_KERBEROS,
|
|
DONT_USE_KERBEROS
|
|
)
|
|
from samba import NTSTATUSError
|
|
from samba.ntstatus import (
|
|
NT_STATUS_OBJECT_NAME_NOT_FOUND,
|
|
NT_STATUS_NO_SUCH_DOMAIN
|
|
)
|
|
import samba
|
|
samba.ensure_third_party_module("dns", "dnspython")
|
|
import dns.resolver
|
|
|
|
def uint32(v):
|
|
return ctypes.c_uint32(v).value
|
|
|
|
|
|
def check_runtime_error(runtime, val):
|
|
if runtime is None:
|
|
return False
|
|
|
|
err32 = uint32(runtime.args[0])
|
|
if err32 == val:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
name_formats = [
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_FQDN_1779,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_DISPLAY,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_GUID,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_CANONICAL,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_DNS_DOMAIN,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_UPN_AND_ALTSECID,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT_NAME_SANS_DOMAIN_EX,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_LIST_GLOBAL_CATALOG_SERVERS,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_UPN_FOR_LOGON,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_LIST_SERVERS_WITH_DCS_IN_SITE,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_STRING_SID_NAME,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_ALT_SECURITY_IDENTITIES_NAME,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_LIST_NCS,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_LIST_DOMAINS,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_MAP_SCHEMA_GUID,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT_NAME_SANS_DOMAIN,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_LIST_ROLES,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_LIST_INFO_FOR_SERVER,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_LIST_SERVERS_FOR_DOMAIN_IN_SITE,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_LIST_DOMAINS_IN_SITE,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_LIST_SERVERS_IN_SITE,
|
|
drsuapi.DRSUAPI_DS_NAME_FORMAT_LIST_SITES,
|
|
]
|
|
|
|
|
|
def warning(message):
|
|
print("\033[37;41;1m" "Warning: %s" "\033[00m" % (message))
|
|
|
|
###############################################################################
|
|
#
|
|
# Packet generation functions:
|
|
#
|
|
# All the packet generation functions have the following form:
|
|
# packet_${protocol}_${opcode}(packet, conversation, context)
|
|
#
|
|
# The functions return true, if statistics should be collected for the packet
|
|
# false, the packet has been ignored.
|
|
#
|
|
# Where:
|
|
# protocol is the protocol, i.e. cldap, dcerpc, ...
|
|
# opcode is the protocol op code i.e. type of the packet to be
|
|
# generated.
|
|
#
|
|
# packet contains data about the captured/generated packet
|
|
# provides any extra data needed to generate the packet
|
|
#
|
|
# conversation Details of the current client/server interaction
|
|
#
|
|
# context state data for the current interaction
|
|
#
|
|
#
|
|
#
|
|
# The following protocols are not currently handled:
|
|
# smb
|
|
# smb2
|
|
# browser
|
|
# smb_netlogon
|
|
#
|
|
# The following drsuapi replication packets are currently ignored:
|
|
# DsReplicaSync
|
|
# DsGetNCChanges
|
|
# DsReplicaUpdateRefs
|
|
|
|
|
|
# Packet generators that do NOTHING are assigned to the null_packet
|
|
# function which allows the conversation generators to notice this and
|
|
# avoid a whole lot of pointless work.
|
|
def null_packet(packet, conversation, context):
|
|
return False
|
|
|
|
|
|
def packet_cldap_3(packet, conversation, context):
|
|
# searchRequest
|
|
net = Net(creds=context.creds, lp=context.lp)
|
|
net.finddc(domain=context.lp.get('realm'),
|
|
flags=(nbt.NBT_SERVER_LDAP |
|
|
nbt.NBT_SERVER_DS |
|
|
nbt.NBT_SERVER_WRITABLE))
|
|
return True
|
|
|
|
|
|
packet_cldap_5 = null_packet
|
|
# searchResDone
|
|
|
|
packet_dcerpc_0 = null_packet
|
|
# Request
|
|
# Can be ignored, it's the continuation of an existing conversation
|
|
|
|
packet_dcerpc_2 = null_packet
|
|
# Request
|
|
# Server response, so should be ignored
|
|
|
|
packet_dcerpc_3 = null_packet
|
|
|
|
packet_dcerpc_11 = null_packet
|
|
# Bind
|
|
# creation of the rpc dcerpc connection is managed by the higher level
|
|
# protocol drivers. So we ignore it when generating traffic
|
|
|
|
|
|
packet_dcerpc_12 = null_packet
|
|
# Bind_ack
|
|
# Server response, so should be ignored
|
|
|
|
|
|
packet_dcerpc_13 = null_packet
|
|
# Bind_nak
|
|
# Server response, so should be ignored
|
|
|
|
|
|
packet_dcerpc_14 = null_packet
|
|
# Alter_context
|
|
# Generated as part of the connect process
|
|
|
|
|
|
def packet_dcerpc_15(packet, conversation, context):
|
|
# Alter_context_resp
|
|
# This means it was GSSAPI/krb5 (probably)
|
|
# Check the kerberos_state and issue a diagnostic if kerberos not enabled
|
|
if context.user_creds.get_kerberos_state() == DONT_USE_KERBEROS:
|
|
warning("Kerberos disabled but have dcerpc Alter_context_resp "
|
|
"indicating Kerberos was used")
|
|
return False
|
|
|
|
|
|
def packet_dcerpc_16(packet, conversation, context):
|
|
# AUTH3
|
|
# This means it was NTLMSSP
|
|
# Check the kerberos_state and issue a diagnostic if kerberos enabled
|
|
if context.user_creds.get_kerberos_state() == MUST_USE_KERBEROS:
|
|
warning("Kerberos enabled but have dcerpc AUTH3 "
|
|
"indicating NTLMSSP was used")
|
|
return False
|
|
|
|
|
|
def packet_dns_0(packet, conversation, context):
|
|
# query
|
|
name, rtype = context.guess_a_dns_lookup()
|
|
dns.resolver.query(name, rtype)
|
|
return True
|
|
|
|
|
|
packet_dns_1 = null_packet
|
|
# response
|
|
# Server response, so should be ignored
|
|
|
|
|
|
def packet_drsuapi_0(packet, conversation, context):
|
|
# DsBind
|
|
context.get_drsuapi_connection_pair(True)
|
|
return True
|
|
|
|
|
|
NAME_FORMATS = [getattr(drsuapi, _x) for _x in dir(drsuapi)
|
|
if 'NAME_FORMAT' in _x]
|
|
|
|
|
|
def packet_drsuapi_12(packet, conversation, context):
|
|
# DsCrackNames
|
|
drs, handle = context.get_drsuapi_connection_pair()
|
|
|
|
names = drsuapi.DsNameString()
|
|
names.str = context.server
|
|
|
|
req = drsuapi.DsNameRequest1()
|
|
req.format_flags = 0
|
|
req.format_offered = 7
|
|
req.format_desired = random.choice(name_formats)
|
|
req.codepage = 1252
|
|
req.language = 1033 # German, I think
|
|
req.format_flags = 0
|
|
req.count = 1
|
|
req.names = [names]
|
|
|
|
(result, ctr) = drs.DsCrackNames(handle, 1, req)
|
|
return True
|
|
|
|
|
|
def packet_drsuapi_13(packet, conversation, context):
|
|
# DsWriteAccountSpn
|
|
req = drsuapi.DsWriteAccountSpnRequest1()
|
|
req.operation = drsuapi.DRSUAPI_DS_SPN_OPERATION_REPLACE
|
|
req.unknown1 = 0 # Unused, must be 0
|
|
req.object_dn = context.user_dn
|
|
req.count = 1 # only 1 name
|
|
spn_name = drsuapi.DsNameString()
|
|
spn_name.str = 'foo/{}'.format(context.username)
|
|
req.spn_names = [spn_name]
|
|
(drs, handle) = context.get_drsuapi_connection_pair()
|
|
(level, res) = drs.DsWriteAccountSpn(handle, 1, req)
|
|
return True
|
|
|
|
|
|
def packet_drsuapi_1(packet, conversation, context):
|
|
# DsUnbind
|
|
(drs, handle) = context.get_drsuapi_connection_pair()
|
|
drs.DsUnbind(handle)
|
|
del context.drsuapi_connections[-1]
|
|
return True
|
|
|
|
|
|
packet_drsuapi_2 = null_packet
|
|
# DsReplicaSync
|
|
# This is between DCs, triggered on a DB change
|
|
# Ignoring for now
|
|
|
|
|
|
packet_drsuapi_3 = null_packet
|
|
# DsGetNCChanges
|
|
# This is between DCs, trigger with DB operation,
|
|
# or DsReplicaSync between DCs.
|
|
# Ignoring for now
|
|
|
|
|
|
packet_drsuapi_4 = null_packet
|
|
# DsReplicaUpdateRefs
|
|
# Ignoring for now
|
|
|
|
|
|
packet_epm_3 = null_packet
|
|
# Map
|
|
# Will be generated by higher level protocol calls
|
|
|
|
|
|
def packet_kerberos_(packet, conversation, context):
|
|
# Use the presence of kerberos packets as a hint to enable kerberos
|
|
# for the rest of the conversation.
|
|
# i.e. kerberos packets are not explicitly generated.
|
|
context.user_creds.set_kerberos_state(MUST_USE_KERBEROS)
|
|
context.user_creds_bad.set_kerberos_state(MUST_USE_KERBEROS)
|
|
context.machine_creds.set_kerberos_state(MUST_USE_KERBEROS)
|
|
context.machine_creds_bad.set_kerberos_state(MUST_USE_KERBEROS)
|
|
context.creds.set_kerberos_state(MUST_USE_KERBEROS)
|
|
return False
|
|
|
|
|
|
packet_ldap_ = null_packet
|
|
# Unknown
|
|
# The ldap payload was probably encrypted so just ignore it.
|
|
|
|
|
|
def packet_ldap_0(packet, conversation, context):
|
|
# bindRequest
|
|
if packet.extra[5] == "simple":
|
|
# Perform a simple bind.
|
|
context.get_ldap_connection(new=True, simple=True)
|
|
else:
|
|
# Perform a sasl bind.
|
|
context.get_ldap_connection(new=True, simple=False)
|
|
return True
|
|
|
|
|
|
packet_ldap_1 = null_packet
|
|
# bindResponse
|
|
# Server response ignored for traffic generation
|
|
|
|
|
|
def packet_ldap_2(packet, conversation, context):
|
|
# unbindRequest
|
|
# pop the last one off -- most likely we're in a bind/unbind ping.
|
|
del context.ldap_connections[-1:]
|
|
return False
|
|
|
|
|
|
def packet_ldap_3(packet, conversation, context):
|
|
# searchRequest
|
|
|
|
(scope, dn_sig, filter, attrs, extra, desc, oid) = packet.extra
|
|
if not scope:
|
|
scope = 0
|
|
|
|
samdb = context.get_ldap_connection()
|
|
dn = context.get_matching_dn(dn_sig)
|
|
|
|
samdb.search(dn,
|
|
scope=int(scope),
|
|
attrs=attrs.split(','),
|
|
controls=["paged_results:1:1000"])
|
|
return True
|
|
|
|
|
|
packet_ldap_4 = null_packet
|
|
# searchResEntry
|
|
# Server response ignored for traffic generation
|
|
|
|
|
|
packet_ldap_5 = null_packet
|
|
# Server response ignored for traffic generation
|
|
|
|
packet_ldap_6 = null_packet
|
|
|
|
packet_ldap_7 = null_packet
|
|
|
|
packet_ldap_8 = null_packet
|
|
|
|
packet_ldap_9 = null_packet
|
|
|
|
packet_ldap_16 = null_packet
|
|
|
|
packet_lsarpc_0 = null_packet
|
|
# lsarClose
|
|
|
|
packet_lsarpc_1 = null_packet
|
|
# lsarDelete
|
|
|
|
packet_lsarpc_2 = null_packet
|
|
# lsarEnumeratePrivileges
|
|
|
|
packet_lsarpc_3 = null_packet
|
|
# LsarQuerySecurityObject
|
|
|
|
packet_lsarpc_4 = null_packet
|
|
# LsarSetSecurityObject
|
|
|
|
packet_lsarpc_5 = null_packet
|
|
# LsarChangePassword
|
|
|
|
packet_lsarpc_6 = null_packet
|
|
# lsa_OpenPolicy
|
|
# We ignore this, but take it as a hint that the lsarpc handle should
|
|
# be over a named pipe.
|
|
#
|
|
|
|
|
|
def packet_lsarpc_14(packet, conversation, context):
|
|
# lsa_LookupNames
|
|
c = context.get_lsarpc_named_pipe_connection()
|
|
|
|
objectAttr = lsa.ObjectAttribute()
|
|
pol_handle = c.OpenPolicy2(u'', objectAttr,
|
|
security.SEC_FLAG_MAXIMUM_ALLOWED)
|
|
|
|
sids = lsa.TransSidArray()
|
|
names = [lsa.String("This Organization"),
|
|
lsa.String("Digest Authentication")]
|
|
level = lsa.LSA_LOOKUP_NAMES_ALL
|
|
count = 0
|
|
c.LookupNames(pol_handle, names, sids, level, count)
|
|
return True
|
|
|
|
|
|
def packet_lsarpc_15(packet, conversation, context):
|
|
# lsa_LookupSids
|
|
c = context.get_lsarpc_named_pipe_connection()
|
|
|
|
objectAttr = lsa.ObjectAttribute()
|
|
pol_handle = c.OpenPolicy2(u'', objectAttr,
|
|
security.SEC_FLAG_MAXIMUM_ALLOWED)
|
|
|
|
sids = lsa.SidArray()
|
|
sid = lsa.SidPtr()
|
|
|
|
x = dom_sid("S-1-5-7")
|
|
sid.sid = x
|
|
sids.sids = [sid]
|
|
sids.num_sids = 1
|
|
names = lsa.TransNameArray()
|
|
level = lsa.LSA_LOOKUP_NAMES_ALL
|
|
count = 0
|
|
|
|
c.LookupSids(pol_handle, sids, names, level, count)
|
|
return True
|
|
|
|
|
|
def packet_lsarpc_39(packet, conversation, context):
|
|
# lsa_QueryTrustedDomainInfoBySid
|
|
# Samba does not support trusted domains, so this call is expected to fail
|
|
#
|
|
c = context.get_lsarpc_named_pipe_connection()
|
|
|
|
objectAttr = lsa.ObjectAttribute()
|
|
|
|
pol_handle = c.OpenPolicy2(u'', objectAttr,
|
|
security.SEC_FLAG_MAXIMUM_ALLOWED)
|
|
|
|
domsid = security.dom_sid(context.domain_sid)
|
|
level = 1
|
|
try:
|
|
c.QueryTrustedDomainInfoBySid(pol_handle, domsid, level)
|
|
except NTSTATUSError as error:
|
|
# Object Not found is the expected result from samba,
|
|
# while No Such Domain is the expected result from windows,
|
|
# anything else is a failure.
|
|
if not check_runtime_error(error, NT_STATUS_OBJECT_NAME_NOT_FOUND) \
|
|
and not check_runtime_error(error, NT_STATUS_NO_SUCH_DOMAIN):
|
|
raise
|
|
return True
|
|
|
|
|
|
packet_lsarpc_40 = null_packet
|
|
# lsa_SetTrustedDomainInfo
|
|
# Not currently supported
|
|
|
|
|
|
packet_lsarpc_43 = null_packet
|
|
# LsaStorePrivateData
|
|
|
|
|
|
packet_lsarpc_44 = null_packet
|
|
# LsaRetrievePrivateData
|
|
|
|
|
|
packet_lsarpc_68 = null_packet
|
|
# LsarLookupNames3
|
|
|
|
|
|
def packet_lsarpc_76(packet, conversation, context):
|
|
# lsa_LookupSids3
|
|
c = context.get_lsarpc_connection()
|
|
sids = lsa.SidArray()
|
|
sid = lsa.SidPtr()
|
|
# Need a set
|
|
x = dom_sid("S-1-5-7")
|
|
sid.sid = x
|
|
sids.sids = [sid]
|
|
sids.num_sids = 1
|
|
names = lsa.TransNameArray2()
|
|
level = lsa.LSA_LOOKUP_NAMES_ALL
|
|
count = 0
|
|
lookup_options = lsa.LSA_LOOKUP_OPTION_SEARCH_ISOLATED_NAMES
|
|
client_revision = lsa.LSA_CLIENT_REVISION_2
|
|
c.LookupSids3(sids, names, level, count, lookup_options, client_revision)
|
|
return True
|
|
|
|
|
|
def packet_lsarpc_77(packet, conversation, context):
|
|
# lsa_LookupNames4
|
|
c = context.get_lsarpc_connection()
|
|
sids = lsa.TransSidArray3()
|
|
names = [lsa.String("This Organization"),
|
|
lsa.String("Digest Authentication")]
|
|
level = lsa.LSA_LOOKUP_NAMES_ALL
|
|
count = 0
|
|
lookup_options = lsa.LSA_LOOKUP_OPTION_SEARCH_ISOLATED_NAMES
|
|
client_revision = lsa.LSA_CLIENT_REVISION_2
|
|
c.LookupNames4(names, sids, level, count, lookup_options, client_revision)
|
|
return True
|
|
|
|
|
|
def packet_nbns_0(packet, conversation, context):
|
|
# query
|
|
n = Node()
|
|
try:
|
|
n.query_name("ANAME", context.server, timeout=4, broadcast=False)
|
|
except:
|
|
pass
|
|
return True
|
|
|
|
|
|
packet_nbns_1 = null_packet
|
|
# response
|
|
# Server response, not generated by the client
|
|
|
|
|
|
packet_rpc_netlogon_0 = null_packet
|
|
|
|
packet_rpc_netlogon_1 = null_packet
|
|
|
|
packet_rpc_netlogon_4 = null_packet
|
|
# NetrServerReqChallenge
|
|
# generated by higher level protocol drivers
|
|
# ignored for traffic generation
|
|
|
|
packet_rpc_netlogon_14 = null_packet
|
|
|
|
packet_rpc_netlogon_15 = null_packet
|
|
|
|
packet_rpc_netlogon_21 = null_packet
|
|
# NetrLogonDummyRoutine1
|
|
# Used to determine security settings. Triggered from schannel setup
|
|
# So no need for an explicit generator
|
|
|
|
|
|
packet_rpc_netlogon_26 = null_packet
|
|
# NetrServerAuthenticate3
|
|
# Triggered from schannel set up, no need for an explicit generator
|
|
|
|
|
|
def packet_rpc_netlogon_29(packet, conversation, context):
|
|
# NetrLogonGetDomainInfo [531]
|
|
c = context.get_netlogon_connection()
|
|
(auth, succ) = context.get_authenticator()
|
|
query = netr_WorkstationInformation()
|
|
|
|
c.netr_LogonGetDomainInfo(context.server,
|
|
context.netbios_name,
|
|
auth,
|
|
succ,
|
|
2, # TODO are there other values?
|
|
query)
|
|
return True
|
|
|
|
|
|
def packet_rpc_netlogon_30(packet, conversation, context):
|
|
# NetrServerPasswordSet2
|
|
c = context.get_netlogon_connection()
|
|
(auth, succ) = context.get_authenticator()
|
|
DATA_LEN = 512
|
|
# Set the new password to the existing password, this generates the same
|
|
# work load as a new value, and leaves the account password intact for
|
|
# subsequent runs
|
|
newpass = context.machine_creds.get_password().encode('utf-16-le')
|
|
pwd_len = len(newpass)
|
|
filler = [ord(x) for x in os.urandom(DATA_LEN - pwd_len)]
|
|
pwd = netlogon.netr_CryptPassword()
|
|
pwd.length = pwd_len
|
|
pwd.data = filler + [ord(x) for x in newpass]
|
|
context.machine_creds.encrypt_netr_crypt_password(pwd)
|
|
c.netr_ServerPasswordSet2(context.server,
|
|
# must ends with $, so use get_username instead
|
|
# of get_workstation here
|
|
context.machine_creds.get_username(),
|
|
context.machine_creds.get_secure_channel_type(),
|
|
context.netbios_name,
|
|
auth,
|
|
pwd)
|
|
return True
|
|
|
|
|
|
packet_rpc_netlogon_34 = null_packet
|
|
|
|
|
|
def packet_rpc_netlogon_39(packet, conversation, context):
|
|
# NetrLogonSamLogonEx [4331]
|
|
def connect(creds):
|
|
c = context.get_netlogon_connection()
|
|
|
|
# Disable Kerberos in cli creds to extract NTLM response
|
|
old_state = creds.get_kerberos_state()
|
|
creds.set_kerberos_state(DONT_USE_KERBEROS)
|
|
|
|
logon = samlogon_logon_info(context.domain,
|
|
context.netbios_name,
|
|
creds)
|
|
logon_level = netlogon.NetlogonNetworkTransitiveInformation
|
|
validation_level = netlogon.NetlogonValidationSamInfo4
|
|
netr_flags = 0
|
|
c.netr_LogonSamLogonEx(context.server,
|
|
context.machine_creds.get_workstation(),
|
|
logon_level,
|
|
logon,
|
|
validation_level,
|
|
netr_flags)
|
|
|
|
creds.set_kerberos_state(old_state)
|
|
|
|
context.last_samlogon_bad =\
|
|
context.with_random_bad_credentials(connect,
|
|
context.user_creds,
|
|
context.user_creds_bad,
|
|
context.last_samlogon_bad)
|
|
return True
|
|
|
|
|
|
def samlogon_target(domain_name, computer_name):
|
|
target_info = ntlmssp.AV_PAIR_LIST()
|
|
target_info.count = 3
|
|
computername = ntlmssp.AV_PAIR()
|
|
computername.AvId = ntlmssp.MsvAvNbComputerName
|
|
computername.Value = computer_name
|
|
|
|
domainname = ntlmssp.AV_PAIR()
|
|
domainname.AvId = ntlmssp.MsvAvNbDomainName
|
|
domainname.Value = domain_name
|
|
|
|
eol = ntlmssp.AV_PAIR()
|
|
eol.AvId = ntlmssp.MsvAvEOL
|
|
target_info.pair = [domainname, computername, eol]
|
|
|
|
return ndr_pack(target_info)
|
|
|
|
|
|
def samlogon_logon_info(domain_name, computer_name, creds):
|
|
|
|
target_info_blob = samlogon_target(domain_name, computer_name)
|
|
|
|
challenge = b"abcdefgh"
|
|
# User account under test
|
|
response = creds.get_ntlm_response(flags=CLI_CRED_NTLMv2_AUTH,
|
|
challenge=challenge,
|
|
target_info=target_info_blob)
|
|
|
|
logon = netlogon.netr_NetworkInfo()
|
|
|
|
logon.challenge = [ord(x) for x in challenge]
|
|
logon.nt = netlogon.netr_ChallengeResponse()
|
|
logon.nt.length = len(response["nt_response"])
|
|
logon.nt.data = [ord(x) for x in response["nt_response"]]
|
|
logon.identity_info = netlogon.netr_IdentityInfo()
|
|
|
|
(username, domain) = creds.get_ntlm_username_domain()
|
|
logon.identity_info.domain_name.string = domain
|
|
logon.identity_info.account_name.string = username
|
|
logon.identity_info.workstation.string = creds.get_workstation()
|
|
|
|
return logon
|
|
|
|
|
|
def packet_rpc_netlogon_40(packet, conversation, context):
|
|
# DsrEnumerateDomainTrusts
|
|
c = context.get_netlogon_connection()
|
|
c.netr_DsrEnumerateDomainTrusts(
|
|
context.server,
|
|
netlogon.NETR_TRUST_FLAG_IN_FOREST |
|
|
netlogon.NETR_TRUST_FLAG_OUTBOUND |
|
|
netlogon.NETR_TRUST_FLAG_INBOUND)
|
|
return True
|
|
|
|
|
|
def packet_rpc_netlogon_45(packet, conversation, context):
|
|
# NetrLogonSamLogonWithFlags [7]
|
|
def connect(creds):
|
|
c = context.get_netlogon_connection()
|
|
(auth, succ) = context.get_authenticator()
|
|
|
|
# Disable Kerberos in cli creds to extract NTLM response
|
|
old_state = creds.get_kerberos_state()
|
|
creds.set_kerberos_state(DONT_USE_KERBEROS)
|
|
|
|
logon = samlogon_logon_info(context.domain,
|
|
context.netbios_name,
|
|
creds)
|
|
logon_level = netlogon.NetlogonNetworkTransitiveInformation
|
|
validation_level = netlogon.NetlogonValidationSamInfo4
|
|
netr_flags = 0
|
|
c.netr_LogonSamLogonWithFlags(context.server,
|
|
context.machine_creds.get_workstation(),
|
|
auth,
|
|
succ,
|
|
logon_level,
|
|
logon,
|
|
validation_level,
|
|
netr_flags)
|
|
|
|
creds.set_kerberos_state(old_state)
|
|
|
|
context.last_samlogon_bad =\
|
|
context.with_random_bad_credentials(connect,
|
|
context.user_creds,
|
|
context.user_creds_bad,
|
|
context.last_samlogon_bad)
|
|
return True
|
|
|
|
|
|
def packet_samr_0(packet, conversation, context):
|
|
# Open
|
|
c = context.get_samr_context()
|
|
c.get_handle()
|
|
return True
|
|
|
|
|
|
def packet_samr_1(packet, conversation, context):
|
|
# Close
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
# close the last opened handle, may not always be accurate
|
|
# but will do for load simulation
|
|
if c.user_handle is not None:
|
|
s.Close(c.user_handle)
|
|
c.user_handle = None
|
|
elif c.group_handle is not None:
|
|
s.Close(c.group_handle)
|
|
c.group_handle = None
|
|
elif c.domain_handle is not None:
|
|
s.Close(c.domain_handle)
|
|
c.domain_handle = None
|
|
c.rids = None
|
|
elif c.handle is not None:
|
|
s.Close(c.handle)
|
|
c.handle = None
|
|
c.domain_sid = None
|
|
return True
|
|
|
|
|
|
def packet_samr_3(packet, conversation, context):
|
|
# QuerySecurity
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
if c.user_handle is None:
|
|
packet_samr_34(packet, conversation, context)
|
|
s.QuerySecurity(c.user_handle, 1)
|
|
return True
|
|
|
|
|
|
def packet_samr_5(packet, conversation, context):
|
|
# LookupDomain
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
h = c.get_handle()
|
|
d = lsa.String()
|
|
d.string = context.domain
|
|
c.domain_sid = s.LookupDomain(h, d)
|
|
return True
|
|
|
|
|
|
def packet_samr_6(packet, conversation, context):
|
|
# EnumDomains
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
h = c.get_handle()
|
|
s.EnumDomains(h, 0, 0)
|
|
return True
|
|
|
|
|
|
def packet_samr_7(packet, conversation, context):
|
|
# OpenDomain
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
h = c.get_handle()
|
|
if c.domain_sid is None:
|
|
packet_samr_5(packet, conversation, context)
|
|
|
|
c.domain_handle = s.OpenDomain(h,
|
|
security.SEC_FLAG_MAXIMUM_ALLOWED,
|
|
c.domain_sid)
|
|
return True
|
|
|
|
|
|
SAMR_QUERY_DOMAIN_INFO_LEVELS = [8, 12]
|
|
|
|
|
|
def packet_samr_8(packet, conversation, context):
|
|
# QueryDomainInfo [228]
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
if c.domain_handle is None:
|
|
packet_samr_7(packet, conversation, context)
|
|
level = random.choice(SAMR_QUERY_DOMAIN_INFO_LEVELS)
|
|
s.QueryDomainInfo(c.domain_handle, level)
|
|
return True
|
|
|
|
|
|
packet_samr_14 = null_packet
|
|
# CreateDomainAlias
|
|
# Ignore these for now.
|
|
|
|
|
|
def packet_samr_15(packet, conversation, context):
|
|
# EnumDomainAliases
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
if c.domain_handle is None:
|
|
packet_samr_7(packet, conversation, context)
|
|
|
|
s.EnumDomainAliases(c.domain_handle, 100, 0)
|
|
return True
|
|
|
|
|
|
def packet_samr_16(packet, conversation, context):
|
|
# GetAliasMembership
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
if c.domain_handle is None:
|
|
packet_samr_7(packet, conversation, context)
|
|
|
|
sids = lsa.SidArray()
|
|
sid = lsa.SidPtr()
|
|
sid.sid = c.domain_sid
|
|
sids.sids = [sid]
|
|
s.GetAliasMembership(c.domain_handle, sids)
|
|
return True
|
|
|
|
|
|
def packet_samr_17(packet, conversation, context):
|
|
# LookupNames
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
if c.domain_handle is None:
|
|
packet_samr_7(packet, conversation, context)
|
|
|
|
name = lsa.String(context.username)
|
|
c.rids = s.LookupNames(c.domain_handle, [name])
|
|
return True
|
|
|
|
|
|
def packet_samr_18(packet, conversation, context):
|
|
# LookupRids
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
if c.rids is None:
|
|
packet_samr_17(packet, conversation, context)
|
|
rids = []
|
|
for r in c.rids:
|
|
for i in r.ids:
|
|
rids.append(i)
|
|
s.LookupRids(c.domain_handle, rids)
|
|
return True
|
|
|
|
|
|
def packet_samr_19(packet, conversation, context):
|
|
# OpenGroup
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
if c.domain_handle is None:
|
|
packet_samr_7(packet, conversation, context)
|
|
|
|
rid = 0x202 # Users I think.
|
|
c.group_handle = s.OpenGroup(c.domain_handle,
|
|
security.SEC_FLAG_MAXIMUM_ALLOWED,
|
|
rid)
|
|
return True
|
|
|
|
|
|
def packet_samr_25(packet, conversation, context):
|
|
# QueryGroupMember
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
if c.group_handle is None:
|
|
packet_samr_19(packet, conversation, context)
|
|
s.QueryGroupMember(c.group_handle)
|
|
return True
|
|
|
|
|
|
def packet_samr_34(packet, conversation, context):
|
|
# OpenUser
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
if c.rids is None:
|
|
packet_samr_17(packet, conversation, context)
|
|
c.user_handle = s.OpenUser(c.domain_handle,
|
|
security.SEC_FLAG_MAXIMUM_ALLOWED,
|
|
c.rids[0].ids[0])
|
|
return True
|
|
|
|
|
|
def packet_samr_36(packet, conversation, context):
|
|
# QueryUserInfo
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
if c.user_handle is None:
|
|
packet_samr_34(packet, conversation, context)
|
|
level = 1
|
|
s.QueryUserInfo(c.user_handle, level)
|
|
return True
|
|
|
|
|
|
packet_samr_37 = null_packet
|
|
|
|
|
|
def packet_samr_39(packet, conversation, context):
|
|
# GetGroupsForUser
|
|
c = context.get_samr_context()
|
|
s = c.get_connection()
|
|
if c.user_handle is None:
|
|
packet_samr_34(packet, conversation, context)
|
|
s.GetGroupsForUser(c.user_handle)
|
|
return True
|
|
|
|
|
|
packet_samr_40 = null_packet
|
|
|
|
packet_samr_44 = null_packet
|
|
|
|
|
|
def packet_samr_57(packet, conversation, context):
|
|
# Connect2
|
|
c = context.get_samr_context()
|
|
c.get_handle()
|
|
return True
|
|
|
|
|
|
def packet_samr_64(packet, conversation, context):
|
|
# Connect5
|
|
c = context.get_samr_context()
|
|
c.get_handle()
|
|
return True
|
|
|
|
|
|
packet_samr_68 = null_packet
|
|
|
|
|
|
def packet_srvsvc_16(packet, conversation, context):
|
|
# NetShareGetInfo
|
|
s = context.get_srvsvc_connection()
|
|
server_unc = "\\\\" + context.server
|
|
share_name = "IPC$"
|
|
level = 1
|
|
s.NetShareGetInfo(server_unc, share_name, level)
|
|
return True
|
|
|
|
|
|
def packet_srvsvc_21(packet, conversation, context):
|
|
"""NetSrvGetInfo
|
|
|
|
FIXME: Level changed from 102 to 101 here, to bypass Windows error.
|
|
|
|
Level 102 will cause WERR_ACCESS_DENIED error against Windows, because:
|
|
|
|
> If the level is 102 or 502, the Windows implementation checks whether
|
|
> the caller is a member of one of the groups previously mentioned or
|
|
> is a member of the Power Users local group.
|
|
|
|
It passed against Samba since this check is not implemented by Samba yet.
|
|
|
|
refer to:
|
|
|
|
https://msdn.microsoft.com/en-us/library/cc247297.aspx#Appendix_A_80
|
|
|
|
"""
|
|
srvsvc = context.get_srvsvc_connection()
|
|
server_unc = "\\\\" + context.server
|
|
level = 101
|
|
srvsvc.NetSrvGetInfo(server_unc, level)
|
|
return True
|