diff --git a/proxy/layer-nbns/layer-nbns.go b/proxy/layer-nbns/layer-nbns.go new file mode 100644 index 0000000..a6b93de --- /dev/null +++ b/proxy/layer-nbns/layer-nbns.go @@ -0,0 +1,246 @@ +// 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 9c73c0e..5b753b7 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -12,6 +12,7 @@ import ( "net" "os" "proxy/cmd" + nbns "proxy/layer-nbns" . "proxy/util" "strconv" "time" @@ -81,7 +82,7 @@ func main() { c2, VmReader, VmWriter = cmd.Start(*sockProxy) go pipeForward(cmd.In) go pipeForward(cmd.Out) - if NewIP == nil { + if NewIP == nil && !Passthrough { sendDHCPRequest(dhcp4.Discover, net.IPv4zero) } if *sockMain != "-" { @@ -121,6 +122,17 @@ func pipeForward(prefix string) { WritePcap("/tmp/pkg_"+strconv.FormatInt(time.Now().Unix(), 10)+".pcap", frameBytes) } + // Forward original frame to other plug + if Passthrough { + if _, err := writer.Write(frameLength); err != nil { + log.Error("Error forwarding original packet length", err) + } + if _, err := writer.Write(frameBytes); err != nil { + log.Error("Error forwarding original packet data", err) + } + continue + } + // Convert frame to full stack packet packet := gopacket.NewPacket(frameBytes, layers.LayerTypeEthernet, gopacket.Default) @@ -136,10 +148,8 @@ func pipeForward(prefix string) { // Handle DHCPv4 packet (based on IPv4) if dhcpLayer := packet.Layer(layers.LayerTypeDHCPv4); dhcpLayer != nil { - if !Passthrough { - handleDHCP(dhcpLayer.LayerContents(), frame.DstMAC, frame.SrcMAC, prefix) - continue - } + handleDHCP(dhcpLayer.LayerContents(), frame.DstMAC, frame.SrcMAC, prefix) + continue } filterIP(prefix, &ipv4Packet.DstIP, &ipv4Packet.SrcIP, ipv4Packet.LayerType()) @@ -164,11 +174,24 @@ func pipeForward(prefix string) { if err := udpPacket.SetNetworkLayerForChecksum(ipv4Packet); err != nil { 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)") + } } } // Drop IPv6 packets - if ipv6layer := packet.Layer(layers.LayerTypeIPv6); ipv6layer != nil && !Passthrough { + if ipv6layer := packet.Layer(layers.LayerTypeIPv6); ipv6layer != nil { log.Info(prefix, "IPv6 packet dropped") continue } @@ -183,17 +206,6 @@ func pipeForward(prefix string) { log.Debug("End packet") - // Forward original frame to other plug - if Passthrough { - if _, err := writer.Write(frameLength); err != nil { - log.Error("Error forwarding original packet length", err) - } - if _, err := writer.Write(frameBytes); err != nil { - log.Error("Error forwarding original packet data", err) - } - continue - } - // Serialize packet back to binary buf := gopacket.NewSerializeBuffer() opts := gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true} diff --git a/test-env/envctl b/test-env/envctl index a42f012..1c94488 100755 --- a/test-env/envctl +++ b/test-env/envctl @@ -39,10 +39,10 @@ start) $qemu -m 512 -nic vde,mac='52:54:00:12:34:66',sock=$RUN/sw_proxy2.sock -hda alpine2.qcow2 -daemonize -vnc :2 -pidfile $alpine2 ;;& win1 | win | vms | all) - $qemu -vga cirrus -smp 1 -net nic,model=rtl8139 -net vde,sock=$RUN/sw_proxy1.sock -soundhw sb16 -m 128 -usb -usbdevice tablet -drive file=images/win98.raw,format=raw,index=0,media=disk -daemonize -vnc :1 -pidfile $win1 + $qemu -vga cirrus -smp 1 -net nic,model=rtl8139 -net vde,sock=$RUN/sw_proxy1.sock -soundhw sb16 -m 128 -usb -usbdevice tablet -drive file=images/win98.raw,format=raw,index=0,media=disk -cdrom images/Win98SE.iso -daemonize -vnc :1 -pidfile $win1 ;;& win2 | win | vms | all) - $qemu -vga cirrus -smp 1 -net nic,model=rtl8139 -net vde,sock=$RUN/sw_proxy2.sock -soundhw sb16 -m 128 -usb -usbdevice tablet -drive file=images/win98-2.raw,format=raw,index=0,media=disk -daemonize -vnc :2 -pidfile $win2 + $qemu -vga cirrus -smp 1 -net nic,model=rtl8139,macaddr='52:54:00:12:34:66' -net vde,sock=$RUN/sw_proxy2.sock -soundhw sb16 -m 128 -usb -usbdevice tablet -drive file=images/win98-2.raw,format=raw,index=0,media=disk -cdrom images/Win98SE.iso -daemonize -vnc :2 -pidfile $win2 ;;& kali | vms | all) $qemu -m 1024 -nic user -nic vde,mac='52:54:00:12:34:76',sock=$RUN/sw_proxy3.sock -hda kali.qcow2 -daemonize -vnc :3 -pidfile $kali