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
,
Этот метод поможет мне избавиться от дилеммы кода фильтра, надеюсь, он будет вам полезен.