This commit is contained in:
chn
2019-10-31 21:52:02 +08:00
parent 6872156480
commit e848cb4402
3 changed files with 153 additions and 23 deletions

View File

@@ -2,6 +2,8 @@
#### 抓取数据包
通过 0x100 来确定是否处理一个数据包。0x200 标记 request 以示区别。0x400 标记首包用于指示创建一条新的流。0x800 用来指示是否保留win ua。
模块根据握手时的第一个包SYN以后简称首包来确定是否追踪这个 TCP 流以及修改的策略(保留包含 `"Windows NT"` 的 UA还是替换掉所有的 UA。当首包 mark 的 `0x10` 位被置为 1 时,追踪这条流。当首包 mark 的 `0x20` 位被置为 1 时,将保留包含 `"Windows NT"` 的 UA否则将替换所有的 UA。
在确定追踪这条流的前提下,模块根据数据包的 mark 来确定是否抓取这个数据包。仅当 `0x10` 位被置为 1 时,模块会抓取这个数据包,否则不会抓取之。除了首包以外,`0x20` 位不起作用。如果一个数据包 mark 的 `0x10` 位被置为 1但是所属的流没有被跟踪或者这个包是服务端发来的并且不是三次握手时的 SYN ACK 包),则会发出警告并丢弃(返回 `NF_DROP`)这个数据包。应该避免这样的情况出现。
@@ -10,6 +12,14 @@
#### 流追踪的过程
当拿到一个数据包的时候。如果是首包,在追踪对应的流,则将对应的流销毁后,建立新的流。如果不是首包,如果不存在对应的流,则丢弃;否则,进入按照下面的流程处理。
sniffing 状态下,收集包直到得到全部的 HTTP 头,修改后发出,置于 waiting。然后直到 push再置于 sniffing。
需要记录每条流的最后活动时间下一个期待的包的seq服务端已经确认的seq。两个seq之间的数据需要保留以备重传。
我们首先假定不会发生乱序TCP disorder和丢包的情况并假定追踪的流是合法的 HTTP 1.x 请求。捕获到首包之后,会开始追踪这条流并将状态置为 `rkpstm_established_sniffing`,然后返回 `NF_ACCEPT`
主循环:当流的状态被置为 `rkpstm_established_sniffing` 时,每收到一个数据包(应用层长度不为零),都会截留(返回 `NF_STOLEN`,包括包含 HTTP 头的最后一个数据包)直到捕获到整个 HTTP 头部(在应用层中读到 `\r\n\r\n`)。当确认捕获到整个 HTTP 头部后,会根据情况检查并修改 UA然后将截获的数据包发出将状态置为 `rkpstm_established_waiting`(除非最后一个包就带有 PUSH后返回 `NF_STOLEN`。之后不含 PUSH 的数据包都将直接返回 `NF_ACCEPT`。当捕获到包含 PUSH 的数据包时,会将流的状态置为 `rkpstm_established_sniffing`,返回 `NF_ACCEPT`。对于不含应用层数据的包(单纯的 ACK 包),会直接放行并不修改状态。
@@ -35,8 +45,10 @@
* `u_int8_t enum {...} status`:流的状态。
* `u_int32_t id[3]`:这 16 个字节按顺序存储源地址、目标地址、源端口、目标端口。
* `struct sk_buff* buff`:截取的数据包。保证已经 `skb_ensure_writable`
* `struct sk_buff* buff_prev`由于乱序而需要暂缓处理的数据包。保证已经 `skb_ensure_writable`
* `struct sk_buff* buff_prev`担心需要重传而保留的数据包。已经修改过 ua
* `struct sk_buff* buff_next`:由于乱序而需要暂缓处理的数据包。保证已经 `skb_ensure_writable`
* `u_int32_t seq`:存储下一个期待收到的包的序列号。
* `u_int32_t seq_ack`:存储服务端已经应答的序列号。
* `time_t last_active`:记录最后一次活动的时间(单位为秒)。超过 1200 秒没有活动的连接会被放弃。
* `u_int8_t scan_matched`:在 `rpstm_established_sniffing` 状态下,指示直到 `buff` 中最后一个包的应用层末尾,匹配 `"\r\n\r\n"` 的字节数。一旦匹配到,则会短暂地被用来记录匹配 `"User-Agent: "` 等其它字符串的进度。当它没有意义时,总是被置为 0。
* `u_int8_t windows_preserve`:当 UA 中包含 `"Windows NT"` 时,是否保留不变。实际上,如果不需要保留,根本就不需要去确认是否包含 `"Windows NT"`
@@ -45,48 +57,64 @@
成员函数:
* `struct rkpStream* rpStream_new(struct sk_buff*)`:构造函数。传入一个数据包,用它上面的数据来构造流;传入的不是 SYN 也可以。
* `void rkpStream_del(struct rkpStream*)`:析构函数。
* `u_int32_t rkpStream_judge(struct rkpStream*, struct sk_buff*)`:检查一个数据包。保证只是检查,不会变动数据包或流的任何内容。
* `u_int32_t rkpStream_judge(struct rkpStream*, struct sk_buff*)`:检查一个数据包。保证只是检查,然后返回数据,不会变动数据包或流的任何内容。
返回值的低八位用于通知上层函数如何处理,不同位表示不同的意义:
* `0x01` 位:表示这个包是否属于这个流。置为 1 时表示属于。
* `0x02` 位:表示这个包是否可能需要修改。置为 1 时表示可能需要。
* `0x04` 位:表示这个包是否需要被截留。置为 1 时表示需要
* `0x08` 位:发生了某些错误(比如,没有找全 HTTP 头却要 PSH或者读取已经截取的应用层数据的前几个字节知道不是支持的 HTTP 方法,等)。上层函数应该向用户发出警告,将所有流中的数据放出,并停止模块的运行。
* `0x02` 位:表示这个包是否需要被截留。置为 1 时表示需要。
* `0x04` 位:发生了某些错误,上层函数应该向用户发出警告,将所有流中的数据放出,并停止模块的运行
事实上,可能的返回结果和处理方法:
* `rtn & 0x01 == 0x00`:不属于这个流。当然是去尝试下一个或者宣布这个包被错误地抓到啦。
* `else`:稍后需要调用 `rkpStream_execute`,但在这之前和之后需要还需要根据不同情况做一点事情
* `rtn & 0x02 == 0x02`:确保这个包可以修改(调用 `skb_ensure_aritable`)。否则不用调用。
* `rtn & 0x04 == 0x04`返回 `NF_STOLEN`。否则返回 `NF_ACCEPT`。当然,实际上,被截留的包一定需要确保可修改,但这不需要在代码中体现。
包属于这个流,但是因为看不上它,需要丢弃
* `else`:稍后需要调用 `rkpStream_execute`,但在这之前和之后需要还需要根据不同情况做一点事情。返回 `NF_STOLEN`
* ~~`rtn & 0x02 == 0x02`确保这个包可以修改(调用 `skb_ensure_aritable`。否则不用调用。~~
* ~~`rtn & 0x04 == 0x04`:返回 `NF_STOLEN`。否则返回 `NF_ACCEPT`。当然,实际上,被截留的包一定需要确保可修改,但这不需要在代码中体现。~~
高 24 位用于通知 `rkpStream_execute` 如何处理。其中,低位共同表示包的处理方法,其它位在个别情况下才有意义。具体:
高 24 位用于通知 `rkpStream_execute` 如何处理。其中,低位共同表示包的处理方法,其它位在个别情况下才有意义。具体:
* `0x0`:包不属于这个流。除了这种情况以外,都需要更新时间,以后不再重复
* `0x1`:包属于这个流,但直接放行就可以(不携带应用层数据,或者是重传)
* `0x2`:包是属于未来的、包含有应用层数据的数据包,需要置入 `buff_prev`。除了这三个以外,都需要更新序列号,不再重复
* `0x3`:包是 HTTP 头部的一部分,并且即使算上这个数据包,也没有收集到完整的头部。仅仅在这时,之后的四位才有用,它用来标记 `"\r\n\r\n"` 已经匹配了几个字符
* `0x4`:包是 HTTP 头部的一部分,算上这个数据包,已经收集到了完整的头部,并且头部是在这个数据包中结束的,并且没有 PSH 标志。这时,高 16 位有意义,它用来标记 `"\r\n\r\nx"` 中,`'x'` 的位置相对于这个包中应用层第一个字节的偏移
* `0x5`:与 `0x4` 类似,区别在于有 PSH需要设置状态为 `rkpstm_established_sniffing`
* `0x6`:包不是 HTTP 头部的一部分,并且没有设置 PSH总之放行即可。它与 `0x1` 的区别在于,是否需要更新序列号。
* `0x7`:包不是 HTTP 头部的一部分,并且设置了 PSH。放行并设置状态为 `rkpstm_established_sniffing`
* `0x1`:包是客户端的请求。除非设置这个位,其它位都没有意义
* `0x2`:包携带应用层数据。除非设置这个位,否则之后的都没有意义
* `0x4`:包是属于未来的。
* `0x8`:包是属于过去的。这两个都没设置的话,说明包是现在的
* `0x10`:包中出现了 http 头部结尾。只有在流为 sniffing 时可能设置这个
* `0x20`:包有设置 push
* `void rkpStream_execute(struct rkpStream*, struct sk_buff*, u_int32_t)`:执行需要执行的动作。第三个参数传入 `rkpStream_judge` 对于这个包的返回值,它的低八位会被忽略。它要做的事情完全由 `rkpStream_judge` 的返回值来通知。以下数值是指 `rtn & 0x0f00 >> 8` 的值。
* `void rkpStream_execute(struct rkpStream*, struct sk_buff*, u_int32_t)`:执行需要执行的动作。第三个参数传入 `rkpStream_judge` 对于这个包的返回值,它的低八位会被忽略。它要做的事情完全由 `rkpStream_judge` 的返回值来通知。以下数值是指 `rtn & 0xff00 >> 8` 的值。
* `0x0`:返回。
* `0x1`:更新时间,然后返回。
* `0x2`:更新时间,置入 `buff_prev`
* `0x3`:更新时间、序列号,设置 `scan_matched`。调用 `__rkpStream_reexecute` 来尝试处理 `buff_prev` 中的包
* `0x`
* `0x2`:更新时间、确认序列号,释放 `buff_prev` 中的部分数据包
* `0x3`:更新时间,置入 `buff_next`
* `0x4`:更新时间、序列号,设置 `scan_matched`。调用 `__rkpStream_reexecute` 来尝试处理 `buff_prev` 中的包。
* `0x5`:更新时间、序列号,置入 buff置为 waiting。调用 modify 来决定是否修改ua并修改之。调用 flash 来发出数据包。调用 reexecute 来尝试处理 buff_next。
* `0x6`:与上面相似,但是置为 sniffing。
* `0x7`:更新时间、序列号。
* `0x8`:更新时间、序列号,置为 sniffing。
* `int8_t __rpStream_check_sequence(struct rpStream*, struct sk_buff*)`:视为私有变量。检查一个数据包是重传(返回 -1、乱序返回 1还是正常的返回 0
*
#### `rkpManager` 类
用来管理一个流
构造函数,使用两个端口号之和(转换字节序)来搜索。
析构函数
find 函数,搜索符合条件的流并返回
creat 函数,创建指定的流
flush 函数,关闭超时的流。
#### 其它细节
* 为什么通过第二个包而不是第三个包来判定连接已经建立?

28
src/common.h Normal file
View File

@@ -0,0 +1,28 @@
#include <linux/module.h>
#include <linux/version.h>
#include <linux/kmod.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/netdevice.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/random.h>
#include <linux/moduleparam.h>
#include <asm/limits.h>
const char* str_ua = "User-Agent: ";
const char* str_end = "\r\n\r\n";
const char* str_win = "Windows NT";
u_int32_t mark_capture = 0x100;
module_param(mark_capture, uint, 0);
u_int32_t mark_request = 0x200;
module_param(mark_request, uint, 0);
u_int32_t mark_first = 0x400;
module_param(mark_first, uint, 0);
u_int32_t mark_winPreserve = 0x800;
module_param(mark_winPreserve, uint, 0);

74
src/rkpStream.h Normal file
View File

@@ -0,0 +1,74 @@
#include "common.h"
struct rkpStream
{
u_int8_t enum
{
__rkpStream_sniffing,
__rkpStream_waiting
} status;
u_int32_t id[3]; // 按顺序存储客户地址、服务地址、客户端口、服务端口,已经转换字节序
struct sk_buff *buff, *buff_prev, *buff_next;
u_int32_t seq, seq_ack; // 下一个服务端发出的字节的序列号,以及下一个服务端确认收到的字节的序列号
time_t last_active;
u_int8_t scan_matched;
u_int8_t win_preserve;
struct rkpStream* next;
};
struct rkpStream* rkpStream_new(struct sk_buff*); // 构造函数,得到的流的状态是捕获这个数据包之前的状态。内存不够时返回 0。
void rkpStream_del(struct rkpStream*); // 析构函数
u_int32_t rkpStream_judge(struct rkpStream*, struct sk_buff*); // 判断一个数据包是否属于这个流以及如何处理它
u_int8_t rkpStream_execute(struct rkpStream*, struct sk_buff*, u_int32_t); // 执行上面判断的结果(如果包属于这个流)
void __rkpStream_refresh(struct rkpStream*); // 刷新流的时间戳
u_int8_t __rkpStream_belong(struct rkpStream*, struct sk_buff*); // 判断一个数据包是否属于一个流
struct rkpStream* rpStream_new(struct sk_buff* skb)
{
struct rkpStream* rkps = kmalloc(sizeof(struct rkpStream), GFP_KERNEL);
struct iphdr* iph = ip_hdr(skb);
strcut tcphdr* tcph = tcp_hdr(skb);
u_int16_t app_data_len = ntohs(iph -> tot_len) - iph -> ihl * 4 - tcph -> doff * 4;
if(rkps == 0)
return rkps;
rkps -> status = rkpStream::__rkpStream_sniffing;
rkps -> id[0] = ntohl(iph -> saddr);
rkps -> id[1] = ntohl(iph -> daddr);
rkps -> id[2] = (((u_int32_t)ntohs(tcph -> sport)) << 16 ) + ntohs(tcph -> dport);
buff = buff_prev = buff_next = 0;
seq = seq_ack = ntohl(tcph -> seq);
__rkpStream_refresh(rkps);
rkps -> scan_matched = 0;
rkps -> win_preserve = (skb -> mark & mark_winPreserve != 0);
rkps -> next = 0;
return rkps;
}
void rkpStream_del(struct rkpStream* rkps)
{
kfree_skb_list(rkps -> buff);
kfree_skb_list(rkps -> buff_prev);
kfree_skb_list(rkps -> buff_next);
kfree(kkps);
}
u_int32_t rkpStream_judge(struct rkpStream* rkps, struct sk_buff* skb)
{
u_int32_t rtn = 0;
if(!__rkpStream_belong(rkps, skb))
return rtn;
else
{
rtn &= 0x01;
if(!(skb -> mark & mark_request))
return rtn;
else
{
rtn &= (0x01 << 8);
}
}
}