231 lines
7.1 KiB
Python
231 lines
7.1 KiB
Python
"""
|
|
MikroTik RouterBoard Password Generator
|
|
|
|
Reverse-engineered password generation algorithm based on MAC address analysis.
|
|
|
|
IMPORTANT DISCOVERY: Two different MAC addresses generate the SAME password:
|
|
MAC: 18:FD:74:F9:04:FC → Password: c8fd-8bff
|
|
MAC: 18:FD:74:F9:04:90 → Password: c8fd-8bff
|
|
|
|
The only difference is the last byte (FC vs 90), proving it's NOT used!
|
|
|
|
Pattern discovered:
|
|
- Only uses first 5 MAC bytes (byte 5 is ignored)
|
|
- Byte 0: MAC[0] XOR 0xD0
|
|
- Byte 1: MAC[1] (direct copy)
|
|
- Byte 2: NOT(MAC[2])
|
|
- Byte 3: 0xFF (constant or derived)
|
|
|
|
WARNING: This is based on reverse engineering two MAC addresses that both
|
|
produce the same password. More data points would help verify the constants.
|
|
"""
|
|
|
|
|
|
class MikroTikPasswordGenerator:
|
|
"""Generate MikroTik RouterBoard passwords from MAC addresses."""
|
|
|
|
@staticmethod
|
|
def parse_mac(mac_address):
|
|
"""
|
|
Parse a MAC address string into a list of bytes.
|
|
|
|
Accepts formats like "18:FD:74:F9:04:FC" or "18-FD-74-F9-04-FC"
|
|
|
|
Args:
|
|
mac_address (str): MAC address string
|
|
|
|
Returns:
|
|
list: List of 6 integers (0-255)
|
|
|
|
Raises:
|
|
ValueError: If MAC address format is invalid
|
|
"""
|
|
mac_address = mac_address.upper()
|
|
|
|
# Split by colon or dash
|
|
import re
|
|
parts = re.split(r'[:-]', mac_address)
|
|
|
|
if len(parts) != 6:
|
|
raise ValueError(f"Invalid MAC address: {mac_address}. Expected 6 octets.")
|
|
|
|
try:
|
|
mac_bytes = [int(part, 16) for part in parts]
|
|
except ValueError:
|
|
raise ValueError(f"Invalid MAC address format: {mac_address}")
|
|
|
|
return mac_bytes
|
|
|
|
@staticmethod
|
|
def bitwise_not(byte):
|
|
"""
|
|
Bitwise NOT operation (inverts all bits in a byte).
|
|
|
|
Args:
|
|
byte (int): Byte value (0-255)
|
|
|
|
Returns:
|
|
int: NOT(byte) as a byte (0-255)
|
|
"""
|
|
return byte ^ 0xFF
|
|
|
|
@staticmethod
|
|
def xor_op(byte, mask):
|
|
"""
|
|
XOR operation on a byte with a mask.
|
|
|
|
Args:
|
|
byte (int): Byte value (0-255)
|
|
mask (int): XOR mask (0-255)
|
|
|
|
Returns:
|
|
int: byte XOR mask (0-255)
|
|
"""
|
|
return (byte ^ mask) & 0xFF
|
|
|
|
@staticmethod
|
|
def format_password(pwd_bytes):
|
|
"""
|
|
Format password bytes into MikroTik format "XXXX-XXXX".
|
|
|
|
Args:
|
|
pwd_bytes (list): List of 4 bytes
|
|
|
|
Returns:
|
|
str: Formatted password like "c8fd-8bff"
|
|
"""
|
|
if len(pwd_bytes) != 4:
|
|
raise ValueError(f"Expected 4 password bytes, got {len(pwd_bytes)}")
|
|
|
|
hex_string = ''.join(f'{byte:02x}' for byte in pwd_bytes)
|
|
return f"{hex_string[:4]}-{hex_string[4:]}"
|
|
|
|
@staticmethod
|
|
def generate_password(mac_address):
|
|
"""
|
|
Generate a password from a MAC address.
|
|
|
|
Uses only the first 5 bytes of the MAC address (byte 5 is ignored).
|
|
|
|
Algorithm:
|
|
PWD[0] = MAC[0] XOR 0xD0
|
|
PWD[1] = MAC[1] (direct copy)
|
|
PWD[2] = NOT(MAC[2]) (bitwise NOT)
|
|
PWD[3] = 0xFF (constant or derived)
|
|
|
|
Args:
|
|
mac_address (str): MAC address string (e.g., "18:FD:74:F9:04:FC")
|
|
|
|
Returns:
|
|
str: Generated password (e.g., "c8fd-8bff")
|
|
|
|
Example:
|
|
>>> gen = MikroTikPasswordGenerator()
|
|
>>> pwd1 = gen.generate_password("18:FD:74:F9:04:FC")
|
|
>>> pwd2 = gen.generate_password("18:FD:74:F9:04:90")
|
|
>>> pwd1 == pwd2
|
|
True
|
|
'c8fd-8bff'
|
|
"""
|
|
mac_bytes = MikroTikPasswordGenerator.parse_mac(mac_address)
|
|
|
|
if len(mac_bytes) != 6:
|
|
raise ValueError(f"Expected 6 MAC bytes, got {len(mac_bytes)}")
|
|
|
|
# Extract the first 5 MAC bytes (byte 5 is ignored)
|
|
b0, b1, b2, b3, b4 = mac_bytes[:5]
|
|
|
|
# Generate password bytes based on discovered pattern
|
|
pwd_byte_0 = MikroTikPasswordGenerator.xor_op(b0, 0xD0)
|
|
pwd_byte_1 = b1 # Direct copy
|
|
pwd_byte_2 = MikroTikPasswordGenerator.bitwise_not(b2) # NOT operation
|
|
pwd_byte_3 = 0xFF # Constant (or possibly derived from b3)
|
|
|
|
pwd_bytes = [pwd_byte_0, pwd_byte_1, pwd_byte_2, pwd_byte_3]
|
|
|
|
# Format as hex string with dash
|
|
return MikroTikPasswordGenerator.format_password(pwd_bytes)
|
|
|
|
|
|
def test():
|
|
"""
|
|
Test the generator with the two known MAC/password pairs.
|
|
Both MACs should generate the same password.
|
|
"""
|
|
mac1 = "18:FD:74:F9:04:FC"
|
|
mac2 = "18:FD:74:F9:04:90"
|
|
expected = "c8fd-8bff"
|
|
|
|
gen = MikroTikPasswordGenerator()
|
|
generated1 = gen.generate_password(mac1)
|
|
generated2 = gen.generate_password(mac2)
|
|
|
|
print("Testing MikroTik Password Generator")
|
|
print("=" * 45)
|
|
print()
|
|
print("Test 1: First MAC address")
|
|
print(f" MAC Address: {mac1}")
|
|
print(f" Expected Password: {expected}")
|
|
print(f" Generated Password: {generated1}")
|
|
result1 = generated1 == expected
|
|
print(f" Result: {'✓ PASS' if result1 else '✗ FAIL'}")
|
|
print()
|
|
print("Test 2: Second MAC address (last byte different)")
|
|
print(f" MAC Address: {mac2}")
|
|
print(f" Expected Password: {expected}")
|
|
print(f" Generated Password: {generated2}")
|
|
result2 = generated2 == expected
|
|
print(f" Result: {'✓ PASS' if result2 else '✗ FAIL'}")
|
|
print()
|
|
|
|
# Key insight: both should be the same
|
|
print(f"Both generate same password: {'✓ YES' if generated1 == generated2 else '✗ NO'}")
|
|
print(f"Proves last byte is ignored: {'✓ CONFIRMED' if result1 and result2 else '✗ NOT CONFIRMED'}")
|
|
|
|
return result1 and result2
|
|
|
|
|
|
def main():
|
|
"""Main entry point with example usage."""
|
|
import sys
|
|
|
|
print("MikroTik RouterBoard Password Generator")
|
|
print("=" * 45)
|
|
print()
|
|
|
|
# Run test
|
|
test_passed = test()
|
|
print()
|
|
|
|
# Example usage
|
|
if len(sys.argv) > 1:
|
|
mac_address = sys.argv[1]
|
|
try:
|
|
gen = MikroTikPasswordGenerator()
|
|
password = gen.generate_password(mac_address)
|
|
print(f"MAC: {mac_address}")
|
|
print(f"Password: {password}")
|
|
except ValueError as e:
|
|
print(f"Error: {e}")
|
|
sys.exit(1)
|
|
else:
|
|
print("Usage:")
|
|
print(" python mikrotik_password.py <MAC_ADDRESS>")
|
|
print()
|
|
print("Examples:")
|
|
print(" python mikrotik_password.py 18:FD:74:F9:04:FC")
|
|
print(" python mikrotik_password.py 18-FD-74-F9-04:FC")
|
|
print()
|
|
print("Algorithm:")
|
|
print(" PWD[0] = MAC[0] XOR 0xD0")
|
|
print(" PWD[1] = MAC[1]")
|
|
print(" PWD[2] = NOT(MAC[2])")
|
|
print(" PWD[3] = 0xFF")
|
|
print()
|
|
print("Note: Only first 5 MAC bytes are used (byte 5 is ignored)")
|
|
|
|
sys.exit(0 if test_passed else 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|