Internal network penetration Swiss Army knife - impacket tool analysis (Part 1)

0 20
Internal network penetration Swiss Army knife - impacket tool analysis (Part 1)i...

Internal network penetration Swiss Army knife - impacket tool analysis (Part 1)

impacket tool analysis of NTLM protocol implementation

Internal network penetration Swiss Army knife - impacket tool analysis (Part 1)

impacket is a series of Python implementations of network protocols, including basic network protocols such as IP, TCP, ICMP, and more importantly, it implements a large number of Windows communication protocols, including the ntlm and kerberos protocols used for Windows authentication, the ldap used for storing Active Directory data, and a large number of msrpc protocols. With impacket, developers can construct protocol corresponding data packets through a series of Python classes and methods, or deserialize the original data packets returned by the server into corresponding Python classes. Through Impacket, developers can write Python scripts to build custom network security tools, perform network protocol analysis, penetration testing, vulnerability exploitation, and other tasks. It is widely used in penetration testing, red team operations, and network security research.

The impacket tool analysis series of articles will take the protocol as the breakthrough point, and carry out detailed analysis on different protocol implementations. Due to the large number of protocols implemented in impacket, we will focus on the analysis of commonly used protocols in internal network penetration, such as ntlm, kerberos, ldap, smb, rpc, and so on. Through code interpretation and examples, we will help everyone understand the principle and usage of this tool. As the first article in the series, we will first introduce the implementation of the ntlm protocol.

We use the impacket0.11.0 version as an example. The implementation of ntlm in impacket is located in a file named ntlm.py, which is located in impacket-ntlm.py

01 NTLM Constants

USE_NTLMv2

1695613377_651101c13533415fd3e11.png!small?1695613377477

On lines 35-36, the author first defines two global variables USE_NTLMv2, TEST_CASE to represent the use of NTLMv2 and test cases. NTLMv1 is very rare in most network environments, so here it is set to use NTLMv1 by default.

Auth Level

1695613382_651101c68add066e2d6ad.png!small?1695613384159

Lines 55-60 define 6 constants, each representing 6 authentication levels that will be used in dcerpc. However, these parameters are not used in ntlm.

1695613388_651101cc9cfdc418d8143.png!small?1695613389075

NEGOTIATE FLAGS

1695613396_651101d42ca28ff43af4e.png!small?1695613396765

Lines 62-191 describe the meaning of each bit in the FLAG field of the ntlm message. NEGOTIATE FLAGS is a field in the ntlm message, with a fixed length of 4 bytes (32bit). Each field and its meaning are as follows:

Identifier BitsNameDescription
0x00000001Negotiate UnicodeIndicates support for the use of Unicode strings in secure buffer data.
0x00000002Negotiate OEMIndicates support for the use of OEM strings in secure buffer data.
0x00000004Request TargetRequests that the server include the domain name of the target server in the Type2 message.
0x00000008unknownReserved field (not used)
0x00000010Negotiate SignSpecifies that authenticated communications between the client and server should carry a digital signature (message integrity).
0x00000020Negotiate SealSpecifies that authenticated communications between the client and server should be encrypted (message confidentiality).
0x00000040Negotiate Datagram StyleIndicates that datagram verification is being used.
0x00000080Negotiate Lan Manager KeyIndicates that the lm Session Key should be applied to sign and seal authenticated communications.
0x00000100Negotiate NetwareReserved field (not used)
0x00000200Negotiate NTLMIndicates that NTLM authentication is being used.
0x00000400unknownReserved field (not used)
0x00000800Negotiate Anonymous由客户端在 Type 3 消息中发送,表明匿名上下文已建立。这也会影响响应字段(如“匿名响应”部分中详述)。
Sent by the client in Type 3 messages to indicate that an anonymous context has been established. This also affects the response fields (as detailed in the 'Anonymous Response' section).0x00001000Negotiate Domain Supplied
Sent by the client in Type 1 messages to indicate that the message includes the domain name where the client is located, which the server uses to determine if the client is eligible for local authentication.0x00002000Negotiate Workstation Supplied
Sent by the client in Type 1 messages to indicate that the message includes the name of the client's workstation, which the server uses to determine if the client is eligible for local authentication.0x00004000Reserved field (not used)
Negotiate Local Call0x00008000Negotiate Always Sign
Setting this field generates a session key regardless of whether Negotiate Sign or Negotiate Seal is set.0x00010000Target Type Domain
Indicates that the type of the Target field in the Type 2 message is the domain name.0x00020000Target Type Server
Indicates that the type of the Target field in the Type 2 message is the server name.0x00040000Reserved field (not used)
Target Type Share0x00080000Negotiate NTLM2 Key
Indicates that the NTLM2 signature and sealing scheme should be used to protect authenticated communication. Note that this refers to a specific session security scheme and is unrelated to the use of NTLMv2 authentication.Request Init ResponseReserved field (not used)
0x00200000Request Accept ResponseReserved field (not used)
0x00400000Request Non-NT Session KeyReserved field (not used)
0x00800000Negotiate Target InfoSent by the server in Type 2 messages to indicate that it includes the TargetInfo field in the message, which is used to calculate the NTLMv2 response.
0x01000000unknownReserved field (not used)
0x02000000unknownReserved field (not used)
0x04000000unknownReserved field (not used)
0x08000000unknownReserved field (not used)
0x10000000unknownReserved field (not used)
0x20000000Negotiate 128Indicates support for 128-bit encryption.
0x40000000Negotiate Key Exchangeindicating that the client will provide the main encryption key in the 'session key' field of Type 3 messages.
0x80000000Negotiate 56indicating support for 56-bit encryption.

02NTLM message structure

AVPAIRS

AVPAIRS is a structure used in both Challenge and ChallengeResponse, consisting of a series of AV_PAIRs, and ends with an AV_PAIR structure with AvId as MsvAvEOL. The structure of AV_PAIR is as follows.

1695613403_651101db5910ae9c4b724.png!small?1695613403744

Among them, AvId represents the type of AV_PAIR, AvLen represents the length of the value, followed by the content of the value, and a class AV_PAIRS is defined in impacket to represent this structure.

class AV_PAIRS:def __init__(self, data = None):self.fields = {}if data is not None:self.fromString(data)def __setitem__(self,key,value):self.fields[key] = (len(value),value)def __getitem__(self, key):if key in self.fields:return self.fields[key]return Nonedef __delitem__(self, key):del self.fields[key]def __len__(self):return len(self.getData())def __str__(self):return len(self.getData())def fromString(self, data):tInfo = datafType = 0xffwhile fType is not NTLMSSP_AV_EOL:fType = struct.unpack('<H',tInfo[:struct.calcsize('<H')])[0]tInfo = tInfo[struct.calcsize('<H'):]length = struct.unpack('<H',tInfo[:struct.calcsize('<H')])[0]tInfo = tInfo[struct.calcsize('<H'):]content = tInfo[:length]self.fields[fType]=(length,content)tInfo = tInfo[length:]def dump(self):for i in list(self.fields.keys()):print("%s: {%r}" % (i,self[i]))def getData(self):if NTLMSSP_AV_EOL in self.fields:del self.fields[NTLMSSP_AV_EOL]ans = b''for i in list(self.fields.keys()):ans+= struct.pack('<HH', i, self[i][0])ans+= self[i][1]# end with a NTLMSSP_AV_EOLans += struct.pack('<HH', NTLMSSP_AV_EOL, 0)return ans

The AV_PAIRS class uses the dictionary fields to store AV_PAIR. getData and fromString are used for serialization and deserialization of the AV_PAIRS data type.

Version

The second class defined by ntlm in impacket is Version. The main function of this class is to be used for deserialization of the version field in the NTLM message. Let's take a look at the definition of the version field in the protocol.

1695613413_651101e515b63ae0c4a46.png!small?1695613413493

The first 8 bytes represent the operating system information, and the last byte represents the NTLMSSP version, which is fixed to the constant NTLMSSP_REVISION_W2K3(0x0F). Let's take a look at the representation method in impacket.

class VERSION(Structure):NTLMSSP_REVISION_W2K3 = 0x0Fstructure = (('ProductMajorVersion', '<B=0'),('ProductMinorVersion', '<B=0'),('ProductBuild', '<H=0'),('Reserved', '3s=""'),('NTLMRevisionCurrent', '<B=self.NTLMSSP_REVISION_W2K3'),)

It can be seen that the VERSION class inherits from the Structure class, which is a very core class in impacket.基本上all the data structures in impacket are implemented by inheriting this class, and the usage methods of this class can be found in the documentation of the Structure class.

subclasses can define commonHdr and/or structure.each of them is an tuple of either two: (fieldName, format) or three: (fieldName, ':', class) fields.[it can't be a dictionary, because order is important]where format specifies how the data in the field will be converted to/from bytes (string)class is the class to use when unpacking ':' fields.each field can only contain one value (or an array of values for *)i.e. struct.pack('Hl',1,2) is valid, but format specifier 'Hl' is not (you must use 2 different fields)format specifiers:specifiers from module pack can be used with the same format see struct.__doc__ (pack/unpack is finally called)x [padding byte]c [character]b [signed byte]B [unsigned byte]h [signed short]H [unsigned short]l [signed long]L [unsigned long]i [signed integer]I [unsigned integer]q [signed long long (quad)]Q [unsigned long long (quad)]s       [string (array of chars), must be preceded with length in format specifier, padded with zeros]p       [pascal string (includes byte count), must be preceded with length in format specifier, padded with zeros]f       [float]d       [double]=       [native byte ordering, size and alignment]@       [native byte ordering, standard size and alignment]!       [network byte ordering]<       [little endian]>       [big endian]usual printf like specifiers can be used (if started with %) [not recommended, there is no way to unpack this]%08x    will output an 8 bytes hex%s      will output a string%s\\x00  will output a NUL terminated string%d%d    will output 2 decimal digits (against the very same specification of Structure)...some additional format specifiers::       just copy the bytes from the field into the output string (input may be string, other structure, or anything responding to __str__()) (for unpacking, all what's left is returned)z same as :, but adds a NUL byte at the end (asciiz) (for unpacking the first NUL byte is used as terminator)  [asciiz string]u same as z, but adds two NUL bytes at the end (after padding to an even size with NULs). (same for unpacking) [unicode string]w DCE-RPC/NDR string (it's a macro for [ '<L=(len(field)+1)/2','"\\x00\\x00\\x00\\x00','<L=(len(field)+1)/2',':' ])?-field length of field named 'field', formatted as specified with ? ('?' may be '!H' for example). The input value overrides the real length?1*?2 array of elements. Each formatted as '?2', the number of elements in the array is stored as specified by '?1' (?1 is optional, or can also be a constant (number), for unpacking)'xxxx literal xxxx (field's value doesn't change the output. quotes must not be closed or escaped)"xxxx literal xxxx (field's value doesn't change the output. quotes must not be closed or escaped)_ will not pack the field. Accepts a third argument, which is an unpack code. See _Test_UnpackCode for an example.?=packcode will evaluate packcode in the context of the structure, and pack the result as specified by ?. Unpacking is straightforward.?&fieldname "Address of field fieldname".For packing, it will simply pack the id() of fieldname. Or use 0 if fieldname doesn't exist.For unpacking, it is used to determine whether the fieldname has to be unpacked or not, i.e., by adding a & to fieldname, you turn another field (fieldname) into an optional field.

From the document, we can see that the 'structure' field is a tuple list containing 2 or 3 elements, used to define the names and structures of various fields, such as the field (‘ProductMajorVersion’, ‘<B=0’), which indicates that the field name is ProductMajorVersion, the format is <B=0. From the document, we can see the format of ?=packcode, which first calculates the value of packcode, and then packs the value 0 according to the specified ? method, i.e., in little-endian order occupying one byte. Those unfamiliar with packing and unpacking in Python can refer to the usage method of the built-in struct package of Python. Therefore, it is indicated that the ProductMajorVersion field occupies 1 byte, with a default value of 0. This is a process of deserialization. Serialization is relatively simple, which is to calculate the number of bytes occupied according to <B, and then unpack the corresponding bytes and assign them to the ProductMajorVersion field.

Negotiate

Negotiate is one of the three major data packets used by NTLM authentication, usually referred to as Type1. The message structure is as follows

1695613423_651101ef4b6e1601ec1bb.png!small?16956134236391695613429_651101f5dfcfc69f42739.png!small?1695613431264

Here are two new field formats, DomainNameFields and WorkstationFields, let's take a look at the definitions of these two fields

1695613436_651101fc40c73cf7fa1ef.png!small?1695613436691

DomainNameLen indicates the length of the field, DomainNameMaxLen and DomainNameLen are the same, DomainNameBufferOffset indicates the offset of the value of this field. Let's take a look at the class design of impacket.

class NTLMAuthNegotiate(Structure):structure = (('', '"NTLMSSP\x00'),('message_type', '<L=1'),('flags','<L'),('domain_len','<H-domain_name'),('domain_max_len','<H-domain_name'),('domain_offset', '<L=0'),('host_len','<H-host_name'),('host_maxlen', '<H-host_name'),('host_offset', '<L=0'),('os_version', ':'),('host_name',':'),('domain_name', ':'))def __init__(self):Structure.__init__(self)self['flags'] = (NTLMSSP_NEGOTIATE_128     |NTLMSSP_NEGOTIATE_KEY_EXCH|# NTLMSSP_LM_KEY      |NTLMSSP_NEGOTIATE_NTLM    |NTLMSSP_NEGOTIATE_UNICODE     |# NTLMSSP_ALWAYS_SIGN |NTLMSSP_NEGOTIATE_SIGN        |NTLMSSP_NEGOTIATE_SEAL        |# NTLMSSP_TARGET      |0)self['host_name'] = ''self['domain_name'] = ''self['os_version'] = ''self._workstation = ''def setWorkstation(self, workstation):self._workstation = workstationdef getWorkstation(self):return self._workstationdef __hasNegotiateVersion(self):return (self['flags'] & NTLMSSP_NEGOTIATE_VERSION) == NTLMSSP_NEGOTIATE_VERSIONdef getData(self):if len(self.fields['host_name']) > 0:self['flags'] |= NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIEDif len(self.fields['domain_name']) > 0:self['flags'] |= NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIEDversion_len = len(self.fields['os_version'])if version_len > 0:self['flags'] |= NTLMSSP_NEGOTIATE_VERSIONelif self.__hasNegotiateVersion():raise Exception('Must provide the os_version field if the NTLMSSP_NEGOTIATE_VERSION flag is set')if (self['flags'] & NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED) == NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED:self['host_offset'] = 32 + version_lenif (self['flags'] & NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED) == NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED:self['domain_offset'] = 32 + len(self['host_name']) + version_lenreturn Structure.getData(self)def fromString(self, data):Structure.fromString(self, data)domain_offset = self['domain_offset']domain_end    = self['domain_len'] + domain_offsetself['domain_name'] = data[ domain_offset : domain_end ]host_offset = self['host_offset']host_end    = self['host_len'] + host_offsetself['host_name'] = data[ host_offset : host_end ]if len(data) >= 36 and self.__hasNegotiateVersion():self['os_version'] = VERSION(data[32:])else:self['os_version'] = ''

Here is also a representation method for the Field field: ?-field, indicating that the field packs the length of field. In this class, the fromString method of the Structure class is also overridden. The fromString method is used for deserialization, i.e., unpacking. A simple experiment can be conducted to test deserialization.

from impacket.ntlm import NTLMAuthNegotiateif __name__ == '__main__':nego_data = bytes.fromhex('4e544c4d5353500001000000978208e2000000000000000000000000000000000a00614a0000000f')nego = NTLMAuthNegotiate()nego.fromString(nego_data)nego.dump()

The result is as follows

NTLMAuthNegotiate: {'NTLMSSP\x00'}message_type: {1}flags: {3792208535}domain_len: {0}domain_max_len: {0}domain_offset: {0}host_len: {0}host_maxlen: {0}host_offset: {0}os_version: {ProductMajorVersion: {10}ProductMinorVersion: {0}ProductBuild: {19041}Reserved: {b'\x00\x00\x00'}NTLMRevisionCurrent: {15}}host_name: {b''}domain_name: {b''}

Challenge

class NTLMAuthChallenge(Structure):structure = (('', '"NTLMSSP\x00'),('message_type', '<L=2'),('domain_len','<H-domain_name'),('domain_max_len','<H-domain_name'),('domain_offset', '<L=40'),('flags', '<L=0'),('challenge', '8s'),('reserved', '8s=""'),('TargetInfoFields_len', '<H-TargetInfoFields'),('TargetInfoFields_max_len', '<H-TargetInfoFields'),('TargetInfoFields_offset', '<L'),('VersionLen','_-Version','self.checkVersion(self["flags"])'), ('Version', ':'),('domain_name',':'),('TargetInfoFields', ':'))@staticmethoddef checkVersion(flags):if flags is not None:if flags & NTLMSSP_NEGOTIATE_VERSION == 0:return 0return 8def getData(self):if self['TargetInfoFields'] is not None and type(self['TargetInfoFields']) is not bytes:raw_av_fields = self['TargetInfoFields'].getData()self['TargetInfoFields'] = raw_av_fieldsreturn Structure.getData(self)def fromString(self, data):Structure.fromString(self, data)self['domain_name'] = data[self['domain_offset']:][:self['domain_len']]self['TargetInfoFields'] = data[self['TargetInfoFields_offset']:][:self['TargetInfoFields_len']]return self

Challenge messages are also known as Type2. In the definition of Type2, there appears a new field format ('VersionLen', '_-Version', 'self.checkVersion(self["flags"])'). This indicates that the 'VersionLen' field represents the length of 'Version', and this field will not be added to the data during serialization. Moreover, its value can be overridden by the result of 'self.checkVersion(self["flags"])'. The author has rewritten the 'fromString' method to deserialize the 'domain_name' and 'TargetInfoFields' fields, but in fact, this section is not necessary. The judgment of the 'Field' field's corresponding value is already implemented in the 'findLengthFieldFor' implementation of 'Structure', which means that the value of 'domain_name' and 'TargetInfoFields' is also determined when 'domain_field' and 'TargetInfo_Fields' are determined. The length is also determined.

1695613448_65110208b32d799e6ea88.png!small?1695613449187

Commenting out this code segment also allows for normal serialization.

1695613453_6511020da8aec35e06394.png!small?1695613454379

Example

if __name__ == '__main__':challenge_data = bytes.fromhex('4e544c4d53535000020000000400040038000000158289e267bf6a81e0c5dcd300000000000000006a006a003c0000000a0063450000000f4a004400020004004a004400010008004400430030003100040010006a0064002e006c006f00630061006c0003001a0044004300300031002e006a0064002e006c006f00630061006c00050010006a0064002e006c006f00630061006c00070008004e3a1907b2dbd90100000000')challenge = NTLMAuthChallenge()challenge.fromString(nego_data)challenge.dump()

NTLMAuthChallenge: {'NTLMSSP\x00'}message_type: {2}domain_len: {4}domain_max_len: {4}domain_offset: {56}flags: {3800662549}challenge: {b'g\xbfj\x81\xe0\xc5\xdc\xd3'}reserved: {b'\x00\x00\x00\x00\x00\x00\x00\x00'}TargetInfoFields_len: {106}TargetInfoFields_max_len: {106}TargetInfoFields_offset: {60}VersionLen: {8}Version: {b'\n\x00cE\x00\x00\x00\x0f'}domain_name: {b'J\x00D\x00'}TargetInfoFields: {b'\x02\x00\x04\x00J\x00D\x00\x01\x00\x08\x00D\x00C\x000\x001\x00\x04\x00\x10\x00j\x00d\x00.\x00l\x00o\x00c\x00a\x00l\x00\x03\x00\x1a\x00D\x00C\x000\x001\x00.\x00j\x00d\x00.\x00l\x00o\x00c\x00a\x00l\x00\x05\x00\x10\x00j\x00d\x00.\x00l\x00o\x00c\x00a\x00l\x00\x07\x00\x08\x00N:\x19\x07\xb2\xdb\xd9\x01\x00\x00\x00\x00'}

ChallengeResponse

ChallengeResponse, also known as Type3, has more fields compared to other types. Important fields include lanman, ntlm, session_key, and MIC.

class NTLMAuthChallengeResponse(Structure):structure = (('', '"NTLMSSP\x00'),('message_type','<L=3'),('lanman_len','<H-lanman'),('lanman_max_len','<H-lanman'),('lanman_offset','<L'),('ntlm_len','<H-ntlm'),('ntlm_max_len','<H-ntlm'),('ntlm_offset','<L'),('domain_len','<H-domain_name'),('domain_max_len','<H-domain_name'),('domain_offset','<L'),('user_len','<H-user_name'),('user_max_len','<H-user_name'),('user_offset','<L'),('host_len','<H-host_name'),('host_max_len','<H-host_name'),('host_offset','<L'),('session_key_len','<H-session_key'),('session_key_max_len','<H-session_key'),('session_key_offset','<L'),('flags','<L'),('VersionLen','_-Version','self.checkVersion(self["flags"])'), ('Version',':=""'),('MICLen','_-MIC','self.checkMIC(self["flags"])'),('MIC',':=""'),('domain_name',':'),('user_name',':'),('host_name',':'),('lanman', ':'),('ntlm', ':'),('session_key', ':'))...

lanman, representing LMChallengeResponse, calculated by lm, and the value of this field is mostly empty in modern network environments.

ntlm, representing NTChallengeResponse, which exists in both ntlm v1 and v2, and the calculation method is different.

session_key, used for negotiating the encryption key of the communication protocol using the ntlm protocol.

MIC, used to ensure the integrity of the ChallengeResponse message and prevent tampering.

03NTLMSSP

In addition to defining the basic data structure, there are two commonly used high-level functions in the ntlm implementation. Since ntlm is basically used as a client in impacket, and the client needs to construct Type1 and Type3 in ntlm authentication, getNTLMSSPType1 and getNTLMSSPType3 functions are defined here to construct packets.

getNTLMSSPType1

getNTLMSSPType1 is used to construct Type1 packets. Although the function receives 4 parameters, the workstation and domain parameters are not used, and it is mainly used for initializing the negotiation flag.

def getNTLMSSPType1(workstation='', domain='', signingRequired = False, use_ntlmv2 = USE_NTLMv2):# 在继续之前做一些编码检查。有点脏,但在处理时发现效果不错。# international characters.import sysencoding = sys.getfilesystemencoding()if encoding is not None:try:workstation.encode('utf-16le')except:workstation = workstation.decode(encoding)try:domain.encode('utf-16le')except:domain = domain.decode(encoding)# Let's prepare a Type 1 NTLMSSP Messageauth = NTLMAuthNegotiate()# auth['os_version'] = bytes.fromhex('0a00614a0000000f')auth['flags']=0if signingRequired:auth['flags'] = NTLMSSP_NEGOTIATE_KEY_EXCH | NTLMSSP_NEGOTIATE_SIGN | NTLMSSP_NEGOTIATE_ALWAYS_SIGN | \NTLMSSP_NEGOTIATE_SEALif use_ntlmv2:auth['flags'] |= NTLMSSP_NEGOTIATE_TARGET_INFOauth['flags'] |= NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY | NTLMSSP_NEGOTIATE_UNICODE | \NTLMSSP_REQUEST_TARGET |  NTLMSSP_NEGOTIATE_128 | NTLMSSP_NEGOTIATE_56# 这次我们不添加工作站/域字段。通常 Windows 客户端不会添加此类信息,但...# 我们将保存工作站名称以供以后使用。auth.setWorkstation(workstation)return auth

getNTLMSSPType3

Type 3 消息是 NTLMSSP 握手过程的最后一步,用于向服务器发送身份验证凭据以进行身份验证,这也是ntlm认证最核心的部分。

def getNTLMSSPType3(type1, type2, user, password, domain, lmhash = '', nthash = '', use_ntlmv2 = USE_NTLMv2):# 在某人发送 password = None 的情况下进行安全检查。这是不允许的。将其设置为 '' 并希望一切顺利。if password is None:password = ''# 在继续之前做一些编码检查。有点脏,但在处理时发现效果不错。# international characters.import sysencoding = sys.getfilesystemencoding()if encoding is not None:try:user.encode('utf-16le')except:user = user.decode(encoding)try:password.encode('utf-16le')except:password = password.decode(encoding)try:domain.encode('utf-16le')except:domain = user.decode(encoding)ntlmChallenge = NTLMAuthChallenge(type2)# Let's start with the original flags sent in the type1 messageresponseFlags = type1['flags']# responseFlags = 3767042613# Token received and parsed. Depending on the authentication # method we will create a valid ChallengeResponsentlmChallengeResponse = NTLMAuthChallengeResponse(user, password, ntlmChallenge['challenge'])clientChallenge = b("".join([random.choice(string.digits+string.ascii_letters) for _ in range(8)]))serverName = ntlmChallenge['TargetInfoFields']ntResponse, lmResponse, sessionBaseKey = computeResponse(ntlmChallenge['flags'], ntlmChallenge['challenge'],clientChallenge, serverName, domain, user, password,lmhash, nthash, use_ntlmv2)

Since the response needs to be calculated through the information in Type2, it can be seen that the function takes type1 and type2 as input parameters. At line 25, it initializes the NTLMAuthChallenge class using type2, which automatically performs deserialization. At lines 32 and 36, it retrieves the challenge and TargetInfoFields fields of Type2, respectively. Line 34 generates an 8-byte client challenge. Note that the client challenge is composed of numbers and letters, but a normal client challenge is almost impossible to be composed entirely of visible characters, so it can also be used as a feature to identify the impacket's ntlm implementation. Line 38 is the most important step in calculating the ntResponse and lmResponse. Many people may confuse the concepts of ntlmv1, ntlmv2, nthash, and lmhash, but we can discover through computeResponse that the basic structure of ntlmv1 and ntlmv2 is the same, the only difference being the calculation method of ntResponse and lmResponse.

computeResponseNTLMv1

computeResponseNTLMv1 is used to calculate the ntResponse and lmResponse in ntlmv1

def computeResponseNTLMv1(flags, serverChallenge, clientChallenge, serverName, domain, user, password, lmhash='',nthash='', use_ntlmv2=USE_NTLMv2):if user == '' and password == ''# Special case for anonymous authenticationlmResponse = ''ntResponse = ''else:lmhash = LMOWFv1(password, lmhash, nthash)nthash = NTOWFv1(password, lmhash, nthash)if flags & NTLMSSP_NEGOTIATE_LM_KEY:ntResponse = ''lmResponse = get_ntlmv1_response(lmhash, serverChallenge)elif flags & NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:md5 = hashlib.new('md5')chall = (serverChallenge + clientChallenge)md5.update(chall)ntResponse = ntlmssp_DES_encrypt(nthash, md5.digest()[:8])lmResponse = clientChallenge + b'\x00'*16else:ntResponse = get_ntlmv1_response(nthash,serverChallenge)lmResponse = get_ntlmv1_response(lmhash, serverChallenge)sessionBaseKey = generateSessionKeyV1(password, lmhash, nthash)return ntResponse, lmResponse, sessionBaseKey

The calculation of ntResponse and lmResponse in ntlmv1 is divided into three cases. If the flag NTLMSSP_NEGOTIATE_LM_KEY is set in the Type, then only lmResponse is present. The lmResponse is encrypted by lmhash using serverChallenge. If the flag NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY is set in the Type, the lmResponse part is filled with clientChallenge and 16 zeros, and ntResponse uses nthash encryption. The encrypted content is the first 8 bytes of md5 after concatenating serverChallenge and clientChallenge. In other cases, ntResponse and lmResponse are encrypted by nthash and lmhash respectively using serverChallenge. It can be found that in all three cases, the calculated ntResponse and lmResponse are both 24 bytes long.

computeResponseNTLMv2

In ntlmv2, there are significant changes in the calculation of ntResponse and lmResponse. Firstly, the encryption key no longer uses lmhash, and it does not directly use nthash as the key, but instead uses a responseKeyNT derived from nthash. The encryption method also changes from des to hmac_md5

def computeResponseNTLMv2(flags, serverChallenge, clientChallenge, serverName, domain, user, password, lmhash='',)nthash='', use_ntlmv2=USE_NTLMv2):responseServerVersion = b'\x01'hiResponseServerVersion = b'\x01'responseKeyNT = NTOWFv2(user, password, domain, nthash)av_pairs = AV_PAIRS(serverName)# In order to support SPN target name validation, we have to add this to the serverName av_pairs. Otherwise we will# get access denied# This is set at Local Security Policy -> Local Policies -> Security Options -> Server SPN target name validation# levelif TEST_CASE is False:av_pairs[NTLMSSP_AV_TARGET_NAME] = 'cifs/'.encode('utf-16le') + av_pairs[NTLMSSP_AV_HOSTNAME][1]if av_pairs[NTLMSSP_AV_TIME] is not None:aTime = av_pairs[NTLMSSP_AV_TIME][1]else:aTime = struct.pack('<q', (116444736000000000 + calendar.timegm(time.gmtime()) * 10000000) )av_pairs[NTLMSSP_AV_TIME] = aTimeserverName = av_pairs.getData()else:aTime = b'\x00'*8temp = responseServerVersion + hiResponseServerVersion + b'\x00' * 6 + aTime + clientChallenge + b'\x00' * 4 + \serverName + b'\x00' * 4ntProofStr = hmac_md5(responseKeyNT, serverChallenge + temp)ntChallengeResponse = ntProofStr + templmChallengeResponse = hmac_md5(responseKeyNT, serverChallenge + clientChallenge) + clientChallengesessionBaseKey = hmac_md5(responseKeyNT, ntProofStr)if user == '' and password == ''# Special case for anonymous authenticationntChallengeResponse = ''lmChallengeResponse = ''return ntChallengeResponse, lmChallengeResponse, sessionBaseKey

ntChallengeResponse consists of two parts, a 16-byte Response and an NTLMv2_CLIENT_CHALLENGE structure. NTLMv2_CLIENT_CHALLENGE includes the current time, client challenge, and TargetInfo information from Type2. The Response is obtained by encrypting (serverChallenge + NTLMv2_CLIENT_CHALLENGE) with responseKeyNT. lmChallengeResponse contains a 16-byte Response and ChallengeFromClient. The Response calculation is relatively simple, encrypting serverChallenge + clientChallenge with responseKeyNT, and filling the ChallengeFromClient field with clientChallenge.

From here, it can be seen that both NTLMv1 and NTLMv2 convert passwords into hashes through OWF (one way function) before using them as encryption keys. This is also the reason why we can use nthash for PTH.

This article briefly introduces the implementation of NTLM in the impacket library, including some underlying data structure design, encryption process, and two auxiliary functions for constructing data packets. Since NTLM is an embedded protocol, we will elaborate on the interaction between the application layer and NTLM in subsequent application layer protocols.

你可能想看:

d) Adopt identification technologies such as passwords, password technologies, biometric technologies, and combinations of two or more to identify users, and at least one identification technology sho

Article 2 of the Cryptography Law clearly defines the term 'cryptography', which does not include commonly known terms such as 'bank card password', 'login password', as well as facial recognition, fi

It is possible to perform credible verification on the system boot program, system program, important configuration parameters, and application programs of computing devices based on a credible root,

Internal and external cultivation | Under the high-confrontation offensive and defensive, internal network security cannot be ignored

2. The International Criminal Police Organization arrests more than 1,000 network criminals from 20 countries, seize 27 million US dollars

In today's rapidly developing digital economy, data has become an important engine driving social progress and enterprise development. From being initially regarded as part of intangible assets to now

4.5 Main person in charge reviews the simulation results, sorts out the separated simulation issues, and allows the red and blue teams to improve as soon as possible. The main issues are as follows

As announced today, Glupteba is a multi-component botnet targeting Windows computers. Google has taken action to disrupt the operation of Glupteba, and we believe this action will have a significant i

2.1. Obtain the password of the optical network terminal super administrator account (telecomadmin)

Analysis and reflection on some practical issues of network intrusion detection system based on traffic

最后修改时间:
admin
上一篇 2025年03月30日 10:09
下一篇 2025年03月30日 10:32

评论已关闭