windows非域下的LM/NT认证过程

前言:作为windows LM/NTLM认证过程的学习笔记

参考文章:https://daiker.gitbook.io/windows-protocol/ntlm-pian/4
参考文章:https://davenport.sourceforge.net/ntlm.html
参考文章:https://rootclay.gitbook.io/ntlm/ntlm-ren-zheng-xie-yi-yu-ssp-shang
参考文章:https://learn.microsoft.com/en-us/archive/blogs/apgceps/ntlm-2
参考文章:https://learn.microsoft.com/zh-cn/openspecs/windows_protocols/ms-nlmp/c0250a97-2940-40c7-82fb-20d208c71e96

更新_2020_01_16

本地认证流程

这里本地流程是以NTLM认证为准,认证过程为 winlogon.exe -> 接收用户输入 -> lsass.exe -> (认证)

首先,用户注销、重启、锁屏后,操作系统会让winlogon.exe显示登录界面,也就是输入框,接收输入后,将密码交给lsass进程,这个进程中会存一份明文密码,将明文密码加密成NTLM Hash,对SAM数据库比较认证。

Windows Logon Process(即 winlogon.exe):是Windows NT 用户登陆程序,用于管理用户登录和退出。

LSASS:用于微软Windows系统的安全机制。它用于本地安全和登陆策略

LM和NTLM的关系

在NTLM协议问世之前,它的前身就是LM(LAN Manager)协议。

不同点:加密算法

相同点:认证机制,就是上文中说的认证流程

目前大多数的Windows都采用NTLM协议认证,LM协议已经基本淘汰了,这里讲就是当作了解。

LM简介

LM协议的认证流程

早期SMB协议在网络上传输明文口令,后来出现 LAN Manager Challenge/Response 验证机制,简称LM。但是由于LM同样存在一些不安全性,然后才有了后面的NTLM协议以及Kerberos协议

当Client A访问Server B通过LM协议进行身份验证过程,如下所示

这里需要知道下首先假设Server B的密码为WELCOME,Server B已经缓存了密码的LM-HASH(原始密码在任何情况下都不能被缓存),我们通过上面的脚本计算WELCOME的LM-HASH为c23413a8a1e7665faad3b435b51404ee

  • 当Client A对Server B进行访问,Server B向Client A发送了一个随机8个字节大小的挑战码0001020304050607

  • Client A会根据自己的访问Server B的密码明文计算并缓存密码的LM-HASH(Client A缓存输入密码的哈希值),然后在LM-HASH后5个0x00变成c23413a8a1e7665faad3b435b51404ee0000000000,变为21字节,然后划分成三组,每组7字节,如下所示

C23413A8A1E766 | 5FAAD3B435B514 | 04EE0000000000

接着将每组7字节做为参数传递给str_to_key()函数,最终得到三组DES算法密钥KEY,每组KEY大小为8字节

C21A04748A0E9CCC | 5ED4B47642ACD428 | 0476800000000000

然后分别用三组DES算法密钥KEY对8字节挑战0001020304050607进行标准DES算法加密后得到

C21A04748A0E9CCC ---- 对0001020304050607进行标准DES加密 --> CA1200723C41D577

5ED4B47642ACD428 ---- 对0001020304050607进行标准DES加密 --> AB18C764C6DEF34F

0476800000000000 ---- 对0001020304050607进行标准DES加密 --> A61BFA0671EA5FC8

Client A最终获得一个24字节响应CA1200723C41D577AB18C764C6DEF34FA61BFA0671EA5FC8,这个结果被称为response

Client A将CA1200723C41D577AB18C764C6DEF34FA61BFA0671EA5FC8送往Server B,Server B会根据自己缓存的LM-HASH进行同样的计算,并将计算结果与来自A的响应进行比较,如果匹配则身份验证通过

伪代码可以写成下面这个样子,如下所示

C = 8-byte server challenge
K1 | K2 | K3 = LM-Hash | 5-bytes-0
response = DES(K1, C) | DES(K2, C) |  DES(K3, C)

简单的概述就是:首先A保留了自己给B发送的明文密码通过算法生成的LM-HASH,由于A向B发起请求,B会给A返回一个8字节的挑战码,然后A再把自己生成的LM-HASH拆分三组,每组7字节,然后对每组的LM-HASH用挑战码进行DES加密,再把这个值发送给B,B也同样跟A一样用挑战码进行同样的操作,这里B不同的是,缓存的LM-HASH是自己服务器中存储的,而不是A的,这样就起到了区分的作用,然后再比较,如果一样则通过,反之。

LM Hash的生成

这边比如输入的密码为123ABC,该长度未达到7个字符

将密码转化为16进制,分两组,填充为14个字符,空余位使用0x00字符填补,转换的结果就是31323341424300000000000000

将密码分割为两组7个字节的块31323341424300 00000000000000

将每组转化为比特流,这里要转换的为两组

31323341424300 
->(转换为二进制) 110001001100100011001101000001010000100100001100000000

00000000000000
->(转换为二进制) 0

不足56bit的话则在左边加0

110001001100100011001101000001010000100100001100000000 -> 00110001001100100011001101000001010000100100001100000000
0 -> 00000000000000000000000000000000000000000000000000000000

将每组比特流(这边的分7bit为一组)转换为16进制作为被加密的值,使用DES加密,字符串KGS!@#$%为DES算法加密密钥Key(0x4B47532140232425),得到8个结果,每个结果转换为16进制。

DES(00110001001100100011001101000001010000100100001100000000) -> 48D7EB912F5E697C
DES(00000000000000000000000000000000000000000000000000000000) -> AAD3B435B51404EE

前面的结果得到的是48D7EB912F5E697C,由于我们的密码不超过7字节,所以后面的一半是固定的,也就是AAD3B435B51404EE,所以整个加密的结果就是48D7EB912F5E697CAAD3B435B51404EE

代码实现

#coding=utf-8
import re
import binascii
from pyDes import *

def DesEncrypt(str, Des_Key):
    k = des(binascii.a2b_hex(Des_Key), ECB, pad=None)
    EncryptStr = k.encrypt(str)
    return binascii.b2a_hex(EncryptStr)

def group_just(length,text):
    text_area = re.findall(r'.{%d}' % int(length), text)
    text_area_padding = [i + '0' for i in text_area]
    hex_str = ''.join(text_area_padding)
    hex_int = hex(int(hex_str, 2))[2:].rstrip("L")
    if hex_int == '0':
        hex_int = '0000000000000000'
    return hex_int

password = '123ABC'

# 1. 用户的密码转换为大写,密码转换为16进制字符串,不足14字节将会用0来再后面补全

pass_hex = password.upper().encode("hex").ljust(28,'0')

# 2. 密码的16进制字符串被分成两个7byte部分。每部分转换成比特流,并且长度位56bit,长度不足使用0在左边补齐长度
left_str = pass_hex[:14]
right_str = pass_hex[14:]

left_stream = bin(int(left_str, 16)).lstrip('0b').rjust(56, '0') # 
right_stream = bin(int(right_str, 16)).lstrip('0b').rjust(56, '0') # 

# 3. 再分7bit为一组,每组末尾加0,再组成一组
left_stream = group_just(7,left_stream) # 
right_stream = group_just(7,right_stream) # 

# 4. 上步骤得到的二组,分别作为key 为 "KGS!@#$%"进行DES加密。
left_lm = DesEncrypt('KGS!@#$%',left_stream) #
right_lm = DesEncrypt('KGS!@#$%',right_stream) # 

print(left_lm, right_lm)

LM HASH的特点和问题

  • 密码不区分大小写

  • 密码长度最大只能为14个字符,另外如果密码长度不超过7字节,则LM Hash的后8字节是固定值AAD3B435B51404EE

  • DES算法强度不够

知识点:根据LM Hash特征AAD3B435B51404EE,也能够判断用户的密码是否是大于等于7位。

NTLM简介

NTLM使用在Windows NT和Windows 2000 Server(or later)工作组环境中(Kerberos用在域模式下)

在AD域环境中,如果需要认证Windows NT系统,也必须采用NTLM。较之Kerberos,基于NTLM的认证过程要简单很多。NTLM采用一种质询/应答(Challenge/Response)消息交换模式。

NTLM协议的认证,包括NTLMv1和NTLMv2两个版本,其中涉及到NTLM的认证全过程,以及NTLM的EPA(Extended Protection for Authentication)实现。

在本地登录Windows的情况下,操作系统会使用用户输入的密码作为凭证去与系统中的密码进行验证,操作系统中的密码存储在%SystemRoot%\system32\config\sam

当我们登录系统的时候,系统(实际上是LSA组件)会自动地读取SAM文件中的“密码”与我们输入的"密码"进行比对,如果相同,证明认证成功!

这个SAM文件中保留了计算机本地所有用户的凭证信息,可以理解为是一个数据库。

需要注意的是:Windows本身不保存明文密码,只保留密码的Hash。

NTLM Hash与NTLM的关系

在Windows中,密码Hash目前称之为NTLM Hash,其中NTLM认证协议全称是NT LAN Manager

这个NTLM是一种网络认证协议,与NTLM Hash的关系就是:NTLM网络认证协议是以NTLM Hash作为根本凭证进行认证的协议。

也就是说,NTLM与NTLM Hash相互对应。

在本地认证的过程中,其实就是将用户输入的密码转换为NTLM Hash与SAM中的NTLM Hash进行比较。

NTLM Hash的产生过程

NTLM Hash的生成对于NTLMv1还是NTLMv2生成的散列算法都是一样的,NTLMv1和NTLMv2的区别是在于c/r挑战码的不同

假设我的密码是admin,那么操作系统会将admin转换为十六进制,经过Unicode转换后,再调用MD4加密算法加密,这个加密结果的十六进制就是NTLM Hash。

  • admin(密码) -> hex(16进制编码) = 61646d696e
  • 61646d696e -> Unicode(Unicode编码) = 610064006d0069006e00
  • 610064006d0069006e00 -> MD4(MD4算法加密) = 209c6174da490caeb422f3fa5a7ae634

代码生成

import hashlib
print(hashlib.new('md4', "admin".encode("utf-16le")).hexdigest())

NTLM协议的认证流程

NTLM是一种网络认证协议,它是基于挑战(challenge)/响应(Response)认证机制的一种认证模式,NTLM认证中也分为两种,分别是NTLM v1版本和NTLM v2版本。

NTLM协议的认证过程分为三步,如下图所示

  • 协商:主要用于确认双方协议版本(NTLM v1/NTLM V2)

  • 质询:就是挑战(Chalenge)/响应(Response)认证机制起作用的范畴,本小节主要讨论这个机制的运作流程。

  • 验证:验证主要是在质询完成后,验证结果,是认证的最后一步。

这里需要知道一个知识点,关于NTLM协议,它不仅是一种网络认证协议,它更是一种嵌入式协议,所以它支持嵌入HTTP协议(比如在exchange),嵌入SMB协议等等,如下图所示

这边在协商type1的包中可以看到这个过程是通过IPC来进行通信的过程,可以看到,NTLM部分就是完全嵌入到SMB协议中来进行通信的

NTLM协议type3的响应类型

另外一个知识点就是关于type3的响应类型,在Response(Type3)消息中,有6种类型的响应他们使用的加密流程一样,都是Challenge/Response验证机制,不同之处在于Challenge值和加密算法不同,选择那个版本的响应由LmCompatibilityLevel决定,这6种类型的响应方式借用daiker师傅的图来进行展示,如下图所示

  • LM(LAN Manager)响应
  • NTLM v1响应
  • NTLM v2响应
  • LMv2响应
  • NTLM2会话响应
  • 匿名响应。

NTLM协议LmCompatibilityLevel字段

另外关于LmCompatibilityLevel的查看,这边我拿了一台win server2012的服务器,但是发现LmCompatibilityLevel的值在组策略为空白

不过这边是已经规定好了的,各个系统版本默认LmCompatibilityLevel值如下所示,所以在win2012种的话应该是"仅发送 NTLMv2 响应"

  • Windows 2000 以及 Windows XP: 发送 LM & NTLM 响应

  • Windows Server 2003: 仅发送 NTLM 响应

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

协商

客户端向服务器发送Type 1消息,它主要包含客户端支持和服务器请求的功能列表。

客户端向服务器端发送用户信息(用户名)请求 (注:我不知道这么说准不准确,因为实际下图中在抓包中没看到第一步有传输相关的用户信息)

质询

服务器以Type 2消息响应。这包含服务器支持和同意的功能列表。

在Type 2消息响应中,最重要的是它包含服务器产生的challenge。服务器端接受到协商请求,生成一个8字节的随机数,被称之为Challenge,使用客户端要登录用户名对应的NTLM Hash加密Challenge(8字节)生成Response,并且保存到内存中(这里的Response可以称之为Net NTLM Hash)。同时,生成Response后,将Challenge(8字节)发送给客户端。

身份验证

参考文章:https://learn.microsoft.com/en-us/archive/blogs/apgceps/ntlm-2

上面提到过关于NTLM协议Type3的响应类型,因为这边演示的机器的话都是2008及以上的系统,所以这边NTLM响应实际上走的是NTLMv2响应

  • 客户端收到来自服务器的8字节随机挑战值,用SC表示;客户端也会产生8字节的随机数,用CC表示。

这张图是后期补的,主要的是体现NTLMv2中的客户端在身份验证Type3中自己还生成了8个字节的Challenge

  • 客户端由密码、当前时间、CC和服务器的域名共同组成CC*。

  • 计算v2-Hash,Hash算法为HMAC-MD5,使用v1版本中的NT Hash最为Key,账号名、服务器的域名最为输入值。

  • 以v2-Hash为key,Hash算法为HMAC-MD5,SC,CC作为输入值,计算LMv2。

  • 以v2-Hash为key,Hash算法为HMAC-MD5,SC,CC*作为输入值,计算NTv2。

  • 将LMv2、CC、NTv2、CC*连接,组成NTLMv2的响应报文。

其中经过NTLM Hash加密Challenge的结果Response,在网络协议中称之为Net-NTLM hash v2。

关于Net-NTLMv1和Net-NTLMv2中的Chanllenge的区别

上面演示的身份验证type3走的是NTLM中的v2版本,这边了解了关于type3中Net-NTLMv1和Net-NTLMv2不同点在哪里

在NTLM的"协商"的过程中,双方需要确定使用的协议版本,NTLM响应分为NTLM v1/NTLMv2两种协议,不同协议使用不同格式的Challenge和加密算法,所以也就存在不同协议的Net-NTLM hash,即Net-NTLM v1 Hash,Net-NTLM v2 Hash。

Net-NTLMv1大致流程

  • 客户端收到来自服务器的8字节随机挑战值Server Challenge

  • 将16字节的LM Hash添加5字节的0,共21字节,分成3个7字节组,分别标记为K1、K2、K3,后续作为DES密钥使用。

  • 将K1、K2、K3的每个字节后添加1比特"0",并在最后补齐1比特"0",变成三个8字节组,成为DES密钥(64比特)。

  • 分别将K1、K2、K3作为密钥,对Server Challenge进行DES加密,并连接成为response1。

  • 将16字节的NT Hash添加5字节的0,共21字节,分成3个7字节组,分别标记为K4、K5、K6,后续作为DES密钥使用。

  • 分别将K4、K5、K6作为密钥,对随机挑战值C进行DES加密,并连接成为response2。

  • response1连接response2,共同组成响应认证报文,也称NTLM NET-HASH

上述过程的算法伪码表示如下

C=8-byte server challenge,random

K1|K2|K3=LM-Hash|5-byte-0

response1=DES(K1,C)|DES(K2,C)|DES(K3,C)

K4|K5|K6=NT-Hash|5-byte-0

response2=DES(K4,C)|DES(K5,C)|DES(K6,C)

response=response1+response2

Net-NTLMv2大致流程

  • 客户端收到来自服务器的8字节随机挑战值,用SC表示;客户端也会产生8字节的随机数,用CC表示。

  • 客户端由口令明文 、当前时间、 CC和服务器的域名共同组成CC*,计算v2-Hash,Hash算法为HMAC-MD5,使用v1版本中的NT Hash最为Key,账号名、服务器的域名最为输入值。

  • 以v2-Hash为key,Hash算法为HMAC-MD5,SC,CC作为输入值,计算LMv2。

  • 以v2-Hash为key,Hash算法为HMAC-MD5,SC,CC*作为输入值,计算NTv2。

  • 将LMv2、CC、NTv2、CC*连接,组成NTLMv2的响应报文。

response=LMv2|CC|NTv2|CC*

Net-NTLMv1/v2不同点总结

NTLM协议中的Session Key字段

Session Key字段是用来进行NTLM协商加密秘钥的

参考impacket.ntlm.getNTLMSSPType3流程,这边的话首先先生成一个sessionBaseKey,该sessionBaseKey由hmac_md5(responseKeyNT, ntProofStr)组成

接着通过该sessionBaseKey来生成一个keyExchangeKey,这个keyExchangeKey作为后续通信RC4加密算法的密钥Key

keyExchangeKey = KXKEY(ntlmChallenge['flags'], sessionBaseKey, lmResponse, ntlmChallenge['challenge'], password, lmhash, nthash, use_ntlmv2)

客户端会随机生成一个exportedSessionKey(客户端生成的随机的16字节),后续使用来加解密流量,因为是客户端生成的,所以一开始服务端并不知道。

客户端使用keyExchangeKey作为RC4加密算法的密钥Key,用RC4加密算法加密exportedSessionKey得到encryptedRandomSessionKey字段

这个encryptedRandomSessionKey字段就是上面图中流量中看到的Session Key字段,服务端拿到这个的话encryptedRandomSessionKey,用keyExchangeKey解密即可得到exportedSessionKey,然后使用exportedSessionKey进行客户端进行加解密通信。

这里可能有疑问服务端为什么可以拿到keyExchangeKey,因为这个字段是可控的,通过相关的算法和已知的用户hash即可计算出来

上面整个步骤对于攻击者来说由于没有用户hash,也就没办法生成keyExchangeKey,虽然在流量里面能够拿到encryptedRandomSessionKey,但是没有keyExchangeKey,也就没办法运算出exportedSessionKey,也就没法对流量进行加解密。

具体加密流程参考:https://github.com/fortra/impacket/blob/master/impacket/ntlm.py

这边抓个包测试下encryptedRandomSessionKey是否就是流量中看到的Session Key字段,这边断下运行如下所示,可以看到是相同的

session key在NTLMv1和NTLMv2的生成算法

# For NTLMv1
Key = MD4(NT Hash)

# For NTLMv2
NTLMv2 Hash = HMAC_MD5(NT Hash, Uppercase(Username) + UserDomain)
Key = HMAC_MD5(NTLMv2 Hash, HMAC_MD5(NTLMv2 Hash, NTLMv2 Response + Challenge))

NTLM协议中的MIC字段

参考文章:https://learn.microsoft.com/zh-cn/openspecs/windows_protocols/ms-nlmp/c0250a97-2940-40c7-82fb-20d208c71e96

对于计算MIC(Message Integrity Check)值的计算是通过HMAC-MD5算法对exported_session_key(客户端生成的随机的16字节)和三个消息(NEGOTIATE_MESSAGE, CHALLENGE_MESSAGE, AUTHENTICATE_MESSAGE)的串联进行哈希。

Set MIC to HMAC_MD5(ExportedSessionKey, ConcatenationOf(NEGOTIATE_MESSAGE, CHALLENGE_MESSAGE, AUTHENTICATE_MESSAGE))

posted @ 2019-10-25 16:51  zpchcbd  阅读(1680)  评论(0编辑  收藏  举报