校园一卡通破解,可以洗澡吃饭不花钱了
校园一卡通在高校被广泛使用,能够方便地实现转账消费一体化。在学校内可用于食堂,浴室,超市的消费,工作场所的门禁卡。每个学校在实现一卡通方案时会根据特有环境进行定制,下文所讨论的情形限定于我所在的学校
校园卡使用了Mifare Classic 4k卡片承载用户信息,但只有前6个区被使用于存储个人信息。6个区使用不同口令实现访问控制(并且每张卡片的口令都不尽相同),读取和改写卡片内容都需要口令认证。泛讲Mifare Classic的破解,可以借助成熟的工具集nfc-tools (http://code.google.com/p/nfc-tools/downloads/list)。mfoc大概需要10到30 min实现卡口令的破解以及卡数据的dump。如果只是读写自己的一卡通,上述的这种通用的方法简单易行,绰绰有余。但换个场景,局限性就暴露出来了。
设想你走在食堂,突然想复制排在你前面人的卡玩玩 如果使用mfoc,先把他手里的卡拿走,跑到电脑前,完成卡内容dump,再还到他手里。其不可行性不言而喻,通用的破解方法在类似场景就只是理论可行
考虑到校园一卡通并不仅仅是一张通用性的Mifare,获取卡口令就不再局限于mfoc了。为了操作卡片数据,显然食堂和浴室的刷卡器具是有办法直接获取每张卡片的区域口令并由此修改卡片内消费数据的。如不幸它们获取卡口令的方法是联网到数据库根据卡片UID进行查询,那除了mfoc以外获取卡口令的唯一方法就是拿到数据库的访问权限了。
幸运的是,事实并非如此,庞大的机构为了减少网络数据的传输,卡口令其实是在本地根据预定算法加UID(UID是卡片内容的前四个字节,在未认证的情况下也可读)计算出来的,网络一般只传输交易流水。 换言之,如果能够分析出器具计算卡口令的方法,那使用同样的算法就可以快速获取卡口令了。乍看来,获取这种计算方法的途径不止一条:
-- 弄到一套校园一卡通卡片读写程序的源码
-- 搬回来食堂刷卡器进行单片机程序的逆向分析
-- 搬回来一台一卡通冲值终端接上键盘鼠标然后逆向分析上面的PC程序
这三个方法要么理想化要么兴师动众,究其原因也是考虑的太泛了。具体问题具体分析的话,发现了另一个容易被忽略的途径:学校公寓进门时要刷卡,类似一种出入人员登记。楼管人员每天就负责盯着电脑屏幕审视每一个持卡人的学号,姓名等个人信息。显示信息的载体是一个网页,内容会随着每一次刷卡而变动。页面加载的ActiveX控件实现UID的读取以及卡内区域数据的获取,当然也包括卡口令的计算。该页面缺少权限审核,任何人键入网址都会加载ActiveX控件。
ActiveX控件只提供一个顶层接口,它会向下调用其它DLL实现扣费,UID获取,联网查询信息,卡口获取等操作。逆向分析这么盘根错节的程序也是第一次,大致思路是使用IDA Pro找出可能包含获取卡口令操作的函数,然后层层跟进以探究竟。从卡初始化到UID读取然后根据UID计算卡口令,函数调用情况如下:
-> ActiveX控件
-> ReadChipControl::CreateThread
-> AIO_API::LoadLibrary(CardInterface.dll)
-> CardInterface::GetCardNo
-> CardInterface::sub_10002070
-> CardInterface::sub_10001570
-> CardInterface::sub_10023DD0
纯粹的静态分析虽然提示信息丰富,但很多参量无法辨析其含义,所包含的导出函数也无从筛选。一些DLL是使用LoadLibrary动态加载,纯粹的静态分析很难连续跟踪变量的传递和变化。由于没有校园卡配套的读卡器让ActiveX控件完整地进行一轮读卡,只能使用OllyDBG在动态跟踪时强制修改跳转指令使读卡行为发生。动态跟踪过程中可以通过查看内存直接看到函数计算结果,快速确定假设是否合理。最终筛选出用于计算卡口令的函数也是借助动态分析:sub_10001570的一个传入参数在调用结束后被改写为指向keyB内存区域(之前用mfoc破解中每张卡的每个区域的keyB都是b0 b1 b2 b3 b4 b5。但对于校园卡,keyB并不能用于读写卡片)。正是这种直观的数据迅速明确了sub_10001570是和计算卡口令相关的函数。
进一步跟踪发现sub_10023DD0(const, v18, outkeyA)的第三个参数指向的内存用来存放计算出来的Mifare Classic 卡片keyA,也即读写卡片时用到的口令。const在多次跟踪中发现其指不随输入的UID变化,进一步跟踪发现其值是直接从data段拷贝过来的。因此v18就成了唯一决定keyA的参数了。V18指向一个8字节的区域,在计算0区的keyA时V18被填充为
DWORD UID
DWORD UNK
UNK为一些不明含义的字节,如果在这个函数调用前把UID强制改为我一卡通UID,计算出来的keyA并不正确。最有可能的原因前序多处跳转被Ollydbg强制修改而导致程序的执行不完整,进而使得UNK计算不正确。不过好在UNK只有四个字节大小,与其继续在反汇编的代码中路漫漫其修远兮,不如干脆把这个UNK暴力破解出来。
暴力破解的思路非常基本,加载CardInterface.dll,将参数按照上面的分析构造好,每次递增UNK,然后调用sub_10023DD0。将计算出来的keyA和正确值比对,如果正确就输出UNK。用Core i7的八个核分别跑一个破解线程跑了不到一天就得到了UNK:02 36 63 00,用同样的方法也得到了用于计算1区的keyA的UNK为02 36 63 01。规律一下出来了,前面三个字节虽然不懂其含义,但最后一个字节显然是区号。这种方法可以计算0,1,4,5区口令。
理所当然的用这种方法计算2,3区时发现keyA计算不正确,尝试暴力破解很久也没有搜索到正确的UNK。不过由于前两区的口令已经找到了计算方法,大大增加了信心,也明确了待分析的函数片段。2区主要存储的是用户的账户余额等信息,再次分析时就重点考察那些名字看起来和修改账目相关的导出函数,然后递进看哪里计算了这些区域的卡口令。当计算2,3区时,V18填充方案已经发生了改变
WORD UNK1
DWORD UID
WORD UNK2
难怪之前暴力破解完全没有反应,重新修改暴力破解程序,很快将UNK1(02 19)和UNK2(02 36)跑了出来。sub_10023DD0里面没有和卡片沟通以及网络通信的操作,全部都是数学运算,应该是预定的密码学算法,由于暂时没有找到抽练出当中算法的方法,就没深究。目前已经知道如何调用CardInterface库内函数根据UID计算卡片区域口令了。
但为了易于使用,获取卡口令以及读取卡片内容的功能使用手机或平板的NFC功能实现。可是只能在Windows下进行,如何转化为Android下可识别的代码目前还在思考中。暂时的解决方案是在公网服务器使用IIS搭建的CGI调用这个DLL,然后通过网络把结果返回给平板或者手机。平板或手机使用该组口令就可以读取卡片数据了,数据含义辨识虽然无法做到尽善尽美,但基本的信息稍加分析都是可以辨识的。例如姓名学号这些信息都是ASCII方式直接存储,一眼就可以瞟出来。余额,消费记录,校验值等可能需要多次消费后进行数据比对...file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\TempPic\96MK}@J%CU7$DPVSAQ2}Y2X.tmp
-> 智能终端使用NFC获取UID,发送到公网服务器
-> 服务器CGI获得卡UID,调用DLL计算卡口令
-> 服务器将卡口令返还给智能终端
-> 智能终端读取卡数据并辨识含义显示
hush.J
Lord K XVII
music4x@Lord K XVII