校园一卡通在高校被广泛使用,能够方便地实现转账消费一体化。在学校内可用于食堂,浴室,超市的消费,工作场所的门禁卡。每个学校在实现一卡通方案时会根据特有环境进行定制,下文所讨论的情形限定于我所在的学校
        校园卡使用了 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 计算卡口令 
        -> 服务器将卡口令返还给智能终端 
        -> 智能终端读取卡数据并辨识含义显示

《校园一卡通破解, 可以洗澡吃饭不花钱了》 有 6 条评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注