Skip to content

Instantly share code, notes, and snippets.

@ysfchn
Created April 29, 2026 22:31
Show Gist options
  • Select an option

  • Save ysfchn/42d196ab4f58bb0a6deccf09cf765be5 to your computer and use it in GitHub Desktop.

Select an option

Save ysfchn/42d196ab4f58bb0a6deccf09cf765be5 to your computer and use it in GitHub Desktop.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
SD cards has an Card Identification Number (CID) embedded into the chip and set once during
the factory process. The CID is written once and locked as read-only to ensure it never gets
changed.
Most of the time, the CID value of the SD card shouldn't matter, however there are some
devices exist that specifically require a specific SD card model, the most common example is
car GPS systems.
Fortunately, Samsung forgot to lock their own SD cards after writing CID in the factory,
making it possible to change the CID on our own and ending up spoofing the SD card appear
like an another model of another brand. Note that Samsung updated their cards since, so
this exploit won't work anymore unless you happen to find an old Samsung microSD card
produced many years ago (between 2011 and 2017).
The original program "evoplus_cid" is written by Richard Antony Burton and licensed under
"GNU General Public License version 3" (GPLv3):
https://github.com/raburton/evoplus_cid
See also related blog posts:
https://richard.burtons.org/2016/07/01/changing-the-cid-on-an-sd-card/
https://richard.burtons.org/2016/07/31/cid-change-on-sd-card-update-evoplus_cid/
https://orestbida.com/blog/cid-changeable-sdcards/ (for a list of compatible cards)
I've ported the original program into a Python script, so it can be run without the need
of compiling. However, as with the original program, this Python script can only work on
Unix platforms due to "ioctl" call, and it can only work in CPython implementation of Python.
To use the script you also need an on-board SD card slot on your host computer, USB SD card
readers won't work!
USE AT YOUR OWN RISK!
"""
from argparse import ArgumentParser
from fcntl import ioctl
import os
from pathlib import Path
import struct
from sys import stderr
from typing import Annotated, NamedTuple, Tuple, Union, cast, get_args
from ctypes import create_string_buffer, addressof
# _IOWR(MMC_BLOCK_MAJOR, 0, struct mmc_ioc_cmd)
MMC_IOC_CMD = 0xc048b300
SAMSUNG_VENDOR_OPCODE = 62
PROGRAM_CID_OPCODE = 26
# https://github.com/torvalds/linux/blob/dca922e019dd758b4c1b4bec8f1d509efddeaab4/include/linux/mmc/core.h#L35
MMC_RSP_PRESENT = 1 << 0
MMC_RSP_CRC = 1 << 2
MMC_RSP_BUSY = 1 << 3
MMC_RSP_OPCODE = 1 << 4
MMC_CMD_AC = 0 << 5
MMC_RSP_SPI_S1 = 1 << 7
MMC_RSP_SPI_R1 = MMC_RSP_SPI_S1
MMC_RSP_R1B = MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE | MMC_RSP_BUSY
MMC_RSP_R1 = MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE
MMC_CMD_ADTC = 1 << 5
# https://github.com/torvalds/linux/blob/dca922e019dd758b4c1b4bec8f1d509efddeaab4/include/uapi/linux/mmc/ioctl.h
class MmcIocCmd(NamedTuple):
write_flag: Annotated[int, "i"] = 0
is_acmd: Annotated[int, "i"] = 0
opcode: Annotated[int, "I"] = 0
arg: Annotated[int, "I"] = 0
response: Annotated[Tuple[int, int, int, int], "4I"] = (0, 0, 0, 0, )
flags: Annotated[int, "I"] = 0
blksz: Annotated[int, "I"] = 0
blocks: Annotated[int, "I"] = 0
postsleep_min_us: Annotated[int, "I"] = 0
postsleep_max_us: Annotated[int, "I"] = 0
data_timeout_ns: Annotated[int, "I"] = 0
cmd_timeout_ms: Annotated[int, "I"] = 0
pad: Annotated[int, "I"] = 0
data_ptr: Annotated[int, "Q"] = 0
def pack(self):
struct_format = ""
values = []
for key, hint in self.__class__.__annotations__.items():
struct_format += get_args(hint)[1]
current = getattr(self, key)
values.extend(current if isinstance(current, (tuple, list, bytes)) else [current])
return bytearray(struct.pack("<" + struct_format, *values))
def calculate_crc7(data: bytes) -> int:
crc = 0
poly = 0x89
for byte in data:
for bit in range(8):
bit_val = (byte >> (7 - bit)) & 1
crc_msb = (crc >> 6) & 1
crc <<= 1
if crc_msb ^ bit_val:
crc ^= poly
crc &= 0x7F
return (crc << 1) | 1
def cid_with_crc(data: bytes):
"""
Takes an existing CID value and returns it with CRC field included (= 16 bytes).
The given CID value may or may not contain a CRC field. If it does, then it will be validated
and returned as-is, if CRC is NULL (0x00) or non-existing (= 15 bytes), instead it will return
a new CID value with its CRC value correctly set.
"""
# Add CRC field if missing
if len(data) == 15:
return data + bytes([calculate_crc7(data)])
if len(data) != 16:
raise ValueError("Incomplete CID, must be 15 (without CRC) or 16 (with CRC) bytes")
last_byte = data[-1]
# Replace CRC field if it is reported 0x00 in Linux systems
if last_byte == 0:
return cid_with_crc(data[:-1])
# Otherwise, validate the existing CRC
if last_byte != calculate_crc7(data[:-1]):
raise ValueError("A CID was provided with non-matching CRC, remove or null out last byte to generate")
return data
def send_vendor_command(fd: int, arg: int):
cmd = MmcIocCmd(
write_flag = 1,
data_timeout_ns = 0x10000000,
opcode = SAMSUNG_VENDOR_OPCODE,
arg = arg,
flags = MMC_RSP_R1B | MMC_CMD_AC
)
print(cmd, file = stderr)
packed = cmd.pack()
assert len(packed) == 72
print(packed.hex(" "), file = stderr)
returned = ioctl(fd, MMC_IOC_CMD, packed)
print("command return value", returned, file = stderr)
return returned
def write_cid(fd: int, cid_bytes: bytes):
buf = create_string_buffer(cid_bytes)
address = addressof(buf)
cmd = MmcIocCmd(
write_flag = 1,
data_timeout_ns = 0x10000000,
opcode = PROGRAM_CID_OPCODE,
blksz = 16, # CID size = 16
blocks = 1,
data_ptr = address,
flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC
)
print("writing cid...", file = stderr)
print(cmd, file = stderr)
packed = cmd.pack()
assert len(packed) == 72
print(packed.hex(" "), file = stderr)
returned = ioctl(fd, MMC_IOC_CMD, packed)
print("command return value", returned, file = stderr)
return returned
def unlock_card(fd: int):
"""
Unlocks the card for writing the CID
"""
print("stage 1...", file = stderr)
assert not send_vendor_command(fd, 0xEFAC62EC), "failed to enter vendor mode, make sure it is an CID writable card!"
print("stage 2...", file = stderr)
assert not send_vendor_command(fd, 0xEF50), "couldn't unlock the card!"
print("stage 3...", file = stderr)
assert not send_vendor_command(fd, 0x00DECCEE), "failed to exit from vendor mode!"
def read_cid(device_path: Union[str, Path]):
"""
Read the CID value of the card and fix the CRC value if it is wrong.
"""
cid_path = Path(device_path) / "device/cid"
if not cid_path.exists():
raise FileNotFoundError(
"couldn't detect a CID reporting SD card device in given device path, make sure the SD card is attached "
"to an on-board SD card slot, USB SD adapters won't work!"
)
current = bytes.fromhex(cid_path.read_text())
print(f"current cid of {str(device_path)} reported:", file = stderr)
print(current.hex(), file = stderr)
fixed = cid_with_crc(current)
if current != fixed:
print("though the above value is wrongly reported by kernel with a null crc value, correct one is:", file = stderr) # noqa: E501
print(fixed.hex(), file = stderr)
return fixed
def main():
if os.geteuid() != 0:
print("script must be run as root!")
exit(1)
parser = ArgumentParser()
parser.add_argument("path", help = "path of the device, like only 'mmcblk0' part", type = str)
parser.add_argument("--write", "-w", help = "new CID to write as a hexadecimal string (15 or 16 bytes)", type = str, required = False) # noqa: E501
data = parser.parse_args()
dev_path = Path("/sys/block") / cast(str, data.path)
node_path = Path("/dev") / cast(str, data.path)
new_cid = None if not data.write else cid_with_crc(bytes.fromhex(cast(str, data.write)))
print("trying to reading CID value of the card...", file = stderr)
read_cid(dev_path)
print("backup that value just in case!", file = stderr)
if not new_cid:
print("done.", file = stderr)
exit(0)
print("this operation will set the card's CID value to:", new_cid, file = stderr)
prompt = input("type \"yes\" in lowercase and without quotes to confirm and hit enter: ")
if prompt != "yes":
print("cancelled.", file = stderr)
exit(1)
print("trying to access the card...", file = stderr)
fd = os.open(node_path, os.O_RDWR)
print(f"trying to unlock the card @ {fd:x}...", file = stderr)
unlock_card(fd)
print("the card is now seems to be unlocked.", file = stderr)
write_cid(fd, new_cid)
print("set CID to:", new_cid.hex(), file = stderr)
print("done!", file = stderr)
os.close(fd)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment