├── .gitignore ├── LICENSE ├── README.md ├── macouflage.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | macouflage 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Subgraph 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Macouflage 2 | Macouflage is a MAC address anonymization tool similar in functionality to 3 | [GNU Mac Changer](http://directory.fsf.org/wiki/GNU_MAC_Changer). The main 4 | difference is that Macouflage supports additional modes of operation such as 5 | generating spoofed MAC addresses from a list of popular network devices. 6 | 7 | # Data sources 8 | 9 | Macouflage ships with a JSON database derived from IEEE's OUI 10 | (Organizationally Unique Identifier)/MA-L (MAC Address Block Large) [registry](http://standards.ieee.org/develop/regauth/oui/oui.txt). 11 | 12 | The popular mode currently uses the data gathered by Etienne Perot's 13 | [macchiato](https://github.com/EtiennePerot/macchiato) project. 14 | 15 | The data is merged from these data sources by the supplementary [ouiner](https://github.com/mckinney-subgraph/ouiner) 16 | project. 17 | 18 | # Usage 19 | 20 | ``` 21 | $ macouflage 22 | NAME: 23 | macouflage - macouflage is a MAC address anonymization tool 24 | USAGE: 25 | macouflage -i/--interface [-b/--bia] command [command options] [arguments...] 26 | VERSION: 27 | 0.1 28 | AUTHOR(S): 29 | David McKinney 30 | COMMANDS: 31 | show Print the MAC address and exit 32 | ending Don't change the vendor bytes (generate last three bytes: XX:XX:XX:??:??:??) 33 | another Set random vendor MAC of the same kind 34 | any Set random vendor MAC of any kind 35 | permanent Reset to original, permanent hardware MAC 36 | random Set fully random MAC 37 | popular Set a MAC from the popular vendors list 38 | list (popular) Print known vendors 39 | search Search vendor names 40 | mac Set the MAC XX:XX:XX:XX:XX:XX 41 | help, h Shows a list of commands or help for one command 42 | 43 | GLOBAL OPTIONS: 44 | -i, --interface Target device (required) 45 | -b, --bia Pretend to be a burned-in-address 46 | --help, -h show help 47 | --version, -v print the version 48 | ``` 49 | 50 | # Examples 51 | 52 | ## List vendors 53 | ``` 54 | $ macouflage list 55 | # VendorPrefix Vendor 56 | 1 00:00:00 XEROX CORPORATION 57 | 2 00:00:01 XEROX CORPORATION 58 | 3 00:00:02 XEROX CORPORATION 59 | 4 00:00:03 XEROX CORPORATION 60 | etc. 61 | ``` 62 | 63 | ## List popular vendors 64 | ``` 65 | $ macouflage list popular 66 | # VendorPrefix Vendor 67 | 1 00:00:48 SEIKO EPSON CORPORATION 68 | 2 00:01:29 DFI Inc. 69 | 3 00:03:0D Uniwill Computer Corp. 70 | 4 00:08:54 Netronix, Inc. 71 | etc. 72 | ``` 73 | 74 | ## Popular mode 75 | First take down the interface: 76 | ``` 77 | $ sudo ip link set eth0 down 78 | ``` 79 | Then change the MAC: 80 | ``` 81 | $ sudo macouflage -i eth0 popular -- INSERT -- 82 | Current MAC: (Some MAC) (Some vendor)) 83 | Permanent MAC: (Some MAC) (Some vendor) 84 | New MAC: bc:5f:f4:79:32:a6 (ASRock Incorporation) 85 | ``` 86 | 87 | 88 | -------------------------------------------------------------------------------- /macouflage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | lmf "github.com/subgraph/libmacouflage" 6 | "net" 7 | "strings" 8 | ) 9 | 10 | func getCurrentMacInfo(name string) (result string, err error) { 11 | currentMacInfo, err := getMacInfo(name, "CurrentMAC") 12 | if err != nil { 13 | return 14 | } 15 | permanentMac, err := lmf.GetPermanentMac(name) 16 | if err != nil { 17 | fmt.Println(err) 18 | } 19 | permanentMacVendor, err := lmf.FindVendorByMac(permanentMac.String()) 20 | if err != nil { 21 | if strings.HasPrefix(err.Error(), 22 | "No vendor found in OuiDb for vendor prefix") { 23 | permanentMacVendor.Vendor = "Unknown" 24 | } else { 25 | return 26 | } 27 | } 28 | result = fmt.Sprintf("%sPermanent MAC:\t%s (%s)", 29 | currentMacInfo, 30 | permanentMac, permanentMacVendor.Vendor) 31 | err = nil 32 | return 33 | } 34 | 35 | func getMacInfo(name string, macType string) (result string, err error) { 36 | newMac, err := lmf.GetCurrentMac(name) 37 | if err != nil { 38 | return 39 | } 40 | newMacVendor, err := lmf.FindVendorByMac(newMac.String()) 41 | if err != nil { 42 | if err == err.(lmf.NoVendorError) { 43 | newMacVendor.Vendor = "Unknown" 44 | err = nil 45 | } else { 46 | return 47 | } 48 | } 49 | result = fmt.Sprintf("%s:\t%s (%s)\n", 50 | macType, newMac, newMacVendor.Vendor) 51 | return 52 | } 53 | 54 | func listVendors(keyword string, isPopular bool) (results string, err error) { 55 | var ouis []lmf.Oui 56 | var vendors []string 57 | 58 | if isPopular { 59 | ouis, err = lmf.FindAllPopularOuis() 60 | if err != nil { 61 | return 62 | } 63 | } else { 64 | ouis, err = lmf.FindVendorsByKeyword(keyword) 65 | if err != nil { 66 | return 67 | } 68 | } 69 | if len(ouis) == 0 { 70 | results = fmt.Sprintf("No vendors found in search.") 71 | return 72 | } else { 73 | vendors = append(vendors, fmt.Sprintf("#\tVendorPrefix\tVendor")) 74 | for i, result := range ouis { 75 | vendors = append(vendors, fmt.Sprintf("%d\t%s\t%s", i+1, 76 | result.VendorPrefix, result.Vendor)) 77 | } 78 | results = strings.Join(vendors, "\n") 79 | } 80 | return 81 | } 82 | 83 | func spoofMacEnding(name string) (err error) { 84 | currentMacInfo, err := getCurrentMacInfo(name) 85 | if err != nil { 86 | return 87 | } 88 | fmt.Println(currentMacInfo) 89 | changed, err := lmf.SpoofMacSameVendor(name, true) 90 | if err != nil { 91 | return 92 | } 93 | if changed { 94 | newMac, err2 := getMacInfo(name, "New MAC") 95 | if err2 != nil { 96 | err = err2 97 | return 98 | } 99 | fmt.Printf(newMac) 100 | } 101 | return 102 | } 103 | 104 | func spoofMacAnother(name string) (err error) { 105 | currentMacInfo, err := getCurrentMacInfo(name) 106 | if err != nil { 107 | return 108 | } 109 | fmt.Println(currentMacInfo) 110 | changed, err := lmf.SpoofMacSameDeviceType(name) 111 | if err != nil { 112 | return 113 | } 114 | if changed { 115 | newMac, err2 := getMacInfo(name, "New MAC") 116 | if err2 != nil { 117 | err = err2 118 | return 119 | } 120 | fmt.Printf(newMac) 121 | } 122 | return 123 | } 124 | 125 | func spoofMacAny(name string) (err error) { 126 | currentMacInfo, err := getCurrentMacInfo(name) 127 | if err != nil { 128 | return 129 | } 130 | fmt.Println(currentMacInfo) 131 | changed, err := lmf.SpoofMacAnyDeviceType(name) 132 | if err != nil { 133 | return 134 | } 135 | if changed { 136 | newMac, err2 := getMacInfo(name, "New MAC") 137 | if err2 != nil { 138 | err = err2 139 | return 140 | } 141 | fmt.Printf(newMac) 142 | } 143 | return 144 | } 145 | 146 | func revertMac(name string) (err error) { 147 | currentMacInfo, err := getCurrentMacInfo(name) 148 | if err != nil { 149 | return 150 | } 151 | fmt.Println(currentMacInfo) 152 | permMac, err := lmf.GetPermanentMac(name) 153 | if err != nil { 154 | return 155 | } 156 | err = lmf.RevertMac(name) 157 | if err != nil { 158 | return 159 | } 160 | newMac, err := lmf.GetCurrentMac(name) 161 | if err != nil { 162 | return 163 | } 164 | if lmf.CompareMacs(permMac, newMac) { 165 | newMac, err2 := getMacInfo(name, "New MAC") 166 | if err2 != nil { 167 | err = err2 168 | return 169 | } 170 | fmt.Printf(newMac) 171 | } 172 | return 173 | } 174 | 175 | func spoofMacRandom(name string, bia bool) (err error) { 176 | currentMacInfo, err := getCurrentMacInfo(name) 177 | if err != nil { 178 | return 179 | } 180 | fmt.Println(currentMacInfo) 181 | changed, err := lmf.SpoofMacRandom(name, bia) 182 | if err != nil { 183 | return 184 | } 185 | if changed { 186 | newMac, err2 := getMacInfo(name, "New MAC") 187 | if err2 != nil { 188 | err = err2 189 | return 190 | } 191 | fmt.Printf(newMac) 192 | } 193 | return 194 | } 195 | 196 | func spoofMacPopular(name string) (err error) { 197 | currentMacInfo, err := getCurrentMacInfo(name) 198 | if err != nil { 199 | return 200 | } 201 | fmt.Println(currentMacInfo) 202 | changed, err := lmf.SpoofMacPopular(name) 203 | if err != nil { 204 | return 205 | } 206 | if changed { 207 | newMac, err2 := getMacInfo(name, "New MAC") 208 | if err2 != nil { 209 | err = err2 210 | return 211 | } 212 | fmt.Printf(newMac) 213 | } 214 | return 215 | } 216 | 217 | func spoofMac(name string, mac string) (err error) { 218 | currentMacInfo, err := getCurrentMacInfo(name) 219 | if err != nil { 220 | return 221 | } 222 | fmt.Println(currentMacInfo) 223 | pMac, err := net.ParseMAC(mac) 224 | if err != nil { 225 | return 226 | } 227 | err = lmf.SetMac(name, mac) 228 | if err != nil { 229 | return 230 | } 231 | newMac, err := lmf.GetCurrentMac(name) 232 | if err != nil { 233 | return 234 | } 235 | changed := lmf.CompareMacs(pMac, newMac) 236 | if changed { 237 | newMacInfo, err2 := getMacInfo(name, "New MAC") 238 | if err2 != nil { 239 | err = err2 240 | return 241 | } 242 | fmt.Printf(newMacInfo) 243 | } 244 | return 245 | } 246 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "fmt" 6 | "log" 7 | "github.com/codegangsta/cli" 8 | ) 9 | var AppHelpTemplate = `NAME: 10 | {{.Name}} - {{.Usage}} 11 | USAGE: 12 | {{.Name}} {{if .Flags}}-i/--interface [-b/--bia] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...] 13 | VERSION: 14 | {{.Version}}{{if len .Authors}} 15 | AUTHOR(S): 16 | {{range .Authors}}{{ . }}{{end}}{{end}} 17 | COMMANDS: 18 | {{range .Commands}}{{join .Names ", "}}{{range .Subcommands}}{{ " (" }}{{ join .Names "|"}}{{ ")" }}{{end}}{{ "\t\t" }}{{.Usage}} 19 | {{end}}{{if .Flags}} 20 | GLOBAL OPTIONS: 21 | {{range .Flags}}{{.}} 22 | {{end}}{{end}} 23 | ` 24 | 25 | func main() { 26 | cli.AppHelpTemplate = AppHelpTemplate 27 | app := cli.NewApp() 28 | app.Name = "macouflage" 29 | app.Usage ="macouflage is a MAC address anonymization tool" 30 | app.Version = "0.1" 31 | app.Author = "David McKinney" 32 | app.Email = "mckinney@subgraph.com" 33 | app.Flags = []cli.Flag { 34 | cli.StringFlag{ 35 | Name: "i, interface", 36 | Usage: "Target device (required)", 37 | }, 38 | cli.BoolFlag{ 39 | Name: "b, bia", 40 | Usage: "Pretend to be a burned-in-address", 41 | }, 42 | } 43 | app.Commands = []cli.Command { 44 | { 45 | Name: "show", 46 | Usage: "Print the MAC address and exit", 47 | Action: show, 48 | }, 49 | { 50 | Name: "ending", 51 | Usage: "Don't change the vendor bytes (generate last three bytes: XX:XX:XX:??:??:??)", 52 | Action: ending, 53 | }, 54 | { 55 | Name: "another", 56 | Usage: "Set random vendor MAC of the same kind", 57 | Action: another, 58 | }, 59 | { 60 | Name: "any", 61 | Usage: "Set random vendor MAC of any kind", 62 | Action: any, 63 | }, 64 | { 65 | Name: "permanent", 66 | Usage: "Reset to original, permanent hardware MAC", 67 | Action: permanent, 68 | }, 69 | { 70 | Name: "random", 71 | Usage: "Set fully random MAC", 72 | Action: random, 73 | }, 74 | { 75 | Name: "popular", 76 | Usage: "Set a MAC from the popular vendors list", 77 | Action: popular, 78 | }, 79 | { 80 | Name: "list", 81 | Usage: "Print known vendors", 82 | Action: list, 83 | Subcommands: []cli.Command{ 84 | { 85 | Name: "popular", 86 | Usage: "Print known popular vendors", 87 | Action: listPopular, 88 | }, 89 | }, 90 | }, 91 | { 92 | Name: "search", 93 | Usage: "Search vendor names", 94 | Action: search, 95 | }, 96 | { 97 | Name: "mac", 98 | Usage: "Set the MAC XX:XX:XX:XX:XX:XX", 99 | Action: mac, 100 | }, 101 | } 102 | app.Run(os.Args) 103 | } 104 | 105 | func show(c *cli.Context) { 106 | iface := c.GlobalString("i") 107 | if iface != "" { 108 | result, err := getCurrentMacInfo(iface) 109 | if err != nil { 110 | log.Fatal(err) 111 | } 112 | fmt.Println(result) 113 | } else { 114 | log.Fatal("No target device provided via -i, --interface argument") 115 | } 116 | } 117 | 118 | func list(c *cli.Context) { 119 | results, err := listVendors("", false) 120 | if err != nil { 121 | log.Fatal(err) 122 | } 123 | fmt.Println(results) 124 | } 125 | 126 | func listPopular(c *cli.Context) { 127 | results, err := listVendors("", true) 128 | if err != nil { 129 | log.Fatal(err) 130 | } 131 | fmt.Println(results) 132 | } 133 | 134 | func search(c *cli.Context) { 135 | results, err := listVendors(c.Args().First(), false) 136 | if err != nil { 137 | log.Fatal(err) 138 | } 139 | fmt.Println(results) 140 | } 141 | 142 | func ending(c *cli.Context) { 143 | iface := c.GlobalString("i") 144 | if iface != "" { 145 | err := spoofMacEnding(iface) 146 | if err != nil { 147 | log.Fatal(err) 148 | } 149 | } else { 150 | log.Fatal("No target device provided via -i, --interface argument") 151 | } 152 | } 153 | 154 | func another(c *cli.Context) { 155 | iface := c.GlobalString("i") 156 | if iface != "" { 157 | err := spoofMacAnother(iface) 158 | if err != nil { 159 | log.Fatal(err) 160 | } 161 | } else { 162 | log.Fatal("No target device provided via -i, --interface argument") 163 | } 164 | } 165 | 166 | func any(c *cli.Context) { 167 | iface := c.GlobalString("i") 168 | if iface != "" { 169 | err := spoofMacAny(iface) 170 | if err != nil { 171 | log.Fatal(err) 172 | } 173 | } else { 174 | log.Fatal("No target device provided via -i, --interface argument") 175 | } 176 | } 177 | 178 | func permanent(c *cli.Context) { 179 | iface := c.GlobalString("i") 180 | if iface != "" { 181 | err := revertMac(iface) 182 | if err != nil { 183 | log.Fatal(err) 184 | } 185 | } else { 186 | log.Fatal("No target device provided via -i, --interface argument") 187 | } 188 | } 189 | 190 | func random(c *cli.Context) { 191 | iface := c.GlobalString("i") 192 | if iface != "" { 193 | err := spoofMacRandom(iface, c.GlobalBool("b")) 194 | if err != nil { 195 | log.Fatal(err) 196 | } 197 | } else { 198 | log.Fatal("No target device provided via -i, --interface argument") 199 | } 200 | } 201 | 202 | func popular(c *cli.Context) { 203 | iface := c.GlobalString("i") 204 | if iface != "" { 205 | err := spoofMacPopular(iface) 206 | if err != nil { 207 | log.Fatal(err) 208 | } 209 | } else { 210 | log.Fatal("No target device provided via -i, --interface argument") 211 | } 212 | } 213 | 214 | func mac(c *cli.Context) { 215 | if c.Args().First() == "" { 216 | log.Fatal("No MAC address argument specified") 217 | } 218 | iface := c.GlobalString("i") 219 | if iface != "" && c.Args().First() != "" { 220 | err := spoofMac(iface, c.Args().First()) 221 | if err != nil { 222 | log.Fatal(err) 223 | } 224 | } else { 225 | log.Fatal("No target device provided via -i, --interface argument") 226 | } 227 | } 228 | --------------------------------------------------------------------------------