Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IMEI randomisation makes IMEI looks like gibberish #32

Open
Quid68 opened this issue Apr 3, 2024 · 14 comments
Open

IMEI randomisation makes IMEI looks like gibberish #32

Quid68 opened this issue Apr 3, 2024 · 14 comments

Comments

@Quid68
Copy link

Quid68 commented Apr 3, 2024

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
Copy link

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

@Quid68
Copy link
Author

Quid68 commented Apr 8, 2024

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

@richoffpints
Copy link

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

@Quid68
Copy link
Author

Quid68 commented Apr 8, 2024

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
Copy link

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

@frank-dspeed
Copy link

frank-dspeed commented Apr 12, 2024

frank-dspeed added a commit to direktspeed/blue-merle that referenced this issue Apr 12, 2024
@muelli
Copy link
Collaborator

muelli commented Apr 12, 2024

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
Copy link

Erissio commented Apr 13, 2024

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
Copy link

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
Copy link
Collaborator

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
Copy link

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

@narodnik
Copy link

narodnik commented Jun 13, 2024

Maybe focus on top 20 best selling phones:

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

@Linuzifer
Copy link
Collaborator

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

@narodnik
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants