1 __ __ _ ___ _ _
2| \/ |__ _| |_____ / __|_ __| (_)__ ___
3| |\/| / _` | / / -_) \__ \ '_ \ | / _/ -_)
4|_| |_\__,_|_\_\___| |___/ .__/_|_\__\___|
5 |_|
Make Splice #
A splice is where two parts are attached together to form one solid part. In networking, when a third party splices into a network tunnel, this is MITM.
How we got into this mess #
While working on system designed specifically for the purpose of launching wireless attacks, several problems were encountered with pre-existing solutions designed for these purposes.
- They were not intended to be run as system services, and thus required start, stop, and restart manually.
- They were not compatible with remote systems where the user needed to ssh into the host and maintain that ssh connection through the start and stopping of services.
- They required the adoption of bulky programs or services, that would increase the overall footprint of the system, and reduce performance.
So, we had to look at creating a custom solution, which forced us to acquire much more knowledge than we ever originally intended to acquire, and overcome complications we never forsaw arising.
Wanting to be cool like the young folk #
Several of the pre-existing solutions for performing evil twin attacks create a tun interface for the attacker to use for monitoring connections, inject packets, and use to proxy connections. Such notable solutions are airbase-ng, scapy-fakeap, as well as others. Creating this interface proved more elusive than originally expected, because of how linux’s kernel handles the tun/tap interfaces, requiring a process to run using these interfaces in order to keep them up. Thus is how we found ourselves here, writing a program to manage the transfer of packets through our tunnel interface.
Creating a network device can be done quite easily in python, although as mentioned above, creating the interface does not maintain the interfaces up state. 1
1#!/usr/bin/env python3
2
3import fcntl
4import struct
5import os
6from scapy.layers.inet import IP
7from warnings import warn
8from dataclasses import dataclass, field
9from typing import List
10import subprocess
11
12
13@dataclass
14class apData:
15 IFNAMSIZ: int = 16
16 IFF_TUN: int = 0x0001
17 IFF_TAP: int = 0x0002 # Should we want to tunnel layer 2...
18 IFF_NO_PI: int = 0x1000
19 TUNSETIFF: int = 0x400454ca
20 DEVICE: str = "tun0"
21 IP_ADDRESS: str = "10.1.1.1"
22 NETMASK: str = "255.255.255.0"
23 IP_NETWORK: str = "10.1.1"
24 NET_BROADCAST: str = "10.1.1.255"
25
26
27def create_tun():
28 name = "rogue1"
29 if len(name) > apData.IFNAMSIZ:
30 raise Exception(
31 "Tun interface name it too big"
32 )
33 fd = open('/dev/net/tun', 'r+b')
34 ifr_flags = apData.IFF_TUN | apData.IFF_NO_PI
35 ifreq = struct.pack('16sH', name, ifr_flags)
36 fcntl.ioctl(fd, apData.TUNSETIFF, ifreq)
37 if subprocess.call(['ip', 'addr', 'add', apData.IP_ADDRESS, 'dev', apData.DEVICE]):
38 warn("Failed to assign ip address to dev")
39 if subprocess.call(['ip', 'link', 'set', 'dev', apData.DEVICE, 'up']):
40 warn("failed to bring device up")
41
42
43if __name__ == "create_tun.py":
44 create_tun()
A simple buffer size printer #
Below is a simple program in c that outputs the size of the buffer read on the tunnel device. 2 It was written for demonstration purposes only, and fulfills no other purpose than to print the buffer size. It will not provide the features needed for our evil twin tunnel interface.
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <fcntl.h>
5#include <unistd.h>
6#include <sys/ioctl.h>
7#include <linux/if.h>
8#include <linux/if_tun.h>
9
10#define IFNAMSIZ 16
11
12int main() {
13 int tun_fd = open("/dev/net/tun", O_RDWR);
14 struct ifreq ifr;
15 memset(&ifr, 0, sizeof(ifr));
16 ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
17 strcpy(ifr.ifr_name, "tun0");
18 ioctl(tun_fd, TUNSETIFF, (void *)&ifr);
19
20 while (1) {
21 char buffer[1500];
22 int nread = read(tun_fd, buffer, sizeof(buffer));
23 printf("Read %d bytes from device %s\n", nread, ifr.ifr_name);
24 }
25
26 close(tun_fd);
27 return 0;
28}
Tunneling udp packets #
John Millikin provides simple code 3 for creating a program that simply forwards ipv4 packets to and from the localhost using UDP. Again for our purposes, which will need to forward packets via tcp and udp this will not suffice, but his code does provide a means for setting up and configuring the tun device using the linux kernel’s native netlink subsystem.
1/* Copyright (c) John Millikin <john@john-millikin.com> */
2/* SPDX-License-Identifier: 0BSD */
3#include <arpa/inet.h>
4#include <linux/if.h>
5#include <linux/netlink.h>
6#include <linux/rtnetlink.h>
7#include <net/if.h>
8#include <stdint.h>
9#include <string.h>
10#include <poll.h>
11#include <stdlib.h>
12
13int netlink_connect() {
14 int netlink_fd, rc;
15 struct sockaddr_nl sockaddr;
16
17 netlink_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
18 if (netlink_fd == -1) {
19 return -1;
20 }
21
22 memset(&sockaddr, 0, sizeof sockaddr);
23 sockaddr.nl_family = AF_NETLINK;
24 rc = bind(netlink_fd, (struct sockaddr*) &sockaddr, sizeof sockaddr);
25 if (rc == -1) {
26 int bind_errno = errno;
27 close(netlink_fd);
28 errno = bind_errno;
29 return -1;
30 }
31 return netlink_fd;
32}
33
34int netlink_set_addr_ipv4(
35 int netlink_fd
36 , const char *iface_name
37 , const char *address
38 , uint8_t network_prefix_bits
39) {
40 struct {
41 struct nlmsghdr header;
42 struct ifaddrmsg content;
43 char attributes_buf[64];
44 } request;
45
46 struct rtattr *request_attr;
47 size_t attributes_buf_avail = sizeof request.attributes_buf;
48
49 memset(&request, 0, sizeof request);
50 request.header.nlmsg_len = NLMSG_LENGTH(sizeof request.content);
51 request.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE;
52 request.header.nlmsg_type = RTM_NEWADDR;
53 request.content.ifa_index = if_nametoindex(iface_name);
54 request.content.ifa_family = AF_INET;
55 request.content.ifa_prefixlen = network_prefix_bits;
56
57 /* request.attributes[IFA_LOCAL] = address */
58 request_attr = IFA_RTA(&request.content);
59 request_attr->rta_type = IFA_LOCAL;
60 request_attr->rta_len = RTA_LENGTH(sizeof (struct in_addr));
61 request.header.nlmsg_len += request_attr->rta_len;
62 inet_pton(AF_INET, address, RTA_DATA(request_attr));
63
64 /* request.attributes[IFA_ADDRESS] = address */
65 request_attr = RTA_NEXT(request_attr, attributes_buf_avail);
66 request_attr->rta_type = IFA_ADDRESS;
67 request_attr->rta_len = RTA_LENGTH(sizeof (struct in_addr));
68 request.header.nlmsg_len += request_attr->rta_len;
69 inet_pton(AF_INET, address, RTA_DATA(request_attr));
70
71 if (send(netlink_fd, &request, request.header.nlmsg_len, 0) == -1) {
72 return -1;
73 }
74 return 0;
75}
76
77int netlink_link_up(int netlink_fd, const char *iface_name) {
78 struct {
79 struct nlmsghdr header;
80 struct ifinfomsg content;
81 } request;
82
83 memset(&request, 0, sizeof request);
84 request.header.nlmsg_len = NLMSG_LENGTH(sizeof request.content);
85 request.header.nlmsg_flags = NLM_F_REQUEST;
86 request.header.nlmsg_type = RTM_NEWLINK;
87 request.content.ifi_index = if_nametoindex(iface_name);
88 request.content.ifi_flags = IFF_UP;
89 request.content.ifi_change = 1;
90
91 if (send(netlink_fd, &request, request.header.nlmsg_len, 0) == -1) {
92 return -1;
93 }
94 return 0;
95}
96
97int run_proxy(int tuntap_fd, int send_fd, int recv_fd) {
98 struct pollfd poll_fds[2];
99 char recv_buf[UINT16_MAX];
100
101 poll_fds[0].fd = tuntap_fd;
102 poll_fds[0].events = POLLIN;
103 poll_fds[1].fd = recv_fd;
104 poll_fds[1].events = POLLIN;
105
106 while (1) {
107 if (poll(poll_fds, 2, -1) == -1) {
108 return -1;
109 }
110
111 if ((poll_fds[0].revents & POLLIN) != 0) {
112 ssize_t count = read(tuntap_fd, recv_buf, UINT16_MAX);
113 if (count < 0) {
114 return -1;
115 }
116 send(send_fd, recv_buf, count, 0);
117 }
118
119 if ((poll_fds[1].revents & POLLIN) != 0) {
120 ssize_t count = recv(recv_fd, recv_buf, UINT16_MAX, 0);
121 if (count < 0) {
122 return -1;
123 }
124 if (write(tuntap_fd, recv_buf, count) == -1) {
125 return -1;
126 }
127 }
128 }
129
130 return 0;
131}
Taking a closer look at airbase-ng #
Aircrack-Ng provides airbase-ng and is one of the more commonly used means to perform the evil twin attack, it also is the only prepackaged solution that allowed remote network connections to exist while running. Airbase-Ng does most of the work for the user, it setups the wireless interface to act as an access point, and particularly important to this discussion it also creates a tap interface for the user to inject and manipulate packets.
An examination of the code base for airbase-ng 4, showed that airbase does more than just create one tun/tap interface, it actually creates two. This after all only makes sense, because tun tap interfaces are designed to forward traffic between two different networks, one being local and the other being remote. What is not clear is exactly how is the tunnel configured? We know one end of the tap tunnel is left to it’s own devices, but what about the other end? How is it created?
So, in order to discover these things, we started airbase-ng up with sudo airbase-ng -z 2 -c 11 --essid example wlan0
and examined the tun/tap interface created while airbase was running.5 What
was found was a tap interface labeled "at0" that was in a "DOWN" state, because it was not currently
being used. Running ethtool at0
without any flags did not provide any additional information, but
when the -i
flag was used it displayed it’s driver information.
1driver: tun
2version: 1.6
3firmware-version:
4expansion-rom-version:
5bus-info: tap
6supports-statistics: no
7supports-test: no
8supports-eeprom-access: no
9supports-register-dump: no
10supports-priv-flags: no
This provided very little additional information other than the interface used was a tap interface.
The End of Research #
These tunnel interfaces are created by tunneling traffic between two interfaces on completely separate networks through a tun/tap interface. There is nothing complicated about it, and as usual we were found guilty of the usual assumption of things being more complex than they really are. This was our mistake during research.
In the end, several programs were found that would perform the creation of the network archetecture we desired while providing additional features we desired to implement using a separate application. So, having spent more time than was necessary, and making the problem more complex than it really was, it was decided to end research and move towards actual application.
Honorable Mentions #
Original Implementation #
If one desires to find more out more about the tun/tap interface for the linux kernel, there is a tutorial on how to create one of these interfaces on the linux kernel website.6
Honorable Mention: Backreference.org tuntap tutorial #
Out of all the references mentioned on this page, the backreference tutorial 7 is by far the best and most informative. Unlike several of the tutorials, D.Brini actually aims the tutorial towards real world application, and writes with a casual tone that makes reading less burdensome. D.Brini goes through all the steps in c language on how to create an interface and how to get the interface to forward packets to and from two different tunnel interfaces.
Special Mention: The portable tuntap library for c, c++, and python #
If one desires, they can install and facilitate the lovely portable tuntap library written by LaKabane. 8 As a word of warning, it is rather skimpy on documentation.
References #
-
https://gist.github.com/anoduck/2cebdb53c10c1a318ef9cf1e2f386967 ↩︎
-
https://john-millikin.com/creating-tun-tap-interfaces-in-linux ↩︎
-
https://codetoflow.com?uid=55440108-9f4d-4249-9f93-4318e4419af2 ↩︎
-
https://www.xmodulo.com/how-to-find-ethernet-network-interface-card-information-in-linux.html ↩︎
-
https://www.kernel.org/doc/html/latest/networking/tuntap.html ↩︎
-
https://backreference.org/2010/03/26/tuntap-interface-tutorial/index.html ↩︎