声明:该文写成于2018年,该研究进行并完成于2015年,该种水卡于2016年作废,但作废与该研究无关。 声明:作者居住于该大学某一个后娘养的校区,并用过这种水卡系统,主校区是不存在的。
作者是一名江苏省外考入江苏省某全国排名前20大学的学生,被调剂到该校最惨的专业,并在该校最破的校区度过绝大部分的大学时光。这个校区的宿舍楼非常破旧,电路老化,据说我们来之前一年还发生过电路烧毁引起的火灾,因此宿舍里使用电开水壶是被严禁的。我们获取开水的方式是:
从宿管阿姨处购买一张水卡,它和校园卡是完全分离的,仅有打开水一个功能。
提上自己的开水瓶,拿上自己的水卡,到1、3、5楼放置的任何一台开水机处,摆好开水瓶,刷一下水卡,接着移开。
开水机响一声“嘀”,屏幕上显示出目前的水卡余额,并开始出开水。
在出开水的同时,屏幕上的余额显示也会同步减少,约3秒减少0.01元。
a. 余额减少0.16元后,一个平均大小的开水瓶正好装满一瓶开水。开水自动停下,余额显示保持几秒后消失。
b. 如果不需要打满0.16元开水,可以再刷一下水卡,开水机响一声“嘀”,开水停止,余额显示保持几秒后消失。
c. 如果余额少于0.16元,在余额扣减至零时开水停止,余额显示保持几秒后消失。
如余额不足,可至宿管阿姨处用现金充值。
根据对水卡系统的分析,笔者认为它的破解完全具有可行性,理由如下:
离线
这是一个非常简陋的系统,水卡的外观非常粗制滥造,也没有记名。考察开水机背后的管路发现,只存在水管和电线,并不存在网线,而开水机本身也完全不像具有无线连接能力。因此,开水系统不联网的可能性极高,安全破解并修改水卡数据而不被发现是可能的。
记录方式
通过对前述打水流程的理论分析,推断水卡数据的修改过程如下:
a. 刷水卡时,水卡机先确认卡内余额,并将原始余额储存至临时存储器,在屏幕上显示。
i. 若卡内余额不少于0.16元,则将余额预先扣减0.16元并写入卡中。
ii. 若卡内余额不足0.16元,则将余额预先扣减至0并写入卡中。
b. 临时存储器中和屏幕上的余额随出水时间递减。
i. 如果中途刷卡,则以临时存储器中的当前余额覆盖卡内余额。
ii. 如果没有中途刷卡,则什么都不做,卡内余额正是预先扣减到应有数值的余额。
考虑到系统的简陋性,该水卡其实是高精尖超大容量存储器的可能性极低,记录每一条交易记录这样的日志式记录显然不现实。卡内仅存有简单的余额数据的可能性很大。
现有技术
上网稍微搜索,得知目前流行的一种最简单的RFID卡名为M1卡,又称S50卡,可以用某些看不懂的技术手段轻松破解。
设备
于万能的TB购买了不到200元的读写器,洗干净脖子,坐等快递小哥的信息。同时下载好破解软件。
某一天笔者终于收到了快递,拆开一看,一个非常容易理解的设备。一个USB接口,一个平板状物体上面印着“NFC”,一个LED指示灯。将它插进电脑里,这个非常容易理解的设备(下简称“读卡器”)发出了一声非常容易理解的“嘀”,和开水机发出的声调稍低而较圆润的“嘀”遥相呼应。笔者兴奋地把水卡放了上去,LED指示灯由红变绿,并发出了另一声“嘀”,这表示这张卡是可以被识别的。首战告捷。
接着笔者找出了下载好的破解软件mfoc.exe,在当前窗口打开了一个cmd窗口并输入命令:
mfoc -O temp.dmp
表示用默认设定破解当前卡并将其dump保存为temp.dmp。
软件跑了一会之后突然报错,笔者把卡拿开再放回去也不响了,读卡器完全陷入了植物人状态。上网搜索查询后发现,NFC读卡器在Windows环境下特别不稳定,几乎没法使用。要正常使用,必须得用世界主宰宇宙起源Linux。于是笔者搜索发现Kali Linux是最适合该工作的Linux发行版,遂下载其带桌面的LiveCD ISO,刻盘,重启进入Linux。没错,我还有光驱,我是个老人。
那是一个冬天的凌晨三点,笔者感叹了一番光盘启动真是慢之后,进入了Kali Linux 2.0的桌面。接着从左上角的Applications菜单里选择了Wireless Attack\RFID & NFC tools,选择mfoc。系统弹出了一个terminal,并打印出了mfoc的使用方式。笔者用同样的命令,终于成功破解了手上自己的水卡(UID=1E83471B),并用十六进制编辑器打开了得到的dump文件。
水卡拥有16个扇区共1024字节的存储空间,但这个dump文件里除了UID(唯一识别码)所在的第一扇区之外,只有偏移为+0x0180的第7扇区有数据,内容如下:
01 33 98 00 01 A4 0F FF FF FF FF FF FF FF FF FF
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
1F B5 04 72 33 90 FF 07 80 69 FF FF FF FF FF FF
通过阅读该种类型NFC卡的技术文档可知最后一行是该扇区的密钥(KeyA和KeyB),而第二、三行又是空白的,因此数据毫无疑问在第一行,更精确地说,是第一行的前一半,即:
01 33 98 00 01 A4 0F FF
破解该卡时,卡的余额为4.20元,或420分,打开计算器容易发现420转为十六进制是0x1a4,说是巧合实在是太勉强了,认为偏移+0x01a4开始的数据是以分为单位的余额,大端字节序,长度2字节。对了,话说回来,有多少人小时候用过浮点数类型存储货币金额?别装,我知道你们都干过。
笔者接着又去打了半瓶开水,余额变成了4.10元,接着读卡(由于已经获得了整个dump,也就获得了所有的密钥,故不再需要破解这一过程了),发现上述的实际数据区域如下:
01 33 98 00 01 9A 31 FF
前四字节和最后一字节经过其他水卡的测试发现是固定不变的,按下不表。显然0x19a等于410,那么问题来了,后面的一个字节,0F和31是什么呢?笔者试着无视它们,只更改两字节余额,意料之中地得到了开水机无情的Error。显然,它们是有意义的,笔者推测它是某种形式的校验。
经过大量的喝水和测试,笔者得到了大量的测试数据,如下:
01 A4 0F
01 A2 09
01 9F 34
01 9A 31
01 98 33
01 8D 26
01 86 2D
01 80 2B
01 70 DB
01 2C 87
01 2B 80
01 1C B7
00 FE 54
00 F0 5A
00 ED 47
00 E3 49
00 D1 7B
00 00 AA
笔者一开始猜想,这个校验字节是通过余额和UID通过某种逻辑运算(如xor)得出的,经过进一步观察研究之后发现与UID并没有什么关系,摘录当时研究草稿如下:
x表示原位,y表示校验中需要非的位数
00000000 xxxxxxxx yxyxyxyx
00000001 xxxxxxxx yxyxyxyy
00000010 xxxxxxxx yxyxyxxx ? 02 00 a8
00000010 xxxxxxxx yxyxyyxx ? 02 00 ac (FAIL)
上面的草稿里,前八位是余额高位(如0x01a4里的0x01)写成二进制,中间八位是低位(如0x01a4里的0xa4)写成二进制,最后八尾是根据余额数字得出的,通过余额数字算出校验字节的方法。如01 A4 0F,余额数据写成二进制是00000001 10100100,校验按yxyxyxyy模式对低位取非,得00001111,即0F。笔者将yxyxyxyy写成10101011,暂称之为Checksum Generation Pattern,简称CGP的话,那么基础的CGP是10101010,将它与高位取非之后得到最终的CGP,再用这一最终CGP和低位取非即得最终的校验字节。不要说作者绕了圈子,2015年的傻逼作者就是认为这是最容易理解的方式。以屎山代码表示如下:
int flipByCgp(int operand, int CGP)
{
bitset<8> opr(operand);
bitset<8> cgp(CGP);
for(inti = 0; i < 8; i++) {
if(cgp[i] == 1) opr.flip(i);
}
return opr.to_ulong();
}
int genCgpByHigher(int higher)
{
return flipByCgp(0xAA, higher);
}
int getChecksumBySeperateBits(int lower, int higher)
{
return flipByCgp(lower, genCgpByHigher(higher));
}
int getChecksum(int operand)
{
operand = operand % 0x10000;
return getChecksumBySeperateBits(operand % 0x0100, operand / 0x0100);
}
(不要复制,求别)
如此即解决了校验位的问题,笔者将卡余额试着改为10元,算好校验位写卡之后成功打水,于是高高兴兴地把水卡余额改成了327.67元再去测试,仍然成功。
此后笔者用程序实现了卡余额修改和充值的半自动化。
剩余的问题只有一个,就是UID和扇区密钥的关系了。笔者借来很多卡并收集了原始数据如下(左栏UID,右栏扇区KeyA):
UID Sector 7 KeyA
1E 83 47 1B .. 1F B5 04 72
0E 36 62 1B .. 0F 00 21 72
FE 00 51 1B .. FF 36 12 72
4E 9B 67 1B .. 4F AD 24 72
EE 91 44 1B .. EF A7 07 72
FE 4E 9C C9 .. FF 78 DF A0
3E DB 55 E6 .. 3F ED 16 8F
0C C9 56 D5 .. 0D FF 15 BC
并以相似的思路发现这时以16位即两字节为一个单元,其CGP为01 36 43 69。至此,水卡的所有秘密大白于天下。
过了大概一年,学校撤去了所有这种水机,学生们纷纷退回了水卡。学校设置了用校园一卡通收费的新水机,这种水机据说什么都好,既比原来便宜,又比原来安全,问题只有一个:
只有一楼有。
操你妈。
操你妈。
(完)
1、东南大学校园卡也可以用相同的程序进行破解。该卡属于半在线模式,卡中既有你的识别号、卡号、信息等识别信息,又有你的卡钱包余额、消费总次数、今日消费次数和末次消费商户编号等钱包信息。因此,如果复制该卡,绝不要尝试进行任何消费行为!相反,刷跑操、刷讲座、刷门禁进图书馆等只使用识别信息的应用场景则非常安全。
2、大概从2015级开始,东南大学校园卡由普通M1卡安全升级为“UltraSecure”品牌技术保护的安全增强卡,也即是所谓的“hardened Mifare classic 1K cards”。该种卡用普通的mfoc程序无法进行破解,请自行搜索“hardnest attack”寻找这种卡的破解方案。如果想要尝试,请提前准备好linux环境,最好是不带mfoc和libnfc的干净环境。
3、东南大学附属中大医院的职工卡和正文中的水卡工作机制完全相反,是纯在线模式,它只存储账户识别信息,其他区域完全空白,甚至都没有加密。因此,该卡的一切操作包括门禁、食堂等应用都依赖于网络:读卡机从卡中读取识别信息,上传到主机获取持卡人的身份,然后再返回结果(持卡人的门禁许可区域、食堂账户余额等)。这期间不会发生任何写卡操作。也就是说,一张卡从办下来到它生命结束,卡里的内容不会有任何变化!。
比如说,主任的卡里只存了一串数字“2147483647”,这是主任的卡号,主任的食堂账户里有1000元。我把主任的卡复制了一张,然后把原卡还给主任,这时我的复制卡里也只有一串数字“2147483647”;不管是用主任的原卡,还是用我的复制卡,在机子上一刷,显示的余额都是1000元。
主任去食堂吃了一碗豪华猪肉面,消费20元,这时后台服务器上主任的食堂账户里有980元,主任的卡里还是只有一串数字“2147483647”。
这时我再拿我复制的假卡去超市买一瓶水,消费2元,那么机器上就会直接显示原余额980元,现余额978元了,我的假卡里还是只有一串数字“2147483647”。
也就是说,如果复制一张和原卡一模一样的卡,那么两张卡可以同步使用,非常安全,不用担心系统会发现记着同一个人名字的两张卡内容不一样,而如果复制校园卡则一定会发生这种情况。话就讲到这里吧。
当然,系统绝不会发现,但主任是会发现的(因为你的卡扣减的也是主任账户的余额),所以做之前一定要取得主任的同意哦!
2025-12 出上海記(日本語訳)
2025-12 引っ越し随筆
2025-11 A Trip on the California Zephyr Sleeper Train
2025-09 美铁5次“加州微风”号列车旅行记
2025-09 寝台特急そよかぜカリフォルニア旅行記
2024-09 外国人初来日チェックリスト 日本語ぺらぺらの人用
2024-05 Computer troubleshooting series #4: Remove devices from Windows 10 "Cast to Device" right-click menu
2024-02 Be a water thief: Hacking the dormitory water boiler in a top 20 university in China
2024-01 Exodos Shanghainus - Escaping from Shanghai during 2022 COVID lockdown
2024-01 Computer troubleshooting series #3: trouble with scroll wheel in a Microsoft Bluetooth Mouse