Skip to content

Commit 678a57c

Browse files
committed
[+] #28 Support for dynamic port forwarding
1 parent eaf1b6e commit 678a57c

File tree

11 files changed

+237
-22
lines changed

11 files changed

+237
-22
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,10 @@ First use `Jump` to select a client, then type `PTY`, then type `Interact` to dr
218218
- [ ] Upgrade to Metepreter session
219219
- [ ] Electron frontend
220220
- [ ] [#53 Reload config file](https://github.com/WangYihang/Platypus/issues/53)
221-
- [ ] [#28 Suport remote port forwarding](https://github.com/WangYihang/Platypus/issues/28))
222-
- [ ] [#28 Suport dynamic port forwarding](https://github.com/WangYihang/Platypus/issues/28))
223221
- [ ] [#28 Suport enable internet on the internal machine](https://github.com/WangYihang/Platypus/issues/28))
222+
- [ ] Add version checking in Termite
223+
- [x] [#28 Suport dynamic port forwarding](https://github.com/WangYihang/Platypus/issues/28))
224+
- [x] [#28 Suport remote port forwarding](https://github.com/WangYihang/Platypus/issues/28))
224225
- [x] [#28 Suport local port forwarding](https://github.com/WangYihang/Platypus/issues/28))
225226
- [x] Design Private Protocol
226227
- [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
254255
- [x] Upgrade common reverse shell session into full interactive session
255256
- [x] Docker support (Added by [@yeya24](https://github.com/yeya24))
256257

257-
258258
## Contributors
259259

260260
This project exists thanks to all the people who contribute.

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.14
44

55
require (
66
github.com/WangYihang/readline v0.0.0-20200229084751-518dcf4f57b3
7+
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
78
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect
89
github.com/blang/semver v3.5.1+incompatible
910
github.com/creack/pty v1.1.11
@@ -24,6 +25,7 @@ require (
2425
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
2526
github.com/modern-go/reflect2 v1.0.1 // indirect
2627
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
28+
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
2729
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4
2830
github.com/rhysd/go-github-selfupdate v1.2.3
2931
github.com/sevlyar/go-daemon v0.1.5

go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ github.com/WangYihang/readline v0.0.0-20200229084751-518dcf4f57b3 h1:vnLf9dGMiEb
3939
github.com/WangYihang/readline v0.0.0-20200229084751-518dcf4f57b3/go.mod h1:S7Ulsa01ssaZKuWV9IRCrAdCtCdI4dQIt39FYOzAEa4=
4040
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
4141
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
42+
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
43+
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
4244
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
4345
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0=
4446
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
244246
github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I=
245247
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
246248
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
249+
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
250+
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
247251
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI=
248252
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
249253
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

lib/cli/dispatcher/tunnel.go

+13-6
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,26 @@ func (dispatcher Dispatcher) Tunnel(args []string) {
4848
case "pull":
4949
local_address := fmt.Sprintf("%s:%d", dst_host, dst_port)
5050
remote_address := fmt.Sprintf("%s:%d", src_host, src_port)
51-
log.Info("Mapping remote (%s) to local (%s)", remote_address, local_address)
5251
context.AddPullTunnelConfig(context.Ctx.CurrentTermite, local_address, remote_address)
5352
case "push":
5453
local_address := fmt.Sprintf("%s:%d", src_host, src_port)
5554
remote_address := fmt.Sprintf("%s:%d", dst_host, dst_port)
56-
log.Info("Mapping local (%s) to remote (%s)", local_address, remote_address)
5755
context.AddPushTunnelConfig(context.Ctx.CurrentTermite, local_address, remote_address)
5856
case "dynamic":
59-
log.Error("TBD")
60-
// context.AddDynamicTunnelConfig(context.Ctx.CurrentTermite, local_address, remote_address)
57+
context.Ctx.CurrentTermite.StartSocks5Server()
6158
case "internet":
62-
log.Error("TBD")
63-
// context.AddInternetTunnelConfig(context.Ctx.CurrentTermite, local_address, remote_address)
59+
local_address := fmt.Sprintf("%s:%d", src_host, src_port)
60+
remote_address := fmt.Sprintf("%s:%d", dst_host, dst_port)
61+
if _, exists := context.Ctx.Socks5Servers[local_address]; exists {
62+
log.Warn("Socks5 server (%s) already exists", local_address)
63+
} else {
64+
err := context.StartSocks5Server(local_address)
65+
if err != nil {
66+
log.Error("Starting local socks5 server failed: %s", err.Error())
67+
} else {
68+
context.AddPushTunnelConfig(context.Ctx.CurrentTermite, local_address, remote_address)
69+
}
70+
}
6471
default:
6572
log.Error("Invalid mode: %s, should be in {'Pull', 'Push', 'Dynamic', 'Internet'}", mode)
6673
}

lib/context/client.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ func CreateTCPClient(conn net.Conn, server *TCPServer) *TCPClient {
7878

7979
func (c *TCPClient) Close() {
8080
c.conn.Close()
81+
if Ctx.Current == c {
82+
Ctx.Current = nil
83+
}
8184
}
8285

8386
func (c *TCPClient) GetConnString() string {
@@ -1001,7 +1004,7 @@ func (c *TCPClient) Upload(src string, dst string, broadcast bool) bool {
10011004
bar.IncrBy(segmentSize)
10021005
bar.DecoratorEwmaUpdate(time.Since(start))
10031006

1004-
if broadcast && i%64 == 0 {
1007+
if broadcast && i%0x10 == 0 {
10051008
c.NotifyWebSocketUploadingTermite(bytesSent, totalBytes)
10061009
}
10071010
}

lib/context/context.go

+23
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/WangYihang/Platypus/lib/util/str"
1313
"github.com/WangYihang/Platypus/lib/util/ui"
1414
"github.com/WangYihang/readline"
15+
"github.com/armon/go-socks5"
1516
"github.com/fatih/color"
1617
"github.com/gin-gonic/gin"
1718
"gopkg.in/olahol/melody.v1"
@@ -50,6 +51,7 @@ type Context struct {
5051
PullTunnelInstance map[string]PullTunnelInstance
5152
PushTunnelConfig map[string]PushTunnelConfig
5253
PushTunnelInstance map[string]PushTunnelInstance
54+
Socks5Servers map[string](*socks5.Server)
5355
// Set later in platypus.go
5456
Distributor *Distributor
5557
RESTful *gin.Engine
@@ -72,6 +74,7 @@ func CreateContext() {
7274
PullTunnelInstance: make(map[string]PullTunnelInstance),
7375
PushTunnelConfig: make(map[string]PushTunnelConfig),
7476
PushTunnelInstance: make(map[string]PushTunnelInstance),
77+
Socks5Servers: make(map[string]*socks5.Server),
7578
}
7679
}
7780
// Signal Handler
@@ -197,6 +200,7 @@ func Shutdown() {
197200
}
198201

199202
func AddPushTunnelConfig(termite *TermiteClient, local_address string, remote_address string) {
203+
log.Info("Mapping local (%s) to remote (%s)", local_address, remote_address)
200204
termite.AtomLock.Lock()
201205
defer func() { termite.AtomLock.Unlock() }()
202206

@@ -220,6 +224,7 @@ func AddPushTunnelConfig(termite *TermiteClient, local_address string, remote_ad
220224
}
221225

222226
func AddPullTunnelConfig(termite *TermiteClient, local_address string, remote_address string) {
227+
log.Info("Mapping remote (%s) to local (%s)", remote_address, local_address)
223228
tunnel, err := net.Listen("tcp", local_address)
224229
if err != nil {
225230
log.Error(err.Error())
@@ -277,6 +282,24 @@ func WriteTunnel(termite *TermiteClient, token string, data []byte) {
277282
}
278283
}
279284

285+
func StartSocks5Server(local_address string) error {
286+
// Create tcp listener
287+
socks5ServerListener, err := net.Listen("tcp", local_address)
288+
if err != nil {
289+
return err
290+
}
291+
// Create socks5 server
292+
server, err := socks5.New(&socks5.Config{})
293+
if err != nil {
294+
return err
295+
}
296+
Ctx.Socks5Servers[local_address] = server
297+
// Start socks5 server
298+
go server.Serve(socks5ServerListener)
299+
log.Success("Socks server started at: %s", local_address)
300+
return nil
301+
}
302+
280303
// func DeletePullTunnelConfig(local_host string, local_port uint16, remote_host string, remote_port uint16) {
281304
// local_address := fmt.Sprintf("%s:%d", local_host, local_port)
282305
// remote_address := fmt.Sprintf("%s:%d", remote_host, remote_port)

lib/context/server.go

+25
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/WangYihang/Platypus/lib/util/str"
2121
humanize "github.com/dustin/go-humanize"
2222
"github.com/jedib0t/go-pretty/table"
23+
"github.com/phayes/freeport"
2324
)
2425

2526
type WebSocketMessage struct {
@@ -471,10 +472,12 @@ func (s *TCPServer) AddTermiteClient(client *TermiteClient) {
471472
log.Error("Duplicated income connection detected!")
472473

473474
// Respond to termite client that the client is duplicated
475+
client.EncoderLock.Lock()
474476
err := client.Encoder.Encode(message.Message{
475477
Type: message.DUPLICATED_CLIENT,
476478
Body: message.BodyDuplicateClient{},
477479
})
480+
client.EncoderLock.Unlock()
478481
if err != nil {
479482
// TODO: handle network error
480483
log.Error("Network error: %s", err)
@@ -607,50 +610,58 @@ func TermiteMessageDispatcher(client *TermiteClient) {
607610
conn, err := net.Dial("tcp", tc.Address)
608611
if err != nil {
609612
log.Error("Connecting to %s failed: %s", tc.Address, err.Error())
613+
tc.Termite.EncoderLock.Lock()
610614
tc.Termite.Encoder.Encode(message.Message{
611615
Type: message.PUSH_TUNNEL_CONNECT_FAILED,
612616
Body: message.BodyPushTunnelConnectFailed{
613617
Token: token,
614618
Reason: err.Error(),
615619
},
616620
})
621+
tc.Termite.EncoderLock.Unlock()
617622
} else {
618623
log.Success("Connecting to %s succeed", tc.Address)
619624
Ctx.PushTunnelInstance[token] = PushTunnelInstance{
620625
Termite: tc.Termite,
621626
Conn: &conn,
622627
}
628+
tc.Termite.EncoderLock.Lock()
623629
tc.Termite.Encoder.Encode(message.Message{
624630
Type: message.PUSH_TUNNEL_CONNECTED,
625631
Body: message.BodyPushTunnelConnected{
626632
Token: token,
627633
},
628634
})
635+
tc.Termite.EncoderLock.Unlock()
629636
go func() {
630637
for {
631638
buffer := make([]byte, 0x400)
632639
n, err := conn.Read(buffer)
633640
if err != nil {
634641
log.Debug("Reading from %s failed: %s", tc.Address, err.Error())
642+
tc.Termite.EncoderLock.Lock()
635643
tc.Termite.Encoder.Encode(message.Message{
636644
Type: message.PUSH_TUNNEL_DISCONNECTED,
637645
Body: message.BodyPushTunnelDisonnected{
638646
Token: token,
639647
Reason: err.Error(),
640648
},
641649
})
650+
tc.Termite.EncoderLock.Unlock()
642651
conn.Close()
643652
delete(Ctx.PushTunnelInstance, token)
644653
break
645654
} else {
646655
log.Debug("%d bytes read from %s", n, tc.Address)
656+
tc.Termite.EncoderLock.Lock()
647657
tc.Termite.Encoder.Encode(message.Message{
648658
Type: message.PUSH_TUNNEL_DATA,
649659
Body: message.BodyPushTunnelData{
650660
Token: token,
651661
Data: buffer[0:n],
652662
},
653663
})
664+
tc.Termite.EncoderLock.Unlock()
654665
}
655666
}
656667
}()
@@ -703,19 +714,33 @@ func TermiteMessageDispatcher(client *TermiteClient) {
703714
if ti, exists := Ctx.PushTunnelInstance[token]; exists {
704715
_, err := (*ti.Conn).Write(data)
705716
if err != nil {
717+
ti.Termite.EncoderLock.Lock()
706718
ti.Termite.Encoder.Encode(message.Message{
707719
Type: message.PUSH_TUNNEL_CONNECT_FAILED,
708720
Body: message.BodyPushTunnelConnectFailed{
709721
Token: token,
710722
Reason: err.Error(),
711723
},
712724
})
725+
ti.Termite.EncoderLock.Unlock()
713726
(*ti.Conn).Close()
714727
delete(Ctx.PushTunnelInstance, token)
715728
}
716729
} else {
717730
log.Debug("No such tunnel: %s", token)
718731
}
732+
case message.DYNAMIC_TUNNEL_CREATED:
733+
port := msg.Body.(*message.BodyDynamicTunnelCreated).Port
734+
local_address := fmt.Sprintf("127.0.0.1:%d", freeport.GetPort())
735+
remote_address := fmt.Sprintf("127.0.0.1:%d", port)
736+
log.Success("Mapping remote socks server (%s) into local address (%s)", remote_address, local_address)
737+
AddPullTunnelConfig(
738+
Ctx.CurrentTermite,
739+
local_address,
740+
remote_address,
741+
)
742+
case message.DYNAMIC_TUNNEL_CREATE_FAILED:
743+
log.Error(msg.Body.(*message.BodyDynamicTunnelCreateFailed).Reason)
719744
}
720745
}
721746
}

lib/context/termite.go

+12
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ func (c *TermiteClient) GetHashFormat() string {
9393
return c.server.hashFormat
9494
}
9595

96+
func (c *TermiteClient) StartSocks5Server() {
97+
c.EncoderLock.Lock()
98+
c.Encoder.Encode(message.Message{
99+
Type: message.DYNAMIC_TUNNEL_CREATE,
100+
Body: message.BodyDynamicTunnelCreate{},
101+
})
102+
c.EncoderLock.Unlock()
103+
}
104+
96105
func (c *TermiteClient) GatherClientInfo(hashFormat string) bool {
97106
log.Info("Gathering information from termite client...")
98107

@@ -299,6 +308,9 @@ func (c *TermiteClient) Close() {
299308
}
300309
}
301310
c.conn.Close()
311+
if Ctx.CurrentTermite == c {
312+
Ctx.CurrentTermite = nil
313+
}
302314
}
303315

304316
func (c *TermiteClient) AsTable() {

lib/util/message/message.go

+26
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ const (
2626
PUSH_TUNNEL_CONNECT_FAILED
2727
PUSH_TUNNEL_DISCONNECTED
2828
PUSH_TUNNEL_DISCONNECT_FAILED
29+
DYNAMIC_TUNNEL_CREATE
30+
DYNAMIC_TUNNEL_DESTROY
2931

3032
// Termite -> Platypus
3133
PROCESS_STARTED
@@ -40,6 +42,10 @@ const (
4042
PUSH_TUNNEL_CREATE_FAILED
4143
PUSH_TUNNEL_DELETED
4244
PUSH_TUNNEL_DELETE_FAILED
45+
DYNAMIC_TUNNEL_CREATED
46+
DYNAMIC_TUNNEL_CREATE_FAILED
47+
DYNAMIC_TUNNEL_DESTROIED
48+
DYNAMIC_TUNNEL_DESTROY_FAILED
4349
)
4450

4551
type Message struct {
@@ -175,6 +181,19 @@ type BodyPushTunnelDisonnectFailed struct {
175181
Token string
176182
}
177183

184+
type BodyDynamicTunnelCreate struct{}
185+
type BodyDynamicTunnelCreated struct {
186+
Port int
187+
}
188+
type BodyDynamicTunnelCreateFailed struct {
189+
Reason string
190+
}
191+
type BodyDynamicTunnelDestroy struct{}
192+
type BodyDynamicTunnelDestroied struct{}
193+
type BodyDynamicTunnelDestroyFailed struct {
194+
Reason string
195+
}
196+
178197
func RegisterGob() {
179198
// Client Management
180199
gob.Register(&BodyClientInfo{})
@@ -208,4 +227,11 @@ func RegisterGob() {
208227
gob.Register(&BodyPushTunnelDisonnect{})
209228
gob.Register(&BodyPushTunnelDisonnected{})
210229
gob.Register(&BodyPushTunnelDisonnectFailed{})
230+
// Dynamic port forwarding
231+
gob.Register(&BodyDynamicTunnelCreate{})
232+
gob.Register(&BodyDynamicTunnelCreated{})
233+
gob.Register(&BodyDynamicTunnelCreateFailed{})
234+
gob.Register(&BodyDynamicTunnelDestroy{})
235+
gob.Register(&BodyDynamicTunnelDestroied{})
236+
gob.Register(&BodyDynamicTunnelDestroyFailed{})
211237
}

lib/util/ui/prompt.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
func PromptYesNo(message string) bool {
1111
for {
12-
fmt.Print(fmt.Sprintf("%s [Y/N] ", message))
12+
fmt.Printf("%s [Y/N] ", message)
1313
inputReader := bufio.NewReader(os.Stdin)
1414
input, err := inputReader.ReadString('\n')
1515
if err != nil {

0 commit comments

Comments
 (0)