Back

--------------------------

Introduction to BPF (ARP)

--------------------------

...or Linux Socket Filtering

Oh, hi! Welcome back! We will be covering a small presentation on LSF and BPF, since these will prove themselves very useful in the future. I suppose you have prepared our good ol' container. Do (re)check "Ethernet Frames" You need to implement the same environment before diving into this zine. We will have to install a few tools, too: tcpdump (if not installed already) and bpftools [-------------------Prepare environment 1) Install git: root@f8918abfeb55:/home# apt-get install git and then, clone the tools repository root@f8918abfeb55:/home# git clone https://github.com/cloudflare/bpftools 2) Go under bpftools/linux_tools/ a) Install the necessary packages: apt-get install libreadline-dev apt-get install binutils-dev apt-get install bison apt-get install flex b) Now, we are ready to build the executable programs: root@f8918abfeb55:/home/bpftools/linux_tools# make c) Check if executable files were generated: root@f8918abfeb55:/home/bpftools/linux_tools# find . -type f -executable -print ./bpf_jit_disasm ./bpf_dbg ./bpf_asm We will use bpf_asm tool only, for generating the bytecodes [--------------BPF cramming Berkeley Packet Filter (BPF) allows a user-space program to attach a filter onto any socket and allow/disallow certain types of data to come through the socket. Linux Socket Filtering(LSF) is just a derived of BPF (almost identical twins!) Just as BPF, it supports filtering packets, allowing to a user-space process to be picky about what packets to be received. This pickiness of BPF/LSF is possible thanks to the instructions of a low level asm-like filter language. The BPF instruction set includes most arithmetic operations, loads and stores, and forward jumps ( no backward jumps allowed, in order to allow filter programs to terminate): Instruction Addressing mode Description ld 1, 2, 3, 4, 10 Load word into A ldi 4 Load word into A ldh 1, 2 Load half-word into A ldb 1, 2 Load byte into A ldx 3, 4, 5, 10 Load word into X ldxi 4 Load word into X ldxb 5 Load byte into X st 3 Store A into M[] stx 3 Store X into M[] jmp 6 Jump to label ja 6 Jump to label jeq 7, 8 Jump on A == k jneq 8 Jump on A != k jne 8 Jump on A != k jlt 8 Jump on A < k jle 8 Jump on A <= k jgt 7, 8 Jump on A > k jge 7, 8 Jump on A >= k jset 7, 8 Jump on A & k add 0, 4 A + sub 0, 4 A - mul 0, 4 A * div 0, 4 A / mod 0, 4 A % neg !A and 0, 4 A & or 0, 4 A | xor 0, 4 A ^ lsh 0, 4 A << rsh 0, 4 A >> tax Copy A into X txa Copy X into A ret 4, 9 Return Example of low level BPF for ARP filtering ldh [12] jne #0x806, drop ret #-1 drop: ret #0 How to use it, then? ... take a deep breath, yes, that's right, two new structures: struct sock_filter { /* Filter block */ __u16 code; /* Actual filter code */ __u8 jt; /* Jump true */ __u8 jf; /* Jump false */ __u32 k; /* Generic multiuse field */ }; ...and struct sock_fprog { /* Required for SO_ATTACH_FILTER. */ unsigned short len; /* Number of filter blocks */ struct sock_filter __user *filter; }; [-------------------Lend me some bytecodes! Create a file for the ARP filtering - I put everything under ether.txt: root@f8918abfeb55:/home#more ether.txt ldh [12] jne #0x806, drop ret #-1 drop: ret #0 Generate the bytecodes: root@f8918abfeb55:/home# bpftools/linux_tools/bpf_asm ether.txt 4,40 0 0 12,21 0 1 2054,6 0 0 4294967295,6 0 0 0, We will use below output for our example: root@f8918abfeb55:/home# bpftools/linux_tools/bpf_asm -c ether.txt { 0x28, 0, 0, 0x0000000c }, { 0x15, 0, 1, 0x00000806 }, { 0x06, 0, 0, 0xffffffff }, { 0x06, 0, 0, 0000000000 }, Can I haz example? #include<stdio.h> #include<stdlib.h> #include< #include<sys/ioctl.h> #include<arpa/inet.h> #include<linux/if_ether.h> #include<netpacket/packet.h> #include< #include< #include<linux/kernel.h> #include<string.h> struct sock_filter code[] = { { 0x28, 0, 0, 0x0000000c }, { 0x15, 0, 1, 0x00000806 }, { 0x06, 0, 0, 0xffffffff }, { 0x06, 0, 0, 0000000000 }, }; struct sock_fprog bpf = { .len = sizeof(code)/sizeof(code[0]), .filter = code, }; int main(){ int socky; struct ifreq ifr; struct sockaddr_ll socket_ll; unsigned char buf[4096]; memset(&ifr, 0, sizeof(ifr)); memset(&socket_ll, 0, sizeof(socket_ll)); socky = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); strncpy(ifr.ifr_name, "eth0", IFNAMSIZ); ioctl(socky, SIOCGIFINDEX, &ifr); socket_ll.sll_family = AF_PACKET; socket_ll.sll_protocol = htons(ETH_P_ALL); socket_ll.sll_ifindex = ifr.ifr_ifindex; bind(socky, (struct sockaddr *)&socket_ll, sizeof(socket_ll)); setsockopt(socky, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)); while(1){ ssize_t len = recv(soc, buf, sizeof(buf), 0); struct ethhdr* ethhdr = (struct ethhdr*)buf; int proto = ntohs(ethhdr->h_proto); if(len <= 0 ) break; printf("packet len: %3ld \nprotocol: 0x0%0x %s\n", len, proto, proto==ETH_P_ARP ? "[ARP]" : "other"); } return 0; } Compile and run: root@f8918abfeb55:/home# gcc filterz.c -o filterz root@f8918abfeb55:/home# ./filterz ....it will wait to capture arp packets only... Leave this here, and open another terminal for a new container session, and run once again the ethernet frames code ether.c, from our previous zine: root@tr0n:~# docker exec -ti f8918abfeb55 /bin/bash root@f8918abfeb55:/home# Let's send a packet root@f8918abfeb55:/home#./ether By now, you should have captured, in the terminal where filterz program is running, the below result: root@f8918abfeb55:/home# ./filterz packet len: 28 protocol: 0x0806 [ARP] Success! xD [-------------------Options, you gotta have `em! Is this used anywhere?! Once BPF proved itself, it was just a matter of time before it would take over the network world, and be used for creating tools like libpcap or tcpdump. Let's practice a bit on tcpdump; since it's based on bpf, we should easily obtain the bytecodes for our arp scenario: root@f8918abfeb55:/home# tcpdump -d arp (000) ldh [12] (001) jeq #0x806 jt 2 jf 3 (002) ret #262144 (003) ret #0 ...oh, it looks familar xD root@f8918abfeb55:/home# tcpdump -dd arp { 0x28, 0, 0, 0x0000000c }, { 0x15, 0, 1, 0x00000806 }, { 0x6, 0, 0, 0x00040000 }, { 0x6, 0, 0, 0x00000000 }, ...not a stranger anymore Feelin' lucky?! Let's add one more d: root@f8918abfeb55:/home# tcpdump -ddd arp 4 40 0 0 12 21 0 1 2054 6 0 0 262144 6 0 0 0 Hmm...you have that feelin` you had seen it somewhere, right? Well, let's make it look more presentable: root@f8918abfeb55:/home# tcpdump -p -ni eth0 -ddd "arp" |tr "\n" "," && echo " " 4,40 0 0 12,21 0 1 2054,6 0 0 262144,6 0 0 0, Your instincts were on point! Oh, so we can write filters using tcpdump only?! If you don't want to write assembler look-alike syntax,that's a big "yup!" ...and how we can do this? Oh, you so curious! Let's say I want a filter based just on the protocol (2054 is decimal for 0x0806): Do not forget - for testing below tcpdump commands, send the ethernet frame root@f8918abfeb55:/home# tcpdump -p -ni eth0 "ether[12:2]==2054" tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 00:58:38.721131 [|ARP] 0x0000: 6e75 6c6c 5120 7761 7320 6865 7265 nullQ.was.here Let`s explain this ether[12:2] - it takes the next 2 bytes after the first 12 bytes - which is exactly the Type (0x0806). Reminder on the Ethernet Header: 6 bytes - destination address 6 bytes - source address 2 bytes - the type ... and what else? We can also add a limit on packet's length. We already know that the ethernet frame we crafted last time is 28bytes long. Try to run this tcpdump line, for packets bigger than 100 bytes: root@f8918abfeb55:/home# tcpdump -p -ni eth0 "ether[12:2]==2054" and greater 100 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes ^C 0 packets captured 0 packets received by filter 0 packets dropped by kernel As expected, nothing was captured... (keep sending that Ethernet Frame, tho! Perseverance will pay off one day!) Now, let's try with a value closer to our frame/packet's length: root@f8918abfeb55:/home# tcpdump -p -ni eth0 "ether[12:2]==2054" and greater 20 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 01:11:07.250230 [|ARP] 0x0000: 6e75 6c6c 5120 7761 7320 6865 7265 nullQ.was.here Marvellous, I say! And let's re-apply the Triple D! root@f8918abfeb55:/home# tcpdump -p -ni eth0 "ether[12:2]==2054" and greater 20 -d (000) ldh [12] (001) jeq #0x806 jt 2 jf 5 (002) ld #pktlen (003) jge #0x14 jt 4 jf 5 (004) ret #262144 (005) ret #0 root@f8918abfeb55:/home# tcpdump -p -ni eth0 "ether[12:2]==2054" and greater 20 -dd { 0x28, 0, 0, 0x0000000c }, { 0x15, 0, 3, 0x00000806 }, { 0x80, 0, 0, 0x00000000 }, { 0x35, 0, 1, 0x00000014 }, { 0x6, 0, 0, 0x00040000 }, { 0x6, 0, 0, 0x00000000 }, root@f8918abfeb55:/home# tcpdump -p -ni eth0 "ether[12:2]==2054" and greater 20 -ddd 6 40 0 0 12 21 0 3 2054 128 0 0 0 53 0 1 20 6 0 0 262144 6 0 0 0 ... and the final form: root@f8918abfeb55:/home# tcpdump -p -ni eth0 "ether[12:2]==2054" and greater 20 -ddd |tr "\n" "," && echo " " 6,40 0 0 12,21 0 3 2054,128 0 0 0,53 0 1 20,6 0 0 262144,6 0 0 0, root@f8918abfeb55:/home# So, we add that in a piece of code or something? Yes, the "-dd", you can apply it in a piece of code (as per previous code example) ..or you can simply use the output from "-ddd" with iptables: iptables -A INPUT \ -m bpf --bytecode "6,40 0 0 12,21 0 3 2054,128 0 0 0,53 0 1 20,6 0 0 262144,6 0 0 0," \ -j DROP For aesthetic reasons, a --mac-source options would be nice (&others), too ... overall tho', looking good. In an utopian world it would work nicely, then again, this is merely an iptables example! That rule will inform your firewall: "Hey, man! Like a smooth and strong bodyguard that you are, allow ARP packets only, the 0x0806 kewl type; and they gotta be at least this long - 20bytes- to get into the nerdz club! No minors allowed!" Decent stuff, aye?! I hope you enjoyed this zine. Once we study the IP header, we will be able to play more with BPF and tcpdump. have a good week-end! =========================== Better be reading https://www.kernel.org/doc/Documentation/networking/filter.txt https://linux.die.net/man/2/setsockopt http://man7.org/linux/man-pages/man2/seccomp.2.html http://leonerds-code.blogspot.ro/2010/05/pfpacket-linux-socket-filters-and-ipv6.html -------EOF---------