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