BPF в Python, чтобы прослушивать пакеты для нескольких портов TCP

Я получил код от http://allanrbo.blogspot.in/2011/12/raw-sockets-with-bpf-in-python.html. Работает нормально, но я хочу перехватить трафик на нескольких TCP-портах, таких как порт 9000, 80, 22...

Поэтому я изменил filter_list как удар

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   ]

Дело в том, что иногда это работает, иногда это не так, как получение трафика только на 9000, но не 80, иногда получение трафика на 80. Я не совсем понял код. Любая помощь?

1 ответ

Решение

Насколько я могу судить, проблема заключается в логике ваших первых двух условных переходов. В частности:

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),

Инструкция bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, <val>, <jtrue>, <jfalse>) средства

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`

Итак, две строки означают:

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)

Хотя вы, вероятно, хотите что-то похожее на:

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

Я не проверял, но чтобы получить эту логику, я бы сказал, что вам нужно адаптировать смещение перехода следующим образом:

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”)

Редактировать 1: И затем для фильтрации трех портов, это станет:

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

Редактировать 2: Чтобы также фильтровать порт источника (в дополнение к порту назначения), вы можете попробовать что-то вроде этого (все еще не проверено на моей стороне):

# 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),

…

Я знаю, что вы хотите подключить фильтр к вашему необработанному сокету, это то, с чем я недавно работал. Благодаря более чем многолетней тяжелой работе, я получил простой способ прикрепить фильтр к необработанной розетке. Я хотел бы поделиться с вами:)
Сначала убедитесь, что вы уже установили tcpdump (Это инструменты системного менеджера Linux), если ваша платформа является одной из дистрибутивов в Linux, давайте перейдем к следующему шагу.
Во-вторых, вам нужно иметь sudo or root разрешение на выполнение инструмента.
В-третьих, следуйте демонстрационному примеру, измените его в соответствии с вашей ситуацией.

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

Позвольте мне сначала объяснить параметр.
tcpdump -> Дамп трафика по сети
-i -> Конкретный интерфейс
enp4s0 -> Сетевой интерфейс
-dd -> Дамп код сопоставления пакетов как фрагмент программы на Си.
tcp and (port 9000 or port 80 or port 22) -> Синтаксис Berkeley Packet Filter (BPF)

После этой команды вы должны сгенерировать такой же код:

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

Это похоже на беспорядок, но не волнуйтесь, давайте продолжим с храбрым.

Сначала позвольте мне понять, что это, это код фильтра пакетов, если вы уже изучали язык ассемблера раньше, вы будете с ним знакомы.

Во-вторых, потому что это стильный код на C, он не подходит для нашего использования на Python, поэтому нам нужно полировать данные, используйте следующий код на Python

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)

В-третьих, теперь вы можете использовать macroString так же, как filters_list,

Этот метод поможет мне избавиться от дилеммы кода фильтра, надеюсь, он будет вам полезен.

Другие вопросы по тегам