diff -Naru cryptolib/ssh/cipher.go pkg/proto/ssh/cipher.go --- cryptolib/ssh/cipher.go 2023-11-24 21:59:11.581318943 +0300 +++ pkg/proto/ssh/cipher.go 2023-11-24 21:39:11.897832847 +0300 @@ -16,8 +16,8 @@ "hash" "io" - "golang.org/x/crypto/chacha20" "github.com/Neur0toxine/sshpoke/pkg/proto/ssh/internal/poly1305" + "golang.org/x/crypto/chacha20" ) const ( diff -Naru cryptolib/ssh/common.go pkg/proto/ssh/common.go --- cryptolib/ssh/common.go 2023-11-24 21:59:11.677320636 +0300 +++ pkg/proto/ssh/common.go 2023-11-24 21:39:08.401775359 +0300 @@ -287,6 +287,9 @@ // The allowed MAC algorithms. If unspecified then a sensible default is // used. Unsupported values are silently ignored. MACs []string + + // Called on every incoming handshake packet for client. Only receives data and extended data packets. + HandshakePacketReader func(p []byte) } // SetDefaults sets sensible values for unset fields in config. This is diff -Naru cryptolib/ssh/handshake.go pkg/proto/ssh/handshake.go --- cryptolib/ssh/handshake.go 2023-11-24 21:59:11.673320566 +0300 +++ pkg/proto/ssh/handshake.go 2023-11-24 21:59:05.113204819 +0300 @@ -401,6 +401,14 @@ t.printPacket(p, false) } + if t.config.HandshakePacketReader != nil && p[0] == msgChannelData { + data, err := decode(p) + packetData, ok := data.(*channelDataMsg) + if err == nil && ok && packetData != nil { + t.config.HandshakePacketReader(packetData.Rest) + } + } + if first && p[0] != msgKexInit { return nil, fmt.Errorf("ssh: first packet should be msgKexInit") } diff -Naru cryptolib/ssh/tcpip.go pkg/proto/ssh/tcpip.go --- cryptolib/ssh/tcpip.go 2023-11-24 21:59:11.593319154 +0300 +++ pkg/proto/ssh/tcpip.go 2023-11-24 21:39:08.401775359 +0300 @@ -101,14 +101,18 @@ // ListenTCP requests the remote peer open a listening socket // on laddr. Incoming connections will be available by calling // Accept on the returned net.Listener. -func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) { +func (c *Client) ListenTCP(laddr *net.TCPAddr, fakeHost ...string) (net.Listener, error) { c.handleForwardsOnce.Do(c.handleForwards) if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) { return c.autoPortListenWorkaround(laddr) } + host := laddr.IP.String() + if len(fakeHost) > 0 { + host = fakeHost[0] + } m := channelForwardMsg{ - laddr.IP.String(), + host, uint32(laddr.Port), } // send message @@ -133,7 +137,12 @@ } // Register this forward, using the port number we obtained. - ch := c.forwards.add(laddr) + var ch chan forward + if len(fakeHost) > 0 { + ch = c.forwards.add(laddr, fakeHost[0]) + } else { + ch = c.forwards.add(laddr) + } return &tcpListener{laddr, c, ch}, nil } @@ -142,7 +151,9 @@ // forward requests and the tcpListeners. type forwardList struct { sync.Mutex - entries []forwardEntry + fdm sync.RWMutex + fakeDomainsMap map[string]string + entries []forwardEntry } // forwardEntry represents an established mapping of a laddr on a @@ -160,17 +171,30 @@ raddr net.Addr // the raddr of the incoming connection } -func (l *forwardList) add(addr net.Addr) chan forward { +func (l *forwardList) add(addr net.Addr, fakeHost ...string) chan forward { l.Lock() defer l.Unlock() f := forwardEntry{ laddr: addr, c: make(chan forward, 1), } + if len(fakeHost) > 0 { + if l.fakeDomainsMap == nil { + l.fakeDomainsMap = make(map[string]string) + } + defer l.fdm.Unlock() + l.fdm.Lock() + l.fakeDomainsMap[fakeHost[0]] = l.ipFromAddr(addr) + } l.entries = append(l.entries, f) return f.c } +func (l *forwardList) ipFromAddr(addr net.Addr) string { + host, _, _ := net.SplitHostPort(addr.String()) + return host +} + // See RFC 4254, section 7.2 type forwardedTCPPayload struct { Addr string @@ -206,6 +230,15 @@ continue } + l.fdm.RLock() + if addr, ok := l.fakeDomainsMap[payload.Addr]; ok { + payload.Addr = addr + } + if addr, ok := l.fakeDomainsMap[payload.OriginAddr]; ok { + payload.OriginAddr = addr + } + l.fdm.RUnlock() + // RFC 4254 section 7.2 specifies that incoming // addresses should list the address, in string // format. It is implied that this should be an IP