blob: a68c0752f4f4d42fb5018f3d43327c78025cf088 [file] [log] [blame]
/*
* Copyright 2016 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <fcntl.h>
#include <limits.h>
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <pcap.h>
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <endian.h>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
#define le_to_host16 le16toh
#define le_to_host32 le32toh
struct ieee80211_radiotap_hdr {
uint8_t it_version;
uint8_t it_pad;
uint16_t it_len;
uint32_t it_present;
} __attribute__ ((packed));
struct ieee80211_mgmt {
uint16_t frame_control;
uint16_t duration;
uint8_t da[6];
uint8_t sa[6];
uint8_t bssid[6];
uint16_t seq_ctrl;
union {
struct {
uint16_t cap;
uint16_t listen;
uint8_t variable[0];
} __attribute__ ((packed)) assoc_req;
struct {
uint8_t variable[0];
} __attribute__ ((packed)) probe_req;
struct {
uint32_t timestamp1;
uint32_t timestamp2;
uint16_t beacon_interval;
uint16_t capabilities;
uint8_t variable[0];
} __attribute__ ((packed)) probe_resp;
struct {
uint32_t timestamp1;
uint32_t timestamp2;
uint16_t beacon_interval;
uint16_t capabilities;
uint8_t variable[0];
} __attribute__ ((packed)) beacon;
} u;
} __attribute__ ((packed));
#define ASSOC_REQ 0
#define PROBE_REQ 4
#define PROBE_RESP 5
#define BEACON 8
#define AUTH_REQ 11
int usage(const char *progname)
{
fprintf(stderr, "usage: %s -f pcap\n", progname);
exit(1);
}
/* from the very helpful https://eigenstate.org/notes/seccomp */
void enable_seccomp()
{
#define ArchField offsetof(struct seccomp_data, arch)
#define Allow(syscall) \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##syscall, 0, 1), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
struct sock_filter filter[] = {
/* validate arch */
#ifdef __x86_64__
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, ArchField),
BPF_JUMP( BPF_JMP+BPF_JEQ+BPF_K, AUDIT_ARCH_X86_64, 1, 0),
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL),
#else
#error Please add support for this architecture to the SECCOMP BPF code.
#endif
/* load syscall */
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)),
/* list of allowed syscalls */
Allow(exit_group),
Allow(read),
Allow(rename),
Allow(write),
Allow(stat),
Allow(close),
Allow(munmap),
Allow(mmap),
Allow(fstat),
/* and if we don't match above, die */
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL),
};
struct sock_fprog filterprog = {
.len = sizeof(filter)/sizeof(filter[0]),
.filter = filter
};
/* set up the restricted environment */
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
perror("prctl(PR_SET_NO_NEW_PRIVS)");
exit(1);
}
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &filterprog) == -1) {
perror("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER)");
exit(1);
}
}
int is_broadcast_mac(uint8_t *mac)
{
if (mac[0] == 0xff && mac[1] == 0xff && mac[2] == 0xff &&
mac[3] == 0xff && mac[4] == 0xff && mac[5] == 0xff) {
return 1;
}
return 0;
}
void rewrite_ssid(uint8_t *ie, size_t ie_len)
{
while (ie_len >= 2) {
u8 id, elen;
id = *ie++;
elen = *ie++;
ie_len -= 2;
if (elen > ie_len) {
break;
}
/* Replace SSID with 'XXXXXXXX' */
if ((id == 0) && (elen > 0)) {
int i;
for (i = 0; i < elen; ++i) {
ie[i] = 'X';
}
}
ie += elen;
ie_len -= elen;
}
}
int main(int argc, char **argv)
{
int opt;
pcap_t *handle;
pcap_dumper_t *outhandle;
char errbuf[PCAP_ERRBUF_SIZE];
struct rlimit lim;
struct pcap_pkthdr hdr;
const uint8_t *pkt;
const char *filename = NULL;
char outfile[PATH_MAX];
struct stat in_st, out_st;
FILE *pcapfp, *outfp;
while ((opt = getopt(argc, argv, "f:")) != -1) {
switch(opt){
case 'f':
filename = optarg;
break;
default:
usage(argv[0]);
break;
}
}
if (filename == NULL) {
usage(argv[0]);
}
if ((pcapfp = fopen(filename, "rb")) == NULL) {
perror("fopen(pcapfile)");
exit(1);
}
snprintf(outfile, sizeof(outfile), "%s.anon", filename);
if ((outfp = fopen(outfile, "wb")) == NULL) {
perror("fopen(outfile)");
exit(1);
}
/* No more files should be opened after this. */
if (getrlimit(RLIMIT_NOFILE, &lim)) {
perror("getrlimit");
exit(1);
}
lim.rlim_cur = 0;
if (setrlimit(RLIMIT_NOFILE, &lim)) {
perror("setrlimit");
exit(1);
}
/* We're about to parse packets, limit damage if we process
* something malicious. */
enable_seccomp();
if ((handle = pcap_fopen_offline(pcapfp, errbuf)) == NULL) {
perror("Cannot open pcap file");
exit(1);
}
if (pcap_datalink(handle) != DLT_IEEE802_11_RADIO) {
fprintf(stderr, "pcap file is not DLT_IEEE802_11_RADIO");
exit(1);
}
if ((outhandle = pcap_dump_fopen(handle, outfp)) == NULL) {
perror("Cannot open output pcap file");
exit(1);
}
while ((pkt = pcap_next(handle, &hdr)) != NULL) {
struct ieee80211_radiotap_hdr *rtap;
struct ieee80211_mgmt *mlme;
uint16_t fc, type, subtype;
rtap = (struct ieee80211_radiotap_hdr *)pkt;
mlme = (struct ieee80211_mgmt *)(pkt + rtap->it_len);
fc = le_to_host16(mlme->frame_control);
type = (fc >> 2) & 0x0003;
subtype = (fc >> 4) & 0x000f;
if (type == 0 && subtype == ASSOC_REQ) {
uint8_t *ie = mlme->u.assoc_req.variable;
size_t ie_len = hdr.caplen - (ie - (uint8_t *)mlme) - rtap->it_len - 4;
rewrite_ssid(ie, ie_len);
}
if (type == 0 && subtype == PROBE_REQ) {
uint8_t *ie = mlme->u.probe_req.variable;
size_t ie_len = hdr.caplen - (ie - (uint8_t *)mlme) - rtap->it_len - 4;
rewrite_ssid(ie, ie_len);
}
if (type == 0 && subtype == PROBE_RESP) {
uint8_t *ie = mlme->u.probe_resp.variable;
size_t ie_len = hdr.caplen - (ie - (uint8_t *)mlme) - rtap->it_len - 4;
rewrite_ssid(ie, ie_len);
}
if (type == 0 && subtype == BEACON) {
uint8_t *ie = mlme->u.beacon.variable;
size_t ie_len = hdr.caplen - (ie - (uint8_t *)mlme) - rtap->it_len - 4;
rewrite_ssid(ie, ie_len);
}
/* Anonymize the MAC addresses (but preserve OUI). */
mlme->sa[3] = mlme->sa[4] = mlme->sa[5] = 0;
if (!is_broadcast_mac(mlme->da)) {
mlme->da[3] = mlme->da[4] = mlme->da[5] = 0;
}
if (!is_broadcast_mac(mlme->bssid)) {
mlme->bssid[3] = mlme->bssid[4] = mlme->bssid[5] = 0;
}
pcap_dump((u_char *)outhandle, &hdr, pkt);
}
pcap_dump_close(outhandle);
if (stat(filename, &in_st) || stat(outfile, &out_st)) {
perror("stat");
exit(1);
}
if (in_st.st_size != out_st.st_size) {
fprintf(stderr, "Output truncated: got %jd bytes, want %jd bytes",
out_st.st_size, in_st.st_size);
exit(1);
}
if (rename(outfile, filename)) {
perror("rename");
exit(1);
}
exit(0);
}