diff options
Diffstat (limited to 'src/fnettrace-sni/main.c')
-rw-r--r-- | src/fnettrace-sni/main.c | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/src/fnettrace-sni/main.c b/src/fnettrace-sni/main.c new file mode 100644 index 000000000..ffcb3f28a --- /dev/null +++ b/src/fnettrace-sni/main.c | |||
@@ -0,0 +1,207 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014-2022 Firejail Authors | ||
3 | * | ||
4 | * This file is part of firejail project | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU 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 | #include "fnettrace_sni.h" | ||
21 | #include <sys/ioctl.h> | ||
22 | #include <time.h> | ||
23 | #include <linux/filter.h> | ||
24 | #include <linux/if_ether.h> | ||
25 | #define MAX_BUF_SIZE (64 * 1024) | ||
26 | |||
27 | // pkt - start of TLS layer | ||
28 | static void print_tls(uint32_t ip_dest, unsigned char *pkt, unsigned len) { | ||
29 | assert(pkt); | ||
30 | |||
31 | char ip[30]; | ||
32 | sprintf(ip, "%d.%d.%d.%d", PRINT_IP(ip_dest)); | ||
33 | time_t seconds = time(NULL); | ||
34 | struct tm *t = localtime(&seconds); | ||
35 | |||
36 | // expecting a handshake packet and client hello | ||
37 | if (pkt[0] != 0x16 || pkt[5] != 0x01) | ||
38 | goto errout; | ||
39 | |||
40 | |||
41 | // look for server name indication | ||
42 | unsigned char *ptr = pkt; | ||
43 | int i = 0; | ||
44 | char *name = NULL; | ||
45 | while (i < (len - 20)) { | ||
46 | // 3 zeros and 3 matching length fields | ||
47 | if (*ptr == 0 && *(ptr + 1) == 0 && (*(ptr + 2) == 0 || *(ptr + 2) == 1) && *(ptr + 6) == 0) { | ||
48 | uint16_t len1; | ||
49 | memcpy(&len1, ptr + 2, 2); | ||
50 | len1 = ntohs(len1); | ||
51 | |||
52 | uint16_t len2; | ||
53 | memcpy(&len2, ptr + 4, 2); | ||
54 | len2 = ntohs(len2); | ||
55 | |||
56 | uint16_t len3; | ||
57 | memcpy(&len3, ptr + 7, 2); | ||
58 | len3 = ntohs(len3); | ||
59 | |||
60 | if (len1 == (len2 + 2) && len1 == (len3 + 5)) { | ||
61 | *(ptr + 9 + len3) = 0; | ||
62 | name = ptr + 9; | ||
63 | break; | ||
64 | } | ||
65 | } | ||
66 | ptr++; | ||
67 | i++; | ||
68 | } | ||
69 | |||
70 | if (name) | ||
71 | printf("%02d:%02d:%02d %15s %s\n", t->tm_hour, t->tm_min, t->tm_sec, ip, name); | ||
72 | else | ||
73 | goto nosni; | ||
74 | return; | ||
75 | |||
76 | errout: | ||
77 | printf("%02d:%02d:%02d %15s Error: invalid TLS packet\n", t->tm_hour, t->tm_min, t->tm_sec, ip); | ||
78 | return; | ||
79 | |||
80 | nosni: | ||
81 | printf("%02d:%02d:%02d %15s no SNI\n", t->tm_hour, t->tm_min, t->tm_sec, ip); | ||
82 | return; | ||
83 | } | ||
84 | |||
85 | // https://www.kernel.org/doc/html/latest/networking/filter.html | ||
86 | static void custom_bpf(int sock) { | ||
87 | struct sock_filter code[] = { | ||
88 | // sudo tcpdump "tcp port 443 and (tcp[((tcp[12] & 0xf0) >>2)] = 0x16) && (tcp[((tcp[12] & 0xf0) >>2)+5] = 0x01)" -dd | ||
89 | { 0x28, 0, 0, 0x0000000c }, | ||
90 | { 0x15, 27, 0, 0x000086dd }, | ||
91 | { 0x15, 0, 26, 0x00000800 }, | ||
92 | { 0x30, 0, 0, 0x00000017 }, | ||
93 | { 0x15, 0, 24, 0x00000006 }, | ||
94 | { 0x28, 0, 0, 0x00000014 }, | ||
95 | { 0x45, 22, 0, 0x00001fff }, | ||
96 | { 0xb1, 0, 0, 0x0000000e }, | ||
97 | { 0x48, 0, 0, 0x0000000e }, | ||
98 | { 0x15, 2, 0, 0x000001bb }, | ||
99 | { 0x48, 0, 0, 0x00000010 }, | ||
100 | { 0x15, 0, 17, 0x000001bb }, | ||
101 | { 0x50, 0, 0, 0x0000001a }, | ||
102 | { 0x54, 0, 0, 0x000000f0 }, | ||
103 | { 0x74, 0, 0, 0x00000002 }, | ||
104 | { 0xc, 0, 0, 0x00000000 }, | ||
105 | { 0x7, 0, 0, 0x00000000 }, | ||
106 | { 0x50, 0, 0, 0x0000000e }, | ||
107 | { 0x15, 0, 10, 0x00000016 }, | ||
108 | { 0xb1, 0, 0, 0x0000000e }, | ||
109 | { 0x50, 0, 0, 0x0000001a }, | ||
110 | { 0x54, 0, 0, 0x000000f0 }, | ||
111 | { 0x74, 0, 0, 0x00000002 }, | ||
112 | { 0x4, 0, 0, 0x00000005 }, | ||
113 | { 0xc, 0, 0, 0x00000000 }, | ||
114 | { 0x7, 0, 0, 0x00000000 }, | ||
115 | { 0x50, 0, 0, 0x0000000e }, | ||
116 | { 0x15, 0, 1, 0x00000001 }, | ||
117 | { 0x6, 0, 0, 0x00040000 }, | ||
118 | { 0x6, 0, 0, 0x00000000 }, | ||
119 | }; | ||
120 | |||
121 | struct sock_fprog bpf = { | ||
122 | .len = (unsigned short) sizeof(code) / sizeof(code[0]), | ||
123 | .filter = code, | ||
124 | }; | ||
125 | |||
126 | int rv = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)); | ||
127 | if (rv < 0) { | ||
128 | fprintf(stderr, "Error: cannot attach BPF filter\n"); | ||
129 | exit(1); | ||
130 | } | ||
131 | } | ||
132 | |||
133 | static void run_trace(void) { | ||
134 | // grab all Ethernet packets and use a custom BPF filter to get only UDP from source port 53 | ||
135 | int s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); | ||
136 | if (s < 0) | ||
137 | errExit("socket"); | ||
138 | custom_bpf(s); | ||
139 | |||
140 | unsigned char buf[MAX_BUF_SIZE]; | ||
141 | while (1) { | ||
142 | fd_set rfds; | ||
143 | FD_ZERO(&rfds); | ||
144 | FD_SET(s, &rfds); | ||
145 | struct timeval tv; | ||
146 | tv.tv_sec = 1; | ||
147 | tv.tv_usec = 0; | ||
148 | int rv = select(s + 1, &rfds, NULL, NULL, &tv); | ||
149 | if (rv < 0) | ||
150 | errExit("select"); | ||
151 | else if (rv == 0) | ||
152 | continue; | ||
153 | unsigned bytes = recvfrom(s, buf, MAX_BUF_SIZE, 0, NULL, NULL); | ||
154 | |||
155 | if (bytes >= (14 + 20 + 20)) { // size of MAC + IP + TCP headers | ||
156 | uint8_t ip_hlen = (buf[14] & 0x0f) * 4; | ||
157 | uint16_t port_dest; | ||
158 | memcpy(&port_dest, buf + 14 + ip_hlen + 2, 2); | ||
159 | port_dest = ntohs(port_dest); | ||
160 | uint8_t protocol = buf[14 + 9]; | ||
161 | uint32_t ip_dest; | ||
162 | memcpy(&ip_dest, buf + 14 + 16, 4); | ||
163 | ip_dest = ntohl(ip_dest); | ||
164 | uint8_t tcp_hlen = (buf[14 + ip_hlen + 12] & 0xf0) >> 2; | ||
165 | |||
166 | // if TLS packet, extract SNI | ||
167 | if (port_dest == 443 && protocol == 6) // TCP protocol | ||
168 | print_tls(ip_dest, buf + 14 + ip_hlen + tcp_hlen, bytes - 14 - ip_hlen - tcp_hlen); // IP and TCP header len | ||
169 | } | ||
170 | } | ||
171 | |||
172 | close(s); | ||
173 | } | ||
174 | |||
175 | |||
176 | static void usage(void) { | ||
177 | printf("Usage: fnettrace-sni [OPTIONS]\n"); | ||
178 | printf("Options:\n"); | ||
179 | printf(" --help, -? - this help screen\n"); | ||
180 | printf("\n"); | ||
181 | } | ||
182 | |||
183 | int main(int argc, char **argv) { | ||
184 | int i; | ||
185 | |||
186 | for (i = 1; i < argc; i++) { | ||
187 | if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-?") == 0) { | ||
188 | usage(); | ||
189 | return 0; | ||
190 | } | ||
191 | else { | ||
192 | fprintf(stderr, "Error: invalid argument\n"); | ||
193 | return 1; | ||
194 | } | ||
195 | } | ||
196 | |||
197 | if (getuid() != 0) { | ||
198 | fprintf(stderr, "Error: you need to be root to run this program\n"); | ||
199 | return 1; | ||
200 | } | ||
201 | |||
202 | time_t now = time(NULL); | ||
203 | printf("SNI trace for %s\n", ctime(&now)); | ||
204 | run_trace(); | ||
205 | |||
206 | return 0; | ||
207 | } | ||