czwartek, 14 września 2017

Zatruwanie odpowiedzi LLMNR – Responder, llmnr_response

Często przy atakach typu red teaming wykorzystane jest oprogramowanie takie jak Responder (https://github.com/lgandx/Responder).
Responder is a LLMNR, NBT-NS and MDNS poisoner, with built-in HTTP/SMB/MSSQL/FTP/LDAP rogue authentication server supporting NTLMv1/NTLMv2/LMv2, Extended Security 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, Analyze 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 response 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 response 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 response 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 response 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 response 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 response 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 response 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 response 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
  type      = 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 ]###
        sport     = 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 Record ]###
            |  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
  type      = 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 ]###
        sport     = 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 Record ]###
            |  qname     = 'gijnkqvmyboam.'
            |  qtype     = A
            |  qclass    = IN
           \an        \
            |###[ DNS Resource Record ]###
            |  rrname    = 'gijnkqvmyboam.'
            |  type      = 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
  type      = 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 ]###
        type      = 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 ]###
              sport     = 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 Record ]###
                  |  qname     = 'gijnkqvmyboam.'
                  |  qtype     = A
                  |  qclass    = IN
                 \an        \
                  |###[ DNS Resource Record ]###
                  |  rrname    = 'gijnkqvmyboam.'
                  |  type      = 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.