随着射频识别技术的发展,射频卡被广泛应用在了门禁控制、金融支付、库存管理等场景。在此背景下,各种安全认证机制应运而生,为保护个人隐私和敏感数据提供了可靠的保障,本文将通过一道 CTF 题目介绍 M1 卡采用的 AES(Advanced Encryption Standard)认证机制,揭示其背后的原理
1、如果一个读卡器周围有多张卡,读卡器的电磁场激活卡片后,卡片会把自己的 UID 回复给读卡器,读卡器根据 UID 选择卡片,然后读卡器与卡片进行认证,通过之后才进行正常的数据传输,避免信息的混肴和丢失
2、ISO/IEC 14443-3 是一项国际标准,主要涉及近场通讯(NFC)和射频识别(RFID)技术中用于接触式集成电路卡(IC卡)和读卡器之间通信的协议标准,在 14443-3 中读卡器(近距离耦合设备)简写为 PCD,卡片(近距离集成电路卡片)简写为 PICC
我们将通过 2022 DCTF SecureCard 这道题目来学习 M1 卡的 AES 认证机制,题目可以在这里下载:
https://github.com/DragonSecSI/DCTF-2022/blob/master/challs/securecard/chall/card.trace
拿到题目根据后缀名 .trace 结合题目名称 SecureCard 可以推断出这是通过 pm3 嗅探得到的卡片与读卡器之间的 trace 文件,那么我们便可以通过 pm3 的客户端加载这个 trace 文件,看一下内容是什么
没有下载过 pm3 的朋友可以去下载官方提供的编译好的程序(https://www.proxmarkbuilds.org/),直接双击 pm3.bat 即可打开
pm3 功能对应的命令特别多,但我们通过 help 查看帮助可以很快的找到我们需要的命令,可以看到在第一级菜单里面就有 trace 命令
我们直接输入 trace 就可以看到这条命令都有什么操作了,很明显,我们需要从文件里面导入 trace,因此我们应该使用 trace load 命令
输入 trace load 后因为缺少必要的参数,pm3 会贴心的给我们贴出用法,并给出示例,根据示例,我们可以使用 trace load -f card.trace 来把题目附件加载进来
加载了 441 bytes,但是问题来了,我们该怎么看加载进来的 trace 文件呢,在 [?] 这一行已经有提示了,使用 trace list -1 -t 来查看 trace
那么我们执行发现报错了,pm3 提示我们缺少一个必要的参数,建议我们使用 --help 看一下帮助文档
运行帮助发现命令后面需要加一个解析方式,例如 raw 就是仅展示原始数据,不带注释
我们也不知道该选什么的,那就都解析一遍看看效果吧,经过不断的尝试,发现使用命令 trace list -1 -t des 按照 MIFARE DESFire 解析的时候解析的最好,注释最全
我摘出来重要部分通过代码框列为文字版本方便查看
Src | Data (! denotes parity error) | CRC | Annotation
-----+-------------------------------------------------------------------------+-----+--------------------
Rdr |52 | | WUPA
Tag |44 03 | |
Rdr |93 20 | | ANTICOLL
Tag |88 04 29 44 e1 | |
Rdr |93 70 88 04 29 44 e1 a1 33 | ok | SELECT_UID
Tag |24 d8 36 | |
Rdr |95 20 | | ANTICOLL-2
Tag |d2 db 6b 80 e2 | |
Rdr |95 70 d2 db 6b 80 e2 b8 34 | ok | SELECT_UID-2
Tag |20 fc 70 | |
Rdr |e0 80 31 73 | ok | RATS
Tag |06 75 77 81 02 80 02 f0 | ok |
Rdr |0a 00 5a 37 13 00 57 a2 | ok | SELECT APPLICATION (appId 001337)
Tag |0a 00 00 6e d6 | |
Rdr |0b 00 aa 00 9a c4 | ok | AUTH AES (keyNo 0)
Tag |0b 00 af a7 18 45 be 52 8a 7e 8e 08 16 3d 06 3d 95 42 | |
|aa c0 b6 | ok |
Rdr |0a 00 af 2c 2a bd a6 a1 f9 df f5 0b 87 37 6c 30 57 5b | |
|c3 0e 62 4f cd f6 6f 04 0a 3c a1 65 47 47 e2 81 47 28 | |
|b8 | ok | AUTH FRAME / NEXT FRAME
Tag |0a 00 00 60 f9 01 97 5a 30 25 78 5c 0d 43 70 8a de 38 | |
|b2 de b2 | ok |
Rdr |0b 00 f5 01 2c 85 | ok | GET FILE SETTINGS (fileId 01)
Tag |0b 00 00 00 03 00 00 19 00 00 18 6b 65 df 80 ba c2 87 | |
|9d be | ok |
Rdr |0a 00 bd 01 00 00 00 00 00 00 ff 7c | ok | READ DATA (fileId 01, offset 0, len 0)
Tag |0a 00 00 4c be b5 2c 49 15 35 0e af b5 dc fc a9 52 d9 | |
|50 99 4c 12 a1 cf 07 09 82 33 99 57 b4 40 a1 0a 36 01 | |
|7c | ok |
既然是 14443-3 定义的标准,那么我们就从标准入手,逐行分析嗅探数据了解整个交互过程
数据 52 表示 WUPA,是唤醒 Type A 卡的指令(同理 WUPB 是唤醒 Type B 的),卡片收到 WUPA 后会回复 ATQA 告诉读卡器是否遵守面向比特的防冲突机制,这里回复的是 44 03,但 ISO14443 规定的传输方式是首先传输低位,所以实际值是 03 44,格式如下:
实际对应到 ATQA 编码的表中为:
注:RFU 全为 0,Bit frame anticollision 中只有一个为 1 即可
UID size 表示 UID 的长度,"00" 为 4 字节,"01" 为 7 字节,"10" 为 10 字节:
b8 | b7 | 含义 |
---|---|---|
0 | 0 | UID大小:单个 |
0 | 1 | UID大小:double |
1 | 0 | UID大小:三倍 |
接下来进入防冲突循环,读卡器此时并不知道卡片的 UID,因此读卡器发送 93 20,其中 SEL 是 93,NVB 是 20
SEL | NVB |
---|---|
93 | 20 |
SEL 的 93 表示的是 Select cascade level 1
NVB 的 20 表示有效位数。高四位被称为:字节计数,是读卡器发送的所有有效数据位的数量除以 8 的整数部分,低四位被称为:位计数,是读卡器发送的所有有效数据的数量模 8
b8 | b7 | b6 | b5 | 含义 |
---|---|---|---|---|
0 | 0 | 1 | 0 | 字节计数=2 |
0 | 0 | 1 | 1 | 字节计数=3 |
0 | 1 | 0 | 0 | 字节计数=4 |
0 | 1 | 0 | 1 | 字节计数=5 |
0 | 1 | 1 | 0 | 字节计数=6 |
0 | 1 | 1 | 1 | 字节计数=7 |
b4 | b3 | b2 | b1 | 含义 |
---|---|---|---|---|
0 | 0 | 0 | 0 | 位计数=0 |
0 | 0 | 0 | 1 | 位计数=1 |
0 | 0 | 1 | 0 | 位计数=2 |
0 | 0 | 1 | 1 | 位计数=3 |
0 | 1 | 0 | 0 | 位计数=4 |
0 | 1 | 0 | 1 | 位计数=5 |
0 | 1 | 1 | 0 | 位计数=6 |
0 | 1 | 1 | 1 | 位计数=7 |
这时候所有收到的卡片应该回复自己的 UID:88 04 29 44 e1,格式如下:
UID | BCC | |||
---|---|---|---|---|
88 | 04 | 29 | 44 | e1 |
其中 UID 又分为 CT(88)和 UID_CLn(042944)读卡器收到之后把 NVB 设置为 70,然后选择卡片 93 70 88 04 29 44 e1 a1 33
SEL | NVB | UID | BCC | CRC | ||||
---|---|---|---|---|---|---|---|---|
93 | 70 | 88 | 04 | 29 | 44 | e1 | a1 | 33 |
然后卡片向读卡器回复 SAK (24 d8 36)
24 的高八位 表示是否符合14443-4 | 24 的低八位 表示 UID 是否完成 | CRC |
---|---|---|
0010 符合 | 0100 未完成 | 36D8 |
注意这里的符合与否,仅看一位二进制位即可,例如是否符合 14443-4 仅看高八位的第三位是否为 1,为 1 则表示符合,为 0 则表示不符合;是否完成仅看低八位的第二位是否为 0,为 0 则表示完成,为 1 则表示未完成
这里的 UID 未完成所以继续防冲突循环,选择卡片,95 20 表示 ANTICOLL-2
SEL | NVB |
---|---|
95 | 20 |
然后得到 UID:d2 db 6b 80 e2
UID | BCC | |||
---|---|---|---|---|
d2 | db | 6b | 80 | e2 |
这时候再次选择卡片:95 70 d2 db 6b 80 e2 b8 34
SEL | NVB | UID | BCC | CRC | ||||
---|---|---|---|---|---|---|---|---|
95 | 70 | d2 | db | 6b | 80 | e2 | 34b8 |
这次卡片回复了读卡器的 SAK 就表示完成了:20 fc 70
20 的高八位 表示是否符合14443-4 | 20 的低八位 表示 UID 是否完成 | CRC |
---|---|---|
0010 符合 | 0000 完成 | 70fc |
然后读卡器发送 RATS 获取一些具体类型和配置参数什么的:e0 80 31 73
FSDI(Frame Size Diversification Identifier):FSDI字段指示读卡器请求的最大帧大小。它决定了读卡器和智能卡之间数据交换的最大帧大小
CID(Card Identifier):CID字段用于标识智能卡。读卡器可以使用CID来区分与之通信的多个智能卡
Start byte | FSDI | CID | CRC |
---|---|---|---|
e0 | 8 | 0 | 7331 |
卡片回复 ATS :06 75 77 81 02 80 02 f0 这里主要是一些传输速率、时钟频率等信息就不多介绍了
下面的嗅探数据都是 0a 00 和 0b 00,通过 pm3 的解析可以看出来,首先选择了一个 APPLICATION(appId 001337)
Rdr |0a 00 5a 37 13 00 57 a2 | ok | SELECT APPLICATION (appId 001337)
Tag |0a 00 00 6e d6 | |
然后开始进行 AES 认证,经过两次交互完成认证
Rdr |0b 00 aa 00 9a c4 | ok | AUTH AES (keyNo 0)
Tag |0b 00 af a7 18 45 be 52 8a 7e 8e 08 16 3d 06 3d 95 42 | |
|aa c0 b6 | ok |
Rdr |0a 00 af 2c 2a bd a6 a1 f9 df f5 0b 87 37 6c 30 57 5b | |
|c3 0e 62 4f cd f6 6f 04 0a 3c a1 65 47 47 e2 81 47 28 | |
|b8 | ok | AUTH FRAME / NEXT FRAME
Tag |0a 00 00 60 f9 01 97 5a 30 25 78 5c 0d 43 70 8a de 38 | |
|b2 de b2 | ok |
认证结束后选择了一个文件的设置并读取了其中的的数据,我们要寻找的 flag 就在 READ DATA 的回复中
Rdr |0b 00 f5 01 2c 85 | ok | GET FILE SETTINGS (fileId 01)
Tag |0b 00 00 00 03 00 00 19 00 00 18 6b 65 df 80 ba c2 87 | |
|9d be | ok |
Rdr |0a 00 bd 01 00 00 00 00 00 00 ff 7c | ok | READ DATA (fileId 01, offset 0, len 0)
Tag |0a 00 00 4c be b5 2c 49 15 35 0e af b5 dc fc a9 52 d9 | |
|50 99 4c 12 a1 cf 07 09 82 33 99 57 b4 40 a1 0a 36 01 | |
|7c | ok |
从 nfc-tools 源码(https://github.com/nfc-tools/libfreefare/blob/master/libfreefare/mifare_desfire.c#L401-L489)中可以看到 mifare_desfire 的认证方法调用了 mifare_desfire_session_key_new 函数(https://github.com/nfc-tools/libfreefare/blob/master/libfreefare/mifare_desfire_key.c#L171-L210)来生成 session_key 作为加密密钥,同时通过 pm3 的源码(https://github.com/RfidResearchGroup/proxmark3/blob/master/client/src/mifare/desfirecore.c#L1098)可以发现初始向量和密钥都是全为 0 的
这里对源码进行阅读后添加了部分注释
static int
authenticate(FreefareTag tag, uint8_t cmd, uint8_t key_no, MifareDESFireKey key)
{
int rc;
ASSERT_ACTIVE(tag); //检查卡片书否处于激活状态
memset(MIFARE_DESFIRE(tag)->ivect, 0, MAX_CRYPTO_BLOCK_SIZE);
MIFARE_DESFIRE(tag)->authenticated_key_no = NOT_YET_AUTHENTICATED; //设置初始化向量
free(MIFARE_DESFIRE(tag)->session_key); //重置一些标签的属性,包括认证状态、会话密钥等
MIFARE_DESFIRE(tag)->session_key = NULL;
MIFARE_DESFIRE(tag)->authentication_scheme = (AUTHENTICATE_LEGACY == cmd) ? AS_LEGACY : AS_NEW; //根据传进来的参数选择加密方式
BUFFER_INIT(cmd1, 2); //创建缓冲区
BUFFER_INIT(res, 17);
BUFFER_APPEND(cmd1, cmd);
BUFFER_APPEND(cmd1, key_no);
if ((rc = MIFARE_DESFIRE_TRANSCEIVE(tag, cmd1, __cmd1_n, res, __res_size, &__res_n)) < 0)
return rc; //发送认证命令给卡片
size_t key_length = __res_n - 1;
uint8_t PICC_E_RndB[16];
memcpy(PICC_E_RndB, res, key_length); //把卡片产生的的随机数保存到PICC_E_RndB
uint8_t PICC_RndB[16];
memcpy(PICC_RndB, PICC_E_RndB, key_length); //又把PICC_E_RndB保存到PICC_RndB
mifare_cypher_blocks_chained(tag, key, MIFARE_DESFIRE(tag)->ivect, PICC_RndB, key_length, MCD_RECEIVE, MCO_DECYPHER); //这是对传过来的随机数进行解密,得到真的随机数
uint8_t PCD_RndA[16];
RAND_bytes(PCD_RndA, 16); //本地产生PCD随机数
uint8_t PCD_r_RndB[16];
memcpy(PCD_r_RndB, PICC_RndB, key_length); //将卡片的随机数明文复制到PCD_r_RndB
rol(PCD_r_RndB, key_length); //将卡片随机数进行左移
uint8_t token[32];
memcpy(token, PCD_RndA, key_length);
memcpy(token + key_length, PCD_r_RndB, key_length); //将PCD随机数和PICC随机数拼接起来
//下面对拼起来的随机数进行了加密操作
mifare_cypher_blocks_chained(tag, key, MIFARE_DESFIRE(tag)->ivect, token, 2 * key_length, MCD_SEND, (AUTHENTICATE_LEGACY == cmd) ? MCO_DECYPHER : MCO_ENCYPHER);
BUFFER_INIT(cmd2, 33);
BUFFER_APPEND(cmd2, 0xAF);
BUFFER_APPEND_BYTES(cmd2, token, 2 * key_length);
if ((rc = MIFARE_DESFIRE_TRANSCEIVE(tag, cmd2, __cmd2_n, res, __res_size, &__res_n)) < 0) //发送PCD和PICC拼接起来加密后的随机数
return rc;
uint8_t PICC_E_RndA_s[16];
memcpy(PICC_E_RndA_s, res, key_length); //再把卡片响应的内容放到PICC_E_RndA_s
uint8_t PICC_RndA_s[16];
memcpy(PICC_RndA_s, PICC_E_RndA_s, key_length);//并解密卡片相应的数据
mifare_cypher_blocks_chained(tag, key, MIFARE_DESFIRE(tag)->ivect, PICC_RndA_s, key_length, MCD_RECEIVE, MCO_DECYPHER);
uint8_t PCD_RndA_s[key_length];
memcpy(PCD_RndA_s, PCD_RndA, key_length); //
rol(PCD_RndA_s, key_length); //PCD随机数和PICC随机数拼接起来的那个随机数循环左移
if (0 != memcmp(PCD_RndA_s, PICC_RndA_s, key_length)) { //将解密完成的PCD_RndA_s和读卡器自己左移的随机数对比
#ifdef WITH_DEBUG
hexdump(PCD_RndA_s, key_length, "PCD ", 0);
hexdump(PICC_RndA_s, key_length, "PICC ", 0);
#endif
errno = EACCES;
return -1; //不对的话就退出了
}
MIFARE_DESFIRE(tag)->authenticated_key_no = key_no;
MIFARE_DESFIRE(tag)->session_key = mifare_desfire_session_key_new(PCD_RndA, PICC_RndB, key); //对的话根据两个随机数生成密钥
memset(MIFARE_DESFIRE(tag)->ivect, 0, MAX_CRYPTO_BLOCK_SIZE);
switch (MIFARE_DESFIRE(tag)->authentication_scheme) {
case AS_LEGACY:
break;
case AS_NEW:
cmac_generate_subkeys(MIFARE_DESFIRE(tag)->session_key);
break;
}
return 0;
}
其中生成的 session_key 主要由前两次交换的随机数生成,生成的方法是 a[:4] + b[:4] + a[12:16] + b[12:16] ,a 是 PCD(读卡器)的随机数,b 是 PICC(卡片)的随机数
case MIFARE_KEY_AES128:
memcpy(buffer, rnda, 4);
memcpy(buffer + 4, rndb, 4);
memcpy(buffer + 8, rnda + 12, 4);
memcpy(buffer + 12, rndb + 12, 4);
key = mifare_desfire_aes_key_new(buffer);
整体流程梳理如下
对应于嗅探的数据中,a71845be528a7e8e08163d063d9542aa 是卡片生成的随机数 b 加密后的数据
2c2abda6a1f9dff50b87376c30575bc30e624fcdf66f040a3ca1654747e28147 是读卡器生成的随机数 a 和随机数 b 拼接起来加密后的数据
60f901975a3025785c0d43708ade38b2 是卡片加密随机数 a 后的数据
最终生成的 session_key 就是把两个随机数明文拼接一下 010203044e71b50c131415160e816b38
我们通过 python 实现一下这个过程就是:
from Crypto.Cipher import AES
key = b'\x00'*16 #定义初始key和iv
iv = b'\x00'*16
encrypt = AES.new(key, AES.MODE_CBC, iv=iv) #定义了一个加密方法
decrypt = AES.new(key, AES.MODE_CBC, iv=iv) #定义了一个解密方法
b = b'Nq\xb5\x0c\xf7Z\xde\xe4\xda;\x11=\x0e\x81k8' #定义明文随机数b
card_encrypted_b = encrypt.encrypt(b) #将明文b加密为card_encrypted_b
assert card_encrypted_b == bytes.fromhex('a71845be528a7e8e08163d063d9542aa') #检查加密后的b是否与嗅探到的数据相同
assert b == decrypt.decrypt(card_encrypted_b) #将密文解密对应了认证过程中的读卡器解密卡片发过来的随机数b
a = b'\x01\x02\x03\x04\x05\x06\x07\x08\t\x10\x11\x12\x13\x14\x15\x16' #读卡器生成明文随机数a
reader_encrypted_a_plus_b1 = encrypt.encrypt(a + b[1:]+ bytes([b[0]])) #读卡器将随机数a和左移后的随机数b进行拼接
#这里判断一下拼接加密后的数据是不是和嗅探到的数据相同
assert reader_encrypted_a_plus_b1 == bytes.fromhex('2c2abda6a1f9dff50b87376c30575bc30e624fcdf66f040a3ca1654747e28147')
a_plus_b1 = decrypt.decrypt(reader_encrypted_a_plus_b1) #将嗅探到的数据进行解密,对应卡片解密读卡器的数据
b1 = a_plus_b1[16:] #从加密后解密的数据中提取了随机数b的部分
assert b == bytes([b1[-1]])+b1[:-1] #移位还原判断是不是b的明文,这里对应了卡片确认读卡器加密的随机数b是否正确
card_encrypted_a1 = encrypt.encrypt(a[1:] + bytes([a[0]])) #卡片左移随机数a加密发送给读卡器
assert card_encrypted_a1 == bytes.fromhex('60f901975a3025785c0d43708ade38b2') #判断一下加密后的数据是不是和嗅探到的数据相同
a1 = decrypt.decrypt(card_encrypted_a1) #对应读卡器解密随机数a
assert a == bytes([a1[-1]])+a1[:-1] #判断是否是自己生成的随机数a
session_key = a[:4] + b[:4] + a[12:16] + b[12:16] #生成session_key
print("session key:",session_key.hex())
但是问题来了,通过这个 key 和全 0 的 IV 解密最后的数据只有一部分 flag
b'\x98{\xa4WY {\xba\xbf}<@\x90\xb9\xb3\x06pl1c4t3d}V\xb7\xd5S\x80\x00\x00'
肯定是 IV 出问题了,阅读源码发现:尽管每次认证都会重置 IV 为全 0,但是后续使用时没有再重置过 IV 了,所以 IV 可能变为了一个未知数,每次调用 mifare_cryto_preprocess_data 时会有一个 cmac 函数把 IV 更新的 cmac 变量中
原 WP 是通过自己编写了一个 C 语言程序调用 libfreefare 这个库,不断地计算不断地尝试,最终确定 IV 是 6d52ed2a407e1cb75c276fa4a5981a95 的时候可以解出来 flag
但是我们仔细观察就会发现,实际上在完成认证之后进行了三次通信,分别传输的数据为:F501、0003000019000000、BD01000000000000,我们只需要以这三轮的 data 为参数,以新计算出来的 cmac 为下一轮的 IV 就能得到传输 flag 时使用的 IV 了
不太会用 libfreefare 这个库,所以在网上找了很多 python 实现的代码,最终找到了一个用 python2 实现的代码:https://gist.github.com/mbenedettini/1409585,稍微一改得到:
from Crypto.Cipher import AES
from bitstring import BitArray
key = BitArray(hex='010203044e71b50c131415160e816b38')
m = bytes.fromhex('F501')
const_rb = BitArray(hex='00000000000000000000000000000087')
k0 = BitArray(hex=AES.new(key.bytes, AES.MODE_CBC, IV=b'\x00'*16).encrypt(b'\x00'*16).hex())
k0_msb = k0[2:][0:1]
if k0_msb == '0':
k1 = k0 << 1
else:
k1 = (k0 << 1) ^ const_rb
print("K0: {k0} \nK1: {k1}".format(k0=k0, k1=k1))
k1_msb = k1[2:][0:1]
if k1_msb == '0':
k2 = k1 << 1
else:
k2 = (k1 << 1) ^ const_rb
print("K2: {k2}".format(k2=k2))
d = BitArray(hex=m.hex())
padded = False
if len(d.bytes) < 16:
padded = True
d.append('0x80')
while len(d.bytes) < 16:
d.append('0x00')
print("d size: %s" % len(d.bytes))
xor_component = None
if padded:
xor_component = BitArray(bytes.fromhex(k2.hex))
else:
xor_component = BitArray(bytes.fromhex(k1.hex))
xored_d = BitArray().join([ d[0:16*8] ^ xor_component , d[16*8:]])
print("xored_d: %s" % xored_d)
ek_xored_d = BitArray()
BLOCK_SIZE = 16 * 8 # Constant
# Split data into 16-byte long pieces
data_blocks = [xored_d[i:i+BLOCK_SIZE] for i in range(0, len(xored_d), BLOCK_SIZE)]
c = AES.new(key.bytes, AES.MODE_CBC, BitArray(hex='00'*16).bytes)
for block in data_blocks:
ek_xored_d.append(BitArray(hex=c.encrypt(block.bytes).hex()))
print("ek_xored_d: %s" % ek_xored_d)
cmac = ek_xored_d[-16*8:]
print("cmac: ", cmac)
将上面的代码写成函数的形式,调用三次生成 cmac 后,将 cmac3 作为 IV 去解密就可以得到 flag 了
from Crypto.Cipher import AES
from bitstring import BitArray, Bits
def mifare_desfire_aes_key_new(session_key):
const_rb = BitArray(hex='00000000000000000000000000000087')
IV = b'\x00'*16
m = b'\x00'*16
k0 = BitArray(hex=AES.new(session_key, AES.MODE_CBC, IV=IV).encrypt(m).hex())
#print("K0: {k0}".format(k0=k0))
k0_msb = k0[2:][0:1]
if k0_msb == '0':
k1 = k0 << 1
else:
k1 = (k0 << 1) ^ const_rb
#print("K1: {k1}".format(k1=k1))
k1_msb = k1[2:][0:1]
if k1_msb == '0':
k2 = k1 << 1
else:
k2 = (k1 << 1) ^ const_rb
#print("K2: {k2}".format(k2=k2))
return session_key,bytes.fromhex(k1.hex),bytes.fromhex(k2.hex)
def gen_cmac(key, k1, k2, iv, data):
d = BitArray(hex=data.hex())
padded = False
if len(d.bytes) < 16:
padded = True
d.append('0x80')
while len(d.bytes) < 16:
d.append('0x00')
xor_component = None
if padded:
xor_component = BitArray(bytes.fromhex(k2.hex()))
else:
xor_component = BitArray(bytes.fromhex(k1.hex()))
xored_d = BitArray().join([ d[0:16*8] ^ xor_component , d[16*8:]])
#print("xored_d: %s" % xored_d)
ek_xored_d = BitArray()
BLOCK_SIZE = 16 * 8
data_blocks = [xored_d[i:i+BLOCK_SIZE] for i in range(0, len(xored_d), BLOCK_SIZE)]
c = AES.new(key, AES.MODE_CBC, iv)
for block in data_blocks:
ek_xored_d.append(BitArray(hex=c.encrypt(block.bytes).hex()))
cmac = ek_xored_d[-16*8:]
return bytes.fromhex(cmac.hex)
key = b'\x00'*16 #定义初始key和iv
iv = b'\x00'*16
encrypt = AES.new(key, AES.MODE_CBC, iv=iv) #定义了一个加密方法
decrypt = AES.new(key, AES.MODE_CBC, iv=iv) #定义了一个解密方法
#解出随机数b
b = decrypt.decrypt(bytes.fromhex('a71845be528a7e8e08163d063d9542aa'))
#解出随机数a和左移的随机数b
a_plus_b1 = decrypt.decrypt(bytes.fromhex('2c2abda6a1f9dff50b87376c30575bc30e624fcdf66f040a3ca1654747e28147'))
#解出左移的随机数a
a1 = decrypt.decrypt(bytes.fromhex('60f901975a3025785c0d43708ade38b2'))
#还原随机数a
a = bytes([a1[-1]])+a1[:-1]
#生成session_key
session_key = a[:4] + b[:4] + a[12:16] + b[12:16]
key,k1,k2 = mifare_desfire_aes_key_new(session_key)
cmac1 = gen_cmac(key, k1, k2, iv, bytes.fromhex('F501'))
cmac2 = gen_cmac(key, k1, k2, cmac1, bytes.fromhex('0003000019000000'))
#得到最终的IV
cmac3 = gen_cmac(key, k1, k2, cmac2, bytes.fromhex('BD01000000000000'))
#重新创建一个解密方法,使用计算得到的
session = AES.new(session_key, AES.MODE_CBC, iv=cmac3)
flag = session.decrypt(bytes.fromhex('4cbeb52c4915350eafb5dcfca952d950994c12a1cf070982339957b440a10a36'))
print(flag)
最终的 flag 为:dctf{rf1d_15_c0mpl1c4t3d}
b'dctf{rf1d_15_c0mpl1c4t3d}V\xb7\xd5S\x80\x00\x00'