通过一道CTF题目学习M1卡的AES认证机制

admin 2023年8月9日20:02:32评论15 views字数 13783阅读45分56秒阅读模式

前言

随着射频识别技术的发展,射频卡被广泛应用在了门禁控制、金融支付、库存管理等场景。在此背景下,各种安全认证机制应运而生,为保护个人隐私和敏感数据提供了可靠的保障,本文将通过一道 CTF 题目介绍 M1 卡采用的 AES(Advanced Encryption Standard)认证机制,揭示其背后的原理。

注:CTF(Capture The Flag)是网络安全爱好者之间进行技能切磋的比赛,比赛将设置一系列藏有 “flag” 的题目环境,参赛者运用自己的网络安全技能寻找藏匿的 “flag” 来证明他们攻克了特定环境的题目

前置知识

1、如果一个读卡器周围有多张卡,读卡器的电磁场激活卡片后,卡片会把自己的 UID 回复给读卡器,读卡器根据 UID 选择卡片,然后读卡器与卡片进行认证,通过之后才进行正常的数据传输,避免信息的混肴和丢失

2、ISO/IEC 14443-3 是一项国际标准,主要涉及近场通讯(NFC)和射频识别(RFID)技术中用于接触式集成电路卡(IC卡)和读卡器之间通信的协议标准,在 14443-3 中读卡器(近距离耦合设备)简写为 PCD,卡片(近距离集成电路卡片)简写为 PICC

2022 DCTF SecureCard

我们将通过 2022 DCTF SecureCard 这道题目来学习 M1 卡的 AES 认证机制,题目我在这里也提供一份附件:

https://developer.qcloudimg.com/user/attachment/6858803/20230808-832ae4a0.zip

题目着手点

拿到题目根据后缀名 .trace 结合题目名称 SecureCard 可以推断出这是通过 pm3 嗅探得到的卡片与读卡器之间的 trace 文件,那么我们便可以通过 pm3 的客户端加载这个 trace 文件,看一下内容是什么

pm3简易使用指南

没有下载过 pm3 的朋友可以去下载官方提供的编译好的程序,直接双击 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

通过一道CTF题目学习M1卡的AES认证机制

那么我们执行发现报错了,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 这里主要是一些传输速率、时钟频率等信息就不多介绍了

通信过程分析(AES认证)

下面的嗅探数据都是 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 源码 中可以看到 mifare_desfire 的认证方法调用了 mifare_desfire_session_key_new 函数来生成 session_key 作为加密密钥,同时通过 pm3 的源码可以发现初始向量和密钥都是全为 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'Nqxb5x0cxf7Zxdexe4xda;x11=x0ex81k8' #定义明文随机数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'x01x02x03x04x05x06x07x08tx10x11x12x13x14x15x16' #读卡器生成明文随机数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 {xbaxbf}<@x90xb9xb3x06pl1c4t3d}Vxb7xd5Sx80x00x00'

肯定是 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 这个库,原 WP 给了源码我也没跑起来,所以在网上找了很多 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}Vxb7xd5Sx80x00x00'

复制

总结和展望

本文通过一道 CTF 题目的了解了 M1 卡的 AES 认证机制,通过针对开源库的源码进行逐行分析了解了 AES 认证机制,读者可自行阅读源码分析,并扩展到其他内容

选题思路

随着射频识别技术的发展,射频卡被广泛应用在了门禁控制、金融支付、库存管理等场景。在此背景下,各种安全认证机制应运而生,为保护个人隐私和敏感数据提供了可靠的保障。在网络安全领域攻防一直时不断对抗不断进化的,然而对于射频技术底层原理及认证机制需要研究实现源码和 ISO 标准,耗时耗力

本文将通过一道 CTF 题目介绍 M1 卡采用的 AES(Advanced Encryption Standard)认证机制,揭示其背后的原理,通过 python 语言快速模拟认证过程,为安全研究人员及网络安全爱好者提供快速了解 M1 卡 AES 认证机制的方式。同时本文结合开源第三方库的源码逐行注释分析认证及通信过程,也为安全研究人员深入挖掘认证过程漏洞提供的切入点

创作提纲

1、前置知识(主要为没有了解过 M1 卡机制的读者简单介绍卡片与读卡器之间的通信机制,使文章不管是由经验还是没有经验的朋友都能阅读)

2、提供题目附件来源(方便读者一边阅读一边自己动手操作)

3、题目着手点(介绍拿到题目后解题的切入点,有理有据引出下文)

4、pm3简易使用指南(简单介绍第三方软件的使用方法,教读者读取题目附件内容)

5、卡片选择过程分析(结合 ISO 14443 与 pm3 解析的通信过程将通信过程的每一步与 ISO 14443 标准对应起来,解析每个字段的含义,使读者了解 14443 协议,对于射频卡机制有一定了解)

6、AES认证过程分析(来到重点内容,结合第三方库源码逐行注释分析 AES 认证机制,并通过 python 自己实现一遍认证过程,让读者先了解整个认证过程,并逐步带领读者解决存在的问题)

7、总结和展望(总结了整篇分析过程,为后续读者自行探索提供思路)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年8月9日20:02:32
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   通过一道CTF题目学习M1卡的AES认证机制http://cn-sec.com/archives/1943792.html

发表评论

匿名网友 填写信息