mirror of
https://github.com/CHN-beta/xmurp-ua.git
synced 2026-01-11 17:29:28 +08:00
This commit is contained in:
74
README.md
74
README.md
@@ -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
28
src/common.h
Normal 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
74
src/rkpStream.h
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user