DesfireCard Read项目总结

DesfireCard Read项目总结,第1张

--------------------坑-------------------------

DesfireCard Read项目总结

由于之前读卡器模块读卡速度过慢,换了读卡器模块。所以需要开发android端的程序。下面记录一下工作中出现的问题。

一、命名的问题。

函数名的大小写、格式(需要使用统一规则),以及英文的语法(注意例如时态、主谓宾的顺序)问题。

二、SDK的封装。

什么是需要封装进SDK中的以及需要传出的数据,这里是需要考虑的问题。

例如,所定义的函数select_applications的返回值。

这里不能返回数据长度一般为不太复杂的数据,需要判断的东西(即判断length的大小),要放在SDK中,不能写在外面,应该定义length >= 91的时候返回false。

注意数据的传出问题。

三、组包以及USB的通讯。

①首先,需要定义命令。

android端与读卡器模块的通讯需多次按一定的规律发送命令,因此需要组包。具体命令是通过PC端的软件解出来的,我需要发送的包,就是解出来的数据,并且保证在发送包之后能得到同样的数据。

组包的思路是首先定义一个String,例如private String cmd_add_0 = "11 10 00 4d 00 20 00",然后定义一个函数public byte[] create_packet(byte cmd,String add_data),在函数内对String数据进行处理转换成byte[],然后拼接数据或改变某个位置的数据,create_packet的返回值byte[]并不是实际发送的数据(注意这里还有一个序列号需要修改,因为USB通讯序列号是会发生改变的,因此还需要定义一个函数private boolean send_cmd(byte cmd, String add_cmd_data, int ms, byte[] data),data用来传出返回的数据,即接收到的数据。在函数内改变序列号的位置,真正发送的数据的是通过send函数,发送的byte是packet这个byte[],global_seq是一个表示序列号的全局变量,在send_cmd函数内定义一个自加1 *** 作,以控制序列号增长,同时,在发送命令之后,需要验证序列号是否相等,然后才会对data进行赋值)。

byte[] packet  = create_packet(cmd, add_cmd_data)

packet[6] = global_seq//组包完成

byte[] recv = send(packet, ms)//发送命令

.......................................................................

global_seq += 1

这里注意要考虑函数返回值的问题以及recv为null的情况。当recv为null时(即发送命令之后收到的数据异常的情况,我们需要做一个判断),需要返回false,否则可能会出现bug。

②USB的通讯。

1.枚举USB设备。

USB设备的识别是通过PID和VID来识别的。因此,首先要知道读卡器设备的这两个数据并定义在res目录下的xml文件中,然后在manifest中的对应activity中注册。

android:resource="@xml/device_filter" />

同时,需要加上这一句feature。

<uses-feature android :name= "android.hardware.usb.host" android :required= "false" />

USB主要相关类有:UsbManager、UsbDevice、UsbDeviceConnection、UsbEndpoint。

UsbManager :通过getSystemService实例化。

mUsbManager = (UsbManager) context.getSystemService(Context. USB_SERVICE )

UsbDevice:表示的是设备。通过调用UsbManager.getDeviceList,获取了deviceList,然后遍历deviceList,通过调用getVendorId可以获取连接到android的USB设备的VID,然后通过判断当设备的VID来找到设备即device,然后就可以对设备进行 *** 作了(device.getVendorId() == 0x273a)。

UsbDeviceConnection:调用mUsbManager.openDevice(device)返回该类,用来表示打开device的状态。

UsbEndpoint:一个USB有3种EP。Input、Output、Interrupt。USB通讯是通过对EP进行 *** 作来进行通讯的。实例化mDevice之后,通过调用getInterface获取UsbInterface,然后对UsbInterface进行 *** 作即可获取EP。

在获取了EP之后,判断app是否具有USB通讯的权限(具体申请权限写在下面),有权限才能进行openDevice *** 作,否则会报错,openDevice返回UsbDeviceConnection。

mUsbManager .hasPermission(mDevice)

mUsbManager .openDevice(device)

//0、1、2分别对应了不同的EP。Input和Output均是指对android设备而言。

mEndpointIn = GetEndPoint( device,2)

mEndpointOut = GetEndPoint( device,1)

mInterruptEndpoint = GetEndPoint( device,0)

//定义函数

public UsbEndpointGetEndPoint( UsbDevice device, int nEndpoint){

...........

UsbInterface intf = device.getInterface(0)

ep1 = intf.getEndpoint( nEndpoint )//intput和output的EP

if (ep1.getType() == USB_ENDPOINT_XFER_INT ){

ep1 = intf.getEndpoint( nEndpoint )//虽然这个判断感觉没有用。

...........

}

}

读到卡返回的命令为80 03(这里是十进制,对应十六进制是50) ,Interrupt返回的命令为80 02。(注意interrupt也算In的一种)。

IN     50 03

IN     50 03

2.申请通讯权限。android中USB通讯是需要申请通讯权限的。具体思路是动态定义一个broadcast,然后再注册。要确保申请权限之后才执行usb相关 *** 作,否则容易出错,并且要考虑无device情况(即没有插入读卡器模块的情况),不然在未插入设备的情况下打开app会直接挂掉。

以上2步联系紧密。

..................................................

3.现在需要检测卡的状态(是否有卡),当检测到读到卡的时候,才能进行下一步 *** 作。那么如何中读卡器中获取信息呢?主要是UsbRequest类。

........................................

//这个虽然之前实例化了,但是在这里还是要加上,不然会出现错误,具体原因我还没搞清//楚,待查证。

ByteBuffer buffer = ByteBuffer. allocate (16)

try {

mConnection = GetConnection( mDevice )

} catch (java.lang.SecurityException e) {

e.printStackTrace()

}

//这里需要用这个 mInterruptEndpoint 。

UsbRequest request = new UsbRequest()

request.initialize( mConnection , mInterruptEndpoint )

request.queue(buffer,16)

//这里大致就是UsbRequest将收到的数据,存进buffer中。

//queue  boolean queue (ByteBuffer buffer)

//Queues the request to send or receive data on its endpoint.

//requestWait:Waits for the result of a queue(ByteBuffer) operation

// UsbRequest requestWait ()

//由此可知调用wait是为了让queue(buffer)做完,即将数据存进buffer中。

if ( mConnection .requestWait() == request) {

Log. d ( TAG , "after requestWait" )

//然后判断

byte status0 = buffer.get(0)

byte status1 = buffer.get(1)

Log. d ( TAG , "status get:" + status0 + "," + status1)

if (status0 ==80&&status1 ==3) {

mOnCardListener .OnCardState( true )

reader_power_init()//这里检测到卡之后,需要进行初始化 *** 作,这是Read Card之前的准备工作。

return 1

}

if (status0 ==80&&status1 ==2) {

mOnCardListener .OnCardState( false )

return 0

}

if (status0 ==0&&status1 ==0) {

return -1

}

}

return -1

这里我读到了三种状态:

读到卡80, 03 。

没有卡80,02 。

No change 0,0(这个待考证,我只是获取到了这个status的数据,然后进行了判断而已)。

80, 03这个数据是读卡器模块在检测到卡之后自动发送给android设备的,无需任何 *** 作,我这里只是将EP收到的数据读出来进行判断,当收到80,03即表示有卡,那么我就可以进行下一步的 *** 作。

这里需要单独开了一个线程去做detect_card的 *** 作(因为需要一直检测状态)。

//根据detect_card函数的返回值来判断卡的状态。并且加上一个500ms的延时(每500ms调用一次detect_card函数,赋值给 detect_card_ret ), detect_card_ret 应该是一个有三种状态的返回值,而不能直接通过detect_card的返回值赋值。

public void run() {

while ( true ) {

..............................

int ret = mUSBReaderSDK .detect_card()

if (ret==1) // card inserted. detect_card_ret =1

else if (ret==0) //card removed. detect_card_ret =0

//else : -1: no changed, so ignored.try {

Thread. sleep (500)

} catch (InterruptedException e){

e.printStackTrace()

}

................

Time      ret    detect_card_ret         status

0          1               1                        insert card

500        -1          ignore                 insertcard(no changes)

1000       0               0                   remove

1500       -1          ignore               remove

//因为这里只需要对insert和remove状态进行监测,changes状态无需返回值。

detect_card_ret是个全局变量,下面需要通过对此变量进行判断,来决定是否开启ReadThread线程。这里的思路是定义一个ReadButton,每点击一次就new ReadThread,然后start。但是,开启线程之前,需要先做条件判断。首先判断是否有卡(即 detect_card_ret 是否为 1 ),然后就是需要一个线程标志位(readthread_is_running,默认为false)。

当这两个条件满足后,才能真正开启线程。线程标志位需要在开启线程(即start thread)之前置true,执行完线程之后置false(即run函数的末尾处,以便下次开启)。

if ( detect_card_ret ==1&&! readthread_is_running ){

readthread_is_running = true

starThread()

}

private class ReadThread extends Thread{

@Override

public void run(){

....................

readthread_is_running = false

}

}

ReadThread中,定义了如何Read Card。读卡的本质就是一系列数据的收发以及解析过程。

Desfire卡的读卡流程是

power_off

power_on

get_applications

select_applications

list_file

read_file

power_off

四、Util类的积累。

十六进制的byte与String之间的转换,以及小端的转换。

五、整个读卡的流程总结。

随着智能设备以及NFC的普及,用RFID卡支付变得越来越流行。现在的非接触式卡片(包括但不限于社保卡、饭卡、交通卡、门禁卡等)都是使用的RFID技术。与此同时,RFID智能卡也越来越受到攻击者的关注。

专攻RFID智能卡的APP

这是一款名为Punto BIP!的安卓APP,它可以用来黑掉NFC电子支付系统Tarjeta BIP!,而且犯罪成本非常低,人们甚至在各大论坛和博客都可以下载到。趋势科技发布了一篇文章,阐释了 如何利用该安卓应用黑掉RFID支付卡 ,里面专门讨论了RFID支付的风险。

Freebuf小科普

RFID技术:无线射频识别技术,是一种通信技术,可通过无线电讯号识别特定目标并读写相关数据,而无需识别系统与特定目标之间建立机械或光学接触。

NFC技术:即近距离无线通讯技术,该技术由免接触式射频识别(RFID)演变而来。

MIFARE卡:目前世界上使用量最大、技术最成熟、性能最稳定、内存容量最大的一种感应式智能IC卡。Mifare系列卡片根据卡内使用芯片的不同,分为Mifare UltraLight,又称为MF0;Mifare S50和S70,又称为MF1;Mifare Pro,又称为MF2;Mifare Desfire,又称为MF3。

犯罪成本低:普通人分分钟变黑客

qQFvuu.png

在智利那个交通卡案例里,即使不懂技术的犯罪者,只需要在存在NFC功能的安卓手机上安上该APP,然后把该交通卡贴近手机屏幕,并按下“Cargar 10k”,那么就可以立即为交通卡充值1万智利比索(约合17美元)。钱虽不多,但长期下来还是很大一笔收益。

这款安卓APP有四个主要功能:

1、número BIP:用于取得卡号

2、saldo BIP:获得卡内可用余额

3、Data carga:充值可用余额

4、número BIP:改变卡号

最后一个功能尤为危险,卡号被更改的后果非常严重,一旦这种技术被恶意利用,会造成极大的负面社会影响和经济损失。

原理分析

通过对该安卓程序的源码分析,我们发现攻击者会把事先准备好的数据写进卡里,然后随意的调节卡内余额。 它之所以能够任意读写RFID卡中的数据而不受 认证 机制所限制,是因为相应的智能卡为老版本的Mifare中存在多个 安全漏洞 。这些漏洞允许黑客使用普通设备(如Proxmark3)克隆改写Mifare Classic卡里的内容。

Mifare-RFID-implementation-Proxmark3.jpg

黑客可以轻易地通过使用普通工具, 破解 该卡的认证密钥。在认证密钥和本地NFC的支持下,攻击者可以轻易的对卡重写,而再克隆一张新卡也是轻而易举。

Mifare-RFID-Key-cracking-tool.jpg

本站提供安全工具、程序(方法)可能带有攻击性,仅供安全研究与教学之用,风险自负!

为了便于大家进行安全研究,在这里提供通过搜索引擎得到的该恶意软件 下载源 。不过据某国外安全研究人员所述,与原来的程序相比,升级后的版本已经有所改变,想要下载研究的童鞋请谨慎。

社保卡、支付卡和饭卡存在风险

不仅MIFARE Classic卡受到影响,连MIFARE DESFire和MIFARE Ultralight (上文中有介绍) 卡也不幸中招。

趋势科技称,目前受影响的至少有三种卡片:社会保障卡(关联银行服务)、支付卡和就餐卡。社会保障卡(关联银行服务)、支付卡是MIFARE DESFire卡,它们容易受到 侧信道攻击 ;就餐卡是一种Mifare Classic卡,攻击者可以对其额度进行修改;

00.jpg

这些卡内的密码系统发生信息泄漏时若有监控措施,那么密钥可以在七小时内恢复。如果密钥不随机,这些卡会像MIFARE Classic一般被修改克隆。更糟的是,就连xyk也能被配备有NFC的移动设备的安卓应用所 *** 作。

为什么这么危险?除了因为这些卡片采用的是过时的技术外,也有节约制卡成本或者说“便宜无好货”的原因。

专家建议

留意卡内余额,设置扣费提醒,并检查是否自己使用的RFID卡是文中所述的哪一类。


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/yw/7933655.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-11
下一篇2023-04-11

发表评论

登录后才能评论

评论列表(0条)

    保存