├── .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 |
--------------------------------------------------------------------------------