A => .gitignore +1 -0
A => LICENSE +22 -0
@@ 1,22 @@
+Copyright (c) 2021 Humaid AlQassimi. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
A => go.mod +5 -0
@@ 1,5 @@
+module git.sr.ht/~humaid/tlsspy
+
+go 1.16
+
+require github.com/google/gopacket v1.1.19
A => go.sum +14 -0
@@ 1,14 @@
+github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
+github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
A => main.go +120 -0
@@ 1,120 @@
+package main
+
+import (
+ "encoding/binary"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/google/gopacket"
+ "github.com/google/gopacket/layers"
+ "github.com/google/gopacket/pcap"
+)
+
+func main() {
+ fmt.Println("Extracting server names from TLS connections")
+ if strings.HasPrefix(os.Args[1], "live:") {
+ dev := strings.Split(os.Args[1], "live:")[1]
+ if handle, err := pcap.OpenLive(dev, 1600, true, pcap.BlockForever); err != nil {
+ panic(err)
+ } else if err := handle.SetBPFFilter("port 443"); err != nil { // optional
+ panic(err)
+ } else {
+ fmt.Println("Extracting live from", dev)
+ packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
+ for packet := range packetSource.Packets() {
+ handlePacket(packet) // Do something with a packet here.
+ }
+ }
+ } else {
+ if handle, err := pcap.OpenOffline(os.Args[1]); err != nil {
+ panic(err)
+ } else {
+ packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
+ for packet := range packetSource.Packets() {
+ handlePacket(packet) // Do something with a packet here.
+ }
+ }
+ }
+}
+
+func handlePacket(packet gopacket.Packet) {
+ if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
+ tcp, _ := tcpLayer.(*layers.TCP)
+ pl := packet.TransportLayer().LayerPayload()
+ payload := string(pl)
+ if len(payload) > 0 {
+ host := parseTransportLayer(pl)
+ if len(host) > 0 {
+ src := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4).SrcIP
+ dst := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4).DstIP
+ fmt.Printf("From %s:%d to %s:%d\n", src, tcp.SrcPort, dst, tcp.DstPort)
+ //fmt.Printf("From src port %d to dst port %d\n", tcp.SrcPort, tcp.DstPort)
+ fmt.Println("Connection to: ", host)
+ }
+ }
+ }
+}
+
+func prnt(data []byte) {
+ for i, d := range data {
+ fmt.Printf("(%d)%x ", i, d)
+ }
+ fmt.Println()
+ fmt.Println(string(data))
+}
+
+func parseTransportLayer(data []byte) string {
+ if len(data) <= 42 {
+ return ""
+ }
+ // jump session
+ data = data[43:]
+ if len(data) < 45 {
+ return ""
+ }
+ j := uint16(data[0])
+ if len(data) <= int(j) {
+ return ""
+ }
+ data = data[j+1:]
+ if len(data) < 4 {
+ return ""
+ }
+ // cypher suites
+ j = binary.BigEndian.Uint16(data[0:2])
+ if len(data) <= int(j) {
+ return ""
+ }
+ data = data[j+2:]
+
+ // skip comp method
+ j = uint16(data[0])
+ if len(data) <= int(j) {
+ return ""
+ }
+ data = data[j+1:]
+
+ // skip ext length
+ data = data[2:]
+
+ // find server name extension
+ for {
+ if data[0] == 0x00 && data[1] == 0x00 {
+
+ data = data[2:]
+ if len(data) < 10 {
+ return ""
+ }
+ end := binary.BigEndian.Uint16(data[5:7])
+ return string(data[7 : 7+end])
+ } else {
+ data = data[2:]
+ j = binary.BigEndian.Uint16(data[0:2])
+ if len(data) <= int(j) {
+ return ""
+ }
+ data = data[j+2:]
+ }
+ }
+}