From b18e77f6552af7231c02bb3d457f7b25ca27944a Mon Sep 17 00:00:00 2001 From: chn <897331845@qq.com> Date: Sat, 18 May 2019 18:01:57 +0800 Subject: [PATCH] =?UTF-8?q?=E9=99=A4=E4=BA=86=E4=B8=BB=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E4=BB=A5=E5=A4=96=EF=BC=8C=E9=83=BD=E4=BF=AE=E6=94=B9=E5=AE=8C?= =?UTF-8?q?=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 4 +- README.md | 33 ++++- src/xmurp-ua.c | 345 ++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 305 insertions(+), 77 deletions(-) diff --git a/Makefile b/Makefile index 27e1c6f..92459c9 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk include $(INCLUDE_DIR)/kernel.mk PKG_NAME:=xmurp-ua -PKG_RELEASE:=15 +PKG_RELEASE:=16 include $(INCLUDE_DIR)/package.mk @@ -19,7 +19,7 @@ define KernelPackage/xmurp-ua SUBMENU:=Other modules TITLE:=xmurp-ua FILES:=$(PKG_BUILD_DIR)/xmurp-ua.ko - AUTOLOAD:=$(call AutoLoad,99,xmurp-ua) +# AUTOLOAD:=$(call AutoLoad,99,xmurp-ua) KCONFIG:= endef diff --git a/README.md b/README.md index 1a3da09..b7627d9 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,35 @@ iptables -t mangle -A PREROUTING -i br-lan_raw -j MARK --set-mark 1 ``` -  这是我第一次在 GitHub 上搞自己的仓库,可能会有些小问题哈。要是有的话,请各路大佬指正。 \ No newline at end of file +  这是我第一次在 GitHub 上搞自己的仓库,可能会有些小问题哈。要是有的话,请各路大佬指正。 + +--- + + +第一层函数的思路: + +* 如果没有在等待下一个分包,则检测这个包是否是HTTP的开头部分。如果是,则传递给下一层检查。 +* 如果在等待下一个分包,则检测这个包是否是需要的下一个包。如果是,传递给下一层检查;如果不是,报错。 + +检查结果包含: + +* 是否需要下一个分包。 +* 是否需要保留当前包。 +* 是否已经被修改(一旦被修改,就意味着是修改完成,所以肯定不需要下一个分包,也不需要保留当前包)。 +* 如果被修改,是否有上一个分包。 + +对应的动作为: + +* 如果需要下一个分包,并且保留当前包,如果这是第二个被保留的包,则出错。 +* 如果需要下一个分包,并且保留当前包,如果这是第一个被保留的包,则返回 STOLEN,并等待下一个包。 +* 如果需要下一个分包,并且不保留当前包,返回 ACCEPT,并且等待下一个包。 +* 如果不需要下一个分包、没有被修改、没有上一个分包,则返回 ACCEPT,不等待下一个包,当前流修改完毕。 +* 如果不需要下一个分包、没有被修改、有上一个分包,则返回 ACCEPT,不等待下一个包,上一个包标记后进入协议栈,当前流修改完毕。 +* 如果不需要下一个分包、被修改、没有上一个分包,则修改校验和并返回 ACCEPT,当前流修改完毕。 +* 如果不需要下一个分包、被修改、有上一个分包,则这个包修改校验和并返回 ACCEPT,上一个包修改校验和、标记后进入协议栈。 + +第二层函数的思路: + +逐个字节发送给下一层。下一层的返回有这些可能: + +* 如果需要下一个字节。如果匹配到 UA, \ No newline at end of file diff --git a/src/xmurp-ua.c b/src/xmurp-ua.c index 0751f47..8545c02 100644 --- a/src/xmurp-ua.c +++ b/src/xmurp-ua.c @@ -11,128 +11,321 @@ static struct nf_hook_ops nfho; -enum char_scan_enum +enum char_scan_ret // 逐字节扫描函数的返回值,互斥 { - next, - modified_and_next, - scan_finish, - reset, + need_next, // 需要下一个字节来进一步判断 + ua_start, // 接下来就是ua的内容了 + ua_scaning, // 正在扫描ua + ua_mobile, // 确认为移动版ua + ua_finish, // ua结束(同时确认不是桌面版ua) + httph_finish, // http头结束(没有找到ua) + reset, // 状态被重置 }; -enum skb_scan_ret +struct skb_scan_ret // 逐分片扫描函数的返回值,共存 { - need_next_frag = 1, - ua_modified = 2, + u_int8_t ua_found:1 = 0, // 是否已经发现ua的位置 + ua_exist:1 = 0, // 这个分片中是否存在部分或全部ua + need_next_frag:1 = 0, // 是否需要下一个分片 + is_mobile:1 = 0; // 是否已经被判定为移动版ua + char *ua_start, *ua_end; }; -// 根据得到的指针尝试扫描,发现结尾或发现UA或更改UA后返回对应结果。 +// 根据得到的指针尝试扫描,发现结尾或发现UA后返回对应结果。 // 输入零指针则为重置状态。 inline u_int8_t char_scan(char *data) { - const char str_ua_head[] = "User-Agent: ", str_ua[] = "XMURP/1.0", str_end[] = "\r\n\r\n"; - // 不算'\0',长度分别为12、9、4 - static enum + const char str_ua_head[] = "User-Agent: ", str_end[] = "\r\n\r\n", + str_wp[] = "Windows Phone", str_android = "Android", str_ipad = "iPad", str_iphone = "iPhone"; + const u_int8_t len_ua_head = 12, len_end = 4, len_wp = 13, len_android = 7, len_ipad = 4, len_iphone = 6; + enum matching_status // 当前字节处在匹配什么的状态 { - nothing_matching, - ua_head_matching, - ua_modifying, - end_matching, - } status = nothing_matching; - static u_int8_t covered_length; + nothing, // 无 + ua_head, // 正在匹配ua头 + ua, // 正在匹配ua + end, // 正在匹配http头尾 + }; + enum ua_matching_status // 如果正在匹配ua,那么正在匹配哪个关键字 + { + nothing, // 无,只是在匹配ua而已 + wp, // 正在匹配windows phone + android, // 正在匹配安卓 + ipad_or_iphone, // 正在匹配ipad或iphone,因为两者都是ip开头,所以需要这样一个东西。 + ipad, // 正在匹配ipad + iphone, // 正在匹配iphone + mobile_matched, // 已经确认为移动端 + }; + static u_int8_t status = matching_status::nothing; + static u_int8_t ua_status = ua_matching_status::nothing; + static u_int8_t covered_length = 0; - if(data == 0) + if(data == 0) // 重置状态 { - status = nothing_matching; + status = nothing; + ua_status = ua_matching_status::nothing; covered_length = 0; - return reset; + return char_scan_ret::reset; } while(true) { - if(status == nothing_matching) + if(status == matching_status::nothing) // 如果没有在匹配什么,就寻找ua头或者http头尾 { - if(*data == str_ua_head[0]) + if(*data == str_ua_head[0]) // 发现疑似ua头 { - status = ua_head_matching; + status = matching_status::ua_head; covered_length = 1; - return next; + return char_scan_ret::need_next; } - else if(*data == str_end[0]) + else if(*data == str_end[0]) // 发现疑似http头尾 { - status = end_matching; + status = matching_status::end; covered_length = 1; - return next; + return char_scan_ret::need_next; } - else - return next; + else // 啥都没发现 + return char_scan_ret::need_next; } - else if(status == ua_head_matching) + else if(status == matching_status::ua_head) // 如果正在匹配ua头,尝试继续匹配,或将状态置为nothing后重新匹配 { - if(*data == str_ua_head[covered_length]) + if(*data == str_ua_head[covered_length]) // 继续匹配ua头 { covered_length++; - if(covered_length == 12) + if(covered_length == len_ua_head) // ua头匹配完成 { - status = ua_modifying; + status = matching_status::ua; + ua_status = ua_matching_status::nothing; covered_length = 0; - return next; + return char_scan_ret::ua_start; + } + else // 还没有匹配完 + return char_scan_ret::need_next; + } + else + status = matching_status::nothing; // 将状态置为nothing重新匹配 + } + else if(status == matching_status::ua) // 如果正在匹配ua,需要进一步考虑是否正在匹配ua中的特殊字段 + { + if(ua_status == ua_matching_status::nothing) // 如果没有在匹配特殊字段,就尝试匹配特殊字段或结尾 + { + if(*data == '\r') // 如果发现ua结尾 + { + status = nothing_matching; + ua_status = ua_matching_status::nothing; + return char_scan_ret::ua_finish; + } + else if(*data == str_wp[0]) // 如果疑似匹配到windows phone + { + ua_status = ua_matching_status::wp; + covered_length = 1; + return char_scan_ret::ua_scaning; + } + else if(*data == str_android[0]) // 如果疑似匹配到android + { + ua_status = ua_matching_status::android; + covered_length = 1; + return char_scan_ret::ua_scaning; + } + else if(*data == str_ipad[0]) // 如果疑似匹配到ipad或iphone + { + ua_status = ua_matching_status::ipad_or_iphone; + covered_length = 1; + return char_scan_ret::ua_scaning; } else - return next; + return char_scan_ret::ua_scaning; } - else - status = nothing_matching; - } - else if(status == ua_modifying) - { - if(*data == '\r') + else if(ua_status == ua_matching_status::wp) // 如果正在匹配wp,就尝试继续匹配,或将ua状态置为nothing后重新匹配 { - status = nothing_matching; - return scan_finish; + if(*data == str_wp[covered_length]) // 如果可以继续匹配wp + { + covered_length++; + if(covered_length == len_wp) // 如果wp匹配完成 + { + ua_status = ua_matching_status::mobile_matched; + covered_length = 0; + return char_scan_ret::ua_mobile; + } + } + else // 如果没有匹配上,将ua状态置为nothing后重新匹配 + ua_status = ua_matching_status::nothing; } - else + else if(ua_status == ua_matching_status::android) // 如果正在匹配android,就尝试继续匹配,或将ua状态置为nothing后重新匹配 { - if(covered_length < 9) - *data = str_ua[covered_length]; - else - *data = ' '; - covered_length++; - return modified_and_next; + if(*data == str_android[covered_length]) // 如果可以继续匹配android + { + covered_length++; + if(covered_length == len_android) // 如果匹配android完成 + { + ua_status = ua_matching_status::mobile_matched; + covered_length = 0; + return char_scan_ret::ua_mobile; + } + } + else // 如果没有匹配上,将ua状态置为nothing后重新匹配 + ua_status = ua_matching_status::nothing; + } + else if(ua_status == ua_matching_status::ipad_or_iphone) // 如果正在匹配ipad或iphone,就尝试继续匹配,或将ua状态置为nothing后重新匹配 + { + if(covered_length == 1 && *data == 'P') // 如果已经匹配‘i’、可以匹配‘P’ + { + covered_length++; + return char_scan_ret::ua_scaning; + } + else if(covered_length == 2 && *data == 'a') // 如果已经匹配‘iP’、可以匹配‘a’ + { + ua_status = ua_matching_status::ipad; + covered_length++; + return char_scan_ret::ua_scaning; + } + else if(covered_length == 2 && *data == 'h') // 如果已经匹配‘iP’、可以匹配‘h’ + { + ua_status = ua_matching_status::iphone; + covered_length++; + return char_scan_ret::ua_scaning; + } + else // 如果没有匹配到,将ua状态置为nothing后重新匹配 + { + ua_status = ua_matching_status::nothing; + covered_length = 0; + } + } + else if(ua_status == ua_matching_status::ipad) // 如果正在匹配ipad,就尝试继续匹配,或将ua状态置为nothing后重新匹配 + { + if(*data == str_ipad[covered_length]) // 如果可以继续匹配ipad + { + covered_length++; + if(covered_length == len_ipad) // 如果匹配ipad完成 + { + ua_status = ua_matching_status::mobile_matched; + covered_length = 0; + return char_scan_ret::ua_mobile; + } + } + else // 如果没有匹配上,将ua状态置为nothing后重新匹配 + ua_status = ua_matching_status::nothing; + } + else if(ua_status == ua_matching_status::iphone) // 如果正在匹配iphone,就尝试继续匹配,或将ua状态置为nothing后重新匹配 + { + if(*data == str_iphone[covered_length]) // 如果可以继续匹配iphone + { + covered_length++; + if(covered_length == len_iphone) // 如果匹配iphone完成 + { + ua_status = ua_matching_status::mobile_matched; + covered_length = 0; + return char_scan_ret::ua_mobile; + } + } + else // 如果没有匹配上,将ua状态置为nothing后重新匹配 + ua_status = ua_matching_status::nothing; } } - else if(status == end_matching) + else if(status == matching_status::end) { if(*data == str_end[covered_length]) { covered_length++; - if(covered_length == 4) + if(covered_length == len_end) { - status = nothing_matching; - return scan_finish; + status = matching_status::nothing; + covered_length = 0; + return char_scan_ret::httph_finish; } else - return next; + return char_scan_ret::need_next; } else - status = nothing_matching; + status = matching_status::nothing; } } } -// 将数据逐字节发送给下一层,根据下一层的结果(扫描到结尾、扫描到UA、已更改UA),确定是否扫描完毕,以及是否发生了改动,返回到上一层。 -inline u_int8_t skb_scan(char *data_start, char *data_end) +// 将传入的区域修改掉 +inline void ua_modify(char *data1_start, char *data1_end, char *data2_start, char *data2_end) { - register char *i; - register u_int8_t ret, modified = 0; - for(i = data_start; i < data_end; i++) + const char str_ua[] = "XMURP/1.0"; + const u_int8_t len_ua = 9; + u_int8_t i = 0; + char *j = data1_start; + for(; i < len_ua && j < data1_end; i++, j++) + *j = str_ua[i]; + for(; j < data1_end; j++) + *j = ' '; + for(j = data2_start; i < len_ua && j < data2_end; i++, j++) + *j = str_ua[i]; + for(; j < data2_end; j++) + *j = ' '; +} + +// 将数据逐字节发送给下一层,根据下一层的结果,确定是否改动以及改动哪里,ua_found指明在上一个分片中是否找到了ua的位置 +inline u_int8_t skb_scan(char *data_start, char *data_end, u_int8_t ua_found) +{ + + register char *i = data_start; + register u_int8_t ret, is_mobile = 0; + register struct skb_scan_ret skb_ret; + + // 如果在上一个分片中已经找到了ua的位置,那么这个分片的初始位置就已经是ua的一部分了,因此需要做一些预处理 + if(ua_found) + { + skb_ret.ua_found = 1; + skb_ret = char_scan(i); + if(ret == char_scan_ret:ua_scaning) + { + skb_ret.ua_exist = 1; + skb_ret.ua_start = i; + } + else if(ret == char_scan_ret::ua_mobile) + { + skb_ret.ua_exist = 1; + skb_ret.ua_start = i; + skb_ret.ua_mobile = 1; + } + else if(ret == char_scan_ret::ua_finish) + { + skb_ret.need_next_frag = 0; + return skb_ret; + } + i++; + } + + // 扫描整个数据 + for(; i < data_end; i++) { ret = char_scan(i); - if(ret == scan_finish) - return modified; - else if(ret == modified_and_next) - modified = ua_modified; + if(ret == char_scan_ret::need_next) + continue; + else if(ret == char_scan_ret::ua_start) + skb_ret.ua_found = 1; + else if(ret == char_scan_ret:ua_scaning) + if(!skb_ret.ua_exist) + { + skb_ret.ua_exist = 1; + skb_ret.ua_start = i; + } + else + continue; + else if(ret == char_scan_ret::ua_mobile) + skb_ret.ua_mobile = 1; + else if(ret == char_scan_ret::ua_finish) + { + skb_ret.need_next_frag = 0; + skb_ret.ua_end = i; + return skb_ret; + } + else if(ret == char_scan_ret::httph_finish) + { + skb_ret.need_next_frag = 0; + return skb_ret; + } } - return modified + need_next_frag; + + // 如果没有扫描到需要返回的地方 + if(skb_ret.ua_exist) + skb_ret.ua_end = data_end; + skb_ret.need_next_frag = 1; + return skb_ret; } // 捕获数据包,检查是否符合条件。如果符合,则送到下一层,并根据下一层返回的结果,如果必要的话,重新计算校验和以及继续捕获下一个分片。 @@ -143,13 +336,16 @@ unsigned int hook_funcion(void *priv, struct sk_buff *skb, const struct nf_hook_ register struct iphdr *iph; register char *data_start, *data_end; - static u_int8_t catch_next_frag = 0; static u_int32_t saddr, daddr, seq; static u_int16_t sport, dport; + static struct skb_scan_ret ret_last; + static struct sk_buff *skb_last; static u_int32_t n_ua_modified = 0, n_ua_modify_faild = 0; - register u_int8_t jump_to_next_function = 0, ret; + register u_int8_t jump_to_next_function = 0; + + register struct skb_scan_ret ret; // 过滤发往外网的HTTP请求的包,且要求包的应用层内容不短于3字节 if(skb == 0) @@ -166,21 +362,22 @@ unsigned int hook_funcion(void *priv, struct sk_buff *skb, const struct nf_hook_ data_end = (char *)tcph + ntohs(iph->tot_len) - iph->ihl * 4; if(data_end - data_start < 4) return NF_ACCEPT; - if(skb->mark & 0x00000001) + if(skb->mark & 1) return NF_ACCEPT; // 决定是否发送到下一层 - if(catch_next_frag && iph->saddr == saddr && iph->daddr == daddr && + if(ret_last.need_next_frag && iph->saddr == saddr && iph->daddr == daddr && tcph->seq == seq && tcph->source == sport && tcph->dest == dport) jump_to_next_function = 1; else if(data_end - data_start > 3) if(memcmp(data_start, "GET", 3) == 0 || memcmp(data_start, "POST", 4) == 0) { - if(catch_next_frag) + if(ret_last.need_next_frag) { n_ua_modify_faild++; char_scan(0); - catch_next_frag = 0; + ret_last.need_next_frag = 0; + ret_last.ua_found = 0; } jump_to_next_function = 1; } @@ -188,7 +385,7 @@ unsigned int hook_funcion(void *priv, struct sk_buff *skb, const struct nf_hook_ return NF_ACCEPT; // 发送到下一层,并回收数据 - ret = skb_scan(data_start, data_end); + ret = skb_scan(data_start, data_end, ret_last.ua_found); // 处理返回值 if(ret & need_next_frag)