Zatruwanie odpowiedzi LLMNR – Responder, llmnr_response

blog.redteam.pl 6 lat temu
Często przy atakach typu red teaming wykorzystane jest oprogramowanie takie jak Responder (https://github.com/lgandx/Responder).
Responder is simply a LLMNR, NBT-NS and MDNS poisoner, with built-in HTTP/SMB/MSSQL/FTP/LDAP rogue authentication server supporting NTLMv1/NTLMv2/LMv2, Extended safety NTLMSSP and Basic HTTP authentication. Oprogramowanie to w skrócie pozwala na wykradzenie hashy haseł w infrastrukturze Windowsowej, a co z kolei jest pierwszym krokiem w atakach przeprowadzanych w lokalnej sieci. Jeśli więc odwiedzimy w Windowsie adres, który nie istnieje, np. http://redteam/ to stacja zaczyna wysyłać zapytania m.in. protokołem LLMNR. Możemy to zobaczyć wykorzystując w/w program w trybie analizy (przełącznik "A"):

python Responder.py -I eth0 -Av


Warto więc pochylić się nieco bliżej, aby skutecznie przeprowadzać detekcję takich ataków m.in. przez wchodzenie w interakcję z tego typu narzędziami, jak również po prostu analizując odpowiedzi w celu wykrycia tych zatrutych.

Przy pomocy generatora pakietów Scapy możemy również wysyłać tego typu zapytania, np. protokołem LLMNR:

# python
Python 2.7.9 (default, Jun 29 2016, 13:08:31)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from scapy.all import *
>>> send(IP(dst='224.0.0.252')/UDP()/LLMNRQuery(qd=DNSQR(qname='redteam')))
.
Sent 1 packets.
>>> send(IP(dst='224.0.0.252')/UDP()/LLMNRQuery(qd=DNSQR(qname='respproxysrv')))
.
Sent 1 packets.

Co naturalnie również będzie widoczne w narzędziu Responder:


Możemy również napisać prosty skrypt generujący w pętli losowe zapytania:


# python llmnr.py
dpovepea
.
Sent 1 packets.
pzh
.
Sent 1 packets.
qkmhywjwrodscmj
.
Sent 1 packets.
hhdcnlxyiprzrsl
.
Sent 1 packets.
nfuxtretmzju
.
Sent 1 packets.
ooe
.
Sent 1 packets.
r
.
Sent 1 packets.

W ruchu sieciowym w związku z tym zobaczymy:

# tshark -i eth0 -Y "llmnr"
Capturing on 'eth0'
15 5.259791544 10.13.4.201 → 224.0.0.252 LLMNR 68 Standard query 0x0000 A dpovepea
28 6.308703537 10.13.4.201 → 224.0.0.252 LLMNR 63 Standard query 0x0000 A pzh
38 7.348717008 10.13.4.201 → 224.0.0.252 LLMNR 75 Standard query 0x0000 A qkmhywjwrodscmj
49 8.405975946 10.13.4.201 → 224.0.0.252 LLMNR 75 Standard query 0x0000 A hhdcnlxyiprzrsl
58 9.448567200 10.13.4.201 → 224.0.0.252 LLMNR 72 Standard query 0x0000 A nfuxtretmzju
73 10.496610351 10.13.4.201 → 224.0.0.252 LLMNR 63 Standard query 0x0000 A ooe
86 11.540941831 10.13.4.201 → 224.0.0.252 LLMNR 61 Standard query 0x0000 A r
^C7 packets captured

Natomiast w oprogramowaniu Responder:


Oznacza to, że narzędzie poprawnie reaguje na nasz wyżej zaprezentowany skrypt.

Możemy więc przejść do kolejnego etapu, wyłączenia trybu analitycznego, w wyniku czego program zacznie zatruwać odpowiedzi (czyli de facto odpowiadać na zapytania).

# python llmnr.py
rgno
.
Sent 1 packets.
khzylwpskfv
.
Sent 1 packets.
xlvrvzqe
.
Sent 1 packets.
ea
.
Sent 1 packets.
u
.
Sent 1 packets.
iykqsdywqosv
.
Sent 1 packets.
qx
.
Sent 1 packets.

Uruchamiamy narzędzie bez przełącznika "A":
-A, --analyze, analyse mode. This option allows you to see NBT-NS, BROWSER, LLMNR requests without responding.
python Responder.py -I eth0 -v


W wyniku czego odpowiedzi zaczynają być zatruwane i co ma swoje potwierdzenie w ruchu sieciowym:

# tshark -i eth0 -Y "llmnr"
Capturing on 'eth0'
21 14.243637786 10.13.4.201 → 224.0.0.252 LLMNR 64 Standard query 0x0000 A rgno
22 14.245170118 10.13.4.47 → 10.13.4.201 LLMNR 84 Standard query consequence 0x0000 A rgno A 10.13.4.47
23 14.245226115 10.13.4.201 → 10.13.4.47 ICMP 112 Destination unreachable (Port unreachable)
39 15.292447354 10.13.4.201 → 224.0.0.252 LLMNR 71 Standard query 0x0000 A khzylwpskfv
42 15.294066154 10.13.4.47 → 10.13.4.201 LLMNR 98 Standard query consequence 0x0000 A khzylwpskfv A 10.13.4.47
43 15.294113383 10.13.4.201 → 10.13.4.47 ICMP 126 Destination unreachable (Port unreachable)
60 16.340394332 10.13.4.201 → 224.0.0.252 LLMNR 68 Standard query 0x0000 A xlvrvzqe
62 16.342456055 10.13.4.47 → 10.13.4.201 LLMNR 92 Standard query consequence 0x0000 A xlvrvzqe A 10.13.4.47
63 16.342490219 10.13.4.201 → 10.13.4.47 ICMP 120 Destination unreachable (Port unreachable)
78 17.388739399 10.13.4.201 → 224.0.0.252 LLMNR 62 Standard query 0x0000 A ea
81 17.390490534 10.13.4.47 → 10.13.4.201 LLMNR 80 Standard query consequence 0x0000 A ea A 10.13.4.47
82 17.390522646 10.13.4.201 → 10.13.4.47 ICMP 108 Destination unreachable (Port unreachable)
103 18.432417721 10.13.4.201 → 224.0.0.252 LLMNR 61 Standard query 0x0000 A u
109 18.434466875 10.13.4.47 → 10.13.4.201 LLMNR 78 Standard query consequence 0x0000 A u A 10.13.4.47
110 18.434495522 10.13.4.201 → 10.13.4.47 ICMP 106 Destination unreachable (Port unreachable)
138 19.484478355 10.13.4.201 → 224.0.0.252 LLMNR 72 Standard query 0x0000 A iykqsdywqosv
140 19.486041300 10.13.4.47 → 10.13.4.201 LLMNR 100 Standard query consequence 0x0000 A iykqsdywqosv A 10.13.4.47
141 19.486081580 10.13.4.201 → 10.13.4.47 ICMP 128 Destination unreachable (Port unreachable)
171 20.532742982 10.13.4.201 → 224.0.0.252 LLMNR 62 Standard query 0x0000 A qx
177 20.535800830 10.13.4.47 → 10.13.4.201 LLMNR 80 Standard query consequence 0x0000 A qx A 10.13.4.47
178 20.535830152 10.13.4.201 → 10.13.4.47 ICMP 108 Destination unreachable (Port unreachable)
^C21 packets captured

Przy użyciu Scapy możemy również stworzyć prosty skrypt do detekcji tego typu odpowiedzi:


# python llmnr2.py
rgno. 10.13.4.47
rgno. 10.13.4.47
khzylwpskfv. 10.13.4.47
khzylwpskfv. 10.13.4.47
xlvrvzqe. 10.13.4.47
xlvrvzqe. 10.13.4.47
ea. 10.13.4.47
ea. 10.13.4.47
u. 10.13.4.47
u. 10.13.4.47
iykqsdywqosv. 10.13.4.47
iykqsdywqosv. 10.13.4.47
qx. 10.13.4.47
qx. 10.13.4.47

Celem uzyskania więcej informacji o pakietach wystarczy odkomentować linię:

print pkt.show()

W wyniku czego na wygenerowane zapytanie:

# python llmnr.py
gijnkqvmyboam
.
Sent 1 packets.

Responder zatruwa odpowiedź:

[*] [LLMNR] Poisoned answer sent to 10.13.4.201 for name gijnkqvmyboam

Co widać w ruchu sieciowym:

# tshark -i eth0 -Y "llmnr"
Capturing on 'eth0'
8 3.669225514 10.13.4.201 → 224.0.0.252 LLMNR 73 Standard query 0x0000 A gijnkqvmyboam
12 3.671033312 10.13.4.47 → 10.13.4.201 LLMNR 102 Standard query consequence 0x0000 A gijnkqvmyboam A 10.13.4.47
13 3.671212372 10.13.4.201 → 10.13.4.47 ICMP 130 Destination unreachable (Port unreachable)
^C3 packets captured

Skrypt z użyciem Scapy zwraca nam wszystkie informacje w szczegółach:

###[ Ethernet ]###
dst = 01:00:5e:00:00:fc
src = 52:54:00:f4:99:46
kind = 0x800
###[ IP ]###
version = 4L
ihl = 5L
tos = 0x0
len = 59
id = 1
flags =
frag = 0L
ttl = 1
proto = udp
chksum = 0xc9df
src = 10.13.4.201
dst = 224.0.0.252
\options \
###[ UDP ]###
athletics = hostmon
dport = hostmon
len = 39
chksum = 0x5bed
###[ Link Local Multicast Node Resolution - Query ]###
id = 0
qr = 0L
opcode = QUERY
c = 0L
tc = 0L
z = 0L
rcode = ok
qdcount = 1
ancount = 0
nscount = 0
arcount = 0
\qd \
|###[ DNS Question evidence ]###
| qname = 'gijnkqvmyboam.'
| qtype = A
| qclass = IN
an = None
ns = None
ar = None
None
###[ Ethernet ]###
dst = 52:54:00:f4:99:46
src = 52:54:00:f5:9b:7e
kind = 0x800
###[ IP ]###
version = 4L
ihl = 5L
tos = 0x0
len = 88
id = 9089
flags = DF
frag = 0L
ttl = 64
proto = udp
chksum = 0xfa02
src = 10.13.4.47
dst = 10.13.4.201
\options \
###[ UDP ]###
athletics = hostmon
dport = hostmon
len = 68
chksum = 0x968a
###[ Link Local Multicast Node Resolution - Query ]###
id = 0
qr = 1L
opcode = QUERY
c = 0L
tc = 0L
z = 0L
rcode = ok
qdcount = 1
ancount = 1
nscount = 0
arcount = 0
\qd \
|###[ DNS Question evidence ]###
| qname = 'gijnkqvmyboam.'
| qtype = A
| qclass = IN
\an \
|###[ DNS Resource evidence ]###
| rrname = 'gijnkqvmyboam.'
| kind = A
| rclass = IN
| ttl = 30
| rdlen = 4
| rdata = '10.13.4.47'
ns = None
ar = None
None
gijnkqvmyboam. 10.13.4.47
###[ Ethernet ]###
dst = 52:54:00:f5:9b:7e
src = 52:54:00:f4:99:46
kind = 0x800
###[ IP ]###
version = 4L
ihl = 5L
tos = 0xc0
len = 116
id = 40930
flags =
frag = 0L
ttl = 64
proto = icmp
chksum = 0xbcd5
src = 10.13.4.201
dst = 10.13.4.47
\options \
###[ ICMP ]###
kind = dest-unreach
code = port-unreachable
chksum = 0x1a64
unused = 0
###[ IP in ICMP ]###
version = 4L
ihl = 5L
tos = 0x0
len = 88
id = 9089
flags = DF
frag = 0L
ttl = 64
proto = udp
chksum = 0xfa02
src = 10.13.4.47
dst = 10.13.4.201
\options \
###[ UDP in ICMP ]###
athletics = hostmon
dport = hostmon
len = 68
chksum = 0x968a
###[ Link Local Multicast Node Resolution - Query ]###
id = 0
qr = 1L
opcode = QUERY
c = 0L
tc = 0L
z = 0L
rcode = ok
qdcount = 1
ancount = 1
nscount = 0
arcount = 0
\qd \
|###[ DNS Question evidence ]###
| qname = 'gijnkqvmyboam.'
| qtype = A
| qclass = IN
\an \
|###[ DNS Resource evidence ]###
| rrname = 'gijnkqvmyboam.'
| kind = A
| rclass = IN
| ttl = 30
| rdlen = 4
| rdata = '10.13.4.47'
ns = None
ar = None
None
gijnkqvmyboam. 10.13.4.47

Powyższe podejście jest uniwersalne i nie działa tylko na jedno narzędzie, co możemy potwierdzić przy użyciu Metasploit, a dokładniej modułu auxiliary/spoof/llmnr/llmnr_response:

# python llmnr.py
yhywsxy
.
Sent 1 packets.
kvy
.
Sent 1 packets.
ofb
.
Sent 1 packets.
fyyffjvbelauuew
.
Sent 1 packets.
jqjmjixvyvqd
.
Sent 1 packets.
sb
.
Sent 1 packets.


Tym sposobem udało nam się stworzyć prosty honeypot, który wchodzi w interakcję z narzędziami tego typu. Jak również drugi skrypt, który z kolei pozwala na wykrywanie zatrutych odpowiedzi.

W przypadku kiedy stacja Windows wysyła pakiet LLMNR to narzędzie tego typu odpowiada na zapytanie wskazując swój adres IP jako wyszukiwanego hosta. W wyniku czego nadrzędzie wymusza autoryzacje i tym sposobem skrada m.in. hash hasła. Co więcej Responder stara się wymusić wielokrokową interakcję aby pozyskać jak najwięcej hashy NTLM, a co jednocześnie pozwala na dodatkową detekcję jego działania.
Idź do oryginalnego materiału