1 //--------------------------------------------------------------------------
2 // Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
3 // Copyright (C) 2004-2013 Sourcefire, Inc.
4 // Copyright (C) 2001-2004 Jeff Nathan <jeff@snort.org>
5 //
6 // This program is free software; you can redistribute it and/or modify it
7 // under the terms of the GNU General Public License Version 2 as published
8 // by the Free Software Foundation.  You may not use, modify or distribute
9 // this program under any other version of the GNU General Public License.
10 //
11 // This program is distributed in the hope that it will be useful, but
12 // WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License along
17 // with this program; if not, write to the Free Software Foundation, Inc.,
18 // 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 //--------------------------------------------------------------------------
20 
21 /* Snort ARPspoof Plugin
22  *   by Jeff Nathan <jeff@snort.org>
23  *   Version 0.1.4
24  *
25  * Purpose:
26  *
27  * This inspector looks for anomalies in ARP traffic and attempts to
28  * maliciously overwrite  ARP cache information on hosts.
29  *
30  * Arguments:
31  *
32  * To check for unicast ARP requests use:
33  * arpspoof: -unicast
34  *
35  * WARNING: this can generate false positives as Linux systems send unicast
36  * ARP requests repetitively for entries in their cache.
37  *
38  * This plugin also takes a list of IP addresses and MAC address in the form:
39  * arpspoof_detect_host: 10.10.10.10 29:a2:9a:29:a2:9a
40  * arpspoof_detect_host: 192.168.40.1 f0:0f:00:f0:0f:00
41  * and so forth...
42  *
43  * Effect:
44  * By comparing information in the Ethernet header to the ARP frame, obvious
45  * anomalies are detected.  Also, utilizing a user supplied list of IP
46  * addresses and MAC addresses, ARP traffic appearing to have originated from
47  * any IP in that list is carefully examined by comparing the source hardware
48  * address to the user supplied hardware address.  If there is a mismatch, an
49  * alert is generated as either an ARP request or REPLY can be used to
50  * overwrite cache information on a remote host.  This should only be used for
51  * hosts/devices on the **same layer 2 segment** !!
52  *
53  * Bugs:
54  * This is a proof of concept ONLY.  It is clearly not complete.  Also, the
55  * lookup function LookupIPMacEntryByIP is in need of optimization.  The
56  * arpspoof_detect_host functionality may false alarm in redundant environments.
57  * Also, see the comment above pertaining to Linux systems.
58  *
59  * Thanks:
60  *
61  * First and foremost Patrick Mullen who sat beside me and helped every step of
62  * the way.  Andrew Baker for graciously supplying the tougher parts of this
63  * code.  W. Richard Stevens for readable documentation and finally
64  * Marty for being a badass.  All your packets are belong to Marty.
65  *
66  */
67 
68 /*  I N C L U D E S  ************************************************/
69 
70 #ifdef HAVE_CONFIG_H
71 #include "config.h"
72 #endif
73 
74 #include <iomanip>
75 #include <sstream>
76 
77 #include "detection/detection_engine.h"
78 #include "events/event_queue.h"
79 #include "log/messages.h"
80 #include "profiler/profiler.h"
81 #include "protocols/arp.h"
82 #include "protocols/eth.h"
83 #include "protocols/packet.h"
84 #include "protocols/wlan.h"
85 #include "sfip/sf_ip.h"
86 
87 #include "arp_module.h"
88 
89 using namespace snort;
90 
91 static const uint8_t bcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
92 
93 THREAD_LOCAL ProfileStats arpPerfStats;
94 
95 //-------------------------------------------------------------------------
96 // implementation stuff
97 //-------------------------------------------------------------------------
98 
LookupIPMacEntryByIP(const IPMacEntryList & ipmel,uint32_t ipv4_addr)99 static const IPMacEntry* LookupIPMacEntryByIP(const IPMacEntryList& ipmel, uint32_t ipv4_addr)
100 {
101     for ( auto& p : ipmel )
102     {
103         if (p.ipv4_addr == ipv4_addr)
104             return &p;
105     }
106     return nullptr;
107 }
108 
to_hex_string(const uint8_t * data,size_t len)109 static std::string to_hex_string(const uint8_t* data, size_t len)
110 {
111     std::stringstream ss;
112     ss << std::hex;
113     for (size_t i = 0; i < len; i++)
114         ss << std::setw(2) << std::setfill('0') << (unsigned int)data[i];
115     return ss.str();
116 }
117 
arp_config_show(const ArpSpoofConfig * config)118 static void arp_config_show(const ArpSpoofConfig* config)
119 {
120     if ( !config->ipmel.size() )
121         return;
122 
123     ConfigLogger::log_option("hosts");
124 
125     for (auto& ip : config->ipmel)
126     {
127         std::string ip_mac;
128         SfIpString ip_str;
129         snort_inet_ntop(AF_INET, &ip.ipv4_addr, ip_str, sizeof(SfIpString));
130         ip_mac += "{ ip = " + std::string(ip_str);
131         ip_mac += ", mac = " + to_hex_string(ip.mac_addr, sizeof(ip.mac_addr)) + " }";
132         ConfigLogger::log_list("", ip_mac.c_str());
133     }
134 }
135 
136 //-------------------------------------------------------------------------
137 // class stuff
138 //-------------------------------------------------------------------------
139 
140 class ArpSpoof : public Inspector
141 {
142 public:
143     ArpSpoof(ArpSpoofModule*);
144     ~ArpSpoof() override;
145 
146     void show(const SnortConfig*) const override;
147     void eval(Packet*) override;
148 
149 private:
150     ArpSpoofConfig* config;
151 };
152 
ArpSpoof(ArpSpoofModule * mod)153 ArpSpoof::ArpSpoof(ArpSpoofModule* mod)
154 {
155     config = mod->get_config();
156 }
157 
~ArpSpoof()158 ArpSpoof::~ArpSpoof ()
159 {
160     delete config;
161 }
162 
show(const SnortConfig *) const163 void ArpSpoof::show(const SnortConfig*) const
164 {
165     if ( config )
166         arp_config_show(config);
167 }
168 
eval(Packet * p)169 void ArpSpoof::eval(Packet* p)
170 {
171     Profile profile(arpPerfStats);
172 
173     // precondition - what we registered for
174     assert(p->proto_bits & PROTO_BIT__ARP);
175 
176     const uint8_t* dst_mac_addr;
177     const uint8_t* src_mac_addr;
178 
179     if (p->proto_bits & PROTO_BIT__ETH)
180     {
181         const eth::EtherHdr* eh = layer::get_eth_layer(p);
182         src_mac_addr = eh->ether_src;
183         dst_mac_addr = eh->ether_dst;
184     }
185     else
186     {
187         const wlan::WifiHdr* wifih = layer::get_wifi_layer(p);
188         if (wifih == nullptr)
189             return;
190 
191         if ((wifih->frame_control & WLAN_FLAG_TODS) &&
192              (wifih->frame_control & WLAN_FLAG_FROMDS))
193          {
194              dst_mac_addr = wifih->addr3;
195              src_mac_addr = wifih->addr4;
196          }
197          else if (wifih->frame_control & WLAN_FLAG_TODS)
198          {
199              src_mac_addr = wifih->addr2;
200              dst_mac_addr = wifih->addr3;
201          }
202          else if (wifih->frame_control & WLAN_FLAG_FROMDS)
203          {
204              dst_mac_addr = wifih->addr1;
205              src_mac_addr = wifih->addr3;
206          }
207          else
208          {
209              dst_mac_addr = wifih->addr1;
210              src_mac_addr = wifih->addr2;
211          }
212     }
213 
214     const arp::EtherARP* ah = layer::get_arp_layer(p);
215 
216     /* is the ARP protocol type IP and the ARP hardware type Ethernet? */
217     if ((ntohs(ah->ea_hdr.ar_hrd) != 0x0001) ||
218         (ntohs(ah->ea_hdr.ar_pro) != ETHERNET_TYPE_IP))
219         return;
220 
221     ++asstats.total_packets;
222 
223     switch (ntohs(ah->ea_hdr.ar_op))
224     {
225     case ARPOP_REQUEST:
226         if (memcmp((const uint8_t*)dst_mac_addr, (const uint8_t*)bcast, 6) != 0)
227         {
228             DetectionEngine::queue_event(GID_ARP_SPOOF, ARPSPOOF_UNICAST_ARP_REQUEST);
229         }
230         else if (memcmp((const uint8_t*)src_mac_addr,
231             (const uint8_t*)ah->arp_sha, 6) != 0)
232         {
233             DetectionEngine::queue_event(GID_ARP_SPOOF, ARPSPOOF_ETHERFRAME_ARP_MISMATCH_SRC);
234         }
235         break;
236     case ARPOP_REPLY:
237         if (memcmp((const uint8_t*)src_mac_addr,
238             (const uint8_t*)ah->arp_sha, 6) != 0)
239         {
240             DetectionEngine::queue_event(GID_ARP_SPOOF, ARPSPOOF_ETHERFRAME_ARP_MISMATCH_SRC);
241         }
242         else if (memcmp((const uint8_t*)dst_mac_addr,
243             (const uint8_t*)ah->arp_tha, 6) != 0)
244         {
245             DetectionEngine::queue_event(GID_ARP_SPOOF, ARPSPOOF_ETHERFRAME_ARP_MISMATCH_DST);
246         }
247         break;
248     }
249 
250     /* return if the overwrite list hasn't been initialized */
251     if (!config->check_overwrite)
252         return;
253 
254     const IPMacEntry* ipme = LookupIPMacEntryByIP(config->ipmel, ah->arp_spa32);
255     if ( ipme )
256     {
257         auto cmp_ether_src = memcmp(src_mac_addr, ipme->mac_addr, 6);
258         auto cmp_arp_sha = memcmp(ah->arp_sha, ipme->mac_addr, 6);
259 
260         // If the Ethernet source address or the ARP source hardware address
261         // in p doesn't match the MAC address in ipme, then generate an alert
262         if ( cmp_ether_src || cmp_arp_sha )
263         {
264             DetectionEngine::queue_event(GID_ARP_SPOOF, ARPSPOOF_ARP_CACHE_OVERWRITE_ATTACK);
265         }
266     }
267 }
268 
269 //-------------------------------------------------------------------------
270 // api stuff
271 //-------------------------------------------------------------------------
272 
mod_ctor()273 static Module* mod_ctor()
274 { return new ArpSpoofModule; }
275 
mod_dtor(Module * m)276 static void mod_dtor(Module* m)
277 { delete m; }
278 
as_ctor(Module * m)279 static Inspector* as_ctor(Module* m)
280 {
281     return new ArpSpoof((ArpSpoofModule*)m);
282 }
283 
as_dtor(Inspector * p)284 static void as_dtor(Inspector* p)
285 { delete p; }
286 
287 static const InspectApi as_api =
288 {
289     {
290         PT_INSPECTOR,
291         sizeof(InspectApi),
292         INSAPI_VERSION,
293         0,
294         API_RESERVED,
295         API_OPTIONS,
296         MOD_NAME,
297         MOD_HELP,
298         mod_ctor,
299         mod_dtor
300     },
301     IT_NETWORK,
302     PROTO_BIT__ARP,
303     nullptr, // buffers
304     nullptr, // service
305     nullptr, // pinit
306     nullptr, // pterm
307     nullptr, // tinit
308     nullptr, // tterm
309     as_ctor,
310     as_dtor,
311     nullptr, // ssn
312     nullptr, // reset
313 };
314 
315 #ifdef BUILDING_SO
316 SO_PUBLIC const BaseApi* snort_plugins[] =
317 #else
318 const BaseApi* nin_arp_spoof[] =
319 #endif
320 {
321     &as_api.base,
322     nullptr
323 };
324