mirror of
https://github.com/apernet/OpenGFW.git
synced 2024-12-23 09:29:22 +08:00
100 lines
2.3 KiB
Go
100 lines
2.3 KiB
Go
package tcp
|
|
|
|
import (
|
|
"bytes"
|
|
|
|
"github.com/apernet/OpenGFW/analyzer"
|
|
)
|
|
|
|
var _ analyzer.TCPAnalyzer = (*TrojanAnalyzer)(nil)
|
|
|
|
// CCS stands for "Change Cipher Spec"
|
|
var ccsPattern = []byte{20, 3, 3, 0, 1, 1}
|
|
|
|
// TrojanAnalyzer uses length-based heuristics to detect Trojan traffic based on
|
|
// its "TLS-in-TLS" nature. The heuristics are trained using a decision tree with
|
|
// about 2000 samples. This is highly experimental and is known to have significant
|
|
// false positives (about 9% false positives & 3% false negatives).
|
|
// We do NOT recommend directly blocking all positive connections, as this is likely
|
|
// to break many normal TLS connections.
|
|
type TrojanAnalyzer struct{}
|
|
|
|
func (a *TrojanAnalyzer) Name() string {
|
|
return "trojan"
|
|
}
|
|
|
|
func (a *TrojanAnalyzer) Limit() int {
|
|
return 512000
|
|
}
|
|
|
|
func (a *TrojanAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream {
|
|
return newTrojanStream(logger)
|
|
}
|
|
|
|
type trojanStream struct {
|
|
logger analyzer.Logger
|
|
first bool
|
|
count bool
|
|
rev bool
|
|
seq [3]int
|
|
seqIndex 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 s.first {
|
|
s.first = false
|
|
// Stop if it's not a valid TLS connection
|
|
if !(!rev && len(data) >= 3 && data[0] >= 0x16 && data[0] <= 0x17 &&
|
|
data[1] == 0x03 && data[2] <= 0x09) {
|
|
return nil, true
|
|
}
|
|
}
|
|
|
|
if !rev && !s.count && len(data) >= 6 && bytes.Equal(data[:6], ccsPattern) {
|
|
// Client Change Cipher Spec encountered, start counting
|
|
s.count = true
|
|
}
|
|
|
|
if s.count {
|
|
if rev == s.rev {
|
|
// Same direction as last time, just update the number
|
|
s.seq[s.seqIndex] += len(data)
|
|
} else {
|
|
// Different direction, bump the index
|
|
s.seqIndex += 1
|
|
if s.seqIndex == 3 {
|
|
// Time to evaluate
|
|
yes := s.seq[0] >= 180 &&
|
|
s.seq[1] <= 11000 &&
|
|
s.seq[2] >= 40
|
|
return &analyzer.PropUpdate{
|
|
Type: analyzer.PropUpdateReplace,
|
|
M: analyzer.PropMap{
|
|
"seq": s.seq,
|
|
"yes": yes,
|
|
},
|
|
}, true
|
|
}
|
|
s.seq[s.seqIndex] += len(data)
|
|
s.rev = rev
|
|
}
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
func (s *trojanStream) Close(limited bool) *analyzer.PropUpdate {
|
|
return nil
|
|
}
|