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