LM-Hash、NTLM-Hash、Net-NTLMv1、Net-NTLMv2详解

  1. 0x01 Lan Manager与LM Hash
    1. 1.1 Lan Manager认证流程
    2. 1.2 LM Hash
    3. 1.3 响应的计算
  2. 0x02 NT Lan Manager与Net-NTLM Hash
    1. 2.1 NT Lan Manager与Pass Through Authentication认证流程
    2. 2.2 NT Hash与NTLM Hash
    3. 2.3 Net-NTLMv1
    4. 2.4 Net-NTLMv2
  3. 0x03 挑战响应级别配置
  4. 0x04 小插曲 - Net-NTLMv1/v2 Response计算的问题(impacket/smbclient.py NTLM认证过程调试)
  5. 0x05 参考

author: Dlive

Windows有两大认证机制,NTLM与Kerberos,相比于Kerberos,NTLM的各种概念略显混乱

比如NTLM的命名在各种文章中比较混乱,比如NTHash有时候也会被称为NTLM Hash,NTv2 Response有时候也会被称为NTLMv2 Response,大家不必纠结这些概念,理解概念之后的技术细节才更为重要

本文将详细解释这些概念,并从代码层面实现LM/NTLM/Net-NTLMv1/Net-NTLMv2的计算,以及对impacket/smbclient.py进行一个简单调试

0x01 Lan Manager与LM Hash

1.1 Lan Manager认证流程

参考:https://en.wikipedia.org/wiki/LAN_Manager

Lan Manager认证机制是Windows早期操作系统使用的一种局域网认证机制,它使用挑战/响应模式来进行认证操作

客户端接收到服务器返回的8字节响应后,使用用户的LM Hash加密挑战,将生成的密文回复给服务器

服务器也存储了用户的LM Hash,同样使用LM Hash加密8字节的挑战,若其与接收到的客户端24字节响应相同则身份认证成功

1.2 LM Hash

LM Hash的计算算法如下

  1. 将用户口令明文转换为大写,并转换为OEM编码 (ASCII等于其本身)
  2. 口令补零或截断到14位,并且分作前后2个部分,各7字节 (口令大于14在win xp上默认不存储LM Hash,但是在NTLM认证时LM Hash是会按口令截断到14位计算)
  3. 对7字节字符串的每7个比特后面添加1比特0,变成64比特的DES密钥
  4. 将上面的2个Key,使用DES算法,分别加密固定字符串KGS!@#$%,得到2个8字节的密文
  5. 2个8字节的密文连成1个16字节的密文,称为LM Hash
1
2
3
LMHash1 = DES(DOSCHARSET(UPPERCASE(password))1, "KGS!@#$%")
LMHash2 = DES(DOSCHARSET(UPPERCASE(password))2, "KGS!@#$%")
LMHash = LMHash1 + LMHash2

LM Hash问题:

  1. 口令不区分大小写
  2. 口令长度最大为14字节,另外如果口令长度不超过7字节,则LM Hash的后8字节是固定值
  3. DES算法强度不够

举例说明,用户dlive的密码为dlive123,我们来计算一下他的LM Hash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import sys
import pyDes

password = sys.argv[1]

print "password plain text: ", password

password = password.upper()

if len(password) < 14:
password = password.ljust(14, '\x00')
else:
password = password[:14]

password_part_1 = password[:7]
password_part_2 = password[7:]


def str2bin(string):
result = bin(int(string.encode('hex'), 16))[2:].rjust(len(string)*8, '0')
return result


def bin2str(string):
result = hex(int(string, 2))[2:].rjust(16, '0').strip("L").decode('hex')
return result


def padding(string):
bin_str = str2bin(string)
bin_str = [bin_str[i:i+7] for i in xrange(0, len(bin_str), 7)]
result = "".join([i.ljust(8, '0') for i in bin_str])
result = bin2str(result)
return result


def des_encrypt(key, data):
des = pyDes.des(key, pyDes.ECB, pad=None)
return des.encrypt(data)


des_key_1 = padding(password_part_1)
des_key_2 = padding(password_part_2)

data = "KGS!@#$%"

lm_part_1 = des_encrypt(des_key_1, data).encode('hex')
lm_part_2 = des_encrypt(des_key_2, data).encode('hex')

lm_hash = lm_part_1 + lm_part_2

print "lm hash: ", lm_hash

结果如下

1
2
3
➜ /tmp python lmhash.py dlive123
password plain text: dlive123
lm hash: 4c4e06393672eed41aa818381e4e281b

在Windows XP上添加新用户dlive,密码dlive123,mimikatz提取用户hash,可以看到LM Hash与我们计算的一样

1
2
3
4
5
6
7
8
9
mimikatz # lsadump::lsa /patch
Domain : CHINA-F222C4685 / S-1-5-21-839522115-688789844-1801674531
......

RID : 000003e9 (1001)
User : dlive
LM : 4c4e06393672eed41aa818381e4e281b
NTLM : b598c89527ba4a88f40c5df9a86c7f54
......

1.3 响应的计算

参考: http://davenport.sourceforge.net/ntlm.html#theLmResponse

  1. LM Hash后面补5个字节0,共21字节
  2. 分成3组7字节,每7个比特后面添加1比特0,成为3个8字节的DES密钥
  3. 使用步骤2得到的3个密钥,分别对8字节的挑战进行DES获得三组8字节密文,共组成24字节的密文,称为响应
1
2
3
C = 8-byte server challenge
K1 | K2 | K3 = LM-Hash | 5-bytes-0
response = DES(K1, C) | DES(K2, C) | DES(K3, C)

LM Response的计算方法类似于下面Net-NTLMv1 Response计算方法,这里就不单独举例了

0x02 NT Lan Manager与Net-NTLM Hash

2.1 NT Lan Manager与Pass Through Authentication认证流程

参考:
https://en.wikipedia.org/wiki/NT_LAN_Manager
https://msdn.microsoft.com/en-us/library/cc224019.aspx

NT Lan Manager是对Lan Manager的升级,整体流程和Lan Manager机制没有太大区别,但是将其中的响应计算方法换为了更为安全的算法。

NT Lan Manager的协议实现有Net-NTLMv1和Net-NTLMv2两个版本,两者响应值计算均使用了NT Hash。

另外在域环境也是支持NTLM认证的,但是域环境下非域控服务器不存储域用户Hash,所以域环境下的NTLM认证使用了Pass Through Authentication机制来完成用户认证流程。

Pass Through Authentication认证流程如下图所示,服务器只是一个转发作用

2.2 NT Hash与NTLM Hash

1
2
NT Hash = MD4(UTF-16-LE(password)) # 有时也会被叫为NTLM Hash
NTLM Hash = LM Hash + NT Hash

在渗透测试中我们使用mimikatz提取的hash为NTLM Hash(LM Hash + NT Hash)

| | 2000 | xp | 2003 | Vista | Win7 | 2008 | 2012 |

| —- | —- | —- | —- | —– | —- | —- | —- |

| LM | √ | √ | √ | | | | |

| NTLM | √ | √ | √ | √ | √ | √ | √ |

Windows 2000 、Windows XP、Windows 2003在用户口令超过14位时候会只采用NT Hash,但在口令长度小于14时仍会存储LM Hash。而Vista及其后的系统仅存储NT Hash。

另外在渗透测试的过程中,我们经常看到LM Hash为aad3b435b51404eeaad3b435b51404ee的用户。或者mimikatz抓取到的LM Hash为空。这表示该用户口令为空或者未存储LM Hash。

举例说明,用户dlive的密码为dlivedlivedlive,我们来计算一下他的NT Hash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import sys
from Crypto.Hash import MD4

password = sys.argv[1]

password = password.encode('utf-16le')

print "password plain text: ", password

h = MD4.new()
h.update(password)
nt_hash = h.hexdigest()

print "nt hash: ", nt_hash

结果如下

1
2
3
➜ /tmp python nthash.py dlivedlivedlive
password plain text: dlivedlivedlive
nt hash: 27b9d12aa127a07b1d03a4a890326aeb

使用mimikatz提取hash,可以看到LM Hash与我们计算的一样

1
2
3
4
5
6
7
8
mimikatz # lsadump::lsa /patch
Domain : CHINA-F222C4685 / S-1-5-21-839522115-688789844-1801674531
......
RID : 000003e9 (1001)
User : dlive
LM :
NTLM : 27b9d12aa127a07b1d03a4a890326aeb
......

2.3 Net-NTLMv1

参考:http://davenport.sourceforge.net/ntlm.html#theNtlmResponse

响应计算方法1

  1. NT Hash后面补5个字节0,共21字节
  2. 分成3组7字节,每7个比特后面添加1比特0,成为3个8字节的DES密钥
  3. 使用步骤2得到的3个密钥,分别对8字节的挑战进行DES获得三组8字节密文,共组成24字节的密文,称为响应
1
2
3
4
5
6
7
8
9
10
# NTLMv1 Response

# NT Response
# response 和 LAN Manager中的response计算方法一样 ,只是LM Hash被换为了NT Hash
C = 8-byte server challenge
K1 | K2 | K3 = NT-Hash | 5-bytes-0
ntv1_response= DES(K1, C) | DES(K2, C) | DES(K3, C)

# LM Response
# NTLMv1响应时会同时发送LM Response,计算方法和之前LM Response的计算方法相同

响应的计算方法2

我从网上找的资料普遍介绍的是第一种响应计算方法,但是在我自己抓包和我计算结果对比后发现两者的NTLMv1 Response值不一致
被这个问题坑了一下午之后我就直接去跑去调试impacket/smbclient.py的代码了(调试过程见”小插曲”一章),然后发现了NTLMv1 Response的第二种计算方法
当NTLMSSP Negotiate Flags设置了Session Security标志位时,采用这种方法,若未设置该标志位,则采用上面那种方法

  1. NT Hash后面补5个字节0,共21字节
  2. 分成3组7字节,每7个比特后面添加1比特0,成为3个8字节的DES密钥
  3. 拼接8字节Server Challenge和8字节Client Challenge后,求其MD5,然后取MD5值的前8字节
  4. 使用步骤2得到的3个密钥,分别对步骤3中得到的8字节数据进行DES加密获得三组8字节密文,共组成24字节的密文,称为响应
1
2
3
4
5
6
7
8
9
10
11
# NTLMv1 Response

# NT Response
# response 和 LAN Manager中的response计算方法一样 ,只是LM Hash被换为了NT Hash
C = 8-byte server challenge
K1 | K2 | K3 = NT-Hash | 5-bytes-0
ntv1_response = DES(K1, C) | DES(K2, C) | DES(K3, C)

# LM Response
# 这种情况下的LM Response与Client Challenege相同,和不发送LM Response区别不大
lm_response = 8-byte client challenge | 16-bytes-0

上面计算的这个nt_response和lm_response一起被称为Net-NTLMv1 Hash

MSDN关于NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY(wireshark中该标志的名字叫Negotiate Extended Security)解释
(https://msdn.microsoft.com/en-us/library/cc236650.aspx)

1
If set, requests usage of the NTLM v2 session security. NTLM v2 session security is a misnomer because it is not NTLM v2. It is NTLM v1 using the extended session security that is also in NTLM v2. NTLMSSP_NEGOTIATE_LM_KEY and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are mutually exclusive. If both NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and NTLMSSP_NEGOTIATE_LM_KEY are requested, NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY alone MUST be returned to the client. NTLM v2 authentication session key generation MUST be supported by both the client and the DC in order to be used, and extended  session security signing and sealing requires support from the client and the server in order to be used.<25> An alternate name for this field is NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.

MSDN关于session security的解释 (https://msdn.microsoft.com/en-us/library/cc236623.aspx#gt_a765e666-9942-484e-9447-941b79f806ff)

1
session security: The provision of message integrity and/or confidentiality through use of a session key.

关于session security也可以参考 http://davenport.sourceforge.net/ntlm.html#sessionSecurityConcepts , 这里不做太多解释

使用hashcat进行NTLMv1破解时需要提供的格式为username::hostname:LM response:NT response:challenge

下面举例说明Net-NTLMv1 Response的计算

使用的客户端为Win xp(按照0x03设置只发送NTLMv1),服务端为Win 7,客户端访问服务端共享触发认证过程(net use \\192.168.204.143),wireshark抓包分析,因为NTLM Challenge包中设置了Negotiate Extended Security,需要使用第二种方法计算NTLMv1 Response。

下图是NTLMv1 Response的真实值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# -*- coding: utf-8 -*-

import pyDes
import sys
import hashlib

nt_hash = sys.argv[1].decode("hex")
print "nt hash :", nt_hash.encode("hex")
server_challenge = sys.argv[2].decode("hex")
print "server challenge :", server_challenge.encode("hex")

if len(sys.argv) > 3:
# 设置了session_security标志位的情况下,需要使用client_chanllenge计算NTLMv1 response
client_challenge = sys.argv[3].decode("hex")
session_security = True
print "client challenge :", client_challenge.encode("hex")
else:
# 未设置session_security标志位的情况下,只需要使用server_chanllege计算NTLMv1 response
# 网上广为流传的算法
session_security = False


def str2bin(string):
result = bin(int(string.encode('hex'), 16))[2:].rjust(len(string)*8, '0')
return result


def bin2str(string):
result = hex(int(string, 2))[2:].rjust(16, '0').strip("L").decode('hex')
return result


def padding(string):
bin_str = str2bin(string)
bin_str = [bin_str[i:i+7] for i in xrange(0, len(bin_str), 7)]
result = "".join([i.ljust(8, '0') for i in bin_str])
result = bin2str(result)
return result


def des_encrypt(key, data):
des = pyDes.des(key, pyDes.ECB, pad=None)
return des.encrypt(data)


key = nt_hash + "\x00" * 5
k1 = padding(key[:7])
k2 = padding(key[7:14])
k3 = padding(key[14:])

if session_security:
chall = server_challenge + client_challenge
data = hashlib.md5(chall).digest()[:8]
else:
data = server_challenge

response = des_encrypt(k1, data) + des_encrypt(k2, data) + des_encrypt(k3, data)

print response.encode('hex')

运行上面的脚本可以看到,最后计算出的Net-NTLMv1 Response和wireshark中的值相同,为d09841a3706efd45697a00956ba1f32d1abda29e441c843b

1
2
3
4
5
6
7
8
➜ ntlm-study python nthash.py dlivedlivedlive
password plain text: dlivedlivedlive
nt hash: 27b9d12aa127a07b1d03a4a890326aeb
➜ ntlm-study python ntlm_reponse.py 27b9d12aa127a07b1d03a4a890326aeb 72ff3faee30b2be2 23f63969c0e68b94
nt hash : 27b9d12aa127a07b1d03a4a890326aeb
server challenge : 72ff3faee30b2be2
client challenge : 23f63969c0e68b94
d09841a3706efd45697a00956ba1f32d1abda29e441c843b

2.4 Net-NTLMv2

参考:http://davenport.sourceforge.net/ntlm.html#theLmv2Response
响应计算方法

1
2
3
4
5
6
7
8
9
# 不使用LM Hash 
# 加密算法使用HMAC-MD5 替换DES算法
SC = 8-byte server challenge, random
CC = 8-byte client challenge, random
CC* = (X, time, CC, domain name)
v2-Hash = HMAC-MD5(NT-Hash, user name | domain name) # nthash作为算法的key
LMv2 = HMAC-MD5(v2-Hash, SC | CC) # LMv2 Response
NTv2 = HMAC-MD5(v2-Hash, SC | CC*) | CC* # NTv2 Response
Net-NTLMv2 response = LMv2 | NTv2

这个LMv2 Response与NTv2 Response合在一起被称为Net-NTLMv2 Hash

使用hashcat进行NTLMv2破解时需要提供的格式为username::domain:server challenge:ntproofstr:ntv2 response除去ntproofstr之外的部分

使用的客户端为Win xp(按照0x03设置只发送NTLMv2响应),服务端为Win 7,客户端访问服务端共享触发认证过程(net use \\192.168.204.143),wireshark抓包分析。

下图是NTLMv2 Response的真实值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import hmac

sc = "1728a577309f421b".decode("hex")
cc = "6fecd5032e7e415c".decode("hex")
# NTv2 Response中NTProofStr之后的内容
ccsharp = "0101000000000000d6535ec1478ed4016fecd5032e7e415c0000000002001600570049004e002d005700450042002d00500045004e0001001600570049004e002d005700450042002d00500045004e0004001600570049004e002d005700650062002d00500065006e0003001600570049004e002d005700650062002d00500065006e000700080039318ec1478ed4010000000000000000".decode("hex")
user = "dlive"
domain = "WIN-WEB-PEN"
# password: dlivedlivedlive
nt_hash = "27b9d12aa127a07b1d03a4a890326aeb".decode("hex")


def hmac_md5(key, data):
h = hmac.new(key)
h.update(data)
return h.digest()


v2_hash = hmac_md5(nt_hash, user.upper().encode('utf-16le') + domain.encode('utf-16le'))
lmv2 = hmac_md5(v2_hash, sc + cc) + cc
ntv2 = hmac_md5(v2_hash, sc + ccsharp) + ccsharp

print lmv2.encode("hex")
print ntv2.encode("hex")

运行上面的脚本可以看到,最后计算出的Net-NTLMv2 Response和wireshark中的值相同

1
2
3
➜ ntlm-study python ntlmv2_reponse.py
1e37e95b2d1e622c02462df2decebf786fecd5032e7e415c
8e6dcd952a7976a6342d4898afdec4a20101000000000000d6535ec1478ed4016fecd5032e7e415c0000000002001600570049004e002d005700450042002d00500045004e0001001600570049004e002d005700450042002d00500045004e0004001600570049004e002d005700650062002d00500065006e0003001600570049004e002d005700650062002d00500065006e000700080039318ec1478ed4010000000000000000

0x03 挑战响应级别配置

gpedit.msc,设置之后通过gpupdate命令使其立即生效

各个系统默认配置如下

1
2
3
4
5
Windows 2000 以及 Windows XP: 发送 LM & NTLM 响应

Windows Server 2003: 仅发送 NTLM 响应

Windows Vista、Windows Server 2008、Windows 7 以及 Windows Server 2008 R2: 仅发送 NTLMv2 响应

0x04 小插曲 - Net-NTLMv1/v2 Response计算的问题(impacket/smbclient.py NTLM认证过程调试)

其实调代码理解到的比看文章清楚多了,毕竟代码不会骗人,某些文章就不知道了…

在建立SMB会话之前,服务端和客户端首先SMB进行协商,服务端选择一个支持的SMB协议版本进行连接

因为smbclient.py不能让用户自己选择使用smbv1还是smbv2,也不能自己选择使用net-ntlmv1/net-ntlmv2进行认证

所以在我调试0x02中smbv1下的net-ntlmv1认证时,直接修改了smbclient.py代码,强制使用smbv1 & net-ntlmv1

impacket/smbconnection.py 做如下修改,修改协商数据,在协商过程中强制选择SMBv1,第一个红框修改后的negoData,默认值为第二个红框(表示客户端支持SMBv1 & SMBv2,由服务端进行选择)

强制使用Net-NTLMv1进行认证

impacket/smb.py 做如下修改,不再自动选择认证协议,而是直接强制使用NTLMv1

计算Net-NTLMv1 Response (impacket/ntlm.py)

从上图可以看出如果设置了NTLMSSP_NEGOTIATE_LM_KEY,则不发送NTLM response,仅发送LM response,即使用LAN Manager进行认证

MSDN关于该标志位的解释

1
If set, requests LAN Manager (LM) session key computation. NTLMSSP_NEGOTIATE_LM_KEY and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are mutually exclusive. If both NTLMSSP_NEGOTIATE_LM_KEY and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are requested, NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY alone MUST be returned to the client. NTLM v2 authentication session key generation MUST be supported by both the client and the DC in order to be used, and extended session security signing and sealing requires support from the client and the server to be used. An alternate name for this field is NTLMSSP_NEGOTIATE_LM_KEY.

计算Net-NTLMv2 Response (impacket/ntlm.py)

在上方合适的位置下断点即可看到NTLMv1/v2 Response计算的全部过程

0x05 参考

http://davenport.sourceforge.net/ntlm.html#whatIsNtlm
https://www.anquanke.com/post/id/159959
https://medium.com/@petergombos/lm-ntlm-net-ntlmv2-oh-my-a9b235c58ed4
https://xz.aliyun.com/t/2445
https://xz.aliyun.com/t/1943
https://xz.aliyun.com/t/2205
https://klionsec.github.io/2016/08/10/ntlm-kerberos/