BPF in python to sniff packets for multiple TCP ports

2.9k views Asked by At

I got code from http://allanrbo.blogspot.in/2011/12/raw-sockets-with-bpf-in-python.html. It works fine, but I want to sniff the traffic on multiple TCP ports like port 9000, 80, 22...

So I have modified the filter_list like blow

filters_list = [  
    # Must have dst port 67. Load (BPF_LD) a half word value (BPF_H) in   
    # ethernet frame at absolute byte offset 36 (BPF_ABS). If value is equal to  
    # 67 then do not jump, else jump 5 statements.  
    bpf_stmt(BPF_LD | BPF_H | BPF_ABS, 36),  
    bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 9000, 0, 5), <===== Here I added another port
    bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 80, 0, 5),


    # Must be UDP (check protocol field at byte offset 23)  
    bpf_stmt(BPF_LD | BPF_B | BPF_ABS, 23),   
    bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0x06, 0, 3), #<==Changed for TCP "0x06"

    # Must be IPv4 (check ethertype field at byte offset 12)  
    bpf_stmt(BPF_LD | BPF_H | BPF_ABS, 12),   
    bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0x0800, 0, 1),  

    bpf_stmt(BPF_RET | BPF_K, 0x0fffffff), # pass  
    bpf_stmt(BPF_RET | BPF_K, 0), # reject   ]

The thing is, sometimes it is working sometimes it is not, like getting traffic only on 9000 but not 80, sometimes getting traffic on 80. I didnt understood the code completely. Any help?

2

There are 2 answers

5
Qeole On BEST ANSWER

As far as I can tell, the problem seems to come from the logic of your first two conditional jumps. Specifically:

bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 9000, 0, 5), # if false, skip 5 instructions
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 80, 0, 5),

An instruction bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, <val>, <jtrue>, <jfalse>) means

if value currently in register K is equal to <val>
    then add <jtrue> to instruction pointer
        (i.e. skip the next <jtrue> instructions),
    else add <jfalse> instead`

So the two lines mean:

if port is 9000
    then if port is 80
        then go on with checks…
    else skip 5 instructions (i.e. reject)
else
    skip 5 instructions (i.e. pass, as jump offset was not updated from 5 to 6)

While you probably want something that looks more like:

if port is 9000
    then go on with checks…
else
    if port is 80
        then go on with checks…
    else reject

I have not tested, but to get this logics I would say you need to adapt the jump offsets as follows:

bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 9000, 1, 0), # if true skip 1 insn
                                                 # (i.e. port 80 check) else 0
                                                 # and check for port 80
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 80, 0, 5),   # if true skip 0 else skip 5
                                                 # (and land on “reject”)

Edit 1: And then for filtering three ports, that would become:

bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 8084, 2, 0), # skip the next 2 checks if true
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 9000, 1, 0), # skip the next check if true
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 22,   0, 5), # if true go on else reject

Edit 2: To also filter on source port (in addition to destination port), you could try something like this (still not tested on my side):

# Load TCP src port into register K, and check port value
# For packets with IP header len == 20 bytes, TCP src port should be at offset 34
# We adapt the jump offsets to go to next check if no match (or to “reject” after
# the last check), or to skip all remaining checks on ports if a match is found.
bpf_stmt(BPF_LD | BPF_H | BPF_ABS, 34),           # 34 == offset of src port
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 8084, 6, 0),
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 9000, 5, 0),
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 22,   4, 0),

# As before: if no match on src port, check on dst port
bpf_stmt(BPF_LD | BPF_H | BPF_ABS, 36),
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 8084, 2, 0),
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 9000, 1, 0),
bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 22,   0, 5),

…
0
Alopex On

I know you want to attach a filter to your raw socket, it's what I recently work with. Thanks to more than weeks of hard work, I got an easy way to attach a filter to a raw socket. I would like to share with yours :)
First make sure you already install tcpdump (It's a Linux system manager tools), if your platform is one of distribution in Linux, let's go to next step.
Second you need to have a sudo or root permission to execute the tool.
Third follow demo example, change it to suit your situation.

$ sudo tcpdump -i enp4s0 -dd 'tcp and (port 9000 or port 80 or port 22)'  

Let me explain the parameter first.
tcpdump --> Dump traffic on a network
-i --> Specific the interface
enp4s0 --> Network Interface
-dd --> Dump packet-matching code as a C program fragment.
tcp and (port 9000 or port 80 or port 22) --> Berkeley Packet Filter (BPF) syntax

After this command you should generate some code same like this:

{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 8, 0x000086dd },
{ 0x30, 0, 0, 0x00000014 },
{ 0x15, 0, 21, 0x00000006 },
{ 0x28, 0, 0, 0x00000036 },
....

It looks like a mess, but don't worry, let's go on with brave.

First let me clear what it's, this is a the packet filter code, if you already learn assembly language before, you will feel familiar with it.

Second because it's C stylish code, it doesn't fit our python usage, so we need to polish the data, use follow python code

import subprocess

cmd = "sudo tcpdump -i enp4s0 -dd 'tcp and (port 9000 or port 80 or port 22)'"
tcpdumpBinary = subprocess.check_output(cmd, shell=True)
macroString = '( ' + tcpdumpBinary.decode('utf-8').replace(
            '\n', '').replace('{', '[').replace('}', ']') + ')'
macroString = eval(macroString)

Third now you can use the macroString same like the filters_list.

This method help me get rid of the filter code dilemma, I hope it will be helpful to you.