UNPKG

spoof

Version:

Easily spoof your MAC address in OS X & Linux

496 lines (431 loc) 13.4 kB
/*! spoof. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */ module.exports = { findInterface, findInterfaces, normalize, randomize, setInterfaceMAC } const cp = require('child_process') const quote = require('shell-quote').quote const zeroFill = require('zero-fill') const Winreg = require('winreg') // Path to Airport binary on macOS 10.7+ const PATH_TO_AIRPORT = '/System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport' // Windows registry key for interface MAC. Checked on Windows 7 const WIN_REGISTRY_PATH = '\\SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}' // Regex to validate a MAC address // Example: 00-00-00-00-00-00 or 00:00:00:00:00:00 or 000000000000 const MAC_ADDRESS_RE = /([0-9A-F]{1,2})[:-]?([0-9A-F]{1,2})[:-]?([0-9A-F]{1,2})[:-]?([0-9A-F]{1,2})[:-]?([0-9A-F]{1,2})[:-]?([0-9A-F]{1,2})/i // Regex to validate a MAC address in cisco-style // Example: 0123.4567.89ab const CISCO_MAC_ADDRESS_RE = /([0-9A-F]{0,4})\.([0-9A-F]{0,4})\.([0-9A-F]{0,4})/i /** * Returns the list of interfaces found on this machine as reported by the * `networksetup` command. * @param {Array.<string>|null} targets * @return {Array.<Object>)} */ function findInterfaces (targets) { if (!targets) targets = [] targets = targets.map(target => target.toLowerCase()) if (process.platform === 'darwin') { return findInterfacesDarwin(targets) } else if (process.platform === 'linux') { return findInterfacesLinux(targets) } else if (process.platform === 'win32') { return findInterfacesWin32(targets) } } function findInterfacesDarwin (targets) { // Parse the output of `networksetup -listallhardwareports` which gives // us 3 fields per port: // - the port name, // - the device associated with this port, if any, // - the MAC address, if any, otherwise 'N/A' let output = cp.execSync('networksetup -listallhardwareports').toString() const details = [] while (true) { const result = /(?:Hardware Port|Device|Ethernet Address): (.+)/.exec(output) if (!result || !result[1]) { break } details.push(result[1]) output = output.slice(result.index + result[1].length) } const interfaces = [] // to return // Split the results into chunks of 3 (for our three fields) and yield // those that match `targets`. for (let i = 0; i < details.length; i += 3) { const port = details[i] const device = details[i + 1] let address = details[i + 2] address = MAC_ADDRESS_RE.exec(address.toUpperCase()) if (address) { address = normalize(address[0]) } const it = { address: address, currentAddress: getInterfaceMAC(device), device: device, port: port } if (targets.length === 0) { // Not trying to match anything in particular, return everything. interfaces.push(it) continue } for (let j = 0; j < targets.length; j++) { const target = targets[j] if (target === port.toLowerCase() || target === device.toLowerCase()) { interfaces.push(it) break } } } return interfaces } function findInterfacesLinux (targets) { // Parse the output of `ifconfig` which gives us: // - the adapter description // - the adapter name/device associated with this, if any, // - the MAC address, if any let output = cp.execSync('ifconfig', { stdio: 'pipe' }).toString() const details = [] while (true) { const result = /(.*?)HWaddr(.*)/mi.exec(output) if (!result || !result[1] || !result[2]) { break } details.push(result[1], result[2]) output = output.slice(result.index + result[0].length) } const interfaces = [] for (let i = 0; i < details.length; i += 2) { const s = details[i].split(':') let device, port if (s.length >= 2) { device = s[0].split(' ')[0] port = s[1].trim() } let address = details[i + 1].trim() if (address) { address = normalize(address) } const it = { address: address, currentAddress: getInterfaceMAC(device), device: device, port: port } if (targets.length === 0) { // Not trying to match anything in particular, return everything. interfaces.push(it) continue } for (let j = 0; j < targets.length; j++) { const target = targets[j] if (target === port.toLowerCase() || target === device.toLowerCase()) { interfaces.push(it) break } } } return interfaces } function findInterfacesWin32 (targets) { const output = cp.execSync('ipconfig /all', { stdio: 'pipe' }).toString() const interfaces = [] const lines = output.split('\n') let it = false for (let i = 0; i < lines.length; i++) { // Check if new device let result if (lines[i].substr(0, 1).match(/[A-Z]/)) { if (it) { if (targets.length === 0) { // Not trying to match anything in particular, return everything. interfaces.push(it) } else { for (let j = 0; j < targets.length; j++) { const target = targets[j] if (target === it.port.toLowerCase() || target === it.device.toLowerCase()) { interfaces.push(it) break } } } } it = { port: '', device: '' } const result = /adapter (.+?):/.exec(lines[i]) if (!result) { continue } it.device = result[1] } if (!it) { continue } // Try to find address result = /Physical Address.+?:(.*)/mi.exec(lines[i]) if (result) { it.address = normalize(result[1].trim()) it.currentAddress = it.address continue } // Try to find description result = /description.+?:(.*)/mi.exec(lines[i]) if (result) { it.description = result[1].trim() continue } } return interfaces } /** * Returns the first interface which matches `target` * @param {string} target * @return {Object} */ function findInterface (target) { const interfaces = findInterfaces([target]) return interfaces && interfaces[0] } /** * Returns currently-set MAC address of given interface. This is distinct from the * interface's hardware MAC address. * @return {string} */ function getInterfaceMAC (device) { if (process.platform === 'darwin' || process.platform === 'linux') { let output try { output = cp.execSync(quote(['ifconfig', device]), { stdio: 'pipe' }).toString() } catch (err) { return null } const address = MAC_ADDRESS_RE.exec(output) return address && normalize(address[0]) } else if (process.platform === 'win32') { console.error('No windows support for this method yet - PR welcome!') } } /** * Sets the mac address for given `device` to `mac`. * * Device varies by platform: * OS X, Linux: this is the interface name in ifconfig * Windows: this is the network adapter name in ipconfig * * @param {string} device * @param {string} mac * @param {string=} port */ function setInterfaceMAC (device, mac, port) { if (!MAC_ADDRESS_RE.exec(mac)) { throw new Error(mac + ' is not a valid MAC address') } const isWirelessPort = port && port.toLowerCase() === 'wi-fi' if (process.platform === 'darwin') { if (isWirelessPort) { // Turn on the device, assuming it's an airport device. try { cp.execSync(quote(['networksetup', '-setairportpower', device, 'on'])) } catch (err) { throw new Error('Unable to power on wifi device') } } // For some reason this seems to be required even when changing a non-airport device. try { cp.execSync(quote([PATH_TO_AIRPORT, '-z'])) } catch (err) { throw new Error('Unable to disassociate from wifi networks') } // Change the MAC. try { cp.execSync(quote(['ifconfig', device, 'ether', mac])) } catch (err) { throw new Error('Unable to change MAC address') } // Restart airport so it will associate with known networks (if any) if (isWirelessPort) { try { cp.execSync(quote(['networksetup', '-setairportpower', device, 'off'])) cp.execSync(quote(['networksetup', '-setairportpower', device, 'on'])) } catch (err) { throw new Error('Unable to set restart wifi device') } } } else if (process.platform === 'linux') { // Set the device's mac address. // Handles shutting down and starting back up interface. try { cp.execSync(quote(['ifconfig', device, 'down', 'hw', 'ether', mac])) cp.execSync(quote(['ifconfig', device, 'up'])) } catch (err) { throw new Error('Unable to change MAC address') } } else if (process.platform === 'win32') { // Locate adapter's registry and update network address (mac) const regKey = new Winreg({ hive: Winreg.HKLM, key: WIN_REGISTRY_PATH }) regKey.keys((err, keys) => { if (err) { console.log('ERROR: ' + err) } else { // Loop over all available keys and find the right adapter for (let i = 0; i < keys.length; i++) { tryWindowsKey(keys[i].key, device, mac) } } }) } } /** * Tries to set the "NetworkAddress" value on the specified registry key for given * `device` to `mac`. * * @param {string} key * @param {string} device * @param {string} mac */ function tryWindowsKey (key, device, mac) { // Skip the Properties key to avoid problems with permissions if (key.indexOf('Properties') > -1) { return false } const networkAdapterKeyPath = new Winreg({ hive: Winreg.HKLM, key: key }) // we need to format the MAC a bit for Windows mac = mac.replace(/:/g, '') networkAdapterKeyPath.values((err, values) => { let gotAdapter = false if (err) { console.log('ERROR: ' + err) } else { for (let x = 0; x < values.length; x++) { if (values[x].name === 'AdapterModel') { gotAdapter = true break } } if (gotAdapter) { networkAdapterKeyPath.set('NetworkAddress', 'REG_SZ', mac, () => { try { cp.execSync('netsh interface set interface "' + device + '" disable') cp.execSync('netsh interface set interface "' + device + '" enable') } catch (err) { throw new Error('Unable to restart device, is the cmd running as admin?') } }) } } }) } /** * Generates and returns a random MAC address. * @param {boolean} localAdmin locally administered address * @return {string} */ function randomize (localAdmin) { // Randomly assign a VM vendor's MAC address prefix, which should // decrease chance of colliding with existing device's addresses. const vendors = [ [0x00, 0x05, 0x69], // VMware [0x00, 0x50, 0x56], // VMware [0x00, 0x0C, 0x29], // VMware [0x00, 0x16, 0x3E], // Xen [0x00, 0x03, 0xFF], // Microsoft Hyper-V, Virtual Server, Virtual PC [0x00, 0x1C, 0x42], // Parallels [0x00, 0x0F, 0x4B], // Virtual Iron 4 [0x08, 0x00, 0x27] // Sun Virtual Box ] // Windows needs specific prefixes sometimes // http://www.wikihow.com/Change-a-Computer's-Mac-Address-in-Windows const windowsPrefixes = [ 'D2', 'D6', 'DA', 'DE' ] const vendor = vendors[random(0, vendors.length - 1)] if (process.platform === 'win32') { vendor[0] = windowsPrefixes[random(0, 3)] } const mac = [ vendor[0], vendor[1], vendor[2], random(0x00, 0x7f), random(0x00, 0xff), random(0x00, 0xff) ] if (localAdmin) { // Universally administered and locally administered addresses are // distinguished by setting the second least significant bit of the // most significant byte of the address. If the bit is 0, the address // is universally administered. If it is 1, the address is locally // administered. In the example address 02-00-00-00-00-01 the most // significant byte is 02h. The binary is 00000010 and the second // least significant bit is 1. Therefore, it is a locally administered // address.[3] The bit is 0 in all OUIs. mac[0] |= 2 } return mac .map(byte => zeroFill(2, byte.toString(16))) .join(':') .toUpperCase() } /** * Takes a MAC address in various formats: * * - 00:00:00:00:00:00, * - 00-00-00-00-00-00, * - 0000.0000.0000 * * ... and returns it in the format 00:00:00:00:00:00. * * @param {string} mac * @return {string} */ function normalize (mac) { let m = CISCO_MAC_ADDRESS_RE.exec(mac) if (m) { const halfwords = m.slice(1) mac = halfwords.map((halfword) => { return zeroFill(4, halfword) }).join('') return chunk(mac, 2).join(':').toUpperCase() } m = MAC_ADDRESS_RE.exec(mac) if (m) { const bytes = m.slice(1) return bytes .map(byte => zeroFill(2, byte)) .join(':') .toUpperCase() } } function chunk (str, n) { const arr = [] for (let i = 0; i < str.length; i += n) { arr.push(str.slice(i, i + n)) } return arr } /** * Return a random integer between min and max (inclusive). * @param {number} min * @param {number} max * @return {number} */ function random (min, max) { return min + Math.floor(Math.random() * (max - min + 1)) }