From bb563e07982192b563b786e52a1c3ad6e30b7490 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 17 Jun 2018 15:19:49 +0200 Subject: [PATCH] add captured: sends packets to Wireshark SSH remote capture Just point your wireshark to 10.0.0.1:5022. --- cmd/captured/GENERATED_filterexpr.go | 39 ++++++ cmd/captured/captured.go | 23 ++++ cmd/captured/filterexpr.go | 49 ++++++++ cmd/captured/genfilterexpr.go | 3 + cmd/captured/ssh.go | 170 +++++++++++++++++++++++++++ 5 files changed, 284 insertions(+) create mode 100644 cmd/captured/GENERATED_filterexpr.go create mode 100644 cmd/captured/captured.go create mode 100644 cmd/captured/filterexpr.go create mode 100644 cmd/captured/genfilterexpr.go create mode 100644 cmd/captured/ssh.go diff --git a/cmd/captured/GENERATED_filterexpr.go b/cmd/captured/GENERATED_filterexpr.go new file mode 100644 index 0000000..b33b4f5 --- /dev/null +++ b/cmd/captured/GENERATED_filterexpr.go @@ -0,0 +1,39 @@ +package main + +import "golang.org/x/net/bpf" + +var instructions = []bpf.RawInstruction{ + {40, 0, 0, 12}, + {21, 0, 13, 34525}, + {48, 0, 0, 20}, + {21, 27, 0, 58}, + {21, 0, 2, 44}, + {48, 0, 0, 54}, + {21, 24, 25, 58}, + {21, 0, 24, 17}, + {40, 0, 0, 54}, + {21, 21, 0, 67}, + {21, 20, 0, 68}, + {21, 19, 0, 546}, + {21, 18, 0, 547}, + {40, 0, 0, 56}, + {21, 16, 13, 67}, + {21, 0, 16, 2048}, + {48, 0, 0, 23}, + {21, 0, 14, 17}, + {40, 0, 0, 20}, + {69, 12, 0, 8191}, + {177, 0, 0, 14}, + {72, 0, 0, 14}, + {21, 8, 0, 67}, + {21, 7, 0, 68}, + {21, 6, 0, 546}, + {21, 5, 0, 547}, + {72, 0, 0, 16}, + {21, 3, 0, 67}, + {21, 2, 0, 68}, + {21, 1, 0, 546}, + {21, 0, 1, 547}, + {6, 0, 0, 1500}, + {6, 0, 0, 0}, +} diff --git a/cmd/captured/captured.go b/cmd/captured/captured.go new file mode 100644 index 0000000..9e9a0ab --- /dev/null +++ b/cmd/captured/captured.go @@ -0,0 +1,23 @@ +package main + +import ( + "flag" + "log" +) + +var ( + hostKeyPath = flag.String("host_key", + "/perm/breakglass.host_key", + "path to a PEM-encoded RSA, DSA or ECDSA private key (create using e.g. ssh-keygen -f /perm/breakglass.host_key -N '' -t rsa)") +) + +func logic() error { + return listenAndServe() +} + +func main() { + flag.Parse() + if err := logic(); err != nil { + log.Fatal(err) + } +} diff --git a/cmd/captured/filterexpr.go b/cmd/captured/filterexpr.go new file mode 100644 index 0000000..48301a2 --- /dev/null +++ b/cmd/captured/filterexpr.go @@ -0,0 +1,49 @@ +// +build ignore + +package main + +import ( + "bytes" + "fmt" + "go/format" + "io" + "io/ioutil" + "log" + + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" +) + +// udp and (port 67 or port 68) # dhcpv4 +// udp and (port 546 or port 547) # dhcpv6 +// icmp6 # router|neighbor solicitation|announcement +const expression = `icmp6 or (udp and (port 67 or port 68 or port 546 or port 547))` + +func gen(w io.Writer) error { + fmt.Fprintf(w, "package main\n") + instructions, err := pcap.CompileBPFFilter(layers.LinkTypeEthernet, 1500, expression) + if err != nil { + return err + } + fmt.Fprintf(w, "import %q\n", "golang.org/x/net/bpf") + fmt.Fprintf(w, "var instructions = []bpf.RawInstruction{\n") + for _, inst := range instructions { + fmt.Fprintf(w, "{%d, %d, %d, %d},\n", inst.Code, inst.Jt, inst.Jf, inst.K) + } + fmt.Fprintf(w, "}") + return nil +} + +func main() { + var buffer bytes.Buffer + if err := gen(&buffer); err != nil { + log.Fatal(err) + } + gofmt, err := format.Source(buffer.Bytes()) + if err != nil { + log.Fatal(err) + } + if err := ioutil.WriteFile("GENERATED_filterexpr.go", gofmt, 0644); err != nil { + log.Fatal(err) + } +} diff --git a/cmd/captured/genfilterexpr.go b/cmd/captured/genfilterexpr.go new file mode 100644 index 0000000..e74f658 --- /dev/null +++ b/cmd/captured/genfilterexpr.go @@ -0,0 +1,3 @@ +package main + +//go:generate go run filterexpr.go diff --git a/cmd/captured/ssh.go b/cmd/captured/ssh.go new file mode 100644 index 0000000..e9d4599 --- /dev/null +++ b/cmd/captured/ssh.go @@ -0,0 +1,170 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "net" + + "github.com/gokrazy/gokrazy" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcapgo" + "golang.org/x/crypto/ssh" +) + +func handleChannel(newChannel ssh.NewChannel) { + if t := newChannel.ChannelType(); t != "session" { + newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %q", t)) + return + } + + channel, requests, err := newChannel.Accept() + if err != nil { + log.Printf("Could not accept channel (%s)", err) + return + } + + // Sessions have out-of-band requests such as "shell", "pty-req" and "env" + go func(channel ssh.Channel, requests <-chan *ssh.Request) { + s := session{channel: channel} + for req := range requests { + if err := s.request(req); err != nil { + errmsg := []byte(err.Error()) + // Append a trailing newline; the error message is + // displayed as-is by ssh(1). + if errmsg[len(errmsg)-1] != '\n' { + errmsg = append(errmsg, '\n') + } + req.Reply(false, errmsg) + channel.Write(errmsg) + channel.Close() + } + } + }(channel, requests) +} + +type session struct { + channel ssh.Channel +} + +func (s *session) request(req *ssh.Request) error { + switch req.Type { + case "exec": + if got, want := len(req.Payload), 4; got < want { + return fmt.Errorf("exec request payload too short: got %d, want >= %d", got, want) + } + log.Printf("exec, wantReply %v, payload %q", req.WantReply, string(req.Payload[4:])) + + pcapw := pcapgo.NewWriter(s.channel) + if err := pcapw.WriteFileHeader(1600, layers.LinkTypeEthernet); err != nil { + return err + } + + packets := make(chan gopacket.Packet) + for _, ifname := range []string{"uplink0", "lan0"} { + handle, err := pcapgo.OpenEthernet(ifname) + //handle, err := pcap.OpenLive("uplink0", 1600, false /* promisc */, pcap.BlockForever) + if err != nil { + return err + } + + if err := handle.SetBPF(instructions); err != nil { + //if err := handle.SetBPFFilter("icmp6 or (udp and (port 67 or port 68 or port 546 or port 547))"); err != nil { + return err + } + + pkgsrc := gopacket.NewPacketSource(handle, layers.LayerTypeEthernet) + go func() { + for packet := range pkgsrc.Packets() { + packets <- packet + } + }() + } + + req.Reply(true, nil) + + for packet := range packets { + log.Printf("packet: %+v", packet) + if err := pcapw.WritePacket(packet.Metadata().CaptureInfo, packet.Data()); err != nil { + return fmt.Errorf("pcap.WritePacket(): %v", err) + } + } + + return nil + + default: + return fmt.Errorf("unknown request type: %q", req.Type) + } + + return nil +} + +func loadHostKey(path string) (ssh.Signer, error) { + b, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + return ssh.ParsePrivateKey(b) +} + +func listenAndServe() error { + config := &ssh.ServerConfig{ + PublicKeyCallback: func(conn ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) { + return nil, nil // authorize all users + }, + } + + signer, err := loadHostKey(*hostKeyPath) + if err != nil { + return err + } + config.AddHostKey(signer) + + accept := func(listener net.Listener) { + for { + conn, err := listener.Accept() + if err != nil { + log.Printf("accept: %v", err) + continue + } + + go func(conn net.Conn) { + _, chans, reqs, err := ssh.NewServerConn(conn, config) + if err != nil { + log.Printf("handshake: %v", err) + return + } + + // discard all out of band requests + go ssh.DiscardRequests(reqs) + + for newChannel := range chans { + handleChannel(newChannel) + } + }(conn) + } + } + + addrs, err := gokrazy.PrivateInterfaceAddrs() + if err != nil { + return err + } + + for _, addr := range addrs { + hostport := net.JoinHostPort(addr, "5022") + listener, err := net.Listen("tcp", hostport) + if err != nil { + return err + } + fmt.Printf("listening on %s\n", hostport) + go accept(listener) + } + + fmt.Printf("host key fingerprint: %s\n", ssh.FingerprintSHA256(signer.PublicKey())) + + select {} + + return nil +}