diff --git a/atomics/T1040/T1040.yaml b/atomics/T1040/T1040.yaml index 2fb0a94c..690eab6b 100644 --- a/atomics/T1040/T1040.yaml +++ b/atomics/T1040/T1040.yaml @@ -28,7 +28,7 @@ atomic_tests: tshark -c 5 -i #{interface} name: bash elevation_required: true -- name: Packet Capture macOS +- name: Packet Capture macOS using tcpdump or tshark auto_generated_guid: 9d04efee-eff5-4240-b8d2-07792b873608 description: | Perform a PCAP on macOS. This will require Wireshark/tshark to be installed. TCPdump may already be installed. @@ -153,4 +153,70 @@ atomic_tests: cleanup_command: |- pktmon filter remove name: command_prompt - elevation_required: true \ No newline at end of file + elevation_required: true +- name: Packet Capture macOS using /dev/bpfN with sudo + description: | + Opens a /dev/bpf file (O_RDONLY) and captures packets for a few seconds. + supported_platforms: + - macos + input_arguments: + ifname: + description: Specify interface to perform PCAP on. + type: String + default: en0 + csource_path: + description: Path to C program source + type: String + default: PathToAtomicsFolder/T1040/src/macos_pcapdemo.c + program_path: + description: Path to compiled C program + type: String + default: /tmp/t1040_macos_pcapdemo + dependency_executor_name: bash + dependencies: + - description: | + compile C program + prereq_command: | + exit 1 + get_prereq_command: | + cc #{csource_path} -o #{program_path} + executor: + command: | + sudo #{program_path} -i #{ifname} -t 3 + cleanup_command: | + rm -f #{program_path} + name: bash + elevation_required: true +- name: Filtered Packet Capture macOS using /dev/bpfN with sudo + description: | + Opens a /dev/bpf file (O_RDONLY), sets BPF filter for 'udp' and captures packets for a few seconds. + supported_platforms: + - macos + input_arguments: + ifname: + description: Specify interface to perform PCAP on. + type: String + default: en0 + csource_path: + description: Path to C program source + type: String + default: PathToAtomicsFolder/T1040/src/macos_pcapdemo.c + program_path: + description: Path to compiled C program + type: String + default: /tmp/t1040_macos_pcapdemo + dependency_executor_name: bash + dependencies: + - description: | + compile C program + prereq_command: | + exit 1 + get_prereq_command: | + cc #{csource_path} -o #{program_path} + executor: + command: | + sudo #{program_path} -f -i #{ifname} -t 3 + cleanup_command: | + rm -f #{program_path} + name: bash + elevation_required: true diff --git a/atomics/T1040/src/macos_pcapdemo.c b/atomics/T1040/src/macos_pcapdemo.c new file mode 100644 index 00000000..94293c8c --- /dev/null +++ b/atomics/T1040/src/macos_pcapdemo.c @@ -0,0 +1,299 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_IFNAME "en0" +#define DEFAULT_BUFSIZE 32767 + +static const struct option longopts[] = { + { "filter", no_argument, NULL, 'f'}, + { "promisc", no_argument, NULL, 'p'}, + { "ifname", required_argument, NULL, 'i'}, + { "time", required_argument, NULL, 't'}, + { 0, 0, 0, 0 } +}; + +// counters for each protocol seen + +static int64_t gNumTcp = 0; +static int64_t gNumUdp = 0; +static int64_t gNumIcmp = 0; +static int64_t gNumOther = 0; + +static void usage(const char *progname) +{ + printf("usage: %s \n", progname); + printf(" -f --filter Set BPF filter to UDP. Default is unfiltered.\n"); + printf(" -p --promisc Will enable promisc to capture packets not destined for this system.\n"); + printf(" -i --ifname Specify ifname. Default is 'en0'.\n"); + printf(" -t --time Exit after number of seconds. Default is to run until killed.\n"); +} + +typedef struct { + char interfaceName[16]; + unsigned int bufferLength; +} BpfOption; + +typedef struct { + int fd; + char deviceName[16]; + unsigned int bufferLength; + unsigned int lastReadLength; + unsigned int readBytesConsumed; + char *buffer; +} BpfSniffer; + +typedef struct { + char *data; +} CapturedInfo; + +/* + * pick next available /dev/bpf device file. + * @returns 0 and sets sniffer->fd on success, returns -1 on failure. + */ +int pick_bpf_device(BpfSniffer *sniffer) +{ + char dev[16] = {0}; + for (int i = 0; i < 99; ++i) { + sprintf(dev, "/dev/bpf%i", i); + sniffer->fd = open(dev, O_RDONLY); + if (sniffer->fd != -1) { + fprintf(stderr, "opened '%s'\n", dev); + strcpy(sniffer->deviceName, dev); + return 0; + } + } + return -1; +} + +/* + * Based on https://gist.github.com/c-bata/ca188c0184715efc2660422b4b3851c6 + */ +int new_bpf_sniffer(const char *ifname, BpfSniffer *sniffer, int isBpfFilterEnabled, int isPromiscEnabled) +{ + unsigned int bufferLength = DEFAULT_BUFSIZE; + if (pick_bpf_device(sniffer) == -1) + return -1; + + // setup packet buffer length + + if (ioctl(sniffer->fd, BIOCSBLEN, &bufferLength) == -1) { + perror("ioctl BIOCSBLEN"); + return -1; + } + sniffer->bufferLength = bufferLength; + + // specify interface + + struct ifreq interface; + strcpy(interface.ifr_name, ifname); + if(ioctl(sniffer->fd, BIOCSETIF, &interface) > 0) { + perror("ioctl BIOCSETIF"); + return -1; + } + + // immediate packet callback? + + unsigned int enable = 1; + if (ioctl(sniffer->fd, BIOCIMMEDIATE, &enable) == -1) { + perror("ioctl BIOCIMMEDIATE"); + return -1; + } + + // enable Promisc if enabled + + if (isPromiscEnabled) { + printf("Attempting to enable PRMOMISC\n"); + if (ioctl(sniffer->fd, BIOCPROMISC, NULL) == -1) { + perror("ioctl BIOCPROMISC"); + return -1; + } + } + + // set a BPF traffic filter if set + + if (isBpfFilterEnabled) { + // generated using 'tcpdump -i en0 udp -dd' + struct bpf_insn instructions[] = { +{ 0x28, 0, 0, 0x0000000c }, +{ 0x15, 0, 5, 0x000086dd }, +{ 0x30, 0, 0, 0x00000014 }, +{ 0x15, 6, 0, 0x00000011 }, +{ 0x15, 0, 6, 0x0000002c }, +{ 0x30, 0, 0, 0x00000036 }, +{ 0x15, 3, 4, 0x00000011 }, +{ 0x15, 0, 3, 0x00000800 }, +{ 0x30, 0, 0, 0x00000017 }, +{ 0x15, 0, 1, 0x00000011 }, +{ 0x6, 0, 0, 0x00040000 }, +{ 0x6, 0, 0, 0x00000000 }, + }; + struct bpf_program filter = {12, instructions}; + + printf("Adding BPF filter to only match 'udp' traffic\n"); + + if (ioctl(sniffer->fd, BIOCSETF, &filter) == -1) { + perror("ioctl BIOCSETF"); + return -1; + } + } + + // finally, allocate buffer and initialize + + sniffer->readBytesConsumed = 0; + sniffer->lastReadLength = 0; + sniffer->buffer = (char *)malloc(sizeof(char) * sniffer->bufferLength); + return 0; +} + +int read_bpf_packet_data(BpfSniffer *sniffer, CapturedInfo *info) +{ + struct bpf_hdr *bpfPacket; + if (sniffer->readBytesConsumed + sizeof(sniffer->buffer) >= sniffer->lastReadLength) { + sniffer->readBytesConsumed = 0; + memset(sniffer->buffer, 0, sniffer->bufferLength); + + ssize_t lastReadLength = read(sniffer->fd, sniffer->buffer, sniffer->bufferLength); + if (lastReadLength == -1) { + sniffer->lastReadLength = 0; + perror("read bpf packet:"); + return -1; + } + sniffer->lastReadLength = (unsigned int) lastReadLength; + } + + bpfPacket = (struct bpf_hdr*)((long)sniffer->buffer + (long)sniffer->readBytesConsumed); + info->data = sniffer->buffer + (long)sniffer->readBytesConsumed + bpfPacket->bh_hdrlen; + sniffer->readBytesConsumed += BPF_WORDALIGN(bpfPacket->bh_hdrlen + bpfPacket->bh_caplen); + return bpfPacket->bh_datalen; +} + +int close_bpf_sniffer(BpfSniffer *sniffer) +{ + free(sniffer->buffer); + + if (close(sniffer->fd) == -1) + return -1; + return 0; +} + +void ProcessIncomingPacketLoop(BpfSniffer *psniffer, int timeout) +{ + CapturedInfo info = { NULL }; + int dataLength = 0; + time_t tstop = time(NULL) + timeout; + + // loop to process incoming packets + + while((dataLength = read_bpf_packet_data(psniffer, &info)) != -1) + { + char* pend = (info.data + dataLength); + struct ether_header* eh = (struct ether_header*)info.data; + + if (ntohs(eh->ether_type) == ETHERTYPE_IP) { + + struct ip* ip = (struct ip*)((long)eh + sizeof(struct ether_header)); + switch(ip->ip_p) { + case IPPROTO_TCP: + ++gNumTcp; + break; + case IPPROTO_UDP: + ++gNumUdp; + break; + case IPPROTO_ICMP: + ++gNumIcmp; + break; + default: + ++gNumOther; + break; + } + + } else { + gNumOther++; + } + + if (timeout > 0 && time(NULL) >= tstop) { + break; + } + } +} + +void PrintStats() +{ + printf("TCP:%lld UDP:%lld ICMP:%lld Other:%lld\n", gNumTcp, gNumUdp, gNumIcmp, gNumOther); +} + +void sigint_handler(int sig) +{ + PrintStats(); +} + +int main(int argc, char *argv[]) +{ + BpfSniffer sniffer; + int isBpfFilterEnabled = 0; + int isPromiscEnabled = 0; + int timeout = 0; + char ifname[16] = DEFAULT_IFNAME; + int c; + + memset(&sniffer, 0, sizeof(sniffer)); + + while(1) + { + int option_index = 0; + + c = getopt_long(argc, argv, "fpi:t:", longopts, &option_index); + if (c == -1) + break; + + switch (c) { + case 'f': + isBpfFilterEnabled = 1; + break; + case 'p': + isPromiscEnabled = 1; + break; + case 'i': + strcpy(ifname, optarg); + printf("using interface '%s'\n", optarg); + break; + case 't': + timeout = atoi(optarg); + printf("will exit after about %d seconds (if packet activity)\n", timeout); + break; + default: + printf("invalid argument: '%c'\n", c); + usage(argv[0]); + return -1; + } + } + + if (new_bpf_sniffer(ifname, &sniffer, isBpfFilterEnabled, isPromiscEnabled) == -1) + return 1; + + signal(SIGINT, sigint_handler); + + ProcessIncomingPacketLoop(&sniffer, timeout); + + PrintStats(); + + close_bpf_sniffer(&sniffer); + return 0; +}