srlabs / blue-merle

The blue-merle package enhances anonymity and reduces forensic traceability of the GL-E750 Mudi 4G mobile wi-fi router
BSD 3-Clause "New" or "Revised" License
186 stars 29 forks source link

IMEI randomisation makes IMEI looks like gibberish #32

Open Quid68 opened 7 months ago

Quid68 commented 7 months ago

Hi

IMG_2887

Blue merle is NOT generating valid IMEI. Most totally invalid and rejected by ISP.

The deal is your IMEIs fails check sum and have invalid TAC (in several countries).

I am not really good in GitHub so I cannot submit PR.

Fix for Python version

But this is simple code to make it look more valid:

import random

def luhn_check(imei):
    """
    Calculate the Luhn check digit for a given IMEI number.
    """
    sum = 0
    num_digits = len(imei)
    oddeven = num_digits & 1

    for i in range(0, num_digits):
        digit = int(imei[i])
        if not ((i & 1) ^ oddeven):
            digit *= 2
        if digit > 9:
            digit -= 9
        sum += digit

    return (10 - (sum % 10)) % 10

def imei_generate():
    # List of TACs for different manufacturers
    tacs = {
        "Samsung": "352073",
        "iPhone": "352074",
        "Sony": "352075",
        "LG": "352076",
        "Nokia": "352077",
        "Huawei": "352078",
        "Xiaomi": "352079",
        "OnePlus": "352080"
    }

    # Randomly choose a manufacturer
    manufacturer = random.choice(list(tacs.keys()))
    tac = tacs[manufacturer]

    # Generate FAC, USN
    fac = str(random.randint(10, 99)) # Final Assembly Code
    usn = str(random.randint(100000, 999999)) # Unique Serial Number

    # Combine all parts to form the IMEI without the check digit
    imei_without_check = tac + fac + usn

    # Calculate the check digit using the Luhn algorithm
    check_digit = luhn_check(imei_without_check)

    # Combine all parts to form the complete IMEI
    imei = imei_without_check + str(check_digit)

    return imei

# Example usage
print(imei_generate())

Fix for Bash version

#!/bin/bash

# Function to calculate the Luhn check digit
luhn_check() {
    local sum=0
    local num_digits=${#1}
    local oddeven=$((num_digits & 1))

    for ((i=0; i<num_digits; i++)); do
        local digit=${1:$i:1}
        if ((!((i & 1) ^ oddeven))); then
            digit=$((digit * 2))
        fi
        if ((digit > 9)); then
            digit=$((digit - 9))
        fi
        sum=$((sum + digit))
    done

    echo $((10 - (sum % 10)))
}

# Function to generate a random IMEI
generate_imei() {
    # TACs for different manufacturers
    local tacs=(
        "352073" # Samsung
        "352074" # iPhone
        "352075" # Sony
        "352076" # LG
        "352077" # Nokia
        "352078" # Huawei
        "352079" # Xiaomi
        "352080" # OnePlus
    )

    # Randomly choose a manufacturer
    local tac=${tacs[$RANDOM % ${#tacs[@]}]}

    # Generate FAC, USN
    local fac=$(printf "%02d" $((RANDOM % 90 + 10))) # Final Assembly Code
    local usn=$(printf "%06d" $((RANDOM % 900000 + 100000))) # Unique Serial Number

    # Combine all parts to form the IMEI without the check digit
    local imei_without_check="${tac}${fac}${usn}"

    # Calculate the check digit using the Luhn algorithm
    local check_digit=$(luhn_check "$imei_without_check")

    # Combine all parts to form the complete IMEI
    local imei="${imei_without_check}${check_digit}"

    echo "$imei"
}

# Example usage
generate_imei

This will make router mimic to one of this manufacturers: Samsung, iPhone, Sony, LG, Nokia, Huawei, Xiaomi, OnePlus

This ensures that ISP will not reject IMEI, nor shape speed because of “not mobile use”.

You can extend list of manufacturers by TACs from this database

richoffpints commented 6 months ago

So does our fix replace the imi gen script or add to it?

Quid68 commented 6 months ago

You should add it to main script with replacing part of previous one. It is just a part of script

richoffpints commented 6 months ago

Were should i replace it? I looked on ur page and it doesn look like u implemented it on ur fork

Quid68 commented 6 months ago

I provided above options for testing only. But if you need full script here it is:

#!/usr/bin/env python3
import random
import string
import argparse
import serial
import re
from functools import reduce
from enum import Enum

class Modes(Enum):
    DETERMINISTIC = 1
    RANDOM = 2
    STATIC = 3

ap = argparse.ArgumentParser()
ap.add_argument("-v", "--verbose", help="Enables verbose output", action="store_true")
ap.add_argument("-g", "--generate-only", help="Only generates an IMEI rather than setting it", action="store_true")
modes = ap.add_mutually_exclusive_group()
modes.add_argument("-d", "--deterministic", help="Switches IMEI generation to deterministic mode", action="store_true")
modes.add_argument("-s", "--static", help="Sets user-defined IMEI", action="store")
modes.add_argument("-r", "--random", help="Sets random IMEI", action="store_true")

imei_length = 14 # without validation digit
imei_prefix = ["35674108", "35290611", "35397710", "35323210", "35384110",
               "35982748", "35672011", "35759049", "35266891", "35407115",
               "35538025", "35480910", "35324590", "35901183", "35139729",
               "35479164"]

verbose = False
mode = None

TTY = '/dev/ttyUSB3'
BAUDRATE = 9600
TIMEOUT = 3

def luhn_check(imei):
    sum = 0
    num_digits = len(imei)
    oddeven = num_digits & 1

    for i in range(0, num_digits):
        digit = int(imei[i])
        if not ((i & 1) ^ oddeven):
            digit *= 2
        if digit > 9:
            digit -= 9
        sum += digit

    return (10 - (sum % 10)) % 10

def generate_imei(imei_prefix, imsi_d=None):
    if mode == Modes.DETERMINISTIC:
        random.seed(imsi_d)

    imei = random.choice(imei_prefix)
    random_part_length = imei_length - len(imei)
    imei += "".join(random.sample(string.digits, random_part_length))

    validation_digit = luhn_check(imei)
    imei = str(imei) + str(validation_digit)

    return imei

def validate_imei(imei):
    if len(imei) != 14:
        return False

    validation_digit = int(imei[-1])
    imei_verify = imei[0:14]

    validation_digit_verify = luhn_check(imei_verify)

    return validation_digit == validation_digit_verify

def get_imsi():
    with serial.Serial(TTY, BAUDRATE, timeout=TIMEOUT, exclusive=True) as ser:
        ser.write(b'AT+CIMI\r')
        output = ser.read(64)
        imsi_d = re.findall(b'[0-9]{15}', output)
        return b"".join(imsi_d)

def set_imei(imei):
    with serial.Serial(TTY, BAUDRATE, timeout=TIMEOUT, exclusive=True) as ser:
        cmd = b'AT+EGMR=1,7,\"'+imei.encode()+b'\"\r'
        ser.write(cmd)
        output = ser.read(64)

    new_imei = get_imei()
    return new_imei == imei.encode()

def get_imei():
    with serial.Serial(TTY, BAUDRATE, timeout=TIMEOUT, exclusive=True) as ser:
        ser.write(b'AT+GSN\r')
        output = ser.read(64)
        imei_d = re.findall(b'[0-9]{15}', output)
        return b"".join(imei_d)

if __name__ == '__main__':
    args = ap.parse_args()
    if args.verbose:
        verbose = args.verbose
    if args.deterministic:
        mode = Modes.DETERMINISTIC
        imsi_d = get_imsi()
    if args.random:
        mode = Modes.RANDOM
    if args.static is not None:
        mode = Modes.STATIC
        static_imei = args.static

    if mode == Modes.STATIC:
        if validate_imei(static_imei):
            set_imei(static_imei)
        else:
            exit(-1)
    else:
        imei = generate_imei(imei_prefix, imsi_d)
        if not args.generate_only:
            if not set_imei(imei):
                exit(-1)

    exit(0)

It is replacement for this

I added LUHN check to make sure IMEI 100% valid

This part:

imei_prefix = ["35674108", "35290611", "35397710", "35323210", "35384110",
               "35982748", "35672011", "35759049", "35266891", "35407115",
               "35538025", "35480910", "35324590", "35901183", "35139729",
               "35479164"]

Can be widened or fully replaced to mimic other devices using this database

For example this script will mimic to smartphones using smartphones manufacturers TACs (this variant untested):

#!/usr/bin/env python3
import random
import string
import argparse
import serial
import re
from enum import Enum

class Modes(Enum):
    DETERMINISTIC = 1
    RANDOM = 2
    STATIC = 3

ap = argparse.ArgumentParser()
ap.add_argument("-v", "--verbose", help="Enables verbose output", action="store_true")
ap.add_argument("-g", "--generate-only", help="Only generates an IMEI rather than setting it", action="store_true")
modes = ap.add_mutually_exclusive_group()
modes.add_argument("-d", "--deterministic", help="Switches IMEI generation to deterministic mode", action="store_true")
modes.add_argument("-s", "--static", help="Sets user-defined IMEI", action="store")
modes.add_argument("-r", "--random", help="Sets random IMEI", action="store_true")

imei_length = 14 # without validation digit

imei_prefix = [
    "352073", # Samsung
    "352074", # iPhone
    "352075", # Sony
    "352076", # LG
    "352077", # Nokia
    "352078", # Huawei
    "352079", # Xiaomi
    "352080" # OnePlus
]

verbose = False
mode = None

TTY = '/dev/ttyUSB3'
BAUDRATE = 9600
TIMEOUT = 3

def luhn_check(imei):
    sum = 0
    num_digits = len(imei)
    oddeven = num_digits & 1

    for i in range(0, num_digits):
        digit = int(imei[i])
        if not ((i & 1) ^ oddeven):
            digit *= 2
        if digit > 9:
            digit -= 9
        sum += digit

    return (10 - (sum % 10)) % 10

def generate_imei(imei_prefix, imsi_d=None):
    if mode == Modes.DETERMINISTIC:
        random.seed(imsi_d)

    tac = random.choice(imei_prefix)
    imei = tac + "".join(random.sample(string.digits, imei_length - len(tac)))

    validation_digit = luhn_check(imei)
    imei = str(imei) + str(validation_digit)

    return imei

def validate_imei(imei):
    if len(imei) != 15:
        return False

    validation_digit = int(imei[-1])
    imei_verify = imei[0:14]

    validation_digit_verify = luhn_check(imei_verify)

    return validation_digit == validation_digit_verify

def get_imsi():
    with serial.Serial(TTY, BAUDRATE, timeout=TIMEOUT, exclusive=True) as ser:
        ser.write(b'AT+CIMI\r')
        output = ser.read(64)
        imsi_d = re.findall(b'[0-9]{15}', output)
        return b"".join(imsi_d)

def set_imei(imei):
    with serial.Serial(TTY, BAUDRATE, timeout=TIMEOUT, exclusive=True) as ser:
        cmd = b'AT+EGMR=1,7,\"'+imei.encode()+b'\"\r'
        ser.write(cmd)
        output = ser.read(64)

    new_imei = get_imei()
    return new_imei == imei.encode()

def get_imei():
    with serial.Serial(TTY, BAUDRATE, timeout=TIMEOUT, exclusive=True) as ser:
        ser.write(b'AT+GSN\r')
        output = ser.read(64)
        imei_d = re.findall(b'[0-9]{15}', output)
        return b"".join(imei_d)

if __name__ == '__main__':
    args = ap.parse_args()
    if args.verbose:
        verbose = args.verbose
    if args.deterministic:
        mode = Modes.DETERMINISTIC
        imsi_d = get_imsi()
    if args.random:
        mode = Modes.RANDOM
    if args.static is not None:
        mode = Modes.STATIC
        static_imei = args.static

    if mode == Modes.STATIC:
        if validate_imei(static_imei):
            set_imei(static_imei)
        else:
            exit(-1)
    else:
        imei = generate_imei(imei_prefix, imsi_d)
        if not args.generate_only:
            if not set_imei(imei):
                exit(-1)

    exit(0)
richoffpints commented 6 months ago

have your ran the two and seen your script work? its good shit if it works

frank-dspeed commented 6 months ago

i will prepare a fork as it looks like your not git users.

muelli commented 6 months ago

that looks great! Thanks for the contribution.

Can you help me understand your statement:

The deal is your IMEIs fails check sum and have invalid TAC (in several countries).

a bit better? I'm surprised that you seem to have experience an invalid checksum. How did you notice the invalid checksum? And which TACs are invalid?

Erissio commented 6 months ago

that looks great! Thanks for the contribution.

It seems like this is now redundant

which TACs are invalid?

All valid as I checked. But TACs list should be bigger a little bit

narodnik commented 4 months ago

hey guys I scraped a TAC database, and have a list of codes you can use:

https://github.com/narodnik/immi/blob/master/tac_codes.csv

Linuzifer commented 4 months ago

Thanks a lot for the TAC database. In fact, we considered "full randomization" across all possible models at first.

However, this would result in a higher likelihood of detection that the IMEI was in fact manipulated:

  1. Many of the phone models are likely rarely or not at all in use anymore and would stick out from the average
  2. Older phone models may not even support 3G or 4G, another mismatch enabling anomaly detection. Ideally, we would even limit the frequency bands to match each model's baseband capabilities, but this could (a) limit reception while (b) eliminating an anomaly detection risk which we consider low (let's hope we're right about that).
narodnik commented 4 months ago

Great, I agree with what you say. However there should still be a decent selection of various popular devices to avoid correlation.

narodnik commented 4 months ago

Maybe focus on top 20 best selling phones:

https://en.wikipedia.org/wiki/List_of_best-selling_mobile_phones#2022[90]

Linuzifer commented 4 months ago

See Table 1 on page 9 for the models we choose from: https://github.com/srlabs/blue-merle/blob/main/Documentation.pdf

narodnik commented 4 months ago

This is a good list. Thanks so much. Previously was using KaiOS but definitely doing to get a Mudi and run this!