mirror of
https://github.com/samba-team/samba.git
synced 2024-12-25 23:21:54 +03:00
1f97aeac6b
Python 3.8 adds this warning via https://bugs.python.org/issue34850: the "is" and "is not" operator sometimes is used with string and numerical literals. This code "works" on CPython by accident, because of caching on different levels (small integers and strings caches, interned strings, deduplicating constants at compile time). But it shouldn't work on other implementations, and can not work even on early or future CPython versions. Reported-by: L. van Belle <belle@samba.org> Signed-off-by: David Disseldorp <ddiss@samba.org> Reviewed-by: Noel Power <noel.power@suse.com> Autobuild-User(master): David Disseldorp <ddiss@samba.org> Autobuild-Date(master): Mon Apr 27 12:19:59 UTC 2020 on sn-devel-184
974 lines
28 KiB
Python
974 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
|
|
import dns.resolver
|
|
from ldb import SCOPE_BASE
|
|
|
|
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 = SCOPE_BASE
|
|
|
|
samdb = context.get_ldap_connection()
|
|
dn = context.get_matching_dn(dn_sig)
|
|
|
|
# try to guess the search expression (don't bother for base searches, as
|
|
# they're only looking up a single object)
|
|
if (filter is None or filter == '') and scope != SCOPE_BASE:
|
|
filter = context.guess_search_filter(attrs, dn_sig, dn)
|
|
|
|
samdb.search(dn,
|
|
expression=filter,
|
|
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 = [x if isinstance(x, int) else ord(x) for x in os.urandom(DATA_LEN - pwd_len)]
|
|
pwd = netlogon.netr_CryptPassword()
|
|
pwd.length = pwd_len
|
|
pwd.data = filler + [x if isinstance(x, int) else 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 = [x if isinstance(x, int) else ord(x) for x in challenge]
|
|
logon.nt = netlogon.netr_ChallengeResponse()
|
|
logon.nt.length = len(response["nt_response"])
|
|
logon.nt.data = [x if isinstance(x, int) else 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
|