diff --git a/proxy/layer-nbns/layer-nbns.go b/proxy/layer-nbns/layer-nbns.go deleted file mode 100644 index a6b93de..0000000 --- a/proxy/layer-nbns/layer-nbns.go +++ /dev/null @@ -1,246 +0,0 @@ -// based on https://github.com/aler9/landiscover/blob/main/layer-nbns.go (MIT License) - -package layer_nbns - -import ( - "encoding/binary" - "fmt" - "strings" - - "github.com/google/gopacket" - "github.com/google/gopacket/layers" -) - -type layerNbns struct { - layers.BaseLayer - TransactionID uint16 - IsResponse bool - Opcode uint8 - Truncated bool - Recursion bool - Broadcast bool - Questions []nbnsQuestion - Answers []nbnsAnswer - AuthorityCount uint16 - AdditionalCount uint16 -} - -func NewLayerNbns() *layerNbns { - return &layerNbns{} -} - -type nbnsQuestion struct { - Query string - Type uint16 - Class uint16 -} - -type nbnsAnswer struct { - Query string - Type uint16 - Class uint16 - TTL uint32 - Names []nbnsAnswerName -} - -type nbnsAnswerName struct { - Name string - Type uint8 - Flags uint16 -} - -func (l *layerNbns) DecodeFromBytes(data []byte) error { - l.BaseLayer = layers.BaseLayer{Contents: data} - - if len(data) < 12 { - return fmt.Errorf("invalid packet") - } - - l.TransactionID = binary.BigEndian.Uint16(data[0:2]) - l.IsResponse = (data[3] >> 7) == 0x01 - l.Opcode = (data[3] >> 3) & 0x0F - l.Truncated = (data[3] >> 1) == 0x01 - l.Recursion = data[3] == 0x01 - l.Broadcast = (data[4] >> 4) == 0x01 - questionCount := binary.BigEndian.Uint16(data[4:6]) - answerCount := binary.BigEndian.Uint16(data[6:8]) - l.AuthorityCount = binary.BigEndian.Uint16(data[8:10]) - l.AdditionalCount = binary.BigEndian.Uint16(data[10:12]) - pos := 12 - - if questionCount > 0 { - return fmt.Errorf("is question, unsupported") - } - - l.Questions = nil - for i := uint16(0); i < questionCount; i++ { - q := nbnsQuestion{} - - var read int - q.Query, read = dnsQueryDecode(data, pos) - if read <= 0 { - return fmt.Errorf("invalid string") - } - pos += read - - q.Type = binary.BigEndian.Uint16(data[pos+0 : pos+2]) - q.Class = binary.BigEndian.Uint16(data[pos+2 : pos+4]) - pos += 4 - l.Questions = append(l.Questions, q) - } - - l.Answers = nil - for i := uint16(0); i < answerCount; i++ { - a := nbnsAnswer{} - - var read int - a.Query, read = dnsQueryDecode(data, pos) - if read <= 0 { - return fmt.Errorf("invalid string") - } - pos += read - - a.Type = binary.BigEndian.Uint16(data[pos+0 : pos+2]) - a.Class = binary.BigEndian.Uint16(data[pos+2 : pos+4]) - a.TTL = binary.BigEndian.Uint32(data[pos+4 : pos+8]) - dataLen := binary.BigEndian.Uint16(data[pos+8 : pos+10]) - pos += 10 - - if a.Type == 0x21 { // NB_STAT - pos2 := pos - nameCount := data[pos2] - pos2++ - - for j := uint8(0); j < nameCount; j++ { - a.Names = append(a.Names, nbnsAnswerName{ - Name: strings.TrimSuffix(string(data[pos2:pos2+15]), " "), - Type: data[pos2+15], - Flags: binary.BigEndian.Uint16(data[pos2+16 : pos2+18]), - }) - pos2 += 18 - } - } - - pos += int(dataLen) - l.Answers = append(l.Answers, a) - } - - // TODO: Decode Authorities and Additional Information - - return nil -} - -func (l *layerNbns) SerializeTo(b gopacket.SerializeBuffer) error { - data, err := b.AppendBytes(12) - if err != nil { - panic(err) - } - - binary.BigEndian.PutUint16(data[0:2], l.TransactionID) - if l.IsResponse { - data[3] |= 0x01 << 7 - } - data[3] |= l.Opcode << 3 - if l.Truncated { - data[3] |= 0x01 << 1 - } - if l.Recursion { - data[3] |= 0x01 - } - if l.Broadcast { - data[4] |= 0x01 << 4 - } - binary.BigEndian.PutUint16(data[4:6], uint16(len(l.Questions))) - binary.BigEndian.PutUint16(data[6:8], uint16(len(l.Answers))) - binary.BigEndian.PutUint16(data[8:10], l.AuthorityCount) - binary.BigEndian.PutUint16(data[10:12], l.AdditionalCount) - - for _, q := range l.Questions { - enc := dnsQueryEncode(q.Query) - - data, err := b.AppendBytes(len(enc) + 4) - if err != nil { - panic(err) - } - - copy(data[:len(enc)], enc) - data = data[len(enc):] - binary.BigEndian.PutUint16(data[0:2], q.Type) - binary.BigEndian.PutUint16(data[2:4], q.Class) - } - - // TODO: encode answers, authorities and additional informations - - return nil -} - -// Source: https://github.com/aler9/landiscover/blob/main/utils.go -// partpart -func dnsQueryDecode(data []byte, start int) (string, int) { - var read []byte - toread := uint8(0) - pos := start - - for ; true; pos++ { - if pos >= len(data) { // decoding terminated before null character - return "", -1 - } - if data[pos] == 0x00 { - if toread > 0 { // decoding terminated before part parsing - return "", -1 - } - break // query correctly decoded - } - - if toread == 0 { // we need a size or pointer - if len(read) > 0 { // add separator - read = append(read, '.') - } - - if (data[pos] & 0xC0) == 0xC0 { // pointer - ptr := int(binary.BigEndian.Uint16(data[pos:pos+2]) & 0x3FFF) - pos++ // skip next byte - - substr, subread := dnsQueryDecode(data, ptr) - if subread <= 0 { - return "", -1 - } - read = append(read, []byte(substr)...) - break // query correctly decoded - } else { // size - toread = data[pos] - } - } else { // byte inside part - read = append(read, data[pos]) - toread-- - } - } - return string(read), pos + 1 - start -} - -func dnsQueryEncode(in string) []byte { - tmp := strings.Split(in, ".") - - l := 0 - for _, part := range tmp { - bpart := []byte(part) - l++ - l += len(bpart) - } - l++ - - ret := make([]byte, l) - i := 0 - - for _, part := range tmp { - bpart := []byte(part) - ret[i] = uint8(len(bpart)) - i++ - copy(ret[i:], bpart) - i += len(bpart) - } - - ret[i] = uint8(0) - - return ret -} diff --git a/proxy/main.go b/proxy/main.go index 5b753b7..5db5836 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -12,7 +12,6 @@ import ( "net" "os" "proxy/cmd" - nbns "proxy/layer-nbns" . "proxy/util" "strconv" "time" @@ -37,6 +36,7 @@ var DHCPState dhcp4.MessageType var DHCPCandidate net.IP var UId string var Wireshark bool +var Hostname string // Start the two plugs and run two concurrent forward methods func main() { @@ -52,6 +52,7 @@ func main() { pidFile := flag.String("pidfile", "", "Location to write the pid to") logFile := flag.String("logfile", "", "Location to write output to") wireshark := flag.Bool("wireshark", false, "Whether to write all traffic to /tmp") + hostname := flag.String("hostname", "", "Set a windows hostname to filter for in binary payloads") flag.Parse() log.SetLevel(log.Level(*logLvl)) OldMAC, _ = net.ParseMAC(*oldMAC) @@ -60,6 +61,7 @@ func main() { NewIP = net.ParseIP(*newIP).To4() Passthrough = *passthrough Wireshark = *wireshark + Hostname = *hostname UId = GenerateUId(*sockProxy) log.SetFormatter(&log.TextFormatter{ DisableTimestamp: true, @@ -175,17 +177,10 @@ func pipeForward(prefix string) { log.Error(prefix, "Error setting network layer for UDP checksum", err) } - if udpPacket.SrcPort == 137 && udpPacket.DstPort == 137 { - log.Info(prefix, "Trying to decode NBNS payload") - l := nbns.NewLayerNbns() - if err := l.DecodeFromBytes(udpPacket.Payload); err != nil { - log.Warning("Error decoding NBNS: ", err) - } - print(l) - } - - if udpPacket.SrcPort == 138 && udpPacket.DstPort == 138 { - log.Info(prefix, "NBDS payload, not implemented (yet)") + if (udpPacket.SrcPort == 137 && udpPacket.DstPort == 137) || // NBNS + (udpPacket.SrcPort == 138 && udpPacket.DstPort == 138) { // NBDS + log.Info(prefix, "Filtering NBNS/NBDS payload") + filterPayload(prefix, &udpPacket.Payload) } } } @@ -227,6 +222,33 @@ func pipeForward(prefix string) { } } +func filterPayload(prefix string, payload *[]byte) { + // Populate slices with values to on vm and network side + vmVals := [][]byte{OldMAC} + netVals := [][]byte{NewMAC} + if OldIP != nil && !OldIP.Equal(NewIP) { + vmVals = append(vmVals, OldIP) + netVals = append(netVals, NewIP) + } + if Hostname != "" { + vmVals = append(vmVals, []byte(Hostname), DnsQueryEncode(Hostname)) + netVals = append(netVals, []byte(UId), DnsQueryEncode(UId)) + } + + // Choose for which slice to search for and which to replace it with + searchVals := vmVals + replaceVals := netVals + if prefix == cmd.In { + searchVals = netVals + replaceVals = vmVals + } + + // Replace all occurencies of each slice value + for i, search := range searchVals { + *payload = bytes.Replace(*payload, search, replaceVals[i], -1) + } +} + // filterIP checks whether an IP target selected from src and dst equals a given value. If yes, it is changed func filterIP(prefix string, dst interface{}, src interface{}, context gopacket.LayerType) { var target interface{} diff --git a/proxy/util/util.go b/proxy/util/util.go index b4e231a..bfd63f0 100644 --- a/proxy/util/util.go +++ b/proxy/util/util.go @@ -3,6 +3,7 @@ package util import ( "crypto/rand" "encoding/base32" + "encoding/binary" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/pcapgo" @@ -31,9 +32,9 @@ func WritePcap(file string, data []byte) { defer r.Flush() ci := gopacket.CaptureInfo{ - Timestamp: time.Now(), - CaptureLength: len(data), - Length: len(data), + Timestamp: time.Now(), + CaptureLength: len(data), + Length: len(data), } err = r.WritePacket(ci, data) @@ -50,7 +51,7 @@ func WritePIDFile(filename string) { if filename == "" { return } - if err := ioutil.WriteFile(filename, []byte(strconv.Itoa(os.Getpid()) + "\n"), 0644); err != nil { + if err := ioutil.WriteFile(filename, []byte(strconv.Itoa(os.Getpid())+"\n"), 0644); err != nil { log.Errorf("Error writing PID file %s", filename) } } @@ -78,7 +79,7 @@ func GenerateXID() []byte { if _, err := rand.Read(buf); err != nil { log.Error("Error generating random xid") return []byte{36, 23, 250, 224} // chosen by fair dice roll - // guaranteed to be random --> https://xkcd.com/221/ + // guaranteed to be random --> https://xkcd.com/221/ } return buf } @@ -123,4 +124,75 @@ func (c If) IP(a, b net.IP) net.IP { return a } return b -} \ No newline at end of file +} + +// Source: https://github.com/aler9/landiscover/blob/main/utils.go +// partpart +func DnsQueryDecode(data []byte, start int) (string, int) { + var read []byte + toread := uint8(0) + pos := start + + for ; true; pos++ { + if pos >= len(data) { // decoding terminated before null character + return "", -1 + } + if data[pos] == 0x00 { + if toread > 0 { // decoding terminated before part parsing + return "", -1 + } + break // query correctly decoded + } + + if toread == 0 { // we need a size or pointer + if len(read) > 0 { // add separator + read = append(read, '.') + } + + if (data[pos] & 0xC0) == 0xC0 { // pointer + ptr := int(binary.BigEndian.Uint16(data[pos:pos+2]) & 0x3FFF) + pos++ // skip next byte + + substr, subread := DnsQueryDecode(data, ptr) + if subread <= 0 { + return "", -1 + } + read = append(read, []byte(substr)...) + break // query correctly decoded + } else { // size + toread = data[pos] + } + } else { // byte inside part + read = append(read, data[pos]) + toread-- + } + } + return string(read), pos + 1 - start +} + +func DnsQueryEncode(in string) []byte { + tmp := strings.Split(in, ".") + + l := 0 + for _, part := range tmp { + bpart := []byte(part) + l++ + l += len(bpart) + } + l++ + + ret := make([]byte, l) + i := 0 + + for _, part := range tmp { + bpart := []byte(part) + ret[i] = uint8(len(bpart)) + i++ + copy(ret[i:], bpart) + i += len(bpart) + } + + ret[i] = uint8(0) + + return ret +}