python 中的 BPF 用于嗅探多个 TCP 端口的数据包
BPF in python to sniff packets for multiple TCP ports
我从 http://allanrbo.blogspot.in/2011/12/raw-sockets-with-bpf-in-python.html 获得了代码。它工作正常,但我想嗅探多个 TCP 端口上的流量,如端口 9000
、80
、22
...
所以我修改了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 ]
问题是,有时它可以工作,有时却不能,比如只在 9000 上获得流量而不是 80,有时在 80 上获得流量。我没有完全理解代码。有帮助吗?
据我所知,问题似乎出在您前两个条件跳转的逻辑上。具体来说:
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
--> 将数据包匹配代码转储为 C 程序片段。
tcp and (port 9000 or port 80 or port 22)
--> 伯克利包过滤器 (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)
第三,现在您可以像 filters_list
一样使用 macroString
。
这个方法帮助我摆脱了过滤代码的困境,希望对你有所帮助。
我从 http://allanrbo.blogspot.in/2011/12/raw-sockets-with-bpf-in-python.html 获得了代码。它工作正常,但我想嗅探多个 TCP 端口上的流量,如端口 9000
、80
、22
...
所以我修改了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 ]
问题是,有时它可以工作,有时却不能,比如只在 9000 上获得流量而不是 80,有时在 80 上获得流量。我没有完全理解代码。有帮助吗?
据我所知,问题似乎出在您前两个条件跳转的逻辑上。具体来说:
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
--> 将数据包匹配代码转储为 C 程序片段。
tcp and (port 9000 or port 80 or port 22)
--> 伯克利包过滤器 (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)
第三,现在您可以像 filters_list
一样使用 macroString
。
这个方法帮助我摆脱了过滤代码的困境,希望对你有所帮助。