1 /*
2  * Copyright 2007-2010 Boris Kochergin. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * 1. Redistributions of source code must retain the above copyright notice,
8  *    this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright notice,
10  *    this list of conditions and the following disclaimer in the documentation
11  *    and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16  * DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24 
25 #include <fstream>
26 #include <iostream>
27 #include <queue>
28 #include <unordered_map>
29 #include <vector>
30 
31 #ifdef __FreeBSD__
32   #include <sys/ioctl.h>
33 #endif
34 
35 #include <sys/types.h>
36 #include <net/bpf.h>
37 #include <errno.h>
38 #include <pcap.h>
39 #include <signal.h>
40 #include <stdint.h>
41 #include <string.h>
42 #include <unistd.h>
43 
44 #include "address.hpp"
45 #include "arpCounterattack.hpp"
46 #include "configuration.hpp"
47 #include "ethernetInfo.hpp"
48 #include "logger.hpp"
49 #include "string.hpp"
50 
51 using namespace std;
52 
53 const string programName = "ARP Counterattack 1.2.0";
54 string pidFileName = "/var/run/arpCounterattack.pid";
55 Logger logger;
56 vector <Interface> interfaces;
57 EthernetInfo ethernetInfo;
58 queue <ARPCorrection> corrections;
59 size_t maxQueueSize;
60 bool responseStatus;
61 pthread_mutex_t responseStatusLock, responseLock, correctionsLock;
62 pthread_cond_t responseCondition;
63 
signalHandler(int signal)64 void signalHandler(int signal) {
65   switch (signal) {
66     case SIGTERM:
67       logger.lock();
68       logger << logger.time() << "Caught SIGTERM; exiting." << endl;
69       logger.unlock();
70       pthread_mutex_lock(&correctionsLock);
71       unlink(pidFileName.c_str());
72       exit(0);
73   }
74 }
75 
76 /*
77  * If "interface" is in aggressive mode, sends out a gratuitous ARP request and
78  * a gratuitous ARP reply with the "correct" Ethernet/IP address mapping in
79  * order to restore the ARP tables of victim machines to the correct state.
80  * Linux will not update the Ethernet address of an IP address in its ARP table
81  * more than once per second by way of gratuitous ARP requests, so we also
82  * schedule a gratuitous ARP request to be sent from the delayedResponse()
83  * thread at least 1.5 seconds from the time of the attack. Lastly, if logging
84  * is enabled, we log the attack and our response to it.
85  */
respond(Interface & interface,const u_char * packet,const AttackType & type,const string & sha)86 void respond(Interface &interface, const u_char *packet,
87              const AttackType &type, const string &sha) {
88   ARPCorrection correction;
89   string ethernetAddress, manufacturer;
90   /*
91    * Initialize this to false to stop g++ from complaining about the possible
92    * use of uninitialized variables when compiling with -O1 or higher.
93    */
94   bool corrected = false, queued = false;
95   if (interface.mode == AGGRESSIVE_MODE) {
96     ethernetAddress = getEthernetAddress(interface.name);
97     if (ethernetAddress != "") {
98       corrected = sendGratuitousARPPacket(interface.replyDescriptor,
99                                           ethernetAddress,
100                                           sha.c_str(), *getARPSPA(packet),
101                                           ARP_REQUEST) &&
102                   sendGratuitousARPPacket(interface.replyDescriptor,
103                                           ethernetAddress,
104                                           sha.c_str(), *getARPSPA(packet),
105                                           ARP_REPLY);
106     }
107     /*
108      * Lock the correction queue to prevent interference with the
109      * delayedResponse() thread, then add the correction to it.
110      */
111     pthread_mutex_lock(&correctionsLock);
112     if (corrections.size() < maxQueueSize) {
113       correction.interface = &interface;
114       memcpy(correction.sha, sha.c_str(), sha.length());
115       correction.spa = *getARPSPA(packet);
116       gettimeofday(&(correction.attackTime), NULL);
117       corrections.push(correction);
118       queued = true;
119     }
120     else {
121       queued = false;
122     }
123     pthread_mutex_unlock(&correctionsLock);
124     /*
125      * Lock the delayedResponse() thread's status to prevent interference with
126      * that thread.
127      */
128     pthread_mutex_lock(&responseStatusLock);
129     /*
130      * Wake up the delayedResponse() thread if it is sleeping, as there is now
131      * work for it to do.
132      */
133     if (responseStatus == false) {
134       pthread_cond_broadcast(&responseCondition);
135     }
136     pthread_mutex_unlock(&responseStatusLock);
137   }
138   if (logger) {
139     manufacturer = ethernetInfo.find(getEthernetSourceAddress(packet));
140     if (manufacturer == "") {
141       manufacturer = "unknown manufacturer";
142     }
143     logger.lock();
144     logger << logger.time() << "ARP attack detected:" << endl << endl
145            << "\tInterface:\t\t" << interface.name << endl << "\tType:\t\t\t";
146     switch (type) {
147       case GRATUITOUS_ARP_REQUEST_ATTACK:
148         logger << "gratuitous ARP request";
149         break;
150       case GRATUITOUS_ARP_REPLY_ATTACK:
151         logger << "gratuitous ARP reply";
152         break;
153       case ARP_REPLY_ATTACK:
154         logger << "ARP reply";
155         break;
156     }
157     logger << endl << "\tTarget:\t\t\t" << ipToText(*getARPSPA(packet)) << endl
158            << "\tAttacker:\t\t" << ethernetToText(getEthernetSourceAddress(packet))
159            << " (" << manufacturer << ')' << endl << "\tCorrected:\t\t";
160     if (interface.mode == PASSIVE_MODE) {
161       logger << "no (interface is in passive mode)";
162     }
163     else {
164       if (ethernetAddress == "") {
165         logger << "no (getEthernetAddress(): " << strerror(errno) << ')';
166       }
167       else {
168         if (corrected == false) {
169           logger << "no (pcap_inject(): "
170                  << pcap_geterr(interface.replyDescriptor) << ')';
171         }
172         else {
173           logger << "yes (interface is in aggressive mode)";
174         }
175       }
176     }
177     logger << endl << "\tDelayed correction:\t";
178     if (queued == true) {
179       logger << "queued";
180     }
181     else {
182       logger << "not queued (queue is full)";
183     }
184     logger << endl << endl;
185     logger.unlock();
186   }
187 }
188 
189 /*
190  * Iterates through the correction queue and sends out a corrective gratuitous
191  * ARP request at least 1.5 seconds after each respective attack, logging only
192  * problems with doing so. A one-second delay is too short to guarantee that a
193  * Linux machine's ARP table will be updated. For example, an ARP attack may
194  * arrive at a Linux machine a little late due to adverse 802.11 network
195  * conditions. If we see the attack on time and respond one second later, the
196  * Linux machine's ARP table will not be updated.
197  */
delayedResponse(void *)198 void *delayedResponse(void*) {
199   ARPCorrection correction;
200   timeval time;
201   suseconds_t microDelay;
202   timespec nanoDelay;
203   string ethernetAddress;
204   /*
205    * Initialize this to false to stop g++ from complaining about the possible
206    * use of uninitialized variables when compiling with -O1 or higher.
207    */
208   bool corrected = false;
209   nanoDelay.tv_sec = 0;
210   /*
211    * Allows the signal handler to terminate this thread gracefully by locking
212    * "responseLock".
213    */
214   while (pthread_mutex_trylock(&responseLock) == 0) {
215     /* Wait to be woken up by the listen() thread. */
216     pthread_cond_wait(&responseCondition, &responseLock);
217     /* Lock this thread's status to prevent a race with the listen() thread. */
218     pthread_mutex_lock(&responseStatusLock);
219     /* Indicate that this thread is now running. */
220     responseStatus = true;
221     pthread_mutex_unlock(&responseStatusLock);
222     /*
223      * Lock the correction queue to prevent a race with the listen() thread.
224      */
225     pthread_mutex_lock(&correctionsLock);
226     while (!corrections.empty()) {
227       correction = corrections.front();
228       corrections.pop();
229       pthread_mutex_unlock(&correctionsLock);
230       gettimeofday(&time, NULL);
231       microDelay = ((time.tv_sec - correction.attackTime.tv_sec) * 1000000) +
232                    (time.tv_usec - correction.attackTime.tv_usec);
233       if (microDelay < 1500000) {
234         nanoDelay.tv_nsec = (1500000 - microDelay) * 1000;
235         if (nanoDelay.tv_nsec > 1000000000) {
236           nanoDelay.tv_sec = 1;
237           nanoDelay.tv_nsec -= 1000000000;
238         }
239         nanosleep(&nanoDelay, NULL);
240       }
241       ethernetAddress = getEthernetAddress(correction.interface -> name);
242       if (ethernetAddress != "") {
243         corrected = sendGratuitousARPPacket(correction.interface -> replyDescriptor,
244                                              ethernetAddress, correction.sha,
245                                              correction.spa, ARP_REQUEST);
246       }
247       if (ethernetAddress == "" || corrected == false) {
248         logger.lock();
249         logger << logger.time() << "Delayed correction failure:" << endl
250                << endl << "\tInterface:\t\t"
251                << correction.interface -> replyName << endl
252                << "\tError:\t\t\t";
253         if (ethernetAddress == "") {
254           logger << "getEthernetAddress(): " << strerror(errno);
255         }
256         else {
257           if (corrected == false) {
258             logger << "pcap_inject(): "
259                    << pcap_geterr(correction.interface -> replyDescriptor);
260           }
261         }
262         logger << endl << endl;
263         logger.unlock();
264       }
265     }
266     pthread_mutex_unlock(&correctionsLock);
267     /* Lock this thread's status to prevent a race with the listen() thread. */
268     pthread_mutex_lock(&responseStatusLock);
269     /* Indicate that this thread is now sleeping. */
270     responseStatus = false;
271     pthread_mutex_unlock(&responseStatusLock);
272     pthread_mutex_unlock(&responseLock);
273   }
274   return NULL;
275 }
276 
277 /*
278  * Examines ARP traffic on "interface" for attacks, responds to them if the
279  * interface is in aggressive mode, and logs them if logging is enabled.
280  */
listen(void * interface)281 void *listen(void *interface) {
282   Interface *_interface = (Interface*)interface;
283   const u_char *packet = NULL;
284   pcap_pkthdr header;
285   unordered_map <uint32_t, string>::const_iterator itr;
286   while (1) {
287     packet = pcap_next(_interface -> descriptor, &header);
288     if (packet != NULL && header.caplen >= 42 &&
289         /* Check that the hardware type is Ethernet. */
290         ntohs(*(uint16_t*)(packet + 14)) == 0x0001 &&
291         /* Check that the protocol is IPv4. */
292         ntohs(*(uint16_t*)(packet + 16)) == 0x0800) {
293       switch (ntohs(*(uint16_t*)(packet + 20))) {
294         /* ARP request. */
295         case 0x0001:
296           /* Gratuitous ARP request. */
297           if (*getARPSPA(packet) == *getARPTPA(packet)) {
298             itr = _interface -> pairs.find(*getARPSPA(packet));
299             if (itr != _interface -> pairs.end() &&
300                 string((const char*)getARPSHA(packet),
301                        ETHER_ADDR_LEN) != itr -> second) {
302               respond(*_interface, packet, GRATUITOUS_ARP_REQUEST_ATTACK,
303                       itr -> second);
304             }
305           }
306           break;
307         /* ARP reply. */
308         case 0x0002:
309           itr = _interface -> pairs.find(*getARPSPA(packet));
310           if (itr != _interface -> pairs.end() &&
311               string((const char*)getARPSHA(packet),
312                      ETHER_ADDR_LEN) != itr -> second &&
313               memcmp((itr -> second).c_str(),
314                      getEthernetDestinationAddress(packet),
315                      ETHER_ADDR_LEN) != 0) {
316             /*
317              * We'll call an ARP reply with a multicast Ethernet destination
318              * address a gratuitous ARP reply.
319              */
320             if ((getEthernetDestinationAddress(packet)[0] & 0x01) == 1) {
321               respond(*_interface, packet, GRATUITOUS_ARP_REPLY_ATTACK,
322                       itr -> second);
323             }
324             else {
325               respond(*_interface, packet, ARP_REPLY_ATTACK, itr -> second);
326             }
327           }
328           break;
329       }
330     }
331   }
332   /* Not reached. */
333   return NULL;
334 }
335 
main(int argc,char * argv[])336 int main(int argc, char *argv[]) {
337   string configFileName = "/usr/local/etc/arpCounterattack/arpCounterattack.conf",
338          ouiFileName = "/usr/local/etc/arpCounterattack/oui.txt";
339   Configuration conf;
340   vector <string> interfaceNames, addressPair;
341   char option, errorBuffer[PCAP_ERRBUF_SIZE];
342   bpf_program bpfProgram;
343 #ifdef __FreeBSD__
344   u_int immediate = 1;
345 #endif
346   ofstream pidFile;
347   pid_t pid;
348   pthread_t delayedResponseThread;
349   int error;
350   signal(SIGTERM, signalHandler);
351   while ((option = getopt(argc, argv, "c:o:p:")) != -1) {
352     switch (option) {
353       case 'c':
354         configFileName = optarg;
355         break;
356       case 'o':
357         ouiFileName = optarg;
358         break;
359       case 'p':
360         pidFileName = optarg;
361         break;
362       default:
363         return 1;
364     }
365   }
366   if ((error = pthread_mutex_init(&responseStatusLock, NULL)) != 0 ||
367       (error = pthread_mutex_init(&responseLock, NULL)) != 0 ||
368       (error = pthread_mutex_init(&correctionsLock, NULL)) != 0) {
369     cerr << argv[0] << ": pthread_mutex_init(): " << strerror(error) << endl;
370     return 1;
371   }
372   if ((error = pthread_cond_init(&responseCondition, NULL)) != 0) {
373     cerr << argv[0] << ": pthread_cond_init(): " << strerror(error) << endl;
374     return 1;
375   }
376   if (!conf.initialize(configFileName)) {
377     cerr << argv[0] << ": " << conf.error() << endl;
378     return 1;
379   }
380   if (!ethernetInfo.initialize(ouiFileName)) {
381     cerr << argv[0] << ": " << ethernetInfo.error() << endl;
382     return 1;
383   }
384   if (conf.getString("interfaces") == "") {
385     cerr << argv[0] << ": no interfaces specified" << endl;
386     return 1;
387   }
388   if (conf.getString("maxQueueSize") == "") {
389     cerr << argv[0] << ": no maximum delayed correction queue size set"
390          << endl;
391     return 1;
392   }
393   maxQueueSize = conf.getNumber("maxQueueSize");
394   if (conf.getString("logging") == "") {
395     cerr << argv[0] << ": no logging mode specified" << endl;
396     return 1;
397   }
398   else {
399     if (conf.getString("logging") != "on" &&
400         conf.getString("logging") != "off") {
401       cerr << argv[0] << ": " << conf.fileName() << ": "
402            << "unknown logging mode \"" << conf.getString("logging")
403            << "\" specified" << endl;
404       return 1;
405     }
406   }
407   if (conf.getString("logging") == "on") {
408     if (conf.getString("log") == "") {
409       cerr << argv[0] << ": " << conf.fileName() << ": no log file specified"
410            << endl;
411       return 1;
412     }
413     logger.initialize(conf.getString("log"), "[%F %T] ");
414     if (!logger) {
415       cerr << argv[0] << ": " << logger.error() << endl;
416       return 1;
417     }
418   }
419   interfaceNames = explodeString(conf.getString("interfaces"));
420   interfaces.resize(interfaceNames.size());
421   for (size_t i = 0; i < interfaceNames.size(); ++i) {
422     interfaces[i].index = i;
423     interfaces[i].name = interfaceNames[i];
424     if (conf.getString("mode_" + interfaceNames[i]) == "") {
425       cerr << argv[0] << ": " << conf.fileName()
426            << ": no mode specified for " << interfaceNames[i] << endl;
427       return 1;
428     }
429     if (conf.getString("mode_" + interfaceNames[i]) == "passive") {
430       interfaces[i].mode = PASSIVE_MODE;
431     }
432     else {
433       if (conf.getString("mode_" + interfaceNames[i]) == "aggressive") {
434         interfaces[i].mode = AGGRESSIVE_MODE;
435       }
436       else {
437         cerr << argv[0] << ": " << conf.fileName() << ": unknown mode \""
438              << conf.getString("mode_" + interfaceNames[i])
439              << "\" specified for " << interfaceNames[i] << endl;
440         return 1;
441       }
442     }
443     for (size_t j = 0; j < conf.getStrings("pair_" +
444          interfaceNames[i]).size(); ++j) {
445       addressPair = explodeString(conf.getStrings("pair_" +
446                                                   interfaceNames[i])[j]);
447       if (addressPair.size() != 2) {
448         cerr << argv[0] << ": " << conf.fileName()
449              << ": malformed address pair \""
450              << conf.getStrings("pair_" + interfaceNames[i])[j] << '"' << endl;
451         return 1;
452       }
453       interfaces[i].pairs[textToIP(addressPair[0])] = textToEthernet(addressPair[1]);
454       interfaces[i].descriptor = pcap_open_live(interfaceNames[i].c_str(),
455                                                 65535, 1, 0, errorBuffer);
456       if (interfaces[i].descriptor == NULL) {
457         cerr << argv[0] << ": " << errorBuffer << endl;
458         return 1;
459       }
460       if (interfaces[i].mode == AGGRESSIVE_MODE) {
461         if (conf.getString("reply_" + interfaces[i].name) == "" ||
462             conf.getString("reply_" + interfaces[i].name) == interfaces[i].name) {
463           interfaces[i].replyName = interfaces[i].name;
464           interfaces[i].replyDescriptor = interfaces[i].descriptor;
465         }
466         else {
467           interfaces[i].replyName = conf.getString("reply_" + interfaces[i].name);
468           interfaces[i].replyDescriptor = pcap_open_live(interfaces[i].replyName.c_str(),
469                                                          65535, 1, 0, errorBuffer);
470           if (interfaces[i].replyDescriptor == NULL) {
471             cerr << argv[0] << ": " << errorBuffer << endl;
472             return 1;
473           }
474         }
475       }
476       /*
477        * Older versions of libpcap expect the third argument of pcap_compile()
478        * to be of type "char*".
479        */
480      if (pcap_compile(interfaces[i].descriptor, &bpfProgram, (char*)"arp", 0,
481                       0) == -1) {
482         cerr << argv[0] << ": " << pcap_geterr(interfaces[i].descriptor)
483              << endl;
484         return 1;
485       }
486       if (pcap_setfilter(interfaces[i].descriptor, &bpfProgram) == -1) {
487         cerr << argv[0] << ": " << pcap_geterr(interfaces[i].descriptor)
488              << endl;
489         return 1;
490       }
491 /* If running on FreeBSD, put the BPF device into immediate mode. */
492 #ifdef __FreeBSD__
493       if (ioctl(pcap_fileno(interfaces[i].descriptor), BIOCIMMEDIATE,
494                 &immediate) == -1) {
495         cerr << argv[0] << ": " << strerror(errno) << endl;
496         return 1;
497       }
498 #endif
499     }
500   }
501   pid = fork();
502   if (pid < 0) {
503     cerr << argv[0] << ": fork(): " << strerror(errno) << endl;
504     return 1;
505   }
506   /* If this is the child process... */
507   if (pid == 0) {
508     /* Write our PID to the PID file. */
509     pidFile.open(pidFileName.c_str());
510     if (!pidFile) {
511       cerr << argv[0] << ": open(): " << pidFileName << ": " << strerror(errno)
512            << endl;
513       return 1;
514     }
515     pidFile << getpid() << endl;
516     pidFile.close();
517     if (chdir("/") != 0) {
518       cerr << argv[0] << ": chdir(): /: " << strerror(errno) << endl;
519       return 1;
520     }
521     /* Close unneeded file descriptors. */
522     if (close(STDIN_FILENO) != 0) {
523       cerr << argv[0] << ": close(): STDIN_FILENO: " << strerror(errno) << endl;
524       return 1;
525     }
526     if (close(STDOUT_FILENO) != 0) {
527       cerr << argv[0] << ": close(): STDOUT_FILENO: " << strerror(errno) << endl;
528       return 1;
529     }
530     if (close(STDERR_FILENO) != 0) {
531       cerr << argv[0] << ": close(): STDERR_FILENO: " << strerror(errno) << endl;
532       return 1;
533     }
534   }
535   /* Otherwise, if this is the parent process, exit. */
536   else {
537     return 0;
538   }
539   logger << logger.time() << programName << " starting." << endl;
540   /* Start listener threads. */
541   for (size_t i = 0; i < interfaces.size(); ++i) {
542     /*
543      * Don't bother starting a listener thread for an interface with no address
544      * pairs configured.
545      */
546     if (interfaces[i].pairs.size() > 0) {
547       error = pthread_create(&(interfaces[i].thread), NULL, &listen,
548                              &(interfaces[i]));
549       if (error != 0) {
550         logger.lock();
551         logger << logger.time() << "pthread_create(): " << strerror(error)
552                << "; exiting." << endl;
553         logger.unlock();
554         return 1;
555       }
556     }
557   }
558   /* Start delayed response thread. */
559   error = pthread_create(&delayedResponseThread, NULL, &delayedResponse, NULL);
560   if (error != 0) {
561     logger.lock();
562     logger << logger.time() << "pthread_create(): " << strerror(error)
563            << "; exiting." << endl;
564     logger.unlock();
565     return 1;
566   }
567   /* Wait forever for listener threads to return. */
568   for (size_t i = 0; i < interfaces.size(); ++i) {
569     if (interfaces[i].pairs.size() > 0) {
570       error = pthread_join(interfaces[i].thread, NULL);
571       if (error != 0) {
572         logger.lock();
573         logger << logger.time() << "pthread_join(): " << strerror(error)
574                << "; exiting." << endl;
575         logger.unlock();
576         return 1;
577       }
578     }
579   }
580   /* Wait forever for delayed response thread to return. */
581   error = pthread_join(delayedResponseThread, NULL);
582   if (error != 0) {
583     logger.lock();
584     logger << logger.time() << "pthread_join(): " << strerror(error)
585            << "; exiting." << endl;
586     logger.unlock();
587     return 1;
588   }
589   /* Not reached. */
590   return 0;
591 }
592