diff --git a/README.md b/README.md index 888eab5..db3fb8c 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Linux that's in many ways more powerful than the real thing. It's cyber sovereig - HTTP, TLS, DNS, SSH, and many more to come - "Fully encrypted traffic" detection for Shadowsocks, etc. (https://gfw.report/publications/usenixsecurity23/data/paper/paper.pdf) + - Trojan (proxy protocol) detection based on Trojan-killer (https://github.com/XTLS/Trojan-killer) - [WIP] Machine learning based traffic classification - Flow-based multicore load balancing - Connection offloading @@ -90,6 +91,10 @@ to [Expr Language Definition](https://expr-lang.org/docs/language-definition). action: block expr: fet != nil && fet.yes +- name: block trojan + action: block + expr: trojan != nil && trojan.yes + - name: v2ex dns poisoning action: modify modifier: diff --git a/README.zh.md b/README.zh.md index 4914fb8..0b5fa24 100644 --- a/README.zh.md +++ b/README.zh.md @@ -20,6 +20,7 @@ OpenGFW 是一个 Linux 上灵活、易用、开源的 [GFW](https://zh.wikipedi - 完整的 IP/TCP 重组,各种协议解析器 - HTTP, TLS, DNS, SSH, 更多协议正在开发中 - Shadowsocks 等 "全加密流量" 检测 (https://gfw.report/publications/usenixsecurity23/data/paper/paper.pdf) + - 基于 Trojan-killer 的 Trojan 检测 (https://github.com/XTLS/Trojan-killer) - [开发中] 基于机器学习的流量分类 - 基于流的多核负载均衡 - 连接 offloading @@ -85,6 +86,10 @@ workers: action: block expr: fet != nil && fet.yes +- name: block trojan + action: block + expr: trojan != nil && trojan.yes + - name: v2ex dns poisoning action: modify modifier: diff --git a/analyzer/tcp/trojan.go b/analyzer/tcp/trojan.go new file mode 100644 index 0000000..37a430b --- /dev/null +++ b/analyzer/tcp/trojan.go @@ -0,0 +1,91 @@ +package tcp + +import ( + "bytes" + + "github.com/apernet/OpenGFW/analyzer" +) + +var _ analyzer.TCPAnalyzer = (*TrojanAnalyzer)(nil) + +// CCS stands for "Change Cipher Spec" +var trojanCCS = []byte{20, 3, 3, 0, 1, 1} + +const ( + trojanUpLB = 650 + trojanUpUB = 1000 + trojanDownLB1 = 170 + trojanDownUB1 = 180 + trojanDownLB2 = 3000 + trojanDownUB2 = 7500 +) + +// TrojanAnalyzer uses a very simple packet length based check to determine +// if a TLS connection is actually the Trojan proxy protocol. +// The algorithm is from the following project, with small modifications: +// https://github.com/XTLS/Trojan-killer +// Warning: Experimental only. This method is known to have significant false positives and false negatives. +type TrojanAnalyzer struct{} + +func (a *TrojanAnalyzer) Name() string { + return "trojan" +} + +func (a *TrojanAnalyzer) Limit() int { + return 16384 +} + +func (a *TrojanAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream { + return newTrojanStream(logger) +} + +type trojanStream struct { + logger analyzer.Logger + active bool + upCount int + downCount int +} + +func newTrojanStream(logger analyzer.Logger) *trojanStream { + return &trojanStream{logger: logger} +} + +func (s *trojanStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, done bool) { + if skip != 0 { + return nil, true + } + if len(data) == 0 { + return nil, false + } + if !rev && !s.active && len(data) >= 6 && bytes.Equal(data[:6], trojanCCS) { + // Client CCS encountered, start counting + s.active = true + } + if s.active { + if rev { + // Down direction + s.downCount += len(data) + } else { + // Up direction + if s.upCount >= trojanUpLB && s.upCount <= trojanUpUB && + ((s.downCount >= trojanDownLB1 && s.downCount <= trojanDownUB1) || + (s.downCount >= trojanDownLB2 && s.downCount <= trojanDownUB2)) { + return &analyzer.PropUpdate{ + Type: analyzer.PropUpdateReplace, + M: analyzer.PropMap{ + "up": s.upCount, + "down": s.downCount, + "yes": true, + }, + }, true + } + s.upCount += len(data) + } + } + // Give up when either direction is over the limit + return nil, s.upCount > trojanUpUB || s.downCount > trojanDownUB2 +} + +func (s *trojanStream) Close(limited bool) *analyzer.PropUpdate { + return nil +} diff --git a/cmd/root.go b/cmd/root.go index d4fe10d..4baf5b2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -89,6 +89,7 @@ var analyzers = []analyzer.Analyzer{ &tcp.HTTPAnalyzer{}, &tcp.SSHAnalyzer{}, &tcp.TLSAnalyzer{}, + &tcp.TrojanAnalyzer{}, &udp.DNSAnalyzer{}, }