前言
为了防止学生在宿舍私接路由器上网,造成运营商利益损失,我司提供了一套产品(防代理系统)。
防代理技术由我负责开发,它针对学生上网时的上行流量中的HTTP协议中UA字段(User-Agent)监测。统计一个 IP 下 UA 的种类,分析设备个数,来监测用户是否有代理行为。
奈何甲方爸爸说这个监测效率太低,他们表示现在的网站都是https协议的,由于HTTPS协议抓包观察全是加密数据,获取不到UA......因此后来我新增了TTL监测的功能。TTL监测能监测出一个路由器下的不同类型设备。例如PC机和移动端手机。监测原理即是 windows 系统的PC电脑发包初始TTL为128,手机发包初始值为64,每经过一个路由TTL就将减一。因此我在用户上行流量的网关处监测出某个IP下如果有两种不同类型的 TTL 值,就判定他有代理行为。
我看到网上还有根据IP报文中的ID字段监测的,即IPID监测法以及时钟漂移监测法(每台设备的物理时钟有偏差)。
但是看到我这篇文章的兄弟大可不必去尝试了,因为那些监测方法都是针对老系统,例如 Win 7 。然而现在的win 10 系统早已不再支持。

并且作者我也试过上面的法子了,确实不可行。我也翻遍了谷歌和百度,找不到更好的解决方案,如果你曾经不是专业做这个的,那我劝你还是耐心看看我的文章。
正文
令我吐血的是,上周我司发来消息告知如今某宝上有一种校园网防检测路由器的宿舍神器,可能能破解我们的系统。
如下图所示:

之后买来一试,果然发现它能将我的防代理系统防住。它的原理大致就是,学生发的上网流量包经过路由器,将UA和TTL统一改成了一种形式。
这简直就是一种物理外挂........而我就是公司中唯一的反外挂部门成员........这波简直吐血........
因此,设备能改UA和TTL为一种格式,我也只能从别处下手了。
思路
分析用户社交工具的数据包也许能找到线索........一个用户上网一般都会开着社交工具,例如QQ或者微信,而且一个用户一般都只会开一个QQ......
如果我在用户上行流量中抓到一个IP下有多个 QQ 或微信的信息也许就能判定用户是否有代理行为,且这个 IP 就绑在路由器上。
当然这种情况下肯定会误判.....谁也无法保证一个用户只开一个QQ,也许某个用户在一个电脑下就是有好几个QQ登录着呢.........不过这已经是没办法的办法了。
说干就干.....
结果:微信你们就不用测了,因为微信中的数据包全部以加密形式传输,我们无法判断一个IP下有几个微信。
但是幸运的是,在PC机的QQ客户端中抓包,我们发现了用户的QQ号以及上下线事件报文。
如下图所示
OICQ 是 QQ 的控制报文协议

并且打开详情查看,我们能在这个报文中发现QQ号。

OICQ 常见控制报文命令如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
"log out(1)", "Heart Message(2)", "Set status(13)", "Receive message(23)", "Request KEY(29)", //登录时 "Get friend online(39)", "Group name operation(60)", "MEMO Operation(62)", "Download group friend(88)", "Get level(92)", "Request login(98)", //离线时 "Request extra information(101)", "Signature operation(103)", "Get status of friend(129)", "Get friend's status of group(181)", |
QQ客户端使用的端口为4000,服务器的端口为8000,当存在多个QQ客户端时,端口号从4000依次向上累加。
报文分析:
在Windows下,由Wireshark抓包分析,QQ在登录与运行时,会向服务器发送UDP以及OICQ报文,这里假定一台机器上少于100个QQ号码登录,定义过滤器如下:

从oicq过滤中发现可以百分百命中含有QQ号码的报文,确定位置在以太网数据包的第49~52字节,以4字节的无符号整形数表示。
代码如下:
首先我需要获取我本地网卡信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package main import ( "fmt" "github.com/google/gopacket/pcap" ) func main() { // Find all devices devices, err := pcap.FindAllDevs() if err != nil { panic(err) } // Print device information fmt.Println("Devices found:") for _, d := range devices { fmt.Println("\n网卡名: ", d.Name) fmt.Println("网卡描述: ", d.Description) fmt.Println("Devices addresses: ", d.Addresses) for _, address := range d.Addresses { fmt.Println("- IP address: ", address.IP) fmt.Println("- Subnet mask: ", address.Netmask) } } } |

复制黏贴你要抓的网卡名,我们开始抓数据包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
package main import ( "fmt" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/pcap" ) func main() { fmt.Println("开始抓包...") // 网卡名称,这是从你上面复制下来的 deviceName := `\Device\NPF_{7F45CE6C-3994-4107-B999-98702F59BCB8}` snapLen := int32(65535) // 打开网络接口,抓取在线数据 handle, err := pcap.OpenLive(deviceName, snapLen, true, pcap.BlockForever) if err != nil { panic(fmt.Sprintf("pcap open live failed: %v", err)) return } // 抓包 packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) packetSource.NoCopy = true for packet := range packetSource.Packets() { if packet.NetworkLayer() == nil || packet.TransportLayer() == nil { continue } // 腾讯的服务器 IP 是 IPv4 的,因此过滤掉 IPv6 的 _, ok := packet.NetworkLayer().(*layers.IPv4) if !ok { continue } // OICQ 协议是一种 UDP 协议的封装,因此我们只看 UDP,如果是 TCP 的包我们就跳过 udp, ok := packet.TransportLayer().(*layers.UDP) if !ok { continue } // QQ 客户端使用的端口为 4000,服务器的端口为 8000 // 当存在多个QQ客户端时,端口号从4000依次向上累加。 // 因此如果用户上行流量请求的目标端口如果不是 8000 端口,并且原端口发出来的 不在 4000 - 4100 以内,我们就过滤掉 if udp.DstPort != 8000 || (udp.SrcPort < 4000 || udp.SrcPort > 4100) { continue } // 我们发现抓包看来这个值都是 2 ,很有可能是 OICQ 的标志......待定 if len(packet.Data()) < 53 && packet.Data()[42] != 2 { continue } qq := int(packet.Data()[49])*16*16*16*16*16*16 + int(packet.Data()[50])*16*16*16*16 + int(packet.Data()[51])*16*16 + int(packet.Data()[52]) switch packet.Data()[46] { case 29: // 登录 fmt.Printf("qq号为: %d 登录了\n", qq) continue case 98: // 下线 fmt.Printf("qq 号为:%d 下线了\n", qq) continue } // 否则就是其他信息 fmt.Println(qq) fmt.Println("十进制状态位:", packet.Data()[46]) fmt.Println("客户端端口号", udp.SrcPort.String()) fmt.Println("============================") } } |
因此,我可以检测到用户的OICQ报文,就能通过这个方法来判断该用户是否有代理行为。
最后请注意:移动端的QQ抓包我已经试过了,抓不到太多有价值的东西,因此我已放弃,QQ移动端使用的是TCP协议,除非你能从QQ空间那些HTTP协议中的报文中获取有价值的东西,例如Cookie。

如上所示:否则我建议还是放弃的好.........
献上演示视频
记录于 2020-12-23 17:58
文章评论(0)