RPMB分区读写测试

Posted by 婷 on April 12, 2025 本文总阅读量

原理

RPMB操作这节是在协议的6.6.22,他的作用归纳如下

  • 提供一种验签访问RPMB的方法。这项功能是把一些数据存放到特殊的区域,要访问这个区域需要通过签名认证。这个认证由第一次写入eMMC的一个加密秘钥来提供。
  • 验证秘钥是在读写访问过程,使用一段认证消息字段,用于保护读写访问模式下的安全区域数据,可以防止重放攻击。
  • 提供次数统计寄存器和加随机数混杂的方式,用于防范攻击者使用重发攻击的方式。

访问RPMB分区的数据格式

数据格式如下,高位在前,其实总线上的数据帧应该是512bit + 1bit(start) + 2Byte(crc16) + 1bit(end)

img

接下来从低到高,依次解读每个pos的内容

Req/Resp

Request/Response Type 请求和回复类型

长度:2字节

方向:request方向(eMMC到内存),Response方向(从内存到eMMC)

描述:定义请求和应答的类型。请求跟应答有如下这些类型

img

Key/MAC

Authentication Key / Message Authentication Code ,密钥/身份认证码

长度:32字节 (256bit)

方向:请求(Key、MAC)/应答(MAC)

描述:密钥或者还是身份认证码,取决于类型标志,也即前面的Req/Resp字段

Result

Operation result 操作结果

长度:2字节

方向:应答 (device给到host的)

描述:包括写次数和成功读写RPMB的信息。

RPMB操作结果数据结构体:

img

操作结果,也即bit[6:0]的类型有如下这些,带0x008x结果的,是因为write counter达到最大值了,才会带0x008x

img

Write counter

写次数

长度:4字节

方向:请求和应答

描述:这个值表示身份验证的成功次数和主机进行配置的写请求次数

Data Address

数据地址

长度:2字节

方向:请求和应答

描述:读写RPMB的偏移地址,起始地址是0x00,以256字节对齐。此时CMD18和CMD25中的地址参数会被忽略 。

img

img

Nonce

随机数字段

长度:16字节

方向:请求和应答

描述:主机创建的随机数,在请求中带有这个,应用于RPMB验签

Data

数据

长度:256字节

方向:请求和应答

描述:要写的数据

Block count

块个数

长度:2字节

方向:请求和应答

描述:请求读写的块(这里的块个数是半个sector,也就是256字节)个数,这个值和CMD23的参数一样

img

(没看懂,那RPMB读写的时候,用的是哪种CMD23)

RPMB分区的存储映射

这部分主要是RPMB分区内部的结构,分为三部分

image-20250412113718780

Authentication Key

32字节,只能写一次,不能被擦除,也不能被读取。

Write Counter

4字节,只读。所有验证数据请求和配置的次数统计。初始值是0,一直递增。该值不能重置,当到达最大值0xffffffff后,就不会继续累加,然后操作结果字段的第七比特位会置一。 如果溢出了,会体现在RPMB帧中的Result的Operation Result中,前面有提到。

img

Data

数据区,可读写,不可擦除。操作单位是256B。

MAC的计算

身份验证码(MAC)是通过HMAC SHA-256计算出来的。这个算法需要输入一个key和一个信息。计算的结果是一个256bit长的字段,也就是身份验证码(MAC)。其实就是哈希算法的一种罢了。

image-20250412113806306

RPMB中用来计算哈希的是[283:0],不包括start bitend bitcrc16

img

那如果这个时候,host要写RPMB分区0x10地址的两个block(RPMB的block的大小是256B),MAC是怎么计算的呢?

  • host要写两个block,就要发出两个数据帧
  • MAC计算的输入
    • 两个数据帧的[283:0]区域作为一个message
    • host这边自己的key(可能是对的key也能是错的key,毕竟这是写操作嘛)
  • MAC计算的结果则放入最后的一个帧

img

RPMB操作方法

进入RPMB则是切换partion_acess,然后读写则是跟通常一样发送CMD23/CMD18/CMD25。下面列举几种情况。

写入KEY

写KEY是通过CMD25,在CMD25之前要先通CMD23的参数第31bit来设置block count为1,来表明是RPMB编程?

img

img

内核代码中有对应的代码设置bit31的操作

img

img

秘钥信息本身是通过数据包传递的,包的大小是512字节,包括了请求类型信息和秘钥。请求类型0x001表示是秘钥。秘钥(KEY)则是在[315:284]的位置

img

CRC数据以后DAT0会被eMMC拉低 ,代表eMMC处于busy状态。这时候,通过CMD13可以查看CMD25的执行状态。 读结果寄存器(Result register)来查看是否成功写入KEY。 可以去协议中看看这部分,这里就不赘述了。

img

读count值

读counter值是通过CMD25来实现的。请求类型值是0x0002,来表明请求读取计数值。

img

img

counter值本身实在读数据的数据包里面,也即是bit[11:8]。数据包大小是512字节。包含应答类型、nonce、counter值,MAC值和请求结果。如果读失败了,那么结果值是0x6。如果是其他的错误发生,那么结果值就是0x01。如果counter值已经过期了,结果值的bit7会设置为1 。

写数据

写入RPMB数据是通过多块写命令(CMD25)来完成的。在CMD25命令之前要先通过CMD23来设置要写的块数量。

注意!块的单位是256字节。 通过配置EXT_CSD[166]设置写方式。 block数量是所有的256大小块数量,请求类型0x003表示写数据。

img

  • eMMC接收到数据之后,首先检查counter值是否有效,如果counter值无效,那么就返回0x85的错误,数据将被丢弃。
  • 然后检查地址,如果地址不对,那么就返回0x4的错误。如果是多块写,而数据地址没有对齐,那么也是0x4的错误,数据也不会写入。
  • 接着检查MAC是否正确。如果eMMC计算的MAC和数据包中的MAC不匹配,那么返回0x2的错误。
  • 最后对比counter值是否和eMMC存储的一致,不一致返回错误0x3
  • 如果一切正常,那么数据写入RPMB分区,eMMC的counter值会加一。
  • 如果写失败返回0x5,其他错误返回0x1 。

读数据

img

测试操作过程

之前在qemu的文章中有提到uboot下怎么操作,这里就展示内核下是怎么操作的,以下操作均在板子上实现的

  • 先创建32字节的key,并写入key,key写完后最好重启下(看有的资料说要重启下)
# echo 'Authkeymustbe32byteslength_0000' > keyfile.txt
# mmc rpmb write-key /dev/mmcblk0rpmb keyfile.txt
# ls -lh keyfile.txt
-rw-r--r--    1 root     root          32 Jan  1 00:00 keyfile.txt
#

img

  • 创建一个256字节的数据,就是准备要写入RPMB分区的数据
dd if=/dev/random of=data.txt bs=1 count=256

img

img

# hexdump -C data.txt
00000000  81 f9 0e 65 c0 ec 25 53  1a d4 f3 af 6d 95 30 f3  |...e..%S....m.0.|
00000010  35 6e 2a 5f d1 db 9c 68  9b 57 7d eb 71 da 39 96  |5n*_...h.W}.q.9.|
00000020  55 bf 85 df d8 b2 31 ad  43 97 7c bb 03 88 0e 07  |U.....1.C.|.....|
00000030  d5 dd 95 5d bb 7f dd bd  40 90 5f a0 4d 84 02 19  |...]....@._.M...|
00000040  71 c5 de 35 fd 26 3d b3  4e 5a 79 86 ba 55 54 1f  |q..5.&=.NZy..UT.|
00000050  75 85 2a b7 e0 f6 2b b9  2a 4e 27 02 72 cc 4a de  |u.*...+.*N'.r.J.|
00000060  8d ea fa dc 11 be e6 55  41 e6 6c a7 c0 ea 97 d5  |.......UA.l.....|
00000070  f5 31 de 11 03 bf 40 7f  81 c2 91 d6 da c9 90 35  |.1....@........5|
00000080  a0 5b fa e0 7e d3 6b d5  ac 65 db 89 d2 97 5b 8a  |.[..~.k..e....[.|
00000090  1d dd 0f 1d d3 27 d0 84  97 ff 16 01 87 c5 63 86  |.....'........c.|
000000a0  16 b7 f2 1c d4 a5 26 3f  74 70 6b 65 82 0e 07 4d  |......&?tpke...M|
000000b0  4d 42 3a ec fc 27 fe f7  9d d8 13 ae d7 54 9c 5e  |MB:..'.......T.^|
000000c0  89 db 89 8f 5f b2 3d ac  df c0 7c 0d 87 53 43 7e  |...._.=...|..SC~|
000000d0  61 b1 4a e4 03 47 a0 06  96 39 9a f1 63 29 a4 b8  |a.J..G...9..c)..|
000000e0  d2 ea 08 b3 c4 dc 33 b1  b9 ba 9a 57 d9 04 44 59  |......3....W..DY|
000000f0  ac 5d 8f 9a f1 b1 65 72  a7 b3 a4 a7 bc b4 62 a8  |.]....er......b.|
00000100
#
  • 用正确的key写进去,一切正常
mmc rpmb write-block /dev/mmcblk0rpmb 0 data.txt keyfile.txt

img

  • 搞个不对的key写进去,会提示写失败
echo 'Authkeymustbe32byteslength_1234' > wrongkey.txt
mmc rpmb write-block /dev/mmcblk0rpmb 0 data.txt wrongkey.txt

img

  • 不用AuthKey读取RPMB数据, 虽然能读出数据,但其实并不能保证这个数据是否被篡改过的
mmc rpmb read-block /dev/mmcblk0rpmb 0 1 out.txt

img

  • 用正确的AuthKey读取RPMB数据,这个结果能读到数据,并且保证这个数据没有被修改过,而不是攻击者伪造的数据
mmc rpmb read-block /dev/mmcblk0rpmb 0 1 usekey.txt keyfile.txt

img

  • 用错误的AuthKey读取RPMB数据,提示RPMB MAC missmatch的错误,无法读取到数据
# mmc rpmb read-block /dev/mmcblk0rpmb 0 1 no.txt wrongkey.txt
RPMB MAC missmatch
#

img

  • 读取counter值

img

总结:

  • 上面所做的所有操作,必须在可信任的环境下执行,也就是TEE环境,保护AuthKey不被泄露
  • 由于RPMB的读操作没有AuthKey也能返回的,因此,存入RPMB的数据最好是经过加密的

代办事项

可以在optee/ATF中去尝试访问RPMB,反正就是在一个安全的环境下去做

参考链接

想法

是不是以后设计一种加密的算法或者流程,可以参考这个呢?