From ddfb2ce2af5ca837a02a26aaaa9aa91492256bf9 Mon Sep 17 00:00:00 2001 From: KujouRinka Date: Sat, 27 Jan 2024 11:17:39 +0800 Subject: [PATCH 1/6] style: rename vars to avoid namespace pollution --- analyzer/tcp/socks5.go | 72 +++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/analyzer/tcp/socks5.go b/analyzer/tcp/socks5.go index 52ef33d..5823e57 100644 --- a/analyzer/tcp/socks5.go +++ b/analyzer/tcp/socks5.go @@ -10,20 +10,20 @@ import ( const ( Socks5Version = 0x05 - CmdTCPConnect = 0x01 - CmdTCPBind = 0x02 - CmdUDPAssociate = 0x03 + Socks5CmdTCPConnect = 0x01 + Socks5CmdTCPBind = 0x02 + Socks5CmdUDPAssociate = 0x03 - AuthNotRequired = 0x00 - AuthPassword = 0x02 - AuthNoMatchingMethod = 0xFF + Socks5AuthNotRequired = 0x00 + Socks5AuthPassword = 0x02 + Socks5AuthNoMatchingMethod = 0xFF - AuthSuccess = 0x00 - AuthFailure = 0x01 + Socks5AuthSuccess = 0x00 + Socks5AuthFailure = 0x01 - AddrTypeIPv4 = 0x01 - AddrTypeDomain = 0x03 - AddrTypeIPv6 = 0x04 + Socks5AddrTypeIPv4 = 0x01 + Socks5AddrTypeDomain = 0x03 + Socks5AddrTypeIPv6 = 0x04 ) var _ analyzer.Analyzer = (*Socks5Analyzer)(nil) @@ -40,7 +40,7 @@ func (a *Socks5Analyzer) Limit() int { } func (a *Socks5Analyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream { - return newSocksStream(logger) + return newSocks5Stream(logger) } type socks5Stream struct { @@ -65,7 +65,7 @@ type socks5Stream struct { authRespMethod int } -func newSocksStream(logger analyzer.Logger) *socks5Stream { +func newSocks5Stream(logger analyzer.Logger) *socks5Stream { s := &socks5Stream{logger: logger, reqBuf: &utils.ByteBuffer{}, respBuf: &utils.ByteBuffer{}} s.reqLSM = utils.NewLinearStateMachine( s.parseSocks5ReqVersion, @@ -146,14 +146,14 @@ func (s *socks5Stream) parseSocks5ReqMethod() utils.LSMAction { } // For convenience, we only take the first method we can process - s.authReqMethod = AuthNoMatchingMethod + s.authReqMethod = Socks5AuthNoMatchingMethod for _, method := range methods[1:] { switch method { - case AuthNotRequired: - s.authReqMethod = AuthNotRequired + case Socks5AuthNotRequired: + s.authReqMethod = Socks5AuthNotRequired break - case AuthPassword: - s.authReqMethod = AuthPassword + case Socks5AuthPassword: + s.authReqMethod = Socks5AuthPassword break default: // TODO: more auth method to support @@ -165,9 +165,9 @@ func (s *socks5Stream) parseSocks5ReqMethod() utils.LSMAction { func (s *socks5Stream) parseSocks5ReqAuth() utils.LSMAction { switch s.authReqMethod { - case AuthNotRequired: + case Socks5AuthNotRequired: s.reqMap["auth"] = analyzer.PropMap{"method": s.authReqMethod} - case AuthPassword: + case Socks5AuthPassword: meta, ok := s.reqBuf.Get(2, false) if !ok { return utils.LSMActionPause @@ -211,18 +211,18 @@ func (s *socks5Stream) parseSocks5ReqConnInfo() utils.LSMAction { } // verify socks version - if preInfo[0] != 0x05 { + if preInfo[0] != Socks5Version { return utils.LSMActionCancel } var pktLen int switch int(preInfo[3]) { - case AddrTypeIPv4: + case Socks5AddrTypeIPv4: pktLen = 10 - case AddrTypeDomain: + case Socks5AddrTypeDomain: domainLen := int(preInfo[4]) pktLen = 7 + domainLen - case AddrTypeIPv6: + case Socks5AddrTypeIPv6: pktLen = 22 default: return utils.LSMActionCancel @@ -235,7 +235,7 @@ func (s *socks5Stream) parseSocks5ReqConnInfo() utils.LSMAction { // parse cmd cmd := int(pkt[1]) - if cmd != CmdTCPConnect && cmd != CmdTCPBind && cmd != CmdUDPAssociate { + if cmd != Socks5CmdTCPConnect && cmd != Socks5CmdTCPBind && cmd != Socks5CmdUDPAssociate { return utils.LSMActionCancel } s.reqMap["cmd"] = cmd @@ -244,11 +244,11 @@ func (s *socks5Stream) parseSocks5ReqConnInfo() utils.LSMAction { addrType := int(pkt[3]) var addr string switch addrType { - case AddrTypeIPv4: + case Socks5AddrTypeIPv4: addr = net.IPv4(pkt[4], pkt[5], pkt[6], pkt[7]).String() - case AddrTypeDomain: + case Socks5AddrTypeDomain: addr = string(pkt[5 : 5+pkt[4]]) - case AddrTypeIPv6: + case Socks5AddrTypeIPv6: addr = net.IP(pkt[4 : 4+net.IPv6len]).String() default: return utils.LSMActionCancel @@ -278,9 +278,9 @@ func (s *socks5Stream) parseSocks5RespVerAndMethod() utils.LSMAction { func (s *socks5Stream) parseSocks5RespAuth() utils.LSMAction { switch s.authRespMethod { - case AuthNotRequired: + case Socks5AuthNotRequired: s.respMap["auth"] = analyzer.PropMap{"method": s.authRespMethod} - case AuthPassword: + case Socks5AuthPassword: authResp, ok := s.respBuf.Get(2, true) if !ok { return utils.LSMActionPause @@ -318,12 +318,12 @@ func (s *socks5Stream) parseSocks5RespConnInfo() utils.LSMAction { var pktLen int switch int(preInfo[3]) { - case AddrTypeIPv4: + case Socks5AddrTypeIPv4: pktLen = 10 - case AddrTypeDomain: + case Socks5AddrTypeDomain: domainLen := int(preInfo[4]) pktLen = 7 + domainLen - case AddrTypeIPv6: + case Socks5AddrTypeIPv6: pktLen = 22 default: return utils.LSMActionCancel @@ -342,11 +342,11 @@ func (s *socks5Stream) parseSocks5RespConnInfo() utils.LSMAction { addrType := int(pkt[3]) var addr string switch addrType { - case AddrTypeIPv4: + case Socks5AddrTypeIPv4: addr = net.IPv4(pkt[4], pkt[5], pkt[6], pkt[7]).String() - case AddrTypeDomain: + case Socks5AddrTypeDomain: addr = string(pkt[5 : 5+pkt[4]]) - case AddrTypeIPv6: + case Socks5AddrTypeIPv6: addr = net.IP(pkt[4 : 4+net.IPv6len]).String() default: return utils.LSMActionCancel From 96716561e04821eb99bb39bfa9944e4054a3651b Mon Sep 17 00:00:00 2001 From: KujouRinka Date: Sat, 27 Jan 2024 13:39:46 +0800 Subject: [PATCH 2/6] feat: add sock4/4a analyzer --- analyzer/tcp/socks4.go | 196 +++++++++++++++++++++++++++++++++++++++++ cmd/root.go | 3 +- 2 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 analyzer/tcp/socks4.go diff --git a/analyzer/tcp/socks4.go b/analyzer/tcp/socks4.go new file mode 100644 index 0000000..b213c5a --- /dev/null +++ b/analyzer/tcp/socks4.go @@ -0,0 +1,196 @@ +package tcp + +import ( + "net" + + "github.com/apernet/OpenGFW/analyzer" + "github.com/apernet/OpenGFW/analyzer/utils" +) + +const ( + Socks4Version = 0x04 + Socks4ReplyVN = 0x00 + + Socks4CmdTCPConnect = 0x01 + Socks4CmdTCPBind = 0x02 + + Socks4ReqGranted = 0x5A + Socks4ReqRejectOrFailed = 0x5B + Socks4ReqRejectIdentd = 0x5C + Socks4ReqRejectUser = 0x5D +) + +var _ analyzer.Analyzer = (*Socks4Analyzer)(nil) + +type Socks4Analyzer struct{} + +func (a *Socks4Analyzer) Name() string { + return "socks4" +} + +func (a *Socks4Analyzer) Limit() int { + // TODO: should set a value to avoid buffer overflow attack + // ref: https://www.tenable.com/plugins/nessus/11126 + return 0 +} + +func (a *Socks4Analyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream { + return newSocks4Stream(logger) +} + +type socks4Stream struct { + logger analyzer.Logger + + reqBuf *utils.ByteBuffer + reqMap analyzer.PropMap + reqUpdated bool + reqLSM *utils.LinearStateMachine + reqDone bool + + respBuf *utils.ByteBuffer + respMap analyzer.PropMap + respUpdated bool + respLSM *utils.LinearStateMachine + respDone bool + + isSocks4a bool +} + +func newSocks4Stream(logger analyzer.Logger) *socks4Stream { + s := &socks4Stream{logger: logger, reqBuf: &utils.ByteBuffer{}, respBuf: &utils.ByteBuffer{}} + s.reqLSM = utils.NewLinearStateMachine( + s.parseReqIpAndPort, + s.parseReqUserId, + s.parseReqHostname, + ) + s.respLSM = utils.NewLinearStateMachine( + s.parseRespPacket, + ) + return s +} + +func (s *socks4Stream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, d bool) { + if skip != 0 { + return nil, true + } + if len(data) == 0 { + return nil, false + } + var update *analyzer.PropUpdate + var cancelled bool + if rev { + s.respBuf.Append(data) + s.respUpdated = false + cancelled, s.respDone = s.respLSM.Run() + if s.respUpdated { + update = &analyzer.PropUpdate{ + Type: analyzer.PropUpdateMerge, + M: analyzer.PropMap{"resp": s.respMap}, + } + s.respUpdated = false + } + } else { + s.reqBuf.Append(data) + s.reqUpdated = false + cancelled, s.reqDone = s.reqLSM.Run() + if s.reqUpdated { + update = &analyzer.PropUpdate{ + Type: analyzer.PropUpdateMerge, + M: analyzer.PropMap{"req": s.reqMap}, + } + s.reqUpdated = false + } + } + return update, cancelled || (s.reqDone && s.respDone) +} + +func (s *socks4Stream) Close(limited bool) *analyzer.PropUpdate { + s.reqBuf.Reset() + s.respBuf.Reset() + s.reqMap = nil + s.respMap = nil + return nil +} + +func (s *socks4Stream) parseReqIpAndPort() utils.LSMAction { + /* Following field will be parsed in this state: + +-----+-----+----------+--------+ + | VER | CMD | DST.PORT | DST.IP | + +-----+-----+----------+--------+ + */ + pkt, ok := s.reqBuf.Get(8, true) + if !ok { + return utils.LSMActionPause + } + if pkt[0] != Socks4Version { + return utils.LSMActionCancel + } + if pkt[1] != Socks4CmdTCPConnect && pkt[1] != Socks4CmdTCPBind { + return utils.LSMActionCancel + } + + dstPort := uint16(pkt[2])<<8 | uint16(pkt[3]) + dstIp := net.IPv4(pkt[4], pkt[5], pkt[6], pkt[7]).String() + + // Socks4a extension + s.isSocks4a = pkt[4] == 0 && pkt[5] == 0 && pkt[6] == 0 + + s.reqMap = analyzer.PropMap{ + "cmd": pkt[1], + "ip": dstIp, + "port": dstPort, + } + s.reqUpdated = true + return utils.LSMActionNext +} + +func (s *socks4Stream) parseReqUserId() utils.LSMAction { + userIdSlice, ok := s.reqBuf.GetUntil([]byte("\x00"), true, true) + if !ok { + return utils.LSMActionPause + } + userId := string(userIdSlice[:len(userIdSlice)-1]) + s.reqMap["user_id"] = userId + s.reqUpdated = true + return utils.LSMActionNext +} + +func (s *socks4Stream) parseReqHostname() utils.LSMAction { + // Only Socks4a support hostname + if !s.isSocks4a { + return utils.LSMActionNext + } + hostnameSlice, ok := s.reqBuf.GetUntil([]byte("\x00"), true, true) + if !ok { + return utils.LSMActionPause + } + hostname := string(hostnameSlice[:len(hostnameSlice)-1]) + s.reqMap["hostname"] = hostname + s.reqUpdated = true + return utils.LSMActionNext +} + +func (s *socks4Stream) parseRespPacket() utils.LSMAction { + pkt, ok := s.respBuf.Get(8, true) + if !ok { + return utils.LSMActionPause + } + if pkt[0] != Socks4ReplyVN { + return utils.LSMActionCancel + } + if pkt[1] != Socks4ReqGranted && + pkt[1] != Socks4ReqRejectOrFailed && + pkt[1] != Socks4ReqRejectIdentd && + pkt[1] != Socks4ReqRejectUser { + return utils.LSMActionCancel + } + dstPort := uint16(pkt[2])<<8 | uint16(pkt[3]) + dstIp := net.IPv4(pkt[4], pkt[5], pkt[6], pkt[7]).String() + s.respMap = analyzer.PropMap{ + "rep": pkt[1], + "ip": dstIp, + "port": dstPort, + } + s.respUpdated = true + return utils.LSMActionNext +} diff --git a/cmd/root.go b/cmd/root.go index 8aff027..c18e6a2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -87,10 +87,11 @@ var logFormatMap = map[string]zapcore.EncoderConfig{ var analyzers = []analyzer.Analyzer{ &tcp.FETAnalyzer{}, &tcp.HTTPAnalyzer{}, + &tcp.Socks4Analyzer{}, + &tcp.Socks5Analyzer{}, &tcp.SSHAnalyzer{}, &tcp.TLSAnalyzer{}, &tcp.TrojanAnalyzer{}, - &tcp.Socks5Analyzer{}, &udp.DNSAnalyzer{}, } From 1ae0455fd532c68dd8b6773636d5c38d70ee8c87 Mon Sep 17 00:00:00 2001 From: KujouRinka Date: Sat, 27 Jan 2024 14:09:21 +0800 Subject: [PATCH 3/6] docs: add sock4/4a doc --- docs/Analyzers.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/docs/Analyzers.md b/docs/Analyzers.md index 7333f7d..0534e93 100644 --- a/docs/Analyzers.md +++ b/docs/Analyzers.md @@ -269,6 +269,57 @@ Example for blocking Trojan connections: expr: trojan != nil && trojan.yes ``` +## SOCKS4/SOCKS4A + +SOCKS4: + +```json5 +{ + "socks4": { + "req": { + "cmd": 1, // 0x01: connect, 0x02: bind + "ip": "1.1.1.1", + "port": 443, + "user_id": "user_id" + }, + "resp": { + "rep": 90, // 0x5A(90): granted + "ip": "1.1.1.1", + "port": 443 + } + } +} +``` + +SOCKS4A: + +```json5 +{ + "socks4": { + "req": { + "cmd": 1, // 0x01: connect, 0x02: bind + "ip": "0.0.0.1", + "port": 443, + "user_id": "user_id", + "hostname": "google.com" + }, + "resp": { + "rep": 90, // 0x5A(90): granted + "ip": "0.0.0.1", + "port": 443 + } + } +} +``` + +Example for blocking connections to `google.com:80`: + +```yaml +- name: block baidu socks + action: block + expr: string(socks4?.req?.hostname) endsWith "bilibili.com" && socks4?.req?.port == 80 +``` + ## SOCKS5 SOCKS5 without auth: From ff27ee512a8bb1e2038093e17f2bd91d6bfb9e28 Mon Sep 17 00:00:00 2001 From: KujouRinka Date: Sat, 27 Jan 2024 20:45:11 +0800 Subject: [PATCH 4/6] refactor: merge sock4 and socks5 into one --- analyzer/tcp/{socks5.go => socks.go} | 218 ++++++++++++++++++++++----- analyzer/tcp/socks4.go | 196 ------------------------ analyzer/utils/lsm.go | 4 + cmd/root.go | 3 +- 4 files changed, 187 insertions(+), 234 deletions(-) rename analyzer/tcp/{socks5.go => socks.go} (57%) delete mode 100644 analyzer/tcp/socks4.go diff --git a/analyzer/tcp/socks5.go b/analyzer/tcp/socks.go similarity index 57% rename from analyzer/tcp/socks5.go rename to analyzer/tcp/socks.go index 5823e57..18ffc2b 100644 --- a/analyzer/tcp/socks5.go +++ b/analyzer/tcp/socks.go @@ -8,8 +8,24 @@ import ( ) const ( + SocksInvalid = iota + Socks4 + Socks4A + Socks5 + + Socks4Version = 0x04 Socks5Version = 0x05 + Socks4ReplyVN = 0x00 + + Socks4CmdTCPConnect = 0x01 + Socks4CmdTCPBind = 0x02 + + Socks4ReqGranted = 0x5A + Socks4ReqRejectOrFailed = 0x5B + Socks4ReqRejectIdentd = 0x5C + Socks4ReqRejectUser = 0x5D + Socks5CmdTCPConnect = 0x01 Socks5CmdTCPBind = 0x02 Socks5CmdUDPAssociate = 0x03 @@ -26,24 +42,24 @@ const ( Socks5AddrTypeIPv6 = 0x04 ) -var _ analyzer.Analyzer = (*Socks5Analyzer)(nil) +var _ analyzer.Analyzer = (*SocksAnalyzer)(nil) -type Socks5Analyzer struct{} +type SocksAnalyzer struct{} -func (a *Socks5Analyzer) Name() string { - return "socks5" +func (a *SocksAnalyzer) Name() string { + return "socks" } -func (a *Socks5Analyzer) Limit() int { - // TODO: more precise calculate - return 1298 +func (a *SocksAnalyzer) Limit() int { + // Socks4 length limit cannot be predicted + return 0 } -func (a *Socks5Analyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream { - return newSocks5Stream(logger) +func (a *SocksAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream { + return newSocksStream(logger) } -type socks5Stream struct { +type socksStream struct { logger analyzer.Logger reqBuf *utils.ByteBuffer @@ -58,6 +74,8 @@ type socks5Stream struct { respLSM *utils.LinearStateMachine respDone bool + version int + authReqMethod int authUsername string authPassword string @@ -65,23 +83,18 @@ type socks5Stream struct { authRespMethod int } -func newSocks5Stream(logger analyzer.Logger) *socks5Stream { - s := &socks5Stream{logger: logger, reqBuf: &utils.ByteBuffer{}, respBuf: &utils.ByteBuffer{}} +func newSocksStream(logger analyzer.Logger) *socksStream { + s := &socksStream{logger: logger, reqBuf: &utils.ByteBuffer{}, respBuf: &utils.ByteBuffer{}} s.reqLSM = utils.NewLinearStateMachine( - s.parseSocks5ReqVersion, - s.parseSocks5ReqMethod, - s.parseSocks5ReqAuth, - s.parseSocks5ReqConnInfo, + s.parseSocksReqVersion, ) s.respLSM = utils.NewLinearStateMachine( - s.parseSocks5RespVerAndMethod, - s.parseSocks5RespAuth, - s.parseSocks5RespConnInfo, + s.parseSocksRespVersion, ) return s } -func (s *socks5Stream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, d bool) { +func (s *socksStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, d bool) { if skip != 0 { return nil, true } @@ -108,15 +121,19 @@ func (s *socks5Stream) Feed(rev, start, end bool, skip int, data []byte) (u *ana if s.reqUpdated { update = &analyzer.PropUpdate{ Type: analyzer.PropUpdateMerge, - M: analyzer.PropMap{"req": s.reqMap}, + M: analyzer.PropMap{ + "version": s.socksVersion(), + "req": s.reqMap, + }, } s.reqUpdated = false } } + return update, cancelled || (s.reqDone && s.respDone) } -func (s *socks5Stream) Close(limited bool) *analyzer.PropUpdate { +func (s *socksStream) Close(limited bool) *analyzer.PropUpdate { s.reqBuf.Reset() s.respBuf.Reset() s.reqMap = nil @@ -124,18 +141,58 @@ func (s *socks5Stream) Close(limited bool) *analyzer.PropUpdate { return nil } -func (s *socks5Stream) parseSocks5ReqVersion() utils.LSMAction { +func (s *socksStream) parseSocksReqVersion() utils.LSMAction { socksVer, ok := s.reqBuf.GetByte(true) if !ok { return utils.LSMActionPause } - if socksVer != Socks5Version { + if socksVer != Socks4Version && socksVer != Socks5Version { return utils.LSMActionCancel } + s.reqMap = make(analyzer.PropMap) + s.reqUpdated = true + if socksVer == Socks4Version { + s.version = Socks4 + s.reqLSM.AppendSteps( + s.parseSocks4ReqIpAndPort, + s.parseSocks4ReqUserId, + s.parseSocks4ReqHostname, + ) + } else { + s.version = Socks5 + s.reqLSM.AppendSteps( + s.parseSocks5ReqMethod, + s.parseSocks5ReqAuth, + s.parseSocks5ReqConnInfo, + ) + } return utils.LSMActionNext } -func (s *socks5Stream) parseSocks5ReqMethod() utils.LSMAction { +func (s *socksStream) parseSocksRespVersion() utils.LSMAction { + socksVer, ok := s.respBuf.GetByte(true) + if !ok { + return utils.LSMActionPause + } + if (s.version == Socks4 || s.version == Socks4A) && socksVer != Socks4ReplyVN || + s.version == Socks5 && socksVer != Socks5Version || s.version == SocksInvalid { + return utils.LSMActionCancel + } + if socksVer == Socks4ReplyVN { + s.respLSM.AppendSteps( + s.parseSocks4RespPacket, + ) + } else { + s.respLSM.AppendSteps( + s.parseSocks5RespMethod, + s.parseSocks5RespAuth, + s.parseSocks5RespConnInfo, + ) + } + return utils.LSMActionNext +} + +func (s *socksStream) parseSocks5ReqMethod() utils.LSMAction { nMethods, ok := s.reqBuf.GetByte(false) if !ok { return utils.LSMActionPause @@ -159,11 +216,10 @@ func (s *socks5Stream) parseSocks5ReqMethod() utils.LSMAction { // TODO: more auth method to support } } - s.reqMap = make(analyzer.PropMap) return utils.LSMActionNext } -func (s *socks5Stream) parseSocks5ReqAuth() utils.LSMAction { +func (s *socksStream) parseSocks5ReqAuth() utils.LSMAction { switch s.authReqMethod { case Socks5AuthNotRequired: s.reqMap["auth"] = analyzer.PropMap{"method": s.authReqMethod} @@ -199,7 +255,7 @@ func (s *socks5Stream) parseSocks5ReqAuth() utils.LSMAction { return utils.LSMActionNext } -func (s *socks5Stream) parseSocks5ReqConnInfo() utils.LSMAction { +func (s *socksStream) parseSocks5ReqConnInfo() utils.LSMAction { /* preInfo struct +----+-----+-------+------+-------------+ |VER | CMD | RSV | ATYP | DST.ADDR(1) | @@ -263,20 +319,17 @@ func (s *socks5Stream) parseSocks5ReqConnInfo() utils.LSMAction { return utils.LSMActionNext } -func (s *socks5Stream) parseSocks5RespVerAndMethod() utils.LSMAction { - verAndMethod, ok := s.respBuf.Get(2, true) +func (s *socksStream) parseSocks5RespMethod() utils.LSMAction { + method, ok := s.respBuf.Get(1, true) if !ok { return utils.LSMActionPause } - if verAndMethod[0] != Socks5Version { - return utils.LSMActionCancel - } - s.authRespMethod = int(verAndMethod[1]) + s.authRespMethod = int(method[0]) s.respMap = make(analyzer.PropMap) return utils.LSMActionNext } -func (s *socks5Stream) parseSocks5RespAuth() utils.LSMAction { +func (s *socksStream) parseSocks5RespAuth() utils.LSMAction { switch s.authRespMethod { case Socks5AuthNotRequired: s.respMap["auth"] = analyzer.PropMap{"method": s.authRespMethod} @@ -300,7 +353,7 @@ func (s *socks5Stream) parseSocks5RespAuth() utils.LSMAction { return utils.LSMActionNext } -func (s *socks5Stream) parseSocks5RespConnInfo() utils.LSMAction { +func (s *socksStream) parseSocks5RespConnInfo() utils.LSMAction { /* preInfo struct +----+-----+-------+------+-------------+ |VER | REP | RSV | ATYP | BND.ADDR(1) | @@ -360,3 +413,96 @@ func (s *socks5Stream) parseSocks5RespConnInfo() utils.LSMAction { s.respUpdated = true return utils.LSMActionNext } + +func (s *socksStream) parseSocks4ReqIpAndPort() utils.LSMAction { + /* Following field will be parsed in this state: + +-----+----------+--------+ + | CMD | DST.PORT | DST.IP | + +-----+----------+--------+ + */ + pkt, ok := s.reqBuf.Get(7, true) + if !ok { + return utils.LSMActionPause + } + if pkt[0] != Socks4CmdTCPConnect && pkt[0] != Socks4CmdTCPBind { + return utils.LSMActionCancel + } + + dstPort := uint16(pkt[1])<<8 | uint16(pkt[2]) + dstIp := net.IPv4(pkt[3], pkt[4], pkt[5], pkt[6]).String() + + // Socks4a extension + if pkt[3] == 0 && pkt[4] == 0 && pkt[5] == 0 { + s.version = Socks4A + } + + s.reqMap["cmd"] = pkt[0] + s.reqMap["addr"] = dstIp + s.reqMap["addr_type"] = Socks5AddrTypeIPv4 + s.reqMap["port"] = dstPort + s.reqUpdated = true + return utils.LSMActionNext +} + +func (s *socksStream) parseSocks4ReqUserId() utils.LSMAction { + userIdSlice, ok := s.reqBuf.GetUntil([]byte("\x00"), true, true) + if !ok { + return utils.LSMActionPause + } + userId := string(userIdSlice[:len(userIdSlice)-1]) + s.reqMap["auth"] = analyzer.PropMap{ + "user_id": userId, + } + s.reqUpdated = true + return utils.LSMActionNext +} + +func (s *socksStream) parseSocks4ReqHostname() utils.LSMAction { + // Only Socks4a support hostname + if s.version != Socks4A { + return utils.LSMActionNext + } + hostnameSlice, ok := s.reqBuf.GetUntil([]byte("\x00"), true, true) + if !ok { + return utils.LSMActionPause + } + hostname := string(hostnameSlice[:len(hostnameSlice)-1]) + s.reqMap["addr"] = hostname + s.reqMap["addr_type"] = Socks5AddrTypeDomain + s.reqUpdated = true + return utils.LSMActionNext +} + +func (s *socksStream) parseSocks4RespPacket() utils.LSMAction { + pkt, ok := s.respBuf.Get(7, true) + if !ok { + return utils.LSMActionPause + } + if pkt[0] != Socks4ReqGranted && + pkt[0] != Socks4ReqRejectOrFailed && + pkt[0] != Socks4ReqRejectIdentd && + pkt[0] != Socks4ReqRejectUser { + return utils.LSMActionCancel + } + dstPort := uint16(pkt[1])<<8 | uint16(pkt[2]) + dstIp := net.IPv4(pkt[3], pkt[4], pkt[5], pkt[6]).String() + s.respMap = analyzer.PropMap{ + "rep": pkt[0], + "addr": dstIp, + "addr_type": Socks5AddrTypeIPv4, + "port": dstPort, + } + s.respUpdated = true + return utils.LSMActionNext +} + +func (s *socksStream) socksVersion() int { + switch s.version { + case Socks4, Socks4A: + return Socks4Version + case Socks5: + return Socks5Version + default: + return SocksInvalid + } +} diff --git a/analyzer/tcp/socks4.go b/analyzer/tcp/socks4.go deleted file mode 100644 index b213c5a..0000000 --- a/analyzer/tcp/socks4.go +++ /dev/null @@ -1,196 +0,0 @@ -package tcp - -import ( - "net" - - "github.com/apernet/OpenGFW/analyzer" - "github.com/apernet/OpenGFW/analyzer/utils" -) - -const ( - Socks4Version = 0x04 - Socks4ReplyVN = 0x00 - - Socks4CmdTCPConnect = 0x01 - Socks4CmdTCPBind = 0x02 - - Socks4ReqGranted = 0x5A - Socks4ReqRejectOrFailed = 0x5B - Socks4ReqRejectIdentd = 0x5C - Socks4ReqRejectUser = 0x5D -) - -var _ analyzer.Analyzer = (*Socks4Analyzer)(nil) - -type Socks4Analyzer struct{} - -func (a *Socks4Analyzer) Name() string { - return "socks4" -} - -func (a *Socks4Analyzer) Limit() int { - // TODO: should set a value to avoid buffer overflow attack - // ref: https://www.tenable.com/plugins/nessus/11126 - return 0 -} - -func (a *Socks4Analyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream { - return newSocks4Stream(logger) -} - -type socks4Stream struct { - logger analyzer.Logger - - reqBuf *utils.ByteBuffer - reqMap analyzer.PropMap - reqUpdated bool - reqLSM *utils.LinearStateMachine - reqDone bool - - respBuf *utils.ByteBuffer - respMap analyzer.PropMap - respUpdated bool - respLSM *utils.LinearStateMachine - respDone bool - - isSocks4a bool -} - -func newSocks4Stream(logger analyzer.Logger) *socks4Stream { - s := &socks4Stream{logger: logger, reqBuf: &utils.ByteBuffer{}, respBuf: &utils.ByteBuffer{}} - s.reqLSM = utils.NewLinearStateMachine( - s.parseReqIpAndPort, - s.parseReqUserId, - s.parseReqHostname, - ) - s.respLSM = utils.NewLinearStateMachine( - s.parseRespPacket, - ) - return s -} - -func (s *socks4Stream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, d bool) { - if skip != 0 { - return nil, true - } - if len(data) == 0 { - return nil, false - } - var update *analyzer.PropUpdate - var cancelled bool - if rev { - s.respBuf.Append(data) - s.respUpdated = false - cancelled, s.respDone = s.respLSM.Run() - if s.respUpdated { - update = &analyzer.PropUpdate{ - Type: analyzer.PropUpdateMerge, - M: analyzer.PropMap{"resp": s.respMap}, - } - s.respUpdated = false - } - } else { - s.reqBuf.Append(data) - s.reqUpdated = false - cancelled, s.reqDone = s.reqLSM.Run() - if s.reqUpdated { - update = &analyzer.PropUpdate{ - Type: analyzer.PropUpdateMerge, - M: analyzer.PropMap{"req": s.reqMap}, - } - s.reqUpdated = false - } - } - return update, cancelled || (s.reqDone && s.respDone) -} - -func (s *socks4Stream) Close(limited bool) *analyzer.PropUpdate { - s.reqBuf.Reset() - s.respBuf.Reset() - s.reqMap = nil - s.respMap = nil - return nil -} - -func (s *socks4Stream) parseReqIpAndPort() utils.LSMAction { - /* Following field will be parsed in this state: - +-----+-----+----------+--------+ - | VER | CMD | DST.PORT | DST.IP | - +-----+-----+----------+--------+ - */ - pkt, ok := s.reqBuf.Get(8, true) - if !ok { - return utils.LSMActionPause - } - if pkt[0] != Socks4Version { - return utils.LSMActionCancel - } - if pkt[1] != Socks4CmdTCPConnect && pkt[1] != Socks4CmdTCPBind { - return utils.LSMActionCancel - } - - dstPort := uint16(pkt[2])<<8 | uint16(pkt[3]) - dstIp := net.IPv4(pkt[4], pkt[5], pkt[6], pkt[7]).String() - - // Socks4a extension - s.isSocks4a = pkt[4] == 0 && pkt[5] == 0 && pkt[6] == 0 - - s.reqMap = analyzer.PropMap{ - "cmd": pkt[1], - "ip": dstIp, - "port": dstPort, - } - s.reqUpdated = true - return utils.LSMActionNext -} - -func (s *socks4Stream) parseReqUserId() utils.LSMAction { - userIdSlice, ok := s.reqBuf.GetUntil([]byte("\x00"), true, true) - if !ok { - return utils.LSMActionPause - } - userId := string(userIdSlice[:len(userIdSlice)-1]) - s.reqMap["user_id"] = userId - s.reqUpdated = true - return utils.LSMActionNext -} - -func (s *socks4Stream) parseReqHostname() utils.LSMAction { - // Only Socks4a support hostname - if !s.isSocks4a { - return utils.LSMActionNext - } - hostnameSlice, ok := s.reqBuf.GetUntil([]byte("\x00"), true, true) - if !ok { - return utils.LSMActionPause - } - hostname := string(hostnameSlice[:len(hostnameSlice)-1]) - s.reqMap["hostname"] = hostname - s.reqUpdated = true - return utils.LSMActionNext -} - -func (s *socks4Stream) parseRespPacket() utils.LSMAction { - pkt, ok := s.respBuf.Get(8, true) - if !ok { - return utils.LSMActionPause - } - if pkt[0] != Socks4ReplyVN { - return utils.LSMActionCancel - } - if pkt[1] != Socks4ReqGranted && - pkt[1] != Socks4ReqRejectOrFailed && - pkt[1] != Socks4ReqRejectIdentd && - pkt[1] != Socks4ReqRejectUser { - return utils.LSMActionCancel - } - dstPort := uint16(pkt[2])<<8 | uint16(pkt[3]) - dstIp := net.IPv4(pkt[4], pkt[5], pkt[6], pkt[7]).String() - s.respMap = analyzer.PropMap{ - "rep": pkt[1], - "ip": dstIp, - "port": dstPort, - } - s.respUpdated = true - return utils.LSMActionNext -} diff --git a/analyzer/utils/lsm.go b/analyzer/utils/lsm.go index 3b7b0b8..096df60 100644 --- a/analyzer/utils/lsm.go +++ b/analyzer/utils/lsm.go @@ -44,6 +44,10 @@ func (lsm *LinearStateMachine) Run() (cancelled bool, done bool) { return false, true } +func (lsm *LinearStateMachine) AppendSteps(steps ...func() LSMAction) { + lsm.Steps = append(lsm.Steps, steps...) +} + func (lsm *LinearStateMachine) Reset() { lsm.index = 0 lsm.cancelled = false diff --git a/cmd/root.go b/cmd/root.go index c18e6a2..2b21bce 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -87,8 +87,7 @@ var logFormatMap = map[string]zapcore.EncoderConfig{ var analyzers = []analyzer.Analyzer{ &tcp.FETAnalyzer{}, &tcp.HTTPAnalyzer{}, - &tcp.Socks4Analyzer{}, - &tcp.Socks5Analyzer{}, + &tcp.SocksAnalyzer{}, &tcp.SSHAnalyzer{}, &tcp.TLSAnalyzer{}, &tcp.TrojanAnalyzer{}, From bd724f43c0c72141e81bf005b5862550fe822fbe Mon Sep 17 00:00:00 2001 From: KujouRinka Date: Sat, 27 Jan 2024 20:59:41 +0800 Subject: [PATCH 5/6] docs: update socks doc --- docs/Analyzers.md | 67 ++++++++++++++++------------------------------- 1 file changed, 23 insertions(+), 44 deletions(-) diff --git a/docs/Analyzers.md b/docs/Analyzers.md index 0534e93..6f8dfa1 100644 --- a/docs/Analyzers.md +++ b/docs/Analyzers.md @@ -269,64 +269,42 @@ Example for blocking Trojan connections: expr: trojan != nil && trojan.yes ``` -## SOCKS4/SOCKS4A +## SOCKS SOCKS4: ```json5 { - "socks4": { + "socks": { + "version": 4, "req": { - "cmd": 1, // 0x01: connect, 0x02: bind - "ip": "1.1.1.1", + "cmd": 1, + "addr_type": 1, // same with socks5 + "addr": "1.1.1.1", + // for socks4a + // "addr_type": 3, + // "addr": "google.com", "port": 443, - "user_id": "user_id" + "auth": { + "user_id": "user" + } }, "resp": { - "rep": 90, // 0x5A(90): granted - "ip": "1.1.1.1", + "rep": 90, // 0x5A(90) granted + "addr_type": 1, + "addr": "1.1.1.1", "port": 443 } } } ``` -SOCKS4A: - -```json5 -{ - "socks4": { - "req": { - "cmd": 1, // 0x01: connect, 0x02: bind - "ip": "0.0.0.1", - "port": 443, - "user_id": "user_id", - "hostname": "google.com" - }, - "resp": { - "rep": 90, // 0x5A(90): granted - "ip": "0.0.0.1", - "port": 443 - } - } -} -``` - -Example for blocking connections to `google.com:80`: - -```yaml -- name: block baidu socks - action: block - expr: string(socks4?.req?.hostname) endsWith "bilibili.com" && socks4?.req?.port == 80 -``` - -## SOCKS5 - SOCKS5 without auth: ```json5 { - "socks5": { + "socks": { + "version": 5, "req": { "cmd": 1, // 0x01: connect, 0x02: bind, 0x03: udp "addr_type": 3, // 0x01: ipv4, 0x03: domain, 0x04: ipv6 @@ -353,7 +331,8 @@ SOCKS5 with auth: ```json5 { - "socks5": { + "socks": { + "version": 5, "req": { "cmd": 1, // 0x01: connect, 0x02: bind, 0x03: udp "addr_type": 3, // 0x01: ipv4, 0x03: domain, 0x04: ipv6 @@ -382,11 +361,11 @@ SOCKS5 with auth: Example for blocking connections to `google.com:80` and user `foobar`: ```yaml -- name: Block SOCKS5 google.com:80 +- name: Block SOCKS google.com:80 action: block - expr: string(socks5?.req?.addr) endsWith "google.com" && socks5?.req?.port == 80 + expr: string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80 -- name: Block SOCKS5 user foobar +- name: Block SOCKS user foobar action: block - expr: socks5?.req?.auth?.method == 2 && socks5?.req?.auth?.username == "foobar" + expr: socks?.req?.auth?.method == 2 && socks?.req?.auth?.username == "foobar" ``` From 63510eda5ef83304a0a7cb7a530f6979d781be18 Mon Sep 17 00:00:00 2001 From: Toby Date: Sat, 27 Jan 2024 13:40:29 -0800 Subject: [PATCH 6/6] chore: minor doc fix --- docs/Analyzers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Analyzers.md b/docs/Analyzers.md index 6f8dfa1..b068f2f 100644 --- a/docs/Analyzers.md +++ b/docs/Analyzers.md @@ -279,7 +279,7 @@ SOCKS4: "version": 4, "req": { "cmd": 1, - "addr_type": 1, // same with socks5 + "addr_type": 1, // same as socks5 "addr": "1.1.1.1", // for socks4a // "addr_type": 3,