安全研究

WINRM(NTLM认证)与SMB3专栏(1)

2026年04月23日3分钟

winrm我记得认证方式挺多的,但因为只遇到了NTLM认证,就只讲这个叭~

WINRM(NTLM认证)

干讲有点抽象,就直接从代码上看吧

STEP1/NT_HASH计算

from Crypto.Hash import MD4

password = "pass@word1".encode("utf-16le")
md4 = MD4.new()
md4.update(password)
nt_hash = md4.digest()

很简单啊,将password使用utf-16le编码后进行一次MD4哈希就拿到了NT_HASH

STEP2/ResponseKeyNT计算

这一步会引入HMAC_MD5哈希,由于后面会多次用到这个哈希,所以写了个函数

from Crypto.Hash import HMAC, MD5

def HMAC_MD5(key,data):
    hash = HMAC.new(key,digestmod=MD5)
    hash.update(data)
    return hash.digest()

计算方式其实很简单

response = ('ADMINISTRATOR'+'pc').encode("utf-16le")
ResponsekeyNT=HMAC_MD5(nt_hash,response)

将全大写的USERNAMEdomain拼接到一块再经过utf-16le编码作为data,以nt_hashkey计算HMAC_MD5就得到了ResponseKeyNT

STEP3/NTProofStr计算

blob = bytes.fromhex('010100000000000022a2d32cbc72dc01ff545caf96411c670000000002000a0044004500310041005900010004005000430004001200640065003100610079002e0063006f006d0003001800500043002e00640065003100610079002e0063006f006d0005001200640065003100610079002e0063006f006d000700080022a2d32cbc72dc010600040002000000080030003000000000000000000000000030000026544cc05c735b21ae876ab6adeaf35030fb649315896d1d685326c99ddb5f6b0a001000000000000000000000000000000000000900220048005400540050002f00310030002e00310030002e00310030002e00320030003100000000000000000000000000')
ServerChallenge = bytes.fromhex('cd0a6722277096c9')

NTProofStr = HMAC_MD5(ResponsekeyNT,ServerChallenge+blob)

这里面啊两个东西

ServerChallenge是由服务端生成的随机数

blob(binary_large_object)是一串二进制数据块,其中包含了用户名啊域啊DNS啊一些七七八八的东西

两个合一块就变成了要哈希的data

STEP4/NtChallengeResponse计算

NtChallengeResponse = NTProofStr + blob

不多说了

STEP5/KeyExchangeKey计算

KeyExchangeKey = SessionBaseKey = HMAC_MD5(ResponsekeyNT,NTProofStr)

NTLM认证中KeyExchangeKeySessionBaseKey相同,很方便嗷

STEP6/EncryptedRandomKey计算

这个呢就要分两种情况啦

  • Key Exchange: set (多数情况)

​ 这个情况下呢EncryptedRandomKey是由ExportedSessionKey 经过RC4加密拿到的

from Crypto.Cipher import ARC4

EncryptedRandomSessionKey = ARC4.new(KeyExchangeKey).encrypt(ExportedSessionKey)
  • Key Exchange: not set

​ 这个字段直接没了捏,嘻嘻


OK啊,上述所有都是我们的认证阶段,接下来才是流量加密,一个很简单的RC4,密钥的派生需要用到STEP6里提到的ExportedSessionKey

CLIENT_SIGN_MAGIC = b"session key to client-to-server signing key magic constant\x00"
SERVER_SIGN_MAGIC = b"session key to server-to-client signing key magic constant\x00"
CLIENT_SEAL_MAGIC = b"session key to client-to-server sealing key magic constant\x00"
SERVER_SEAL_MAGIC = b"session key to server-to-client sealing key magic constant\x00"

ClientSignKey = md5(ExportedSessionKey + CLIENT_SIGN_MAGIC)
ServerSignKey = md5(ExportedSessionKey + SERVER_SIGN_MAGIC)
ClientSealKey = md5(ExportedSessionKey + CLIENT_SEAL_MAGIC)
ServerSealKey = md5(ExportedSessionKey + SERVER_SEAL_MAGIC)

#client->server
ENC = ARC4.new(ClientSealKey).decrypt(payload).encode()
#server->client
ENC = ARC4.new(ServerSealKey).decrypt(payload).encode()

MAGIC是协议固定的常量,要注意的就是 服务端到客户端客户端到服务端用的是两个密钥(加解密只需要关注SealKey就行,SignKey是用来校验的,不参与加解密)


好的,现在为止加密流程已经过完了,那么解密思路应该就很清晰了

  • 拿到ExportedSessionKey派生SealKey
  • RC4解密EncryptedRandomSessionKey(可以从流量中读到)获取ExportedSessionKey
  • 获取RC4密钥KeyExchangeKey
  • 获取password 推导KeyExchangeKey

那么怎么获取password呢?用john或者hashcat爆破NTLMv2(USERNAME::domain:challenge:NTroofStr:blob)

于是,你就解密了winrm的流量

嘻嘻,又水一期

暂无标签