news 2026/5/11 16:03:47

别再只当SIM卡用了!用Python脚本和APDU命令,带你亲手“解剖”手机卡里的文件系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
别再只当SIM卡用了!用Python脚本和APDU命令,带你亲手“解剖”手机卡里的文件系统

用Python和APDU命令探索USIM卡文件系统的实战指南

当你把手机卡插入设备时,它不仅仅是一个身份标识——实际上,这是一套完整的微型操作系统。本文将带你用Python脚本和APDU命令,像安全研究员一样亲手探索USIM卡内的文件系统结构。

1. 准备工作:搭建USIM卡探索环境

要开始探索USIM卡,你需要准备以下硬件和软件:

  • 支持APDU命令的读卡器:推荐使用ACR122U或类似型号,价格约200-500元
  • 一张可读写的USIM卡:运营商提供的普通USIM卡即可
  • Python开发环境:建议3.8+版本
  • 必要的Python库
    pip install pyscard python-apdu

注意:操作USIM卡有一定风险,建议在测试卡或备份重要数据后进行实验

连接读卡器后,用以下代码检测是否识别到卡片:

from smartcard.System import readers # 获取可用读卡器列表 reader_list = readers() if not reader_list: raise Exception("未检测到读卡器") # 连接第一个读卡器 connection = reader_list[0].createConnection() connection.connect() print(f"已连接: {reader_list[0]}") print(f"ATR: {connection.getATR()}")

2. APDU命令基础:与USIM卡对话的语言

APDU(Application Protocol Data Unit)是与智能卡通信的基本单位。一个完整的APDU命令包含以下部分:

字段长度描述
CLA1字节指令类别
INS1字节指令代码
P11字节参数1
P21字节参数2
Lc0-3字节数据域长度
Data变长命令数据
Le0-3字节期望响应长度

用Python发送APDU命令的通用函数:

def send_apdu(connection, cla, ins, p1, p2, data=None, le=0): apdu = [cla, ins, p1, p2] if data: apdu.append(len(data)) apdu.extend(data) if le > 0: apdu.append(le) response, sw1, sw2 = connection.transmit(apdu) return response, (sw1 << 8) | sw2 # 示例:选择MF(主文件) response, status = send_apdu(connection, 0x00, 0xA4, 0x00, 0x00, [0x3F, 0x00]) print(f"响应: {response}, 状态: {hex(status)}")

3. 探索文件系统:从MF到EF的完整遍历

USIM卡的文件系统采用树状结构,主要包含三种文件类型:

  • MF (Master File):根目录,固定ID为3F00
  • DF (Dedicated File):专用目录,相当于文件夹
  • EF (Elementary File):基础文件,存储实际数据

3.1 选择并解析MF

# 选择MF response, status = send_apdu(connection, 0x00, 0xA4, 0x00, 0x00, [0x3F, 0x00]) if status != 0x9000: raise Exception("选择MF失败") # 获取MF的FCP(文件控制参数) response, status = send_apdu(connection, 0x00, 0xA4, 0x00, 0x00, [0x3F, 0x00], 0x10) print(f"MF FCP: {bytes(response).hex()}")

3.2 遍历DF和EF

以下函数可以递归遍历USIM卡的文件系统:

def explore_filesystem(connection, current_path, depth=0): indent = " " * depth print(f"{indent}探索路径: {'/'.join(f'{b:02X}' for b in current_path)}") # 选择当前路径 if current_path: response, status = send_apdu(connection, 0x00, 0xA4, 0x08, 0x00, current_path) if status != 0x9000: print(f"{indent}选择失败: {hex(status)}") return # 获取当前目录下的文件列表 (假设有EFDIR) response, status = send_apdu(connection, 0x00, 0xA4, 0x00, 0x00, [0x2F, 0x00]) # 尝试选择EFDIR if status == 0x9000: # 读取EFDIR内容 response, status = send_apdu(connection, 0x00, 0xB0, 0x00, 0x00, le=0xFF) print(f"{indent}EFDIR内容: {bytes(response).hex()}") # 尝试选择常见DF common_dfs = [ ([0x7F, 0x10], "DFTELECOM"), ([0x7F, 0x20], "DFGSM"), ([0x7F, 0x21], "DFDCS1800"), ([0x7F, 0xFF], "当前ADF") ] for df_id, df_name in common_dfs: new_path = current_path + df_id response, status = send_apdu(connection, 0x00, 0xA4, 0x00, 0x00, df_id) if status == 0x9000: print(f"{indent}发现DF: {df_name} ({bytes(df_id).hex()})") explore_filesystem(connection, new_path, depth+1) # 从MF开始探索 explore_filesystem(connection, [])

4. 解析关键文件:IMSI、ICCID等

USIM卡中存储着许多重要信息,以下是常见EF文件及其ID:

文件名称FID描述
EF_ICCID2FE2卡唯一标识
EF_IMSI6F07用户标识
EF_LP6F05语言偏好
EF_AD6FAD管理数据

4.1 读取IMSI示例

def read_imsi(connection): # 选择DFTELECOM response, status = send_apdu(connection, 0x00, 0xA4, 0x00, 0x00, [0x7F, 0x10]) if status != 0x9000: raise Exception("选择DFTELECOM失败") # 选择EF_IMSI response, status = send_apdu(connection, 0x00, 0xA4, 0x00, 0x00, [0x6F, 0x07]) if status != 0x9000: raise Exception("选择EF_IMSI失败") # 读取IMSI (前9字节) response, status = send_apdu(connection, 0x00, 0xB0, 0x00, 0x00, le=9) if status != 0x9000: raise Exception("读取IMSI失败") # IMSI格式解析 imsi_bytes = bytes(response) imsi_digits = [] for b in imsi_bytes: imsi_digits.append(f"{(b >> 4) & 0x0F}") imsi_digits.append(f"{b & 0x0F}") # 第一个字节的高4位是长度 imsi_length = int(imsi_digits[0]) imsi = "".join(imsi_digits[1:imsi_length+1]) return imsi print(f"IMSI: {read_imsi(connection)}")

4.2 读取ICCID示例

def read_iccid(connection): # 选择MF response, status = send_apdu(connection, 0x00, 0xA4, 0x00, 0x00, [0x3F, 0x00]) if status != 0x9000: raise Exception("选择MF失败") # 选择EF_ICCID response, status = send_apdu(connection, 0x00, 0xA4, 0x00, 0x00, [0x2F, 0xE2]) if status != 0x9000: raise Exception("选择EF_ICCID失败") # 读取ICCID (10字节) response, status = send_apdu(connection, 0x00, 0xB0, 0x00, 0x00, le=10) if status != 0x9000: raise Exception("读取ICCID失败") # ICCID格式解析 iccid_bytes = bytes(response) iccid_digits = [] for b in iccid_bytes: iccid_digits.append(f"{(b >> 4) & 0x0F}") iccid_digits.append(f"{b & 0x0F}") # 去除可能的填充F iccid = "".join(iccid_digits).rstrip('F') return iccid print(f"ICCID: {read_iccid(connection)}")

5. 高级技巧:处理TLV格式数据

USIM卡中许多数据采用TLV(Tag-Length-Value)格式存储。以下是一个TLV解析工具函数:

def parse_tlv(data): result = {} index = 0 while index < len(data): tag = data[index] index += 1 # 处理多字节tag (简化版) if (tag & 0x1F) == 0x1F: while (data[index] & 0x80) == 0x80: tag = (tag << 8) | data[index] index += 1 tag = (tag << 8) | data[index] index += 1 # 获取长度 length = data[index] index += 1 if length == 0x81: length = data[index] index += 1 elif length == 0x82: length = (data[index] << 8) | data[index+1] index += 2 # 获取值 value = data[index:index+length] index += length result[hex(tag)] = bytes(value).hex() return result # 示例:解析SELECT响应 response, status = send_apdu(connection, 0x00, 0xA4, 0x00, 0x00, [0x6F, 0x07]) # 选择EF_IMSI if status == 0x9000: tlv_data = parse_tlv(response) print("TLV解析结果:") for tag, value in tlv_data.items(): print(f" {tag}: {value}")

6. 安全注意事项与最佳实践

在探索USIM卡文件系统时,需要注意以下安全事项:

  • 避免频繁写入:USIM卡有有限的擦写次数(通常约10万次)
  • 备份重要数据:操作前备份EF_IMSI等关键文件
  • 谨慎处理鉴权相关文件:如EF_Kc、EF_OPc等
  • 遵守法律法规:仅对自己的USIM卡进行实验

以下是一些有用的调试技巧:

# 启用APDU日志 def logged_send_apdu(connection, cla, ins, p1, p2, data=None, le=0): cmd = f"CLA: {cla:02X}, INS: {ins:02X}, P1: {p1:02X}, P2: {p2:02X}" if data: cmd += f", Data: {bytes(data).hex()}" if le: cmd += f", Le: {le:02X}" print(f"发送: {cmd}") response, status = send_apdu(connection, cla, ins, p1, p2, data, le) print(f"响应: {bytes(response).hex() if response else ''}, 状态: {status:04X}") return response, status # 示例使用 logged_send_apdu(connection, 0x00, 0xA4, 0x00, 0x00, [0x3F, 0x00])

在实际项目中,我发现最常遇到的问题是无法正确解析TLV数据。一个实用的技巧是先用SELECT命令获取文件的FCP(文件控制参数),其中包含了文件的结构信息。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/11 15:59:40

英雄联盟智能助手League Akari:重新定义你的游戏体验边界

英雄联盟智能助手League Akari&#xff1a;重新定义你的游戏体验边界 【免费下载链接】League-Toolkit An all-in-one toolkit for LeagueClient. Gathering power &#x1f680;. 项目地址: https://gitcode.com/gh_mirrors/le/League-Toolkit 在英雄联盟的竞技世界中&…

作者头像 李华
网站建设 2026/5/11 15:59:32

SWAT模型实战:从零构建自定义土壤数据库

1. 为什么需要自定义土壤数据库&#xff1f; 刚开始接触SWAT模型时&#xff0c;我也被它自带的庞大土壤数据库震撼过。这个数据库包含了全球各地的土壤类型和参数&#xff0c;看起来非常全面。但当我真正开始建模时&#xff0c;发现了一个致命问题&#xff1a;这些默认数据和我…

作者头像 李华
网站建设 2026/5/11 15:53:35

用LDAP Browser连接OpenLDAP时,这3个配置细节坑了我一整天

用LDAP Browser连接OpenLDAP时&#xff0c;这3个配置细节坑了我一整天 第一次用LDAP Browser连接OpenLDAP服务器时&#xff0c;我本以为照着教程五分钟就能搞定&#xff0c;结果硬是折腾了一整天。明明服务端已经正常启动&#xff0c;客户端工具也装好了&#xff0c;但就是连不…

作者头像 李华
网站建设 2026/5/11 15:48:20

STM32CubeMonitor 从零部署到实战:一站式配置与核心功能解析

1. STM32CubeMonitor是什么&#xff1f;为什么你需要它&#xff1f; 如果你正在开发STM32嵌入式项目&#xff0c;一定会遇到这样的场景&#xff1a;代码烧录进芯片后&#xff0c;想知道某个变量的实时变化趋势&#xff0c;或者需要监控传感器数据的波动情况。传统调试器只能提供…

作者头像 李华