小米一碰传NFC Tag协议解析

流传的NDEF信息

目前网上流传最多的小米一碰传NFC NDEF信息大多为:

text 复制代码
d42a4d636f6d2e7869616f6d692e6d695f636f6e6e6563745f736572766963653a65787465726e616c747970650a4b0801100d2201032a094d492d4e4643544147380f4a31271700000008000e5441475f444953434f564552454465064d4952524f52011141383a36443a41413a31373a44373a35316a02fa7f

其中:

  • 00000008000被认为是设备类型:8为平板,3为电脑
  • 41383a36443a41413a31373a44373a3531被认为是蓝牙Mac地址的字符串

诚然这样的修改确实起效,但是这个NDEF信息中的其他部分是什么含义依然并不清晰。

因此必须逆向APP才能得到的准确含义,以下为解析过程和实际含义解释。

解析真实NDEF含义

NDEF解析

首先,NDEF信息中包含了NFC TAG需要的配置信息,这些信息都是可以通过NDEF协议进行解析的。

使用Python的ndef库,将以上HEX字符串转为bytes后可以得到如下信息:

text 复制代码
Record  0
	flags:
		message_begin: True
		message_end: True
		chunked: False
		short: True
		id: False
	tnf: 4 [EXTERNAL]
	type_len: 42
	type: b'com.xiaomi.mi_connect_service:externaltype'
	id_len: 0
	id: None
	payload_len: 77
	payload: 0a4b0801100d2201032a094d492d4e4643544147380f4a31271700000008000e5441475f444953434f564552454465064d4952524f52011141383a36443a41413a31373a44373a35316a02fa7f

一条NDEF数据被认为是一条Message,一个Message中包含了多个Record

经过解析,这条NDEF数据中只包含了一个Record,其载荷为外置(EXTERNAL)类型的com.xiaomi.mi_connect_service:externaltype

因此下一步需要找到安卓系统如何处理这样的NDEF内容。

处理NDEF信息的APP

根据安卓官方文档,要处理这种类型的数据,就需要APP在Manifestactivity中声明以下内容:

xml 复制代码
<intent-filter>
	<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
	<category android:name="android.intent.category.DEFAULT"/>
	<data android:scheme="vnd.android.nfc" android:host="ext" android:pathPrefix="/com.xiaomi.mi_connect_service:externaltype"/>
</intent-filter>

也就是说要处理Uri为vnd.android.nfc://ext/com.xiaomi.mi_connect_service:externaltype的NDEF内容。

使用ADB命令在手机上搜索可以得到com.xiaomi.mi_connect_service/.nfc.NfcFieldOnPublisher

shell 复制代码
adb shell cmd package query-activities --components -d "vnd.android.nfc://ext/com.xiaomi.mi_connect_service:externaltype"

找到com.xiaomi.mi_connect_service包名的App,名称为小米互联通信服务

查看其中的.nfc.NfcFieldOnPublisher组件,发现使用了com.xiaomi.mi_connect.nfc.proto.v1.NfcTagAdvDataCoder类对数据进行解析。

不难发现,这个NDEF TAG使用了ProtoBuf的数据存储格式,在APP的资源文件中进行对比可以找到AttrProto.proto文件,以下为部分截取的片段。

protobuf 复制代码
syntax = "proto3";

package mi_connect_service;

message AttrAdvData {
    int32 versionMajor = 1;
    int32 versionMinor = 2;
    bytes apps = 3;//已过时。请使用appIds替代。
    bytes flags = 4;
    string name = 5;
    bytes idHash = 6;
    int32 deviceType = 7;
    int32 securityMode = 8;
    repeated bytes appsData = 9;
    repeated bytes supportSetting = 10;
    repeated bytes currentSetting = 11;
    string wifiMac = 12;
    repeated int32 appIds = 13;//引入appIds之前使用的是apps
    int32 commData = 14;
    bool ziped = 15;
    string wiredMac = 16;//有线网卡mac
    string btMac = 17; //Bluetooth Device MAC
}

message AttrOps {
    AttrAdvData advData = 1;
    int32 sequenceId = 14;
}

使用Python将载荷中的数据使用ProtoBuf数据格式解析,可以得到以下JSON:

json 复制代码
{
	"advData": {
		"appIds": [
			16378
		],
		"appsData": [
			"JxcAAAAIAA5UQUdfRElTQ09WRVJFRGUGTUlSUk9SARFBODo2RDpBQToxNzpENzo1MQ=="
		],
		"deviceType": 15,
		"flags": "Aw==",
		"name": "MI-NFCTAG",
		"versionMajor": 1,
		"versionMinor": 13
	}
}

注:flagsappsData下的数据使用base64编码,实际为bytes

text 复制代码
flags: b'03'
app data: [b'27:17:00:00:00:08:00:0e:54:41:47:5f:44:49:53:43:4f:56:45:52:45:44:65:06:4d:49:52:52:4f:52:01:11:41:38:3a:36:44:3a:41:41:3a:31:37:3a:44:37:3a:35:31']

AttrAdvData字段含义

进一步解析APP相关代码,可以得到如下表格:

appId

数字 类型
1 MI_SHARE
2 MI_PLAY
62 MI_ACCOUNT
63 MI_MOVER
16382 MI_TEST
3 MI_MIRROR
16381 MI_REMOTE_CONTROLLER
16379 MI_VOIP
16377 MI_VIDEO_RELAY
16378 MI_TAP
16376 MI_WATCH_CAMERA

flags

数字 操作
1 发送 com.xiaomi.nfc.action.TAG_DISCOVERED 广播,使用ProtoBuf中的wifiMacbtMac数据
3 发送 com.xiaomi.nfc.action.[ACTION] 自定义ACTION广播,使用ProtoBuf中的appsData数据
Others 发送 com.xiaomi.aiot.nfc.message 本地广播,转发NDEF的TagRecord数据

注:接收 com.xiaomi.nfc.action.TAG_DISCOVERED 广播或者 com.xiaomi.nfc.action.[ACTION] 自定义ACTION广播,需要对应的BroadcastReceiver拥有 com.xiaomi.mi_connect_service.permission.RECEIVE_ENDPOINT 权限。

目前可用Action只有TAG_DISCOVERED

shell 复制代码
adb shell "dumpsys package resolvers receiver | grep com.xiaomi.nfc.action."

appsData数据含义

数据:27:17:00:00:00:08:00:0e:54:41:47:5f:44:49:53:43:4f:56:45:52:45:44:65:06:4d:49:52:52:4f:52:01:11:41:38:3a:36:44:3a:41:41:3a:31:37:3a:44:37:3a:35:31

数据 位置 含义
27:17 0 ~ 1 版本号,十进制为39 23,直接通过是否相等判断
00:00:00:08 2 ~ 5 设备类型,十进制为8,int32
00 6 数字键值对数据长度,此处为0表示无数据

数字键值对数据

由于没有真实数据所以只说明数据格式

位置 含义
0 键,整数
1 值长度
2 ~ 2+X-1 值,bytes

尾部

数据 位置 含义
0e 7 自定义ACTION广播的名称的长度,0x0e=14
54: ... :44 8 ~ 8+14-1 自定义ACTION广播的名称,此处含义TAG_DISCOVERED
65: ... :31 之后全部 发送的bytes载荷

自定义ACTION广播

由于这个NDEF只涉及flag=3的情况,所以本文只分析这个广播的参数构建

  • Action: com.xiaomi.nfc.action.[ACTION]
  • Permission: com.xiaomi.mi_connect_service.permission.RECEIVE_ENDPOINT
    解析 appsData 可得 com.xiaomi.nfc.action.TAG_DISCOVERED
  • Flags: 添加了多组flags
    • 16777216 安卓Flag Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
    • 32 安卓Flag Intent.FLAG_INCLUDE_STOPPED_PACKAGES
    • 268435456 小米自定义Flag StaticConfigServiceConnector.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
  • Extra:
    • protocol_value_key 内容为JSON
protocol_value_keyJSON构建
device_type_key 设备类型,将00:00:00:08转为int32,即8
attribute_value_key 数字键值对数据转JSON后,UTF-8编码为Base64的字符串
protocol_payload_key 发送的bytes载荷,转为的Base64的字符串

注:不存在某种数据就不存在这个JSON字段。

数字键值对数据转JSON:键数字转为字符串,目前值直接交给org.json.JSONObject处理(这样写会出BUG,不知道啥时候修复,可能用不到就不修复)。

处理NFC TAG信息的APP

对于以上Intent,使用ADB命令在手机上搜索可以得到com.milink.service/com.miui.circulate.nfc.relay.NfcTransportReceiver

shell 复制代码
adb shell cmd package query-receivers --components -a "com.xiaomi.nfc.action.TAG_DISCOVERED"

找到 com.milink.service 包名的App,名称为投屏或者互联互通服务

查看其中的 com.miui.circulate.nfc.relay.NfcTransportReceiver 组件,发现使用了 com.xiaomi.mi_connect.nfc.proto.v1.NfcTagAdvDataCoder 类对数据进行解析。

device_type_key数据

可以在这个APP中找到更多的设备类型定义:

数值 类型
2 TV
3 PC
5 CAR
8 PAD

protocol_payload_key数据

数据:65:06:4d:49:52:52:4f:52:01:11:41:38:3a:36:44:3a:41:41:3a:31:37:3a:44:37:3a:35:31

数据 位置 含义
65 0 键,整数,101
06 1 值长度,0x16=6
4d:49:52:52:4f:52 2 - 2+6-1 值,UTF-8字符串,MIRROR
01 9 键,整数,1
11 10 值长度,0x11=17
41: ... : 31 11 - 11+17-1 值,UTF-8字符串,A8:6D:AA:17:D7:51

键数值含义

数值 字段名 数据类型 解释
101 actionSuffix String Action后缀
1 btMac String 蓝牙Mac地址
2 wifiMac String WIFI Mac地址
3 wiredMac String 有线Mac地址
121 extAbility bytes 额外能力

actionSuffix

目前已知的actionSuffix为:

  • MIRROR
  • TVCAST
shell 复制代码
adb shell "dumpsys package resolvers receiver | grep com.miui.onehop.action."

根据actionSuffix发送自定义Action的广播

发送的是有序广播:OrderedBroadcast

  • Action: com.miui.onehop.action.[ACTION] 解析protocol_payload_key可得com.miui.onehop.action.MIRROR
  • Permission: com.miui.onehop.permission.MIRROR
  • Flags: 添加了多组flags
    • 32 安卓Flag Intent.FLAG_INCLUDE_STOPPED_PACKAGES
    • 16777216 小米自定义Flag RemoteCallAdapterKt.FLAG_RECEIVER_INCLUDE_BACKGROUND
  • Extra:
    • _device_type int,设备类型,device_type_key
    • _bt_mac String,蓝牙Mac地址,btMac
    • _wifi_mac String,WIFI Mac地址,wifiMac
    • _wired_mac String,有线Mac地址,wiredMac
    • _ability_lyra Boolean,(extAbility[0] & 1) > 0

这个广播目前只由Appcom.milink.service自己接收并处理。

至此NFC Tag中的NDEF数据就已经解析完毕,再向下分析已经没有必要了。

NDEF解析总结

其实使用流传的NDEF修改方法并无问题,只是在修改设备类型时存在不影响结果的错误。

至于其他的字段含义,对于NFC快速流转并无影响,了解数据结构可能对未来协议功能升级后的破解会更加方便。

因此简单来说,可以将NDEF进行如下修改即可:

text 复制代码
d42a4d636f6d2e7869616f6d692e6d695f636f6e6e6563745f736572766963653a65787465726e616c747970650a4b0801100d2201032a094d492d4e4643544147380f4a3127170000000[deviceType]000e5441475f444953434f564552454465064d4952524f520111[btMac]6a02fa7f

deviceType: 3 or 8
btMac: 4138 3a 3644 3a 4141 3a 3137 3a 4437 3a 3531

触发小米一碰传的其他方法

如果想要代码触发小米一碰传功能有两种方法:

  • 发送虚拟的NFC Tag的NDEF信息
  • 直接向投屏或者互联互通服务发送广播

发送虚拟的NFC Tag的NDEF信息

安卓中NDEF广播要求必须有android.nfc.extra.TAGTag数据,但是这个数据只有真实的NFC硬件才能生成。

因此一个安全的解决方案是,对于已有但不能修改的NFC Tag,可以使用代码获取到这个NFC Tag的硬件Tag数据,并对其中的内容修改后转发。

kotlin 复制代码
// 从Intent中获取已有的NDEF数据
val tag: Tag? = IntentCompat.getParcelableExtra(intent, NfcAdapter.EXTRA_TAG, Tag::class.java)
val id: ByteArray = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID)

val btMac = "00:00:00:00:00:00"
val hexPart1 = "0a4b0801100d2201032a094d492d4e4643544147380f4a3127170000000"
val hexPart2 = "000e5441475f444953434f564552454465064d4952524f520111"
val hexPart3 = "6a02fa7f"
// PC
val hex = hexPart1 + "3" + hexPart2 + btMac.toByteArray().toHexString() + hexPart3

val payload = hex.decodeHex()
val externalType = "com.xiaomi.mi_connect_service:externaltype"
val record = NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, externalType.toByteArray(), null, payload)

Intent(NfcAdapter.ACTION_NDEF_DISCOVERED).apply {
	data = Uri.parse("vnd.android.nfc://ext/$externalType")
	putExtra(NfcAdapter.EXTRA_TAG, tag)
	putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, arrayOf(NdefMessage(record)))
	putExtra(NfcAdapter.EXTRA_ID, id)
}.also {
	ContextCompat.startActivity(this, it, null)
}

另一方面,com.xiaomi.mi_connect_service小米互联通信服务)在处理收到的数据时,只有flags != 1flags != 3时才会获取并处理Tag

因此想要模拟这个广播,另一个不安全但是能用的方案就是不传递这个Tag数据,这样在当前使用场景下不会报错。

kotlin 复制代码
// 直接设置为空即可
val tag: Tag? = null
// 一个空的NFC Tag ID
val id: ByteArray = "000000000000".decodeHex()

// 类似的代码 ...

关于如何接收指定的NDEF数据,可以查看官方文档

投屏或者互联互通服务发送广播

模拟NDEF数据总可能会出现一些问题,因此另一个方案就是直接发送投屏的广播。

根据广播权限的定义,应用程序需要拥有com.xiaomi.mi_connect_service.permission.RECEIVE_ENDPOINT权限,才能向同样要求这个权限的广播接收器发送广播。

值得庆幸的是,这个权限定义在com.xiaomi.mi_connect_service小米互联通信服务)中并且没有任何的签名或者系统限制,所以可以直接在测试APP的Manifest中申明这个权限。

kotlin 复制代码
val btMac = "00:00:00:00:00:00"
val hex = "65064d4952524f520111" + btMac.toByteArray().toHexString()
val payload = Base64.encodeToString(hex.decodeHex(), Base64.DEFAULT)

val json = JSONObject()
json.put("device_type_key", 3) // PC
json.put("protocol_payload_key", payload)
val extra = json.toString()

sendBroadcast(Intent("com.xiaomi.nfc.action.TAG_DISCOVERED").apply {
	addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
	addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
	addFlags(268435456)
	putExtra("protocol_value_key", extra)
}, "com.xiaomi.mi_connect_service.permission.RECEIVE_ENDPOINT")

一碰传文件

触发一碰传文件的要求是文件/相册APP在前台,并且选中了要传输的文件。

如果想要触发,那么发送Intent的APP就必须在后台,或者不显示任何界面的发送完广播就退出。

Activity不显示界面可以使用以下Theme:

xml 复制代码
<activity android:theme="@android:style/Theme.NoDisplay" />

分析的版本

以上所有的分析过程建立在对HyperOS中提取出的APP上,并在小米电脑管家1.0.0.489上通过测试。

名称 包名 版本
小米互联通信服务 com.xiaomi.mi_connect_service 3.1.450.10
投屏 com.milink.service 15.0.4.1.e4a233b.2894359
NFC服务 com.android.nfc 14

本文首发于个人博客,并转载于简书

相关推荐
太空漫步112 小时前
android社畜模拟器
android
海绵宝宝_4 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
凯文的内存6 小时前
android 定制mtp连接外设的设备名称
android·media·mtp·mtpserver
天若子6 小时前
Android今日头条的屏幕适配方案
android
林的快手8 小时前
伪类选择器
android·前端·css·chrome·ajax·html·json
望佑8 小时前
Tmp detached view should be removed from RecyclerView before it can be recycled
android
xvch10 小时前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
人民的石头14 小时前
Android系统开发 给system/app传包报错
android
yujunlong391914 小时前
android,flutter 混合开发,通信,传参
android·flutter·混合开发·enginegroup
rkmhr_sef14 小时前
万字详解 MySQL MGR 高可用集群搭建
android·mysql·adb