Lamber's Blog

Windows下基于NTLM的认证流程解读

字数统计: 4.4k阅读时长: 19 min
2023/11/27

NT(New Technology) LAN(Local Area Network) Manager Authentication Protocol

零、序

首先需要讲解一下非常容易与NTLM协议混淆在一起的那些Hash名称

根据年代不同, 密码哈希的存储方式也会有所不同, 并且这些哈希名称和NTLM身份认证协议这个名称也十分相似(比如说: Net-NTLMv2 Hash和Net-NTLMv2, 前者是密码Hash, 而后者则是身份认证协议)

过去: 我们为了解决用户需求,现在有14个标准正在运行。
设计师A: 14个?太荒谬了吧???我们需要制定一个能解决所有人问题的通用标准
设计师B: 确实!
现在: 我们成功的从14个标准减少到了15个标准
研究员: 6

简单来说,它们的关系如下:

这是密码Hash的种类 这是身份认证协议
LM Hash
NT Hash(A.K.A NTLM Hash)
Net-NTLMv1 Hash(A.K.A. NTLMv1 Hash)
Net-NTLMv2 Hash(A.K.A. NTLMv2 Hash)
NTLM(包含如下两个版本)
NTLMv1
NTLMv2

而不同的认证方式,所涉及到的Hash种类也不同,它们的关系如下:

Windows本地认证 Windows网络认证
LM Hash
NT Hash(A.K.A NTLM Hash)
Net-NTLMv1 Hash(A.K.A. NTLMv1 Hash)
Net-NTLMv2 Hash(A.K.A. NTLMv2 Hash)

一、Windows本地认证介绍

用户输入密码 -> winlogon.exe -> lsass.exe -> SAM

用户登陆 Windows 时,Windows 首先会调用 winlogon.exe 进程接收用户输入的密码,之后密码会被传递给 lsass.exe 进程,进程会先在内存中存储一份明文密码,并将密码加密为 NTLM hash,与本地 SAM 数据库中用户的 NTLM hash 对比,一致则登陆成功。

LM Hash – 1987年

LM Hash是Windows使用的最古老的密码存储方式,非常容易被破解,通常位于Windows系统中SAM数据库里面或者域控中NTDS数据库里面。

e.g. LM-Hash.txt(pass: password)

1
e52cac67419a9a224a3b108f3fa6cb6d

LM Hash加密

  1. 将用户密码中所有的小写字母转换为大写,并转换为16进制字符串
  2. 如果转换后的16进制字符串的长度不足14字节,用0补全。
  3. 然后将这14字节分为两组,每组7个字节,然后转为二进制数据,每组二进制数据长度为 7*8 = 56bit 然后每组二进制数据按7bit为一组,分为8组,每组末尾加0又补为8bit,然后转为16进制,这样每组就变成了8字节的16进制数据
  4. 然后用上面生成的两组8字节的16进制数据作为密钥,分别对KGS!@#$%这串明文进行DES加密(传闻可能是Key of Glen and Steve and then the combination of Shift + 12345.)
  5. 然后将DES加密之后的两组密文进行拼接,得到最终的LM Hash值。

用python实现一下LM Hash生成流程(见附录:LM Hash加密脚本)

LM Hash爆破

使用常见hash爆破工具john进行爆破

1
john --format=lm LM-Hash.txt

NT Hash – 1993

这是现代 Windows 系统上存储密码的方式,可以通过转储 SAM 数据库或使用 Mimikatz 来获取。它们还存储在域控制器上的 NTDS.dit 文件中。NTLM Hash可以用来进行哈希传递攻击(Pass The Hash)。

e.g. NT-Hash.txt(pass: 123456)

1
32ed87bdb5fdc5e9cba88547376818d4

e.g. 通过hashdump的方式抓取到的hash(hashdump.txt)

1
2
3
Administrator:500:aad3b435b51404eeaad3b435b51404ee:32ed87bdb5fdc5e9cba88547376818d4:::

用户名:用户SID值:LM Hash:NT Hash:::

NT Hash加密

  1. 先将用户密码转换为16进制格式
  2. 然后将16进制格式的密码转为unicode编码
  3. 然后将unicode编码用md4算法加密生成哈希值

用python实现一下NT Hash生成流程(见附录:NT Hash加密脚本)

NT Hash爆破

单独爆破NT Hash部分

1
john --format=nt NT-Hash.txt

爆破hashdump的方式抓取到的hash(hashdump.txt)

LM Hash和NT Hash的区别

  • LM Hash从Windows Vista/Windows Server 2008开始就已经默认关闭了,但是可以在GPO中启用它
  • LM Hash不区分大小写, NT Hash区分大小写。
  • LM Hash字符集有限,只有142个字符,NT Hash支持整个unicode字符集(65535个字符)
  • LM Hash将用户密码按7个字符为一组拆分成两组,并且会用0填充不足的部分,NT Hash根据用户输入的整个密码计算Hash值。

LM Hash是一种非常弱的单向函数,用于存储密码。LM 哈希最初是为 LAN Manager 操作系统发明的,后来被包含在Windows NT中以实现向后兼容性。

二、Windows网络认证介绍

为了避免在客户端和服务端进行身份验证的时候,直接通过网络直接发送用户的哈希值。所以发明了Net-NTLM协议,用来进行身份验证

NTLM协议流程

NTLM是一种基于质询(Challenge)和响应(Response)的协议,由以下三种消息组成:

  1. Type 1 – 协商(Negotiation)
  2. Type 2 – 质询(Challenge)
  3. Type 3 – 认证(Authentication)

NTLM协议的整体流程大致如下:
0. 用户输入用户名和密码

  1. 客户端发起与服务器的协商请求(Negotiate protocol request),类似于握手过程

  2. 服务端响应,然后身份验证流程开始

  3. 然后客户端向服务端发起协商请求(Negotiation),该请求还包含有关客户端的一些信息

  4. 服务器端向客户端发起质询(Challenge)请求,里面包含NTLM Server Challenge

  5. 客户端接收到 Challenge 后,使用登录用户的密码 hash 对 Challenge 加密,作为 response 发送给服务器

  6. 服务器校验 response

NTLM协议有两个版本

  • NTLMv1
    • Server Challenge: 8 Byte
    • Net-NTLMv1 Hash: DES加密
  • NTLMv2
    • Server Challenge: 16 Byte
    • Net-NTLMv2 Hash: HMAC-MD5加密(Ps: HMAC-MD5与普通MD5相比,简单说就是多了个Key)

Net-NTLMv1 Hash(又被称为 NTLMv1 Hash) – 1993年

服务器发送8个字节的随机数(Server Challenge)来验证客户端身份

Net-NTLMv1 Hash的结构

1
username:hostname:LM responce:NTLM responce:challenge

e.g. Net-NTLMv1 Hash

1
u4-netntlm::kNS:338d08f8e26de93300000000000000000000000000000000:9526fb8c23a90751cdd619b6cea564742e1e4bf33006ba41:cb8086049ec4736c

Net-NTLMv1 Hash加密

Net-NTLMv2 Hash(又被称为 NTLMv2 Hash) – 1998年

Net-NTLMv2 Hash的结构

1
[User name]::[Domain name]:[NTLM Server Challenge]:[HMAC-MD5]:[blob]

e.g. Net-NTLMv2 Hash:

1
jack::WidgetLLC.Internal:9b5afa46ef7bd599:ad3157225b1766669a2a61c158c47db8:010100000000000071ac95fe34edd90140a80b1bed9758f400000000020012005700490044004700450054004c004c0043000100100043004c00490045004e00540030003300040024005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c000300360043004c00490045004e005400300033002e005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c00050024005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c000700080071ac95fe34edd90106000400020000000800300030000000000000000000000000300000c78e803920758ec5672c36696ee163f6a4e61c8b5463c247daef8571677995a40a001000000000000000000000000000000000000900200053004d00540050002f0075006e007300700065006300690066006900650064000000000000000000

Net-NTLMv2 Hash加密

弱口令判断逻辑
通过sensor log的

  • server Chanllge
  • ntlmv2_response
  • username
  • domain name
  • 结合ntlm hash(遍历弱口令列表)
    算出一个值,来和流量中的NTPoofStr对比,若相等则判断为弱口令。

具体加密算法如下

  1. username转换成大写,之后进行unicode编码,之后拼接domain name,使用HMAC-MD5算法,NTLM hash为key,计算出一个NTLMv2 hash。
  2. 接下来使用server Chanllge拼接上ntlmv2_response 16个字节之后的内容,使用HMAC-MD5算法算法,NTLMv2 hash作为key,算出来一个值,这个值就是流量中的NTProofStr。

python脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import hmac  
import base64
nt_hash = bytes.fromhex("034BE0F84BFE8335A0F1AA8CE9436059")
server_challenge = bytes.fromhex("A05764F6FF022B19")
username="yangcw"
username_unicode=username.upper().encode('utf-16le')
domain_name="haitongauto.com"
domain_name_unicode=domain_name.encode('utf-16le')
NTPoofStr=bytes.fromhex("021EDA73B17D5A8B29754319F77A578A")
ntlmv2_response=bytes.fromhex("021EDA73B17D5A8B29754319F77A578A0101000000000000ED59C8FC9FEAD8014F64C5950CB75DA3000000000200160048004100490054004F004E0047004100550054004F0001001800450058004300480041004E0047004500430041005300310004001E0068006100690074006F006E0067006100750074006F002E0063006F006D0003003800450058004300480041004E004700450043004100530031002E0068006100690074006F006E0067006100750074006F002E0063006F006D0005001E0068006100690074006F006E0067006100750074006F002E0063006F006D0007000800ED59C8FC9FEAD801060004000200000008003000300000000000000001000000002000009370CB858F2C3B35E343CDB26D975A518981ACAC80A3CD7765E5C44B4D3FA0A90A001000000000000000000000000000000000000900220070006F0070002F003100390032002E003100360038002E00360031002E00370031000000000000000000")
blob=bytes.fromhex(server_challenge.hex()+ntlmv2_response[16:].hex())
NTLMv2_hash = hmac.new(nt_hash, bytes.fromhex(username_unicode.hex()+domain_name_unicode.hex()),digestmod="md5")
result = hmac.new(bytes.fromhex(NTLMv2_hash.hexdigest()),blob,digestmod="md5")
if result.hexdigest()==NTPoofStr.hex():
     print(username+"为弱口令")

Net-NTLMv2 Hash爆破

Net-NTLMv2 Hash爆破(其余Hash爆破方式类似)
使用工具john进行爆破:

1
john --wordlist=/usr/share/wordlists/rockyou.txt --format=netntlmv2 Net-NTLMv2-Hash.txt

将Net-NTLMv2 Hash先写入一个文件, 然后使用john进行爆破, 爆破结果比较明显, 如图所示:

使用工具hashcat进行爆破

1
hashcat -m 5600 "jack::WidgetLLC.Internal:9b5afa46ef7bd599:ad3157225b1766669a2a61c158c47db8:010100000000000071ac95fe34edd90140a80b1bed9758f400000000020012005700490044004700450054004c004c0043000100100043004c00490045004e00540030003300040024005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c000300360043004c00490045004e005400300033002e005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c00050024005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c000700080071ac95fe34edd90106000400020000000800300030000000000000000000000000300000c78e803920758ec5672c36696ee163f6a4e61c8b5463c247daef8571677995a40a001000000000000000000000000000000000000900200053004d00540050002f0075006e007300700065006300690066006900650064000000000000000000" /usr/share/wordlists/rockyou.txt --force

爆破结果会附加在Net-NTLMv2 Hash之后, 如图所示:

Net-NTLMv1 Hash 与 Net-NTLMv2 Hash的区别

  • Net-NTLMv1 Hash使用DES加密,Net-NTLMv2 Hash使用HMAC-MD5加密

三、搭建NTLM协议认证环境进行流量分析

所有Windows虚拟机镜像都在微软官网进行下载,没有在任何第三方渠道进行下载。完整域环境搭建,请参考另一篇文章。

域控:
DC01

  • 内网IP地址: 192.168.0.1
  • 操作系统: Windows Server 2016 Standard Evaluation
    域成员:
    Client01
  • 内网IP地址: 192.168.0.3
  • 操作系统: Windows10
  • 在该篇文章中所扮演的角色: Wireshark(在此台机器上进行抓取流量包的操作)
    Client02
  • 内网IP地址: 192.168.0.4
  • 操作系统: Windows Server 2012 R2 Standard Evaluation
  • 在该篇文章中所扮演的角色: IIS Web Server
    Client03
  • 内网IP地址: 192.168.0.5
  • 操作系统: Windows Server 2016 Standard Evaluation
  • 在该篇文章中所扮演的角色: ExchangeServer2016-x64-cu11

1. 在DC01上创建测试用账户

在域控 DC01 上,添加账户jack,密码123456。(此处使用弱密码是因为更改过域内策略,如果无法使用弱密码,就按照要求使用强密码即可)
服务器管理器 -> 工具 -> Active Directory用户和计算机 -> Users -> 新建 -> 用户

2. 在Client02上搭建IIS Web Server

在Client02上搭建IIS Web服务器并且启用NTLM认证,步骤如下:

首先安装IIS服务,服务管理器 -> 管理 -> 添加角色和功能 -> … -> 服务器角色 -> Web服务器(IIS) -> … -> 角色服务 -> Windows身份验证。中间省略掉的部分则采用默认值即可。

然后启用Windows身份验证,步骤如下:

服务管理器 -> 工具 -> Internet Information Services (IIS)管理器 -> CLIENT02 -> Default Web Site -> 身份验证

注意,这里将 匿名身份验证 禁用,将 Windows 身份验证 启用。目的是为了让用户在访问这个Web服务器的时候,必须要先经过 Windows 身份验证,也就是这篇文章所述的NTLM认证

3. 在Client03上搭建ExchangeServer

在Client03上搭建ExchangeServer2016-x64-cu11服务,用来进行SMTP协议中的NTLM认证
首先根据上面提供的链接,下载ExchangeServer,然后直接安装即可,中途会反复遇到几次报错,按照报错提示,安装缺失的软件即可。

安装完毕之后,打开 Exchange Management Shell 使用如下命令检查pop3服务的状态

1
Get-Service MSExchangePOP3; Get-Service MSExchangePOP3BE

如果显示 Stop 则需要手动开启一下 pop3服务

1
Start-Service MSExchangePOP3; Start-Service MSExchangePOP3BE

四、各种场景下Net-NTLM认证的流量分析

1、在SMB协议中使用NTLM认证

在Client01机器的命令行中,使用 net use \\192.168.0.1 /user:jack 123456 命令进行认证。
Wireshark使用过滤器 ip.addr == 192.168.0.1 && ip.addr == 192.168.0.3 抓取干净完整的流量包,可以看到在SMB协议中,NTLM认证过程如下所示:

2、在HTTP协议中使用NTLM认证

打开Client01机器的浏览器,访问环境搭建中在Client02里搭建好的IIS Web Server。在本示例中的地址为 http://192.168.0.4
Wireshark使用过滤器 ip.addr == 192.168.0.3 && ip.addr == 192.168.0.4 抓取干净完整的流量包,可以看到在HTTP协议中,NTLM认证过程如下所示:

3、在SMTP协议中使用NTLM认证

使用NTLM认证的方式向搭建好的Exchange服务器发送邮件

用python写了一个在SMTP服务中发起NTLM认证通信的脚本(见附录:NTLM认证通信脚本)

运行脚本,抓取流量

认证流量的文本如下(在SMTP协议认证中,NTLM认证过程是将数据通过base64编码传递的):

1
2
3
4
5
6
7
Client-negotiation: 
TlRMTVNTUAABAAAAt4II4gAAAAAAAAAAAAAAAAAAAAAKAGFKAAAADw==

Server-challenge: TlRMTVNTUAACAAAAEgASADgAAAA1gonim1r6Ru971ZkAAAAAAAAAAMQAxABKAAAACgA5OAAAAA9XAEkARABHAEUAVABMAEwAQwACABIAVwBJAEQARwBFAFQATABMAEMAAQAQAEMATABJAEUATgBUADAAMwAEACQAVwBpAGQAZwBlAHQATABMAEMALgBJAG4AdABlAHIAbgBhAGwAAwA2AEMATABJAEUATgBUADAAMwAuAFcAaQBkAGcAZQB0AEwATABDAC4ASQBuAHQAZQByAG4AYQBsAAUAJABXAGkAZABnAGUAdABMAEwAQwAuAEkAbgB0AGUAcgBuAGEAbAAHAAgAcayV/jTt2QEAAAAA

Client-authentication:
TlRMTVNTUAADAAAAGAAYAJQAAABoAWgBrAAAACQAJABYAAAACAAIAHwAAAAQABAAhAAAABAAEAAUAgAANYKI4goAYUoAAAAPlpzQGvcgZYNK+bFD8MLj4lcAaQBkAGcAZQB0AEwATABDAC4ASQBuAHQAZQByAG4AYQBsAGoAYQBjAGsAQwBMAEkARQBOAFQAMAAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK0xVyJbF2ZmmiphwVjEfbgBAQAAAAAAAHGslf407dkBQKgLG+2XWPQAAAAAAgASAFcASQBEAEcARQBUAEwATABDAAEAEABDAEwASQBFAE4AVAAwADMABAAkAFcAaQBkAGcAZQB0AEwATABDAC4ASQBuAHQAZQByAG4AYQBsAAMANgBDAEwASQBFAE4AVAAwADMALgBXAGkAZABnAGUAdABMAEwAQwAuAEkAbgB0AGUAcgBuAGEAbAAFACQAVwBpAGQAZwBlAHQATABMAEMALgBJAG4AdABlAHIAbgBhAGwABwAIAHGslf407dkBBgAEAAIAAAAIADAAMAAAAAAAAAAAAAAAADAAAMeOgDkgdY7FZyw2aW7hY/ak5hyLVGPCR9rvhXFneZWkCgAQAAAAAAAAAAAAAAAAAAAAAAAJACAAUwBNAFQAUAAvAHUAbgBzAHAAZQBjAGkAZgBpAGUAZAAAAAAAAAAAAB9PQ7SVTdDE/LiHVir1drI=

可以看到,wireshark并未像之前几种情况一样,对Net-NTLM通信过程进行字段解析。

尝试根据Net-NTLM协议的RFC,构造一个提取Net-NTLMv2 Hash的脚本,模拟wireshark解析字段的过程。

Net-NTLMv2 Hash的结构如下

1
[User name]::[Domain name]:[NTLM Server Challenge]:[HMAC-MD5]:[blob]

根据RFC规范,去提取所需要的字段

User name: 查阅上图的The Type 3 Message可以看到,User name的security buffer在36字节到44字节这个位置。继续查阅RFC规范:

byte example description
36 0x080008004c000000 User Name Security Buffer:

Length: 8 bytes (0x0800)
Allocated Space: 8 bytes (0x0800)
Offset: 76 bytes (0x4c000000)

发现在User name的这8个字节中,前两个字节代表User name的长度(A),后四个字节代表User name的偏移(B)。也就是说,在NTLM通信数据中,从第B个字节开始,数A个字节长度,就能得到User name这个字段的值。同理,按照同样的方法,查阅上图的The Type 3 Message也能取到Domain name。

然后我们取NTLM Server Challenge的值,在The Type 2 Message中,可以看到NTLM Server Challenge是从第24个字节开始,往后数8个字节。

最后取[HMAC-MD5]:[blob]部分,根据The Type 3 Message取到NTLM Response的值,然后以0101数据开始的部分为blob部分,0101之前的数据为 HMAC-MD5(NTProofStr)的值。
例如取到的NTLM Response的值为

1
ad3157225b1766669a2a61c158c47db8010100000000000071ac95fe34edd90140a80b1bed9758f400000000020012005700490044004700450054004c004c0043000100100043004c00490045004e00540030003300040024005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c000300360043004c00490045004e005400300033002e005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c00050024005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c000700080071ac95fe34edd90106000400020000000800300030000000000000000000000000300000c78e803920758ec5672c36696ee163f6a4e61c8b5463c247daef8571677995a40a001000000000000000000000000000000000000900200053004d00540050002f0075006e007300700065006300690066006900650064000000000000000000

则HMAC-MD5(NTProofStr)为:

1
ad3157225b1766669a2a61c158c47db8

blob为:

1
010100000000000071ac95fe34edd90140a80b1bed9758f400000000020012005700490044004700450054004c004c0043000100100043004c00490045004e00540030003300040024005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c000300360043004c00490045004e005400300033002e005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c00050024005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c000700080071ac95fe34edd90106000400020000000800300030000000000000000000000000300000c78e803920758ec5672c36696ee163f6a4e61c8b5463c247daef8571677995a40a001000000000000000000000000000000000000900200053004d00540050002f0075006e007300700065006300690066006900650064000000000000000000

根据协议规范写了一个提取Net-NTLMv2 Hash的脚本(Net-NTLMv2_Hash_extract.py),输入authentication和challenge即可提取出Net-NTLMv2 Hash。(见附录:[Net-NTLMv2 Hash提取](#Net-NTLMv2 Hash提取))

运行脚本:

1
python Net-NTLMv2_Hash_extract.py --authentication TlRMTVNTUAADAAAAGAAYAJQAAABoAWgBrAAAACQAJABYAAAACAAIAHwAAAAQABAAhAAAABAAEAAUAgAANYKI4goAYUoAAAAPlpzQGvcgZYNK+bFD8MLj4lcAaQBkAGcAZQB0AEwATABDAC4ASQBuAHQAZQByAG4AYQBsAGoAYQBjAGsAQwBMAEkARQBOAFQAMAAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK0xVyJbF2ZmmiphwVjEfbgBAQAAAAAAAHGslf407dkBQKgLG+2XWPQAAAAAAgASAFcASQBEAEcARQBUAEwATABDAAEAEABDAEwASQBFAE4AVAAwADMABAAkAFcAaQBkAGcAZQB0AEwATABDAC4ASQBuAHQAZQByAG4AYQBsAAMANgBDAEwASQBFAE4AVAAwADMALgBXAGkAZABnAGUAdABMAEwAQwAuAEkAbgB0AGUAcgBuAGEAbAAFACQAVwBpAGQAZwBlAHQATABMAEMALgBJAG4AdABlAHIAbgBhAGwABwAIAHGslf407dkBBgAEAAIAAAAIADAAMAAAAAAAAAAAAAAAADAAAMeOgDkgdY7FZyw2aW7hY/ak5hyLVGPCR9rvhXFneZWkCgAQAAAAAAAAAAAAAAAAAAAAAAAJACAAUwBNAFQAUAAvAHUAbgBzAHAAZQBjAGkAZgBpAGUAZAAAAAAAAAAAAB9PQ7SVTdDE/LiHVir1drI= --challenge TlRMTVNTUAACAAAAEgASADgAAAA1gonim1r6Ru971ZkAAAAAAAAAAMQAxABKAAAACgA5OAAAAA9XAEkARABHAEUAVABMAEwAQwACABIAVwBJAEQARwBFAFQATABMAEMAAQAQAEMATABJAEUATgBUADAAMwAEACQAVwBpAGQAZwBlAHQATABMAEMALgBJAG4AdABlAHIAbgBhAGwAAwA2AEMATABJAEUATgBUADAAMwAuAFcAaQBkAGcAZQB0AEwATABDAC4ASQBuAHQAZQByAG4AYQBsAAUAJABXAGkAZABnAGUAdABMAEwAQwAuAEkAbgB0AGUAcgBuAGEAbAAHAAgAcayV/jTt2QEAAAAA

输出结果:

1
2
3
4
5
6
7
8
生成的Net-NTLMv2 Hash为:

jack::WidgetLLC.Internal:9b5afa46ef7bd599:ad3157225b1766669a2a61c158c47db8:010100000000000071ac95fe34edd90140a80b1bed9758f400000000020012005700490044004700450054004c004c0043000100100043004c00490045004e00540030003300040024005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c000300360043004c00490045004e005400300033002e005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c00050024005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c000700080071ac95fe34edd90106000400020000000800300030000000000000000000000000300000c78e803920758ec5672c36696ee163f6a4e61c8b5463c247daef8571677995a40a001000000000000000000000000000000000000900200053004d00540050002f0075006e007300700065006300690066006900650064000000000000000000

使用hashcat进行爆破(需要自行安装hashcat):
hashcat -m 5600 "Net-NTLMv2 Hash" wordlist.txt --force
或者使用john进行爆破(需要自行安装john):
john --wordlist=wordlist.txt --format=netntlmv2 Net-NTLMv2-Hash.txt

4、在POP3协议中使用NTLM认证

与SMTP协议类似,但是在此域环境的配置下有问题,不支持NTLM认证,在这里先放一个完整的认证流量以作示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+OK The Microsoft Exchange POP3 service is ready.
CAPA
+OK
TOP
UIDL
SASL NTLM GSSAPI PLAIN
USER
STLS
.
AUTH NTLM
+
TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAKAPBVAAAADw==
+ TlRMTVNTUAACAAAAFgAWADgAAAAFgomiamT/6VDuVecAAAAAAAAAAMYAxgBOAAAABgGxHQAAAA9IAEEASQBUAE8ATgBHAEEAVQBUAE8AAgAWAEgAQQBJAFQATwBOAEcAQQBVAFQATwABABgARQBYAEMASABBAE4ARwBFAEMAQQBTADEABAAeAGgAYQBpAHQAbwBuAGcAYQB1AHQAbwAuAGMAbwBtAAMAOABFAFgAQwBIAEEATgBHAEUAQwBBAFMAMQAuAGgAYQBpAHQAbwBuAGcAYQB1AHQAbwAuAGMAbwBtAAUAHgBoAGEAaQB0AG8AbgBnAGEAdQB0AG8ALgBjAG8AbQAHAAgAzTOA+Z/q2AEAAAAA
TlRMTVNTUAADAAAAGAAYAJQAAABsAWwBrAAAAB4AHgBYAAAADAAMAHYAAAASABIAggAAAAAAAAAYAgAABYKIogoA8FUAAAAPYdznrTVtDUxmF1WtBfjr9GgAYQBpAHQAbwBuAGcAYQB1AHQAbwAuAGMAbwBtAHkAYQBuAGcAYwB3AEgAVAAtAEkAVAAtAEwATQBKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN7wrwZiAOdQVt8u+bBLSKwBAQAAAAAAAM0zgPmf6tgBDBnp1aIuEEQAAAAAAgAWAEgAQQBJAFQATwBOAEcAQQBVAFQATwABABgARQBYAEMASABBAE4ARwBFAEMAQQBTADEABAAeAGgAYQBpAHQAbwBuAGcAYQB1AHQAbwAuAGMAbwBtAAMAOABFAFgAQwBIAEEATgBHAEUAQwBBAFMAMQAuAGgAYQBpAHQAbwBuAGcAYQB1AHQAbwAuAGMAbwBtAAUAHgBoAGEAaQB0AG8AbgBnAGEAdQB0AG8ALgBjAG8AbQAHAAgAzTOA+Z/q2AEGAAQAAgAAAAgAMAAwAAAAAAAAAAEAAAAAIAAAk3DLhY8sOzXjQ82ybZdaUYmBrKyAo813ZeXES00/oKkKABAAAAAAAAAAAAAAAAAAAAAAAAkAIgBwAG8AcAAvADEAOQAyAC4AMQA2ADgALgA2ADEALgA3ADEAAAAAAAAAAAA=
+OK User successfully logged on.
STAT
+OK 251 190008736
QUIT
+OK Microsoft Exchange Server 2010 POP3 server signing off.

5、在LDAP协议中使用NTLM认证

首先用ldp命令打开ldap客户端,然后选择 连接 -> 绑定 -> 与凭据绑定

可以看到在LDAP中使用了NTLM认证

LMHash加密脚本

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
#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 00110001001100100011001100110100001101010011011000000000
text_area = re.findall(r'.{%d}' % int(length), text) # ['0011000', '1001100', '1000110', '0110011', '0100001', '1010100', '1101100', '0000000']
text_area_padding = [i + '0' for i in text_area] #['00110000', '10011000', '10001100', '01100110', '01000010', '10101000', '11011000', '00000000']
hex_str = ''.join(text_area_padding) # 0011000010011000100011000110011001000010101010001101100000000000
hex_int = hex(int(hex_str, 2))[2:].rstrip("L") #30988c6642a8d800
if hex_int == '0':
hex_int = '0000000000000000'
return hex_int

def lm_hash(password):
# 1. 用户的密码转换为大写,密码转换为16进制字符串,不足14 byte将会用0来再后面补全。
pass_hex = binascii.hexlify(password.upper().encode()).decode().ljust(28, '0')
print(pass_hex)
# 2. 密码的16进制字符串被分成两个7byte部分。每部分转换成比特流,并且长度位56bit,长度不足使用0在左边补齐长度
left_str = pass_hex[:14] #31323334353600
right_str = pass_hex[14:] #00000000000000
left_stream = bin(int(left_str, 16)).lstrip('0b').rjust(56, '0') # 00110001001100100011001100110100001101010011011000000000
right_stream = bin(int(right_str, 16)).lstrip('0b').rjust(56, '0') # 00000000000000000000000000000000000000000000000000000000
# 3. 再分7bit为一组,每组末尾加0,再组成一组
left_stream = group_just(7,left_stream) # 30988c6642a8d800
right_stream = group_just(7,right_stream) # 0000000000000000
# 4. 上步骤得到的二组,分别作为key 为 "KGS!@#$%"进行DES加密。
left_lm = DesEncrypt('KGS!@#$%',left_stream) #44efce164ab921ca
right_lm = DesEncrypt('KGS!@#$%',right_stream) # aad3b435b51404ee
# 5. 将加密后的两组拼接在一起,得到最终LM HASH值。
return left_lm + right_lm

if __name__ == '__main__':
hash = lm_hash("123456")
print(hash)

NTHash加密脚本

1
2
3
4
5
6
7
import binascii  
import hashlib

passwd = "123456"
unicode_hex_passwd = passwd.encode('utf-16le')
md4_passwd = hashlib.new("md4",unicode_hex_passwd).digest()
print(binascii.hexlify(md4_passwd))

NTLM认证通信脚本

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
import base64
import spnego
import smtplib
from smtplib import SMTPException, SMTPAuthenticationError


# SMTP服务器信息
smtp_server = 'CLIENT03.WidgetLLC.Internal'
smtp_port = 25
username = 'WidgetLLC.Internal\jack'
password = '123456'

# 邮件信息
sender_email = 'jack@widgetllc.internal'
receiver_email = 'recipient_email'
subject = 'Test Subject'
message = 'Test Message'

smtp = smtplib.SMTP(smtp_server, smtp_port)

ehlo_response = smtp.ehlo()

# 定义NTLM认证函数
def ntlm_authenticate(smtp, username, password):
auth = spnego.client(username, password, service="SMTP", protocol="ntlm")
ntlm_negotiate = auth.step()

code, response = smtp.docmd("AUTH", "NTLM " + base64.b64encode(ntlm_negotiate).decode())
if code != 334:
raise SMTPException("Server did not respond as expected to NTLM negotiate message")

# I'm unsure of the format that is returned but if it's like the above this should work
# This is essentially getting the base64 decoded value of challenge response
ntlm_challenge = base64.b64decode(response)
ntlm_auth = auth.step(ntlm_challenge)
code, response = smtp.docmd("", base64.b64encode(ntlm_auth).decode())
if code != 235:
raise SMTPAuthenticationError(code, response)

# 发起NTLM认证
ntlm_authenticate(smtp, username, password)

Net-NTLMv2 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
import base64  
import argparse

def extract_info(auth, start, end):
buffer_slice = base64.b64decode(auth)[start:end]
length = int(buffer_slice[0:2][::-1].hex(), 16)
offset = int(buffer_slice[4:8][::-1].hex(), 16)
data = base64.b64decode(auth)[offset:offset+length]
return data

def parse_authentication(auth, challenge):
user_name = extract_info(auth, 36, 42)
domain_name = extract_info(auth, 28, 36)
ntlm_response = extract_info(auth, 20, 28)
server_challenge = base64.b64decode(challenge)[24:32]

return {
'User name': user_name[::2].decode('utf-8'),
'Domain name': domain_name[::2].decode('utf-8'),
'NTLM Server Challenge': server_challenge.hex(),
'HMAC-MD5': ntlm_response.hex()[:32],
'blob': ntlm_response.hex()[32:]
}
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='解析Base64编码形式的NTLM协议,并生成Net-NTLMv2 Hash')
parser.add_argument('--authentication', required=True, help='Base64-encoded authentication data')
parser.add_argument('--challenge', required=True, help='Base64-encoded challenge data')

args = parser.parse_args()

authentication = args.authentication
challenge = args.challenge

res = parse_authentication(authentication, challenge)
print("生成的Net-NTLMv2 Hash为: \n")
print(res['User name'] + "::"+
res['Domain name'] + ":" +
res['NTLM Server Challenge'] + ":" +
res['HMAC-MD5'] + ":" +
res['blob'] + "\n")
print("使用hashcat进行爆破(需要自行安装hashcat):\n\t hashcat -m 5600 \"Net-NTLMv2 Hash\" wordlist.txt --force")
print("或者使用john进行爆破(需要自行安装john):\n\t john --wordlist=wordlist.txt --format=netntlmv2 Net-NTLMv2-Hash.txt")

参考文献:
【1】Deep dive: Logging on to Windows

CATALOG
  1. 1. 零、序
  2. 2. 一、Windows本地认证介绍
    1. 2.1. LM Hash – 1987年
      1. 2.1.1. LM Hash加密
      2. 2.1.2. LM Hash爆破
    2. 2.2. NT Hash – 1993
      1. 2.2.1. NT Hash加密
      2. 2.2.2. NT Hash爆破
      3. 2.2.3. LM Hash和NT Hash的区别
  3. 3. 二、Windows网络认证介绍
    1. 3.1. NTLM协议流程
    2. 3.2. Net-NTLMv1 Hash(又被称为 NTLMv1 Hash) – 1993年
      1. 3.2.1. Net-NTLMv1 Hash加密
    3. 3.3. Net-NTLMv2 Hash(又被称为 NTLMv2 Hash) – 1998年
      1. 3.3.1. Net-NTLMv2 Hash加密
      2. 3.3.2. Net-NTLMv2 Hash爆破
      3. 3.3.3. Net-NTLMv1 Hash 与 Net-NTLMv2 Hash的区别
  4. 4. 三、搭建NTLM协议认证环境进行流量分析
    1. 4.1. 1. 在DC01上创建测试用账户
    2. 4.2. 2. 在Client02上搭建IIS Web Server
    3. 4.3. 3. 在Client03上搭建ExchangeServer
  5. 5. 四、各种场景下Net-NTLM认证的流量分析
    1. 5.1. 1、在SMB协议中使用NTLM认证
    2. 5.2. 2、在HTTP协议中使用NTLM认证
    3. 5.3. 3、在SMTP协议中使用NTLM认证
    4. 5.4. 4、在POP3协议中使用NTLM认证
    5. 5.5. 5、在LDAP协议中使用NTLM认证
  6. 6.
    1. 6.1. LMHash加密脚本
    2. 6.2. NTHash加密脚本
    3. 6.3. NTLM认证通信脚本
    4. 6.4. Net-NTLMv2 Hash提取