├── .gitattributes ├── .gitignore ├── README.md ├── misc └── local.macspoof.plist ├── scripts ├── spoof-mac └── spoof-mac.py ├── setup.py └── spoofmac ├── __init__.py ├── interface.py ├── util.py └── version.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | 24 | # Manage line endings 25 | *.py text 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | ENV/ 4 | *.egg-info/ 5 | dist/ 6 | build/ 7 | *.swp 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpoofMAC - Spoof your MAC address 2 | 3 | ### *NOTE: Consider using [spoof](https://github.com/feross/spoof), a node.js port of this package.* 4 | 5 | ### For OS X, Windows, and Linux (most flavors) 6 | 7 | I made this because changing your MAC address in Mac OS X is harder than it 8 | should be. The biggest annoyance is that the Wi-Fi card (Airport) needs to be 9 | *manually* disassociated from any connected networks in order for the change 10 | to be applied correctly. Doing this manually every time is tedious and lame. 11 | 12 | Instead of doing that, just run this Python script and change your MAC address 13 | in one command. *Now for Windows and Linux, too!* 14 | 15 | **Note for Windows 10:** While it may appear that this script does not work anymore, it does. The reason for this is that the change only appears in the Network Adapter properties in the Control Panel. Commands such as getmac or ipconfig will still show the original MAC address even though it has been changed. (**Note:** this actually appears to depend on the NIC (Network Interface Card). I tested it with my desktop and ipconfig showed the change, however it does not show the change on my laptop) 16 | 17 | To see this for yourself, follow the below steps: 18 | 19 | 1. Open Control Panel 20 | 2. Click 'Network and Internet' 21 | 3. Click 'Network and Sharing Center' 22 | 4. On the panel to the left, click 'Change adapter settings' 23 | 5. A new window will appear showing all of the network adapters. Right-click the one that is currently active/enabled, and click 'Properties' 24 | 6. Near the top, click the button that says 'Configure' 25 | 7. Another window will open. At the top, click the 'Advanced' tab. 26 | 8. In the list to the left, select either 'Network Address' or 'Locally Administered Address'. This will depend on your system. You should see your MAC address in the text box to the right. This MAC address should change when you run this script on Windows 10. If the text box is empty and 'Not Present' is selected, run the script once to generate a new MAC address and follow the above steps. The 'Value' checkbox should now be selected and there should be a new MAC address in the text box. 27 | 28 | ## Installation 29 | 30 | You can install from [PyPI](https://pypi.python.org/pypi/SpoofMAC/) using `pip` or `easy_install`: 31 | 32 | ``` 33 | pip install SpoofMAC 34 | easy_install SpoofMAC 35 | ``` 36 | 37 | or clone/download the repository and install with `setup.py`. Ex: 38 | 39 | ``` 40 | git clone git://github.com/feross/SpoofMAC.git 41 | cd SpoofMAC 42 | python setup.py install 43 | ``` 44 | 45 | If you're not using the system Python (because you use Homebrew, for example), make sure you add '/usr/local/share/python/' (or equivalent) to your path. 46 | 47 | Or, consider using **[spoof](https://github.com/feross/spoof)**, a node.js port of this package. 48 | 49 | ## Usage 50 | 51 | SpoofMAC installs a command-line script called `spoof-mac.py`. You can always 52 | see up-to-date usage instructions by typing `spoof-mac.py --help`. 53 | 54 | ### Examples 55 | 56 | Some short usage examples. 57 | 58 | #### List available devices: 59 | 60 | ```bash 61 | spoof-mac.py list 62 | - "Ethernet" on device "en0" with MAC address 70:56:51:BE:B3:00 63 | - "Wi-Fi" on device "en1" with MAC address 70:56:51:BE:B3:01 currently set to 70:56:51:BE:B3:02 64 | - "Bluetooth PAN" on device "en1" 65 | ``` 66 | 67 | #### List available devices, but only those on wifi: 68 | 69 | ```bash 70 | spoof-mac.py list --wifi 71 | - "Wi-Fi" on device "en0" with MAC address 70:56:51:BE:B3:6F 72 | ``` 73 | 74 | #### Randomize MAC address *(requires root)* 75 | 76 | You can use the hardware port name, such as: 77 | 78 | ```bash 79 | spoof-mac.py randomize wi-fi 80 | ``` 81 | 82 | or the device name, such as: 83 | 84 | ```bash 85 | spoof-mac.py randomize en0 86 | ``` 87 | 88 | #### Set device MAC address to something specific *(requires root)* 89 | 90 | ```bash 91 | spoof-mac.py set 00:00:00:00:00:00 en0 92 | ``` 93 | 94 | #### Reset device to its original MAC address *(requires root)* 95 | 96 | While not always possible (because the original physical MAC isn't 97 | available), you can try setting the MAC address of a device back 98 | to its burned-in address using `reset`: 99 | 100 | ```bash 101 | spoof-mac.py reset wi-fi 102 | ``` 103 | 104 | (older versions of OS X may call it "airport" instead of "wi-fi") 105 | 106 | Another option to reset your MAC address is to simply restart your computer. 107 | OS X doesn't store changes to your MAC address between restarts. If you want 108 | to make change your MAC address and have it persist between restarts, read 109 | the next section. 110 | 111 | 112 | ## Optional: Run automatically at startup 113 | 114 | OS X doesn't let you permanently change your MAC address. Every time you restart your computer, your address gets reset back to whatever it was before. Fortunately, SpoofMAC can easily be set to run at startup time so your computer will always have the MAC address you want. 115 | 116 | ### Startup Installation Instructions 117 | 118 | First, make sure SpoofMAC is [installed](#installation). Then, run the following commands in Terminal: 119 | 120 | ```bash 121 | # Download the startup file for launchd 122 | curl https://raw.githubusercontent.com/feross/SpoofMAC/master/misc/local.macspoof.plist > local.macspoof.plist 123 | 124 | # Customize location of `spoof-mac.py` to match your system 125 | cat local.macspoof.plist | sed "s|/usr/local/bin/spoof-mac.py|`which spoof-mac.py`|" | tee local.macspoof.plist 126 | 127 | # Copy file to the OS X launchd folder 128 | sudo cp local.macspoof.plist /Library/LaunchDaemons 129 | 130 | # Set file permissions 131 | cd /Library/LaunchDaemons 132 | sudo chown root:wheel local.macspoof.plist 133 | sudo chmod 0644 local.macspoof.plist 134 | ``` 135 | 136 | By default, the above will randomize your MAC address on computer startup. You can change the command that gets run at startup by editing the `local.macspoof.plist` file. 137 | 138 | ```bash 139 | sudo vim /Library/LaunchDaemons/local.macspoof.plist 140 | ``` 141 | 142 | ## Changelog 143 | 144 | - 2.1.1 - Use `ip` command when available, in more situations 145 | - 2.1.0 - Use `ip` command when available; `ifconfig` is deprecated on Arch Linux 146 | - 2.0.6 - Increase MAC address randomness 147 | - 2.0.5 - Allow 2nd character in MAC address to be a letter
 148 | - 2.0.4 - Warn when trying to use a multicast address 149 | - 2.0.3 - More Python 2.7 compatibility fixes 150 | - 2.0.2 - Python 2.7 compatibility fixes 151 | - **2.0.0 - Python 3 support** 152 | - 1.2.2 - Fix for Ubuntu 14.04 153 | - 1.2.1 - Fix line endings (dos2unix) 154 | - **1.2.0 - Add Windows and Linux support (thanks CJ!)** 155 | - 1.1.1 - Fix "ValueError: too many values to unpack" error 156 | - 1.1.0 - Fix regression: List command now shows current MAC address 157 | - **1.0.0 - Complete rewrite to conform to PEP8 (thanks Tyler!)** 158 | - **0.0.0 - Original version (by Feross)** 159 | 160 | ## Contributors 161 | 162 | - Feross Aboukhadijeh [http://feross.org] 163 | - Tyler Kennedy [http://www.tkte.ch] 164 | - CJ Barker [cjbarker@gmail.com] 165 | 166 | *Improvements welcome! (please add yourself to the list)* 167 | 168 | ## Ports 169 | 170 | - [spoof](https://github.com/feross/spoof) - node.js 171 | 172 | ## MIT License 173 | 174 | Copyright (c) 2011-2017 175 | 176 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 177 | 178 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 179 | 180 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 181 | -------------------------------------------------------------------------------- /misc/local.macspoof.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | MacSpoof 7 | 8 | ProgramArguments 9 | 10 | /usr/local/bin/spoof-mac.py 11 | randomize 12 | en0 13 | 14 | 15 | RunAtLoad 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /scripts/spoof-mac: -------------------------------------------------------------------------------- 1 | spoof-mac.py -------------------------------------------------------------------------------- /scripts/spoof-mac.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """SpoofMAC 4 | 5 | Usage: 6 | spoof-mac list [--wifi] 7 | spoof-mac randomize [--local] ... 8 | spoof-mac set ... 9 | spoof-mac reset ... 10 | spoof-mac normalize 11 | spoof-mac -h | --help 12 | spoof-mac --version 13 | 14 | Options: 15 | 16 | -h --help Shows this message. 17 | --version Show package version. 18 | --wifi Try to only show wireless interfaces. 19 | --local Set the locally administered flag on randomized MACs. 20 | """ 21 | import sys 22 | import os 23 | 24 | if sys.platform == 'win32': 25 | import ctypes 26 | 27 | from docopt import docopt 28 | 29 | from spoofmac.version import __version__ 30 | from spoofmac.util import random_mac_address, MAC_ADDRESS_R, normalize_mac_address 31 | 32 | from spoofmac.interface import ( 33 | wireless_port_names, 34 | find_interfaces, 35 | find_interface, 36 | set_interface_mac, 37 | get_os_spoofer 38 | ) 39 | 40 | # Return Codes 41 | SUCCESS = 0 42 | INVALID_ARGS = 1001 43 | UNSUPPORTED_PLATFORM = 1002 44 | INVALID_TARGET = 1003 45 | INVALID_MAC_ADDR = 1004 46 | NON_ROOT_USER = 1005 47 | 48 | 49 | def list_interfaces(args, spoofer): 50 | targets = [] 51 | 52 | # Should we only return prospective wireless interfaces? 53 | if args['--wifi']: 54 | targets += wireless_port_names 55 | 56 | for port, device, address, current_address in spoofer.find_interfaces(targets=targets): 57 | line = [] 58 | line.append('- "{port}"'.format(port=port)) 59 | line.append('on device "{device}"'.format(device=device)) 60 | if address: 61 | line.append('with MAC address {mac}'.format(mac=address)) 62 | if current_address and address != current_address: 63 | line.append('currently set to {mac}'.format(mac=current_address)) 64 | print(' '.join(line)) 65 | 66 | 67 | def main(args, root_or_admin): 68 | spoofer = None 69 | 70 | try: 71 | spoofer = get_os_spoofer() 72 | except NotImplementedError: 73 | return UNSUPPORTED_PLATFORM 74 | 75 | if args['list']: 76 | list_interfaces(args, spoofer) 77 | elif args['randomize'] or args['set'] or args['reset']: 78 | for target in args['']: 79 | # Fill out the details for `target`, which could be a Hardware 80 | # Port or a literal device. 81 | #print("Debuf:",target) 82 | result = find_interface(target) 83 | if result is None: 84 | print('- couldn\'t find the device for {target}'.format( 85 | target=target 86 | )) 87 | return INVALID_TARGET 88 | 89 | port, device, address, current_address = result 90 | if args['randomize']: 91 | target_mac = random_mac_address(args['--local']) 92 | elif args['set']: 93 | target_mac = args[''] 94 | if int(target_mac[1], 16) % 2: 95 | print('Warning: The address you supplied is a multicast address and thus can not be used as a host address.') 96 | elif args['reset']: 97 | if address is None: 98 | print('- {target} missing hardware MAC'.format( 99 | target=target 100 | )) 101 | continue 102 | target_mac = address 103 | 104 | if not MAC_ADDRESS_R.match(target_mac): 105 | print('- {mac} is not a valid MAC address'.format( 106 | mac=target_mac 107 | )) 108 | return INVALID_MAC_ADDR 109 | 110 | if not root_or_admin: 111 | if sys.platform == 'win32': 112 | print('Error: Must run this with administrative privileges to set MAC addresses') 113 | return NON_ROOT_USER 114 | else: 115 | print('Error: Must run this as root (or with sudo) to set MAC addresses') 116 | return NON_ROOT_USER 117 | 118 | set_interface_mac(device, target_mac, port) 119 | elif args['normalize']: 120 | print(normalize_mac_address(args[''])) 121 | 122 | else: 123 | print('Error: Invalid arguments - check help usage') 124 | return INVALID_ARGS 125 | 126 | del spoofer 127 | 128 | return SUCCESS 129 | 130 | 131 | if __name__ == '__main__': 132 | arguments = docopt(__doc__, version=__version__) 133 | try: 134 | root_or_admin = os.geteuid() == 0 135 | except AttributeError: 136 | root_or_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 137 | 138 | sys.exit(main(arguments, root_or_admin)) 139 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Easily spoof your MAC address in OS X, Windows & Linux. 5 | """ 6 | from setuptools import setup, find_packages 7 | 8 | 9 | def get_version(): 10 | """ 11 | Load and return the current package version. 12 | """ 13 | local_results = {} 14 | exec(compile(open('spoofmac/version.py').read(), 'spoofmac/version.py', 'exec'), {}, local_results) 15 | return local_results['__version__'] 16 | 17 | 18 | if __name__ == '__main__': 19 | setup( 20 | name='SpoofMAC', 21 | version=get_version(), 22 | description=__doc__, 23 | long_description=__doc__, 24 | author='Feross Aboukhadijeh', 25 | author_email='feross@feross.org', 26 | url='http://feross.org/spoofmac/', 27 | packages=find_packages(), 28 | include_package_data=True, 29 | install_requires=[ 30 | 'docopt' 31 | ], 32 | scripts=[ 33 | 'scripts/spoof-mac.py', 34 | 'scripts/spoof-mac' 35 | ], 36 | license='MIT' 37 | ) 38 | -------------------------------------------------------------------------------- /spoofmac/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from spoofmac.util import * 3 | from spoofmac.interface import * 4 | -------------------------------------------------------------------------------- /spoofmac/interface.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __all__ = ( 3 | 'find_interfaces', 4 | 'find_interface', 5 | 'set_interface_mac', 6 | 'wireless_port_names' 7 | ) 8 | 9 | import re 10 | import subprocess 11 | import sys 12 | import os.path 13 | 14 | if sys.platform == 'win32': 15 | import platform 16 | from spoofmac.util import normalise_mac_address_windows as normalise 17 | try: 18 | import winreg 19 | except ImportError: 20 | import _winreg as winreg 21 | 22 | from spoofmac.util import MAC_ADDRESS_R 23 | 24 | # The possible port names for wireless devices as returned by networksetup. 25 | wireless_port_names = ('wi-fi', 'airport') 26 | 27 | class OsSpoofer(object): 28 | """ 29 | Abstract class for OS level MAC spoofing. 30 | """ 31 | def find_interfaces(self, target): 32 | raise NotImplementedError("find_interfaces must be implemented") 33 | 34 | def find_interface(self, target): 35 | raise NotImplementedError("find_interface must be implemented") 36 | 37 | def get_interface_mac(self, device): 38 | raise NotImplementedError("get_interface_mac must be implemented") 39 | 40 | def set_interface_mac(self, device, mac, port=None): 41 | raise NotImplementedError("set_interface_mac must be implemented") 42 | 43 | 44 | class LinuxSpooferIP(OsSpoofer): 45 | """ 46 | Linux platform specfic implementation for MAC spoofing. 47 | """ 48 | def get_interface_mac(self, device): 49 | result = subprocess.check_output(["ip", "link", "show", device], stderr=subprocess.STDOUT, universal_newlines=True) 50 | m = re.search("(?<=\w\s)(.*)(?=\sbrd)", result) 51 | if not hasattr(m, "group") or m.group(0) == None: 52 | return None 53 | return m.group(0).strip() 54 | 55 | def find_interfaces(self, targets=None): 56 | """ 57 | Returns the list of interfaces found on this machine as reported 58 | by the `ip` command. 59 | """ 60 | targets = [t.lower() for t in targets] if targets else [] 61 | # Parse the output of `ip` which gives 62 | # us 3 fields used: 63 | # - the adapter description 64 | # - the adapter name/device associated with this, if any, 65 | # - the MAC address, if any 66 | 67 | output = subprocess.check_output(["ip", "address"], stderr=subprocess.STDOUT, universal_newlines=True) 68 | 69 | # search for specific adapter gobble through mac address 70 | details = re.findall("^[\d]+:(.*)", output, re.MULTILINE) 71 | more_details = re.findall("[\s]+link(.*)", output,re.MULTILINE) 72 | 73 | # extract out ip address results from STDOUT (don't show loopback) 74 | for i in range(1, len(details)): 75 | description = None 76 | address = None 77 | adapter_name = None 78 | 79 | s = details[i].split(":") 80 | if len(s) >= 2: 81 | adapter_name = s[0].split()[0] 82 | 83 | info = more_details[i].split(" ") 84 | description = info[0].strip()[1:] 85 | address = info[1].strip() 86 | 87 | current_address = self.get_interface_mac(adapter_name) 88 | 89 | if not targets: 90 | # Not trying to match anything in particular, 91 | # return everything. 92 | yield description, adapter_name, address, current_address 93 | continue 94 | 95 | for target in targets: 96 | if target in (adapter_name.lower(), adapter_name.lower()): 97 | yield description, adapter_name, address, current_address 98 | break 99 | 100 | def find_interface(self, target): 101 | """ 102 | Returns tuple of the first interface which matches `target`. 103 | adapter description, adapter name, mac address of target, current mac addr 104 | """ 105 | try: 106 | return next(self.find_interfaces(targets=[target])) 107 | except StopIteration: 108 | pass 109 | 110 | def set_interface_mac(self, device, mac, port=None): 111 | """ 112 | Set the device's mac address. Handles shutting down and starting back up interface. 113 | """ 114 | # turn off device 115 | cmd = "ip link set {} down".format(device) 116 | subprocess.call(cmd.split()) 117 | # set mac 118 | cmd = "ip link set {} address {}".format(device, mac) 119 | subprocess.call(cmd.split()) 120 | # turn on device 121 | cmd = "ip link set {} up".format(device) 122 | subprocess.call(cmd.split()) 123 | 124 | class LinuxSpoofer(OsSpoofer): 125 | """ 126 | Linux platform specfic implementation for MAC spoofing. 127 | """ 128 | def get_interface_mac(self, device): 129 | result = subprocess.check_output(["ifconfig", device], stderr=subprocess.STDOUT, universal_newlines=True) 130 | m = re.search("(?<=HWaddr\\s)(.*)", result) 131 | if not hasattr(m, "group") or m.group(0) == None: 132 | return None 133 | return m.group(0).strip() 134 | 135 | def find_interfaces(self, targets=None): 136 | """ 137 | Returns the list of interfaces found on this machine as reported 138 | by the `ifconfig` command. 139 | """ 140 | targets = [t.lower() for t in targets] if targets else [] 141 | # Parse the output of `ifconfig` which gives 142 | # us 3 fields used: 143 | # - the adapter description 144 | # - the adapter name/device associated with this, if any, 145 | # - the MAC address, if any 146 | 147 | output = subprocess.check_output(["ifconfig"], stderr=subprocess.STDOUT, universal_newlines=True) 148 | 149 | # search for specific adapter gobble through mac address 150 | details = re.findall("(.*?)HWaddr(.*)", output, re.MULTILINE) 151 | 152 | # extract out ifconfig results from STDOUT 153 | for i in range(0, len(details)): 154 | description = None 155 | address = None 156 | adapter_name = None 157 | 158 | s = details[i][0].split(":") 159 | if len(s) >= 2: 160 | adapter_name = s[0].split()[0] 161 | description = s[1].strip() 162 | 163 | address = details[i][1].strip() 164 | 165 | current_address = self.get_interface_mac(adapter_name) 166 | 167 | if not targets: 168 | # Not trying to match anything in particular, 169 | # return everything. 170 | yield description, adapter_name, address, current_address 171 | continue 172 | 173 | for target in targets: 174 | if target in (adapter_name.lower(), adapter_name.lower()): 175 | yield description, adapter_name, address, current_address 176 | break 177 | 178 | def find_interface(self, target): 179 | """ 180 | Returns tuple of the first interface which matches `target`. 181 | adapter description, adapter name, mac address of target, current mac addr 182 | """ 183 | try: 184 | return next(self.find_interfaces(targets=[target])) 185 | except StopIteration: 186 | pass 187 | 188 | def set_interface_mac(self, device, mac, port=None): 189 | """ 190 | Set the device's mac address. Handles shutting down and starting back up interface. 191 | """ 192 | # turn off device & set mac 193 | cmd = "ifconfig {} down hw ether {}".format(device, mac) 194 | subprocess.call(cmd.split()) 195 | # turn on device 196 | cmd = "ifconfig {} up".format(device) 197 | subprocess.call(cmd.split()) 198 | 199 | class WindowsSpoofer(OsSpoofer): 200 | """ 201 | Windows platform specfic implementation for MAC spoofing. 202 | """ 203 | WIN_REGISTRY_PATH = "SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}" 204 | 205 | def restart_adapter(self, device): 206 | """ 207 | Disables and then re-enables device interface 208 | """ 209 | if platform.release() == 'XP': 210 | description, adapter_name, address, current_address = find_interface(device) 211 | cmd = "devcon hwids =net" 212 | try: 213 | result = subprocess.check_output(cmd, stderr=subprocess.STDOUT) 214 | except FileNotFoundError: 215 | raise 216 | query = '('+description+'\r\n\s*.*:\r\n\s*)PCI\\\\(([A-Z]|[0-9]|_|&)*)' 217 | query = query.encode('ascii') 218 | match = re.search(query,result) 219 | cmd = 'devcon restart "PCI\\' + str(match.group(2).decode('ascii'))+ '"' 220 | subprocess.check_output(cmd, stderr=subprocess.STDOUT) 221 | 222 | else: 223 | cmd = "netsh interface set interface \"" + device + "\" disable" 224 | subprocess.check_output(cmd) 225 | cmd = "netsh interface set interface \"" + device + "\" enable" 226 | subprocess.check_output(cmd) 227 | 228 | def get_ipconfig_all(self): 229 | result = subprocess.check_output(["ipconfig", "/all"], stderr=subprocess.STDOUT) 230 | return result.decode('ascii') 231 | 232 | def get_interface_mac(self, device): 233 | output = self.get_ipconfig_all() 234 | 235 | device = device.lower().strip() 236 | 237 | # search for specific adapter gobble through mac address 238 | m = re.search("adapter "+device+":[\\n\\r]+(.*?)\\s*Physical Address[^\\d]+(\\s\\S+)", output, re.I | re.DOTALL) 239 | if not hasattr(m, "group") or m.group(0) == None: 240 | return None 241 | 242 | adapt_mac = m.group(0) 243 | 244 | # extract physical address then mac 245 | m = re.search("Physical Address[^\\d]+(\\s\\S+)", adapt_mac) 246 | phy_addr = m.group(0) 247 | m = re.search("(?<=:\\s)(.*)", phy_addr) 248 | if not hasattr(m, "group") or m.group(0) == None: 249 | return None 250 | 251 | mac = m.group(0) 252 | return mac 253 | 254 | def find_interfaces(self, targets=None): 255 | """ 256 | Returns the list of interfaces found on this machine as reported 257 | by the `ipconfig` command. 258 | """ 259 | targets = [t.lower() for t in targets] if targets else [] 260 | # Parse the output of `ipconfig /all` which gives 261 | # us 3 fields used: 262 | # - the adapter description 263 | # - the adapter name/device associated with this, if any, 264 | # - the MAC address, if any 265 | 266 | output = self.get_ipconfig_all() 267 | 268 | # search for specific adapter gobble through mac address 269 | details = re.findall("adapter (.*?):[\\n\\r]+(.*?)\\s*Physical Address[^\\d]+(\\s\\S+)", output, re.DOTALL) 270 | 271 | # extract out ipconfig results from STDOUT 272 | for i in range(0, len(details)): 273 | dns = None 274 | description = None 275 | address = None 276 | adapter_name = details[i][0].strip() 277 | 278 | # extract DNS suffix 279 | m = re.search("(?<=:\\s)(.*)", details[i][1]) 280 | if hasattr(m, "group") and m.group(0) != None: 281 | dns = m.group(0).strip() 282 | 283 | # extract description then strip out value 284 | m = re.search("Description[^\\d]+(\\s\\S+)+", details[i][1]) 285 | if hasattr(m, "group") and m.group(0) != None: 286 | descript_line = m.group(0) 287 | m = re.search("(?<=:\\s)(.*)", descript_line) 288 | if hasattr(m, "group") and m.group(0) != None: 289 | description = m.group(0).strip() 290 | 291 | address = details[i][2].strip() 292 | 293 | current_address = self.get_interface_mac(adapter_name) 294 | 295 | if not targets: 296 | # Not trying to match anything in particular, 297 | # return everything. 298 | yield description, adapter_name, address, current_address 299 | continue 300 | 301 | for target in targets: 302 | if target in (adapter_name.lower(), adapter_name.lower()): 303 | yield description, adapter_name, address, current_address 304 | break 305 | 306 | def find_interface(self, target): 307 | """ 308 | Returns tuple of the first interface which matches `target`. 309 | adapter description, adapter name, mac address of target, current mac addr 310 | """ 311 | try: 312 | return next(self.find_interfaces(targets=[target])) 313 | except StopIteration: 314 | pass 315 | 316 | def set_interface_mac(self, device, mac, port=None): 317 | description, adapter_name, address, current_address = self.find_interface(device) 318 | 319 | # Locate adapter's registry and update network address (mac) 320 | reg_hdl = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) 321 | key = winreg.OpenKey(reg_hdl, self.WIN_REGISTRY_PATH) 322 | info = winreg.QueryInfoKey(key) 323 | 324 | # Find adapter key based on sub keys 325 | adapter_key = None 326 | adapter_path = None 327 | 328 | 329 | for x in range(info[0]): 330 | subkey = winreg.EnumKey(key, x) 331 | path = self.WIN_REGISTRY_PATH + "\\" + subkey 332 | 333 | if subkey == 'Properties': 334 | break 335 | 336 | # Check for adapter match for appropriate interface 337 | new_key = winreg.OpenKey(reg_hdl, path) 338 | try: 339 | adapterDesc = winreg.QueryValueEx(new_key, "DriverDesc") 340 | if adapterDesc[0] == description: 341 | adapter_path = path 342 | break 343 | else: 344 | winreg.CloseKey(new_key) 345 | except (WindowsError) as err: 346 | if err.errno == 2: # register value not found, ok to ignore 347 | pass 348 | else: 349 | raise err 350 | 351 | if adapter_path is None: 352 | winreg.CloseKey(key) 353 | winreg.CloseKey(reg_hdl) 354 | return 355 | 356 | # Registry path found update mac addr 357 | adapter_key = winreg.OpenKey(reg_hdl, adapter_path, 0, winreg.KEY_WRITE) 358 | winreg.SetValueEx(adapter_key, "NetworkAddress", 0, winreg.REG_SZ, normalise(mac)) 359 | winreg.CloseKey(adapter_key) 360 | winreg.CloseKey(key) 361 | winreg.CloseKey(reg_hdl) 362 | 363 | # Adapter must be restarted in order for change to take affect 364 | self.restart_adapter(adapter_name) 365 | 366 | 367 | class MacSpoofer(OsSpoofer): 368 | """ 369 | OS X platform specfic implementation for MAC spoofing. 370 | """ 371 | 372 | # Path to Airport binary. This works on 10.7 and 10.8, but might be different 373 | # on older OS X versions. 374 | PATH_TO_AIRPORT = ( 375 | '/System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport' 376 | ) 377 | 378 | def find_interfaces(self, targets=None): 379 | """ 380 | Returns the list of interfaces found on this machine as reported 381 | by the `networksetup` command. 382 | """ 383 | targets = [t.lower() for t in targets] if targets else [] 384 | # Parse the output of `networksetup -listallhardwareports` which gives 385 | # us 3 fields per port: 386 | # - the port name, 387 | # - the device associated with this port, if any, 388 | # - The MAC address, if any, otherwise 'N/A' 389 | details = re.findall( 390 | r'^(?:Hardware Port|Device|Ethernet Address): (.+)$', 391 | subprocess.check_output(( 392 | 'networksetup', 393 | '-listallhardwareports' 394 | ), universal_newlines=True), re.MULTILINE 395 | ) 396 | # Split the results into chunks of 3 (for our three fields) and yield 397 | # those that match `targets`. 398 | for i in range(0, len(details), 3): 399 | port, device, address = details[i:i + 3] 400 | 401 | address = MAC_ADDRESS_R.match(address.upper()) 402 | if address: 403 | address = address.group(0) 404 | 405 | current_address = self.get_interface_mac(device) 406 | 407 | if not targets: 408 | # Not trying to match anything in particular, 409 | # return everything. 410 | yield port, device, address, current_address 411 | continue 412 | 413 | for target in targets: 414 | if target in (port.lower(), device.lower()): 415 | yield port, device, address, current_address 416 | break 417 | 418 | 419 | def find_interface(self, target): 420 | """ 421 | Returns the first interface which matches `target`. 422 | """ 423 | try: 424 | return next(self.find_interfaces(targets=[target])) 425 | except StopIteration: 426 | pass 427 | 428 | 429 | def set_interface_mac(self, device, mac, port): 430 | """ 431 | Sets the mac address for `device` to `mac`. 432 | """ 433 | if port.lower() in wireless_port_names: 434 | # Turn on the device, assuming it's an airport device. 435 | subprocess.call([ 436 | 'networksetup', 437 | '-setairportpower', 438 | device, 439 | 'on' 440 | ]) 441 | 442 | # For some reason this seems to be required even when changing a 443 | # non-airport device. 444 | subprocess.check_call([ 445 | MacSpoofer.PATH_TO_AIRPORT, 446 | '-z' 447 | ]) 448 | 449 | # Change the MAC. 450 | subprocess.check_call([ 451 | 'ifconfig', 452 | device, 453 | 'ether', 454 | mac 455 | ]) 456 | 457 | # Associate airport with known network (if any) 458 | subprocess.check_call([ 459 | 'networksetup', 460 | '-detectnewhardware' 461 | ]) 462 | 463 | 464 | def get_interface_mac(self, device): 465 | """ 466 | Returns currently-set MAC address of given interface. This is 467 | distinct from the interface's hardware MAC address. 468 | """ 469 | 470 | try: 471 | result = subprocess.check_output([ 472 | 'ifconfig', 473 | device 474 | ], stderr=subprocess.STDOUT, universal_newlines=True) 475 | except subprocess.CalledProcessError: 476 | return None 477 | 478 | address = MAC_ADDRESS_R.search(result.upper()) 479 | if address: 480 | address = address.group(0) 481 | 482 | return address 483 | 484 | 485 | def get_os_spoofer(): 486 | """ 487 | OsSpoofer factory initializes approach OS platform dependent spoofer. 488 | """ 489 | spoofer = None 490 | 491 | if sys.platform == 'win32': 492 | spoofer = WindowsSpoofer() 493 | elif sys.platform == 'darwin': 494 | spoofer = MacSpoofer() 495 | elif sys.platform.startswith('linux'): 496 | if os.path.exists("/usr/bin/ip") or os.path.exists("/bin/ip"): 497 | spoofer = LinuxSpooferIP() 498 | else: 499 | spoofer = LinuxSpoofer() 500 | else: 501 | raise NotImplementedError() 502 | 503 | return spoofer 504 | 505 | def find_interfaces(targets=None): 506 | """ 507 | Returns the list of interfaces found on this machine reported by the OS. 508 | 509 | Target varies by platform: 510 | MacOS & Linux this is the interface name in ifconfig or ip 511 | Windows this is the network adapter name in ipconfig 512 | """ 513 | # Wrapper to interface handles encapsulating objects 514 | spoofer = get_os_spoofer() 515 | return spoofer.find_interfaces(targets) 516 | 517 | def find_interface(targets=None): 518 | """ 519 | Returns tuple of the first interface which matches `target`. 520 | adapter description, adapter name, mac address of target, current mac addr 521 | 522 | Target varies by platform: 523 | MacOS & Linux this is the interface name in ifconfig or ip 524 | Windows this is the network adapter name in ipconfig 525 | """ 526 | # Wrapper to interface handles encapsulating objects 527 | spoofer = get_os_spoofer() 528 | return spoofer.find_interface(targets) 529 | 530 | def set_interface_mac(device, mac, port=None): 531 | """ 532 | Sets the mac address for given `device` to `mac`. 533 | 534 | Device varies by platform: 535 | MacOS & Linux this is the interface name in ifconfig or ip 536 | Windows this is the network adapter name in ipconfig 537 | """ 538 | # Wrapper to interface handles encapsulating objects 539 | spoofer = get_os_spoofer() 540 | spoofer.set_interface_mac(device, mac, port) 541 | -------------------------------------------------------------------------------- /spoofmac/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __all__ = ('MAC_ADDRESS_R', 'random_mac_address') 3 | import re 4 | import random 5 | 6 | # Regex to validate a MAC address, as 00-00-00-00-00-00 or 7 | # 00:00:00:00:00:00 or 000000000000. 8 | MAC_ADDRESS_R = re.compile(r""" 9 | ([0-9A-F]{1,2})[:-]? 10 | ([0-9A-F]{1,2})[:-]? 11 | ([0-9A-F]{1,2})[:-]? 12 | ([0-9A-F]{1,2})[:-]? 13 | ([0-9A-F]{1,2})[:-]? 14 | ([0-9A-F]{1,2}) 15 | """, 16 | re.I | re.VERBOSE 17 | ) 18 | # Regex to validate a MAC address in cisco-style, such as 19 | # 0123.4567.89ab 20 | CISCO_MAC_ADDRESS_R = re.compile( 21 | r'([0-9A-F]{,4})\.([0-9A-F]{,4})\.([0-9A-F]{,4})', 22 | re.I 23 | ) 24 | 25 | 26 | def _chunk(l, n): 27 | return [l[i:i + n] for i in range(0, len(l), n)] 28 | 29 | 30 | def random_mac_address(local_admin=True): 31 | """ 32 | Generates and returns a random MAC address. 33 | """ 34 | # Randomly assign a VM vendor's MAC address prefix, which should 35 | # decrease chance of colliding with existing device's addresses. 36 | vendor = random.SystemRandom().choice(( 37 | (0x00,0x05,0x69), #VMware MACs 38 | (0x00,0x50,0x56), #VMware MACs 39 | (0x00,0x0C,0x29), #VMware MACs 40 | (0x00,0x16,0x3E), #Xen VMs 41 | (0x00,0x03,0xFF), #Microsoft Hyper-V, Virtual Server, Virtual PC 42 | (0x00,0x1C,0x42), #Parallells 43 | (0x00,0x0F,0x4B), #Virtual Iron 4 44 | (0x08,0x00,0x27)) #Sun Virtual Box 45 | ) 46 | 47 | mac = [ 48 | vendor[0], 49 | vendor[1], 50 | vendor[2], 51 | random.randint(0x00, 0x7f), 52 | random.randint(0x00, 0xff), 53 | random.randint(0x00, 0xff) 54 | ] 55 | 56 | if local_admin: 57 | # Universally administered and locally administered addresses are 58 | # distinguished by setting the second least significant bit of the 59 | # most significant byte of the address. If the bit is 0, the address 60 | # is universally administered. If it is 1, the address is locally 61 | # administered. In the example address 02-00-00-00-00-01 the most 62 | # significant byte is 02h. The binary is 00000010 and the second 63 | # least significant bit is 1. Therefore, it is a locally administered 64 | # address.[3] The bit is 0 in all OUIs. 65 | mac[0] |= 2 66 | 67 | return ':'.join('{0:02X}'.format(o) for o in mac) 68 | 69 | 70 | def normalize_mac_address(mac): 71 | """ 72 | Takes a MAC address in various formats: 73 | 74 | - 00:00:00:00:00:00, 75 | - 00.00.00.00.00.00, 76 | - 0000.0000.0000 77 | 78 | ... and returns it in the format 00:00:00:00:00:00. 79 | """ 80 | m = CISCO_MAC_ADDRESS_R.match(mac) 81 | if m: 82 | new_mac = ''.join([g.zfill(4) for g in m.groups()]) 83 | return ':'.join(_chunk(new_mac, 2)).upper() 84 | 85 | m = MAC_ADDRESS_R.match(mac) 86 | if m: 87 | return ':'.join([g.zfill(2) for g in m.groups()]).upper() 88 | 89 | return None 90 | 91 | def normalise_mac_address_windows(mac): 92 | """ 93 | Takes a MAC address in various formats: 94 | 95 | - 00:00:00:00:00:00, 96 | - 00-00-00-00-00-00, 97 | - 00.00.00.00.00.00, 98 | - 0000.0000.0000 99 | 100 | ... and returns it in the format 00-00-00-00-00-00. 101 | """ 102 | m = CISCO_MAC_ADDRESS_R.match(mac) 103 | if m: 104 | new_mac = ''.join([g.zfill(4) for g in m.groups()]) 105 | return '-'.join(_chunk(new_mac, 2)).upper() 106 | 107 | m = MAC_ADDRESS_R.match(mac) 108 | if m: 109 | return '-'.join([g.zfill(2) for g in m.groups()]).upper() 110 | 111 | return None 112 | -------------------------------------------------------------------------------- /spoofmac/version.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __version__ = '2.1.1' 3 | --------------------------------------------------------------------------------