├── .gitignore ├── go.mod ├── udpspoof ├── cli_test.go ├── main.go ├── udp.go └── cli.go ├── go.sum ├── udpreceiver └── main.go ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .glide/ 2 | .idea/ 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dimalinux/spoofsourceip 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/google/gopacket v1.1.17 7 | github.com/spf13/pflag v1.0.3 8 | ) 9 | -------------------------------------------------------------------------------- /udpspoof/cli_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFixWindowsIPConfigMAC(t *testing.T) { 10 | in := "AB-CD-EF-01-02-03" 11 | out := standardizeMACFormat(in) 12 | expectedOut := "AB:CD:EF:01:02:03" 13 | assert.Equal(t, expectedOut, out) 14 | } 15 | 16 | func TestFixMacosArpMAC(t *testing.T) { 17 | in := "1:2:FF:4:05:6" 18 | out := standardizeMACFormat(in) 19 | expectedOut := "01:02:FF:04:05:06" 20 | assert.Equal(t, expectedOut, out) 21 | } 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY= 2 | github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= 3 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 4 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 5 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 6 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 7 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 8 | golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 9 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 10 | -------------------------------------------------------------------------------- /udpspoof/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | 7 | "github.com/google/gopacket/pcap" 8 | ) 9 | 10 | // addrAsString returns a string with the format "ipv4:port" or "[ipv6]:port". 11 | func addrAsString(ip net.IP, port uint16) string { 12 | return (&net.UDPAddr{IP: ip, Port: int(port)}).String() 13 | } 14 | 15 | func main() { 16 | cliOps := parseCommandLineArgs() 17 | 18 | log.Printf("Source IP / MAC: %s / %v", addrAsString(cliOps.sourceIP, cliOps.sourcePort), cliOps.sourceMac) 19 | log.Printf("Dest IP / MAC: %s / %v", addrAsString(cliOps.destIP, cliOps.destPort), cliOps.destMac) 20 | 21 | handle, err := pcap.OpenLive(cliOps.networkIface.Name, 1024, false, pcap.BlockForever) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | defer handle.Close() 26 | 27 | frameBytes, err := createSerializedUDPFrame(cliOps.udpFrameOptions) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | if err := handle.WritePacketData(frameBytes); err != nil { 33 | log.Fatal(err) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /udpreceiver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "log" 6 | "net" 7 | 8 | "github.com/spf13/pflag" 9 | ) 10 | 11 | const maxPayload = 1040 12 | 13 | var bindAddress = ":8888" // Default listen on all IPv4 and IPv6 addresses 14 | 15 | func init() { 16 | pflag.StringVar(&bindAddress, "listen", bindAddress, "Local IP:port to listen for UDP packets on") 17 | pflag.Parse() 18 | } 19 | 20 | func main() { 21 | udpAddr, err := net.ResolveUDPAddr("udp", bindAddress) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | 26 | conn, err := net.ListenUDP("udp", udpAddr) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | defer conn.Close() 31 | 32 | log.Printf("Listening on %v", conn.LocalAddr()) 33 | 34 | for { 35 | payloadData := make([]byte, maxPayload) 36 | sz, addr, err := conn.ReadFromUDP(payloadData) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | // This is the best we can do without using more complicated methods. The downside is a 41 | // false positive as truncated when there is an exact fit at the maximum configured size. 42 | truncated := sz == maxPayload 43 | log.Printf("Datagram received from=%v truncated=%t payloadHex=%s", 44 | addr, truncated, hex.EncodeToString(payloadData[0:sz])) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /udpspoof/udp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/google/gopacket" 7 | "github.com/google/gopacket/layers" 8 | ) 9 | 10 | type udpFrameOptions struct { 11 | sourceIP, destIP net.IP 12 | sourcePort, destPort uint16 13 | sourceMac, destMac net.HardwareAddr 14 | isIPv6 bool 15 | payloadBytes []byte 16 | } 17 | 18 | type serializableNetworkLayer interface { 19 | gopacket.NetworkLayer 20 | SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error 21 | } 22 | 23 | // createSerializedUDPFrame creates an Ethernet frame encapsulating our UDP 24 | // packet for injection to the local network 25 | func createSerializedUDPFrame(opts udpFrameOptions) ([]byte, error) { 26 | 27 | buf := gopacket.NewSerializeBuffer() 28 | serializeOpts := gopacket.SerializeOptions{ 29 | FixLengths: true, 30 | ComputeChecksums: true, 31 | } 32 | ethernetType := layers.EthernetTypeIPv4 33 | if opts.isIPv6 { 34 | ethernetType = layers.EthernetTypeIPv6 35 | } 36 | eth := &layers.Ethernet{ 37 | SrcMAC: opts.sourceMac, 38 | DstMAC: opts.destMac, 39 | EthernetType: ethernetType, 40 | } 41 | var ip serializableNetworkLayer 42 | if !opts.isIPv6 { 43 | ip = &layers.IPv4{ 44 | SrcIP: opts.sourceIP, 45 | DstIP: opts.destIP, 46 | Protocol: layers.IPProtocolUDP, 47 | Version: 4, 48 | TTL: 32, 49 | } 50 | } else { 51 | ip = &layers.IPv6{ 52 | SrcIP: opts.sourceIP, 53 | DstIP: opts.destIP, 54 | NextHeader: layers.IPProtocolUDP, 55 | Version: 6, 56 | HopLimit: 32, 57 | } 58 | ip.LayerType() 59 | } 60 | 61 | udp := &layers.UDP{ 62 | SrcPort: layers.UDPPort(opts.sourcePort), 63 | DstPort: layers.UDPPort(opts.destPort), 64 | // we configured "Length" and "Checksum" to be set for us 65 | } 66 | udp.SetNetworkLayerForChecksum(ip) 67 | err := gopacket.SerializeLayers(buf, serializeOpts, eth, ip, udp, gopacket.Payload(opts.payloadBytes)) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | return buf.Bytes(), nil 73 | } 74 | -------------------------------------------------------------------------------- /udpspoof/cli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "log" 6 | "net" 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/spf13/pflag" 11 | ) 12 | 13 | type cliOptions struct { 14 | networkIface *net.Interface 15 | udpFrameOptions 16 | } 17 | 18 | const ( 19 | defaultInterfaceName = "en0" 20 | defaultPayloadHex = "DEADBEEF" 21 | defaultSourcePort = 9999 22 | defaultDestPort = 8888 // matches UDP receiver's default UDP listen port 23 | ) 24 | 25 | func parseCommandLineArgs() *cliOptions { 26 | 27 | cliOps := &cliOptions{} 28 | var sourceMACArg, sourceIPArg string 29 | var destMACArg, destIPArg string 30 | var interfaceArg, payloadHexArg string 31 | 32 | // 33 | // Setup flags and parse 34 | // 35 | pflag.CommandLine.SortFlags = false 36 | pflag.StringVarP(&interfaceArg, "interface", "i", defaultInterfaceName, 37 | "Network interface for packet injection") 38 | 39 | pflag.StringVar(&sourceMACArg, "source-mac", "", 40 | "MAC address in the Ethernet frame source (default is not spoofed)") 41 | pflag.StringVar(&sourceIPArg, "source-ip", "", 42 | "Sending address in the IP header (default is not spoofed)") 43 | pflag.Uint16Var(&cliOps.sourcePort, "source-port", defaultSourcePort, 44 | "UDP Source port") 45 | 46 | pflag.StringVar(&destMACArg, "dest-mac", "", 47 | "MAC address of the destination IP or gateway (required)") 48 | pflag.StringVar(&destIPArg, "dest-ip", "", 49 | "Destination IP address (required)") 50 | pflag.Uint16Var(&cliOps.destPort, "dest-port", defaultDestPort, 51 | "Destination port") 52 | 53 | pflag.StringVarP(&payloadHexArg, "payload", "p", defaultPayloadHex, 54 | "UDP payload specified in hex") 55 | pflag.Parse() 56 | 57 | // 58 | // Validate/process parsed args 59 | // 60 | var err error 61 | if interfaceArg == "" { 62 | log.Fatal("Network interface (--interface/-i) is required. Example: udpspoof -i eth0 ...") 63 | } 64 | if cliOps.networkIface, err = net.InterfaceByName(interfaceArg); err != nil { 65 | log.Fatalf("Invalid network interface name '%s': %s", interfaceArg, err) 66 | } 67 | if cliOps.networkIface.HardwareAddr == nil { 68 | log.Fatalf("Network interface '%s' has no MAC address", interfaceArg) 69 | } 70 | 71 | if destMACArg == "" { 72 | log.Fatal("Destination MAC (--dest-mac) is required") 73 | } 74 | destMACArg = standardizeMACFormat(destMACArg) 75 | if destMACArg == cliOps.networkIface.HardwareAddr.String() { 76 | log.Fatal("Destination MAC must be different from the injecting network interface's MAC") 77 | } 78 | if cliOps.destMac, err = net.ParseMAC(destMACArg); err != nil { 79 | log.Fatalf("Invalid destination MAC '%s': %s", destMACArg, err) 80 | } 81 | 82 | if sourceMACArg == "" { 83 | // default the source MAC to its legitimate value if unset 84 | cliOps.sourceMac = cliOps.networkIface.HardwareAddr 85 | } else { 86 | sourceMACArg = standardizeMACFormat(sourceMACArg) 87 | if cliOps.sourceMac, err = net.ParseMAC(sourceMACArg); err != nil { 88 | log.Fatalf("Invalid source MAC '%s': %s", sourceMACArg, err) 89 | } 90 | } 91 | 92 | if destIPArg == "" { 93 | log.Fatal("Destination IP (--dest-ip) is required") 94 | } 95 | if cliOps.destIP = net.ParseIP(destIPArg); cliOps.destIP == nil { 96 | log.Fatalf("Invalid dest IP '%s'", destIPArg) 97 | } 98 | cliOps.isIPv6 = strings.Contains(destIPArg, ":") 99 | 100 | if sourceIPArg == "" { 101 | // default the source IP to its legitimate value 102 | sourceIPArg = getInterfaceIP(cliOps.networkIface, cliOps.isIPv6) 103 | if sourceIPArg == "" { 104 | log.Fatalf("Unable to find compatible IPv6=%t source IP", cliOps.isIPv6) 105 | } 106 | } else { 107 | sourceIsIPv6 := strings.Contains(sourceIPArg, ":") 108 | if sourceIsIPv6 != cliOps.isIPv6 { 109 | log.Fatal("Source and destination IP must be of same type (IPv4 or IPv6") 110 | } 111 | } 112 | if cliOps.sourceIP = net.ParseIP(sourceIPArg); cliOps.sourceIP == nil { 113 | log.Fatalf("Invalid source IP '%s'", sourceIPArg) 114 | } 115 | 116 | // don't give an error if the user includes a "0x" prefix. 117 | payloadHexArg = strings.TrimPrefix(payloadHexArg, "0x") 118 | if cliOps.payloadBytes, err = hex.DecodeString(payloadHexArg); err != nil { 119 | log.Fatalf("Unable to decode hex payload: %s", err) 120 | } 121 | 122 | return cliOps 123 | } 124 | 125 | // standardizeMACFormat fixes dash-separated MAC addresses from Windows ipconfig 126 | // and macOS arp results which don't include leading zeros (:9: instead of :09:) 127 | func standardizeMACFormat(macAddr string) string { 128 | macAddr = strings.Replace(macAddr, "-", ":", -1) 129 | return regexp.MustCompile(`(\b)(\d)(\b)`).ReplaceAllString(macAddr, "${1}0${2}${3}") 130 | } 131 | 132 | // getInterfaceIP searches addresses of the passed-in interface for the first address 133 | // matching the requested type (IPv4 or IPv6) and returns it's value as a string. If 134 | // no compatible address is found, the empty string is returned. 135 | func getInterfaceIP(iface *net.Interface, useIPv6 bool) string { 136 | if addresses, err := iface.Addrs(); err == nil { 137 | for _, addr := range addresses { 138 | addrStr := addr.String() 139 | isIPv6 := strings.Contains(addrStr, ":") 140 | if isIPv6 == useIPv6 { 141 | return addrStr[0:strings.Index(addrStr, "/")] 142 | } 143 | } 144 | } 145 | return "" 146 | } 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spoof Source IP 2 | 3 | Tool to play with spoofing the source IP on a UDP datagram. It's written 4 | in Golang using the gopacket library and, hopefully, provides a useful 5 | example for creating packets using gopacket. 6 | 7 | ## Installation 8 | 9 | #### Dependencies 10 | To build the binaries from source, you'll need Golang installed and your 11 | GOPATH environment variable configured. I developed the code using 12 | Golang 1.9. 13 | 14 | gopacket, the library used from Google, is not written in pure 15 | Golang. It's uses `libpcap-dev` (package name on Linux systems) and 16 | provides bindings using cgo which requires a C compiler. I'm told the 17 | installation needed to build gopacket projects on Windows can be 18 | complicated, but it's fairly easy on Linux and macOS. 19 | 20 | #### Permissions 21 | `udpspoof` creates raw packets which requires special permissions. If 22 | you don't wish to use `sudo` or run `udpspoof` as root, you can 23 | assign the needed capability directly to the executable on Linux 24 | systems: 25 | ``` 26 | $ sudo setcap 'CAP_NET_RAW+eip' $GOPATH/bin/udpspoof 27 | ``` 28 | On macOS, if you installed Wireshark, your user was most likely already 29 | granted the needed permissions to run `udpspoof` without sudo. 30 | 31 | `udpreceiver` does not use gopacket and requires no special permissions 32 | to run on ports >= 1024. 33 | 34 | #### Download/install 35 | Install `udpreceiver` and `udpspoof`: 36 | ``` 37 | $ go get -u github.com/dimalinux/spoofsourceip/... 38 | ``` 39 | Only build/install the receiver (which does not depend on libpcap-dev): 40 | ``` 41 | go get -u github.com/dimalinux/spoofsourceip/udpreceiver 42 | ``` 43 | Only build/install the sender: 44 | ``` 45 | go get -u github.com/dimalinux/spoofsourceip/udpspoof 46 | ``` 47 | 48 | The binaries will be in $GOPATH/bin/ and the source in 49 | $GOPATH/src/github.com/dimalinux/spoofsourceip/. 50 | 51 | 52 | ## UDP Receiver (udpreceiver) 53 | You can test if your spoofed datagrams are getting through with 54 | `udpreceiver`. It does not depend on `libpcap-dev` and is easy to build 55 | and run anywhere. By default, udpreceiver listens for UDP datagrams on 56 | port 8888 for any local IP, but this can be modified with the flag 57 | `--listen IPv4:port`. For IPv6, use `--listen [IPv6]:port`. To listen 58 | on a specific port from any local IP address, use `--listen :port`. 59 | 60 | You can sanity check the receiver using nmap: 61 | ``` 62 | $ sudo nmap -sU -p 8888 --data-length 9 PUT_IP_ADDRESS_HERE 63 | ``` 64 | In response to the above command, udpreceiver will show output like this: 65 | ``` 66 | 2018/01/01 11:28:59 Datagram received fromIP=192.168.0.101 fromPort=44258 payload=0x1d0fda75758fb8e2ba 67 | ``` 68 | 69 | ## UDP Spoofer (udpspoof) 70 | 71 | `udpspoof` injects a UDP packet at the Ethernet frame level on the interface 72 | specified by the --interface (-i) flag. For `udpspoof` to work, the 73 | destination MAC needs to be different from the MAC address of the interface 74 | performing the injection. Either use 2 hosts, or a single host with 75 | multiple interfaces (e.g. a host with both wired and wireless interfaces). 76 | 77 | You cannot use the loopback interface for testing, as the loopback 78 | interface operates above the ethernet frame level and has no MAC address. 79 | 80 | The source MAC and source IP are both spoofable. 81 | 82 | View the full options list with the `--help` flag: 83 | ``` 84 | $ udpspoof --help 85 | Usage of udpspoof: 86 | -i, --interface string Network interface for packet injection (default "en0") 87 | --source-mac string MAC address in the Ethernet frame source (default is not spoofed) 88 | --source-ip string Sending address in the IP header (default is not spoofed) 89 | --source-port uint16 UDP Source port (default 9999) 90 | --dest-mac string MAC address of the destination IP or gateway (required) 91 | --dest-ip string Destination IP address (required) 92 | --dest-port uint16 Destination port (default 8888) 93 | -p, --payload string UDP payload specified in hex (default "DEADBEEF") 94 | ``` 95 | 96 | ### Full example: Receive DNS response on different host than request 97 | 98 | Start the UDP receiver on a 2nd host which will receive a DNS 99 | response from Google's DNS server. The default port of 8888 is fine 100 | for our needs: 101 | ``` 102 | host2$ udpreceiver 103 | 2018/01/01 11:35:42 Listening on [::]:8888 104 | ``` 105 | 106 | I've pre-captured the UDP payload of a DNS query looking up 107 | "www.google.com" below. (If you want a different query, it's easy 108 | to capture your own in Wireshark.) We'll send that payload to 8.8.8.8 109 | (Google's public DNS server), but set the source IP and port to match 110 | our second host above that is running `udpreceiver`. Since the 111 | destination IP (8.8.8.8) is not on the local network, we set the 112 | destination hardware MAC address to the local network MAC on our 113 | router-gateway. 114 | 115 | On macOS and Linux, you can use the `arp` command to retrieve the MAC 116 | address of your gateway (or any local peer by IP): 117 | ``` 118 | $ arp 45.63.20.1 119 | Address HWtype HWaddress Flags Mask Iface 120 | 45.63.20.1 ether fe:00:01:4a:6f:91 C ens3 121 | ``` 122 | 123 | Run the DNS query on host1 with the source IP/port set to the values for 124 | the UDP receiver on host2. The destination IP, 8.8.8.8 (Google's public 125 | DNS server), is not on your local network, so set the destination MAC 126 | address to your gateway router's LAN MAC address. 127 | 128 | ``` 129 | host1 $ udpspoof -i LOCAL_NETWORK_INTERFACE \ 130 | --source-ip HOST2_IP --source-port 8888 131 | --dest-ip 8.8.8.8 --dest-port 53 132 | --dest-mac YOUR_GATEWAY_ROUTERS_LAN_MAC_ADDR 133 | --payload 519f012000010000000000010377777706676f6f676c6503636f6d00000100010000291000000000000000 134 | ``` 135 | 136 | After issuing the DNS query from host1, host2, will get a response like this 137 | one. 138 | ``` 139 | 2018/01/01 11:38:24 Datagram received from=8.8.8.8:53 truncated=false payloadHex=519f818000010001000000010377777706676f6 140 | f676c6503636f6d0000010001c00c00010001000000330004acd902e40000290200000000000000 141 | ``` 142 | Note 1: Your response payload won't look the same as above since, among 143 | other reasons, the IP address you receive for www.google.com will be 144 | different. 145 | 146 | Note 2: In most cases, the above example will work even if host1 and 147 | host 2 are sitting behind a NAT firewall. The source IP will be 148 | replaced by the NAT router's IP before the query is sent to Google. 149 | When Google sends the DNS response, the NAT router will forward it to 150 | the forged source IP of the original DNS request. 151 | --------------------------------------------------------------------------------