diff --git a/Proxmark3GUI.pro b/Proxmark3GUI.pro index b01adb0..bfdf6a5 100644 --- a/Proxmark3GUI.pro +++ b/Proxmark3GUI.pro @@ -20,6 +20,7 @@ SOURCES += \ common/pm3process.cpp \ common/util.cpp \ module/mifare.cpp \ + ui/mf_trailerdecoderdialog.cpp \ ui/mf_sim_simdialog.cpp \ ui/mf_uid_parameterdialog.cpp \ ui/mainwindow.cpp \ @@ -29,12 +30,14 @@ HEADERS += \ common/pm3process.h \ common/util.h \ module/mifare.h \ + ui/mf_trailerdecoderdialog.h \ ui/mf_sim_simdialog.h \ ui/mf_uid_parameterdialog.h \ ui/mainwindow.h \ ui/mf_attack_hardnesteddialog.h \ FORMS += \ + ui/mf_trailerdecoderdialog.ui \ ui/mf_sim_simdialog.ui \ ui/mf_uid_parameterdialog.ui \ ui/mainwindow.ui \ @@ -49,7 +52,7 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target -VERSION = 0.1.1 +VERSION = 0.1.2 QMAKE_TARGET_PRODUCT = "Proxmark3GUI" QMAKE_TARGET_DESCRIPTION = "Proxmark3GUI" QMAKE_TARGET_COMPANY = "wh201906" diff --git a/README.md b/README.md index 809caac..d62b9ae 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,65 @@ # Proxmark3GUI A GUI for [Proxmark3](https://github.com/Proxmark/proxmark3) client -[中文](README/README.zh_CN.md) +[中文](README/doc/README_zh_CN.md) *** ## Features -+ Support raw commands of Proxmark3 client ++ Easy to find available Serial Port ++ Support raw commands of Proxmark3 client(Official/Iceman) + Have a friendly UI to test Mifare cards -+ Easy to edit Mifare data files -+ Support binary(.bin .dump) files and text(.eml) files -+ ... + + Support different card size(MINI, 1K, 2K, 4K) + + Easy to edit Mifare block data + + Easy to read all/selected blocks with well-designed read logic + + Easy to write all/selected blocks + + Support binary(.bin .dump) files and text(.eml) files + + Analyze Access Bits + + Support Chinese Magic Card ++ Customize UI ++ ... *** -## Previews -![nested_attack](README/mf_nested_attack.gif) +## Preview +![preview](README/img/preview.png) -![mf_load_file](README/mf_load_file.gif) +more previews [here](README/doc/previews.md) -![mf_edit_file](README/mf_edit_file.gif) +*** -![raw_command](README/raw_command.gif) +## About Iceman fork/repo + +The [Iceman fork/repo](https://github.com/RfidResearchGroup/proxmark3) has more powerful functions like offline sniff. These guys even developed a new hardware called Proxmark3 RDV4 with smart card support. But the official repo and the Iceman repo is not fully compatible. +This GUI was designed for only official repo at first, but I'm trying to make it compatible with Iceman repo. + +Supported functions when using Iceman client: + +[supported functions](README/doc/supported_Iceman.md) + +*** + +## About Compiled Windows clients + +A cool guy [Gator96100](https://github.com/Gator96100) creates [ProxSpace](https://github.com/Gator96100/ProxSpace) and makes it possible to compile both the firmware and client on Windows. +Also, he makes the [pre-compiled Windows client](http://www.proxmark.org/forum/viewtopic.php?id=3975) so you can download it and run your PM3 client on Windows instantly. +I included his compiled client in my releases so you can use the GUI on the fly, and you can also use the GUI with your prefered client. +Great thanks to him. *** ## Update Log: +### V0.1.2 ++ Optimize read logic ++ Make UI Customizable ++ Save client path automatically ++ Add Trailer Decoder ++ Support read/write selected blocks ++ Support a few Iceman functions ++ Fix some bugs + ### V0.1.1 + Complete Mifare module(support simulate and sniff) diff --git a/README/README.zh_CN.md b/README/README.zh_CN.md deleted file mode 100644 index fdb8734..0000000 --- a/README/README.zh_CN.md +++ /dev/null @@ -1,38 +0,0 @@ -# Proxmark3GUI -一个自制的[Proxmark3](https://github.com/Proxmark/proxmark3) GUI - -[English](../README.md) - -*** - -## 特色功能 - -+ 支持直接输入PM3命令 -+ 有针对于Mifare卡(IC卡)的图形界面 -+ 支持编辑Mifare扇区数据文件 -+ 可以打开二进制/文本格式的扇区数据文件 -+ ... - -*** - -## 预览图 -![nested_attack](mf_nested_attack.gif) - -![mf_load_file](mf_load_file.gif) - -![mf_edit_file](mf_edit_file.gif) - -![raw_command](raw_command.gif) - -*** - -## 更新日志: - -### V0.1.1 -+ 完成整个Mifare模块(支持模拟卡和嗅探功能) - -### V0.1 -+ 支持处理Mifare卡片及相关数据文件 - -### V0.0.1 -+ 一个带串口选择框的实验版本 \ No newline at end of file diff --git a/README/doc/README_zh_CN.md b/README/doc/README_zh_CN.md new file mode 100644 index 0000000..b76c129 --- /dev/null +++ b/README/doc/README_zh_CN.md @@ -0,0 +1,66 @@ +# Proxmark3GUI +一个自制的[Proxmark3](https://github.com/Proxmark/proxmark3) GUI + +[English](../../README.md) + +*** + +## 功能 + ++ 快速查找可用端口并连接 ++ 支持直接输入PM3命令(官方版/冰人版) ++ 有针对于Mifare卡(IC卡)的图形界面 + + 支持不同大小的卡片(MINI, 1K, 2K, 4K) + + 支持编辑Mifare扇区数据 + + 支持读取全卡/选中扇区,读卡逻辑更加智能 + + 支持写入全卡/选中扇区 + + 可以打开二进制/文本格式的扇区数据文件 + + 分析访问控制位(Access Bits) + + 支持UID卡操作(UID快速读写,UFUID锁卡) ++ 自定义UI界面 ++ ... + +*** + +## 预览图 +![preview](../img/preview_zh_CN.png) + + [更多预览](../doc/previews.md) + +*** + + ## 关于冰人版 + 这个GUI一开始是针对官方版本做的,现在正在尽力让它兼容冰人版的功能 + (没钱买RDV4也没钱买两台PM3,测一次冰人就要烧一次固件 qwq) + + [已支持功能](../doc/supported_Iceman.md) +*** + +## 关于预编译Windows客户端 + +一个国外大佬 [Gator96100](https://github.com/Gator96100) 做了个 [ProxSpace](https://github.com/Gator96100/ProxSpace) 以便在Windows平台上编译PM3固件和客户端,他还把自己编译好的客户端放到了[论坛](http://www.proxmark.org/forum/viewtopic.php?id=3975)里面 +文件都是放到Google Drive上面的,国内网络无法访问,所以我在release版本里面放了个带预编译客户端版本的GUI。这个GUI也可以搭配你自己的客户端使用 +(本来打算在CSDN下载里面放几个最新版客户端的,结果不能把下载币改为0) +感谢大佬 + +*** + +## 更新日志: + +### V0.1.2 ++ 优化读卡逻辑 ++ UI自定义 ++ 自动保存客户端路径 ++ 添加访问控制位解码器(也可用于自己构造访问控制位) ++ 支持仅读写选中块 ++ 支持部分冰人功能 ++ 修复部分bug + +### V0.1.1 ++ 完成整个Mifare模块(支持模拟卡和嗅探功能) + +### V0.1 ++ 支持处理Mifare卡片及相关数据文件 + +### V0.0.1 ++ 一个带串口选择框的实验版本 \ No newline at end of file diff --git a/README/doc/previews.md b/README/doc/previews.md new file mode 100644 index 0000000..b9ae1cb --- /dev/null +++ b/README/doc/previews.md @@ -0,0 +1,16 @@ +## Previews + +Raw Command: +![raw_command](../img/raw_command.gif) + +Mifare Nested Attack: +![nested_attack](../img/mf_nested_attack.gif) + +Mifare Load File: +![mf_loadfile](../img/mf_loadfile.gif) + +Mifare Edit File: +![mf_editfile](../img/mf_editfile.gif) + +Mifare Trailer Decoder: +![mf_trailer](../img/mf_trailer.gif) \ No newline at end of file diff --git a/README/doc/supported_Iceman.md b/README/doc/supported_Iceman.md new file mode 100644 index 0000000..70dbef0 --- /dev/null +++ b/README/doc/supported_Iceman.md @@ -0,0 +1,9 @@ +## About Iceman fork/repo + +The [Iceman fork/repo](https://github.com/RfidResearchGroup/proxmark3) has more powerful functions like offline sniff. These guys even developed a new hardware called Proxmark3 RDV4 with smart card support. But the official repo and the Iceman repo is not fully compatible. This GUI was designed for only official repo at first, but I'm trying to make it compatible with Iceman repo. +Supported functions when using Iceman client: ++ Command Line ++ Mifare Card info ++ Mifare Check default keys ++ Mifare Nested Attack ++ Mifare Read/Write \ No newline at end of file diff --git a/README/img/mf_editfile.gif b/README/img/mf_editfile.gif new file mode 100644 index 0000000..69580a2 Binary files /dev/null and b/README/img/mf_editfile.gif differ diff --git a/README/img/mf_loadfile.gif b/README/img/mf_loadfile.gif new file mode 100644 index 0000000..d98e55f Binary files /dev/null and b/README/img/mf_loadfile.gif differ diff --git a/README/img/mf_nested_attack.gif b/README/img/mf_nested_attack.gif new file mode 100644 index 0000000..18f5363 Binary files /dev/null and b/README/img/mf_nested_attack.gif differ diff --git a/README/img/mf_trailer.gif b/README/img/mf_trailer.gif new file mode 100644 index 0000000..64497de Binary files /dev/null and b/README/img/mf_trailer.gif differ diff --git a/README/img/preview.png b/README/img/preview.png new file mode 100644 index 0000000..0c26557 Binary files /dev/null and b/README/img/preview.png differ diff --git a/README/img/preview_zh_CN.png b/README/img/preview_zh_CN.png new file mode 100644 index 0000000..119f702 Binary files /dev/null and b/README/img/preview_zh_CN.png differ diff --git a/README/img/raw_command.gif b/README/img/raw_command.gif new file mode 100644 index 0000000..4262519 Binary files /dev/null and b/README/img/raw_command.gif differ diff --git a/README/mf_edit_file.gif b/README/mf_edit_file.gif deleted file mode 100644 index 25c5de3..0000000 Binary files a/README/mf_edit_file.gif and /dev/null differ diff --git a/README/mf_load_file.gif b/README/mf_load_file.gif deleted file mode 100644 index f8dc95e..0000000 Binary files a/README/mf_load_file.gif and /dev/null differ diff --git a/README/mf_nested_attack.gif b/README/mf_nested_attack.gif deleted file mode 100644 index 0ac5bc4..0000000 Binary files a/README/mf_nested_attack.gif and /dev/null differ diff --git a/README/raw_command.gif b/README/raw_command.gif deleted file mode 100644 index 0a8c67b..0000000 Binary files a/README/raw_command.gif and /dev/null differ diff --git a/common/pm3process.cpp b/common/pm3process.cpp index 6ea2492..08aef5a 100644 --- a/common/pm3process.cpp +++ b/common/pm3process.cpp @@ -16,18 +16,29 @@ PM3Process::PM3Process(QThread* thread, QObject* parent): QProcess(parent) void PM3Process::connectPM3(const QString path, const QString port) { + QString result; + Util::ClientType clientType = Util::CLIENTTYPE_OFFICIAL; setRequiringOutput(true); // using "-f" option to make the client output flushed after every print. start(path, QStringList() << port << "-f", QProcess::Unbuffered | QProcess::ReadWrite); if(waitForStarted(10000)) { - while(waitForReadyRead(1000)) - ; + waitForReadyRead(1000); setRequiringOutput(false); - QString result = *requiredOutput; + result = *requiredOutput; + if(result.indexOf("[=]") != -1) + { + clientType = Util::CLIENTTYPE_ICEMAN; + setRequiringOutput(true); + write("hw version\r\n"); + waitForReadyRead(1000); + result = *requiredOutput; + setRequiringOutput(false); + } if(result.indexOf("os: ") != -1) // make sure the PM3 is connected { + emit changeClientType(clientType); result = result.mid(result.indexOf("os: ")); result = result.left(result.indexOf("\r\n")); result = result.mid(3, result.lastIndexOf(" ") - 3); diff --git a/common/pm3process.h b/common/pm3process.h index 17cee5f..093ebfd 100644 --- a/common/pm3process.h +++ b/common/pm3process.h @@ -9,6 +9,8 @@ #include #include +#include "util.h" + class PM3Process : public QProcess { Q_OBJECT @@ -34,6 +36,7 @@ private: signals: void PM3StatedChanged(bool st, QString info = ""); void newOutput(QString output); + void changeClientType(Util::ClientType); }; #endif // PM3PROCESS_H diff --git a/common/util.cpp b/common/util.cpp index f7f7997..3d73cc3 100644 --- a/common/util.cpp +++ b/common/util.cpp @@ -5,6 +5,8 @@ Util::Util(QObject *parent) : QObject(parent) isRequiringOutput = false; requiredOutput = new QString(); timeStamp = QTime::currentTime(); + this->clientType = CLIENTTYPE_OFFICIAL; + qRegisterMetaType("Util::ClientType"); } void Util::processOutput(QString output) @@ -24,20 +26,20 @@ void Util::execCMD(QString cmd) emit write(cmd + "\r\n"); } -QString Util::execCMDWithOutput(QString cmd, unsigned long timeout) +QString Util::execCMDWithOutput(QString cmd, unsigned long waitTime) { QTime currTime = QTime::currentTime(); - QTime targetTime = QTime::currentTime().addMSecs(timeout); + QTime targetTime = QTime::currentTime().addMSecs(waitTime); isRequiringOutput = true; requiredOutput->clear(); execCMD(cmd); - while( QTime::currentTime() < targetTime) + while(QTime::currentTime() < targetTime) { QApplication::processEvents(); if(timeStamp > currTime) { currTime = timeStamp; - targetTime = timeStamp.addMSecs(timeout); + targetTime = timeStamp.addMSecs(waitTime); } } isRequiringOutput = false; @@ -47,6 +49,15 @@ QString Util::execCMDWithOutput(QString cmd, unsigned long timeout) void Util::delay(unsigned int msec) { QTime timer = QTime::currentTime().addMSecs(msec); - while( QTime::currentTime() < timer ) + while(QTime::currentTime() < timer) QApplication::processEvents(QEventLoop::AllEvents, 100); } +Util::ClientType Util::getClientType() +{ + return this->clientType; +} + +void Util::setClientType(Util::ClientType clientType) +{ + this->clientType = clientType; +} diff --git a/common/util.h b/common/util.h index 3b29a69..adc9291 100644 --- a/common/util.h +++ b/common/util.h @@ -8,23 +8,35 @@ #include #include #include +#include class Util : public QObject { Q_OBJECT public: + enum ClientType + { + CLIENTTYPE_OFFICIAL, + CLIENTTYPE_ICEMAN, + }; + + Q_ENUM(Util::ClientType) + explicit Util(QObject *parent = nullptr); void execCMD(QString cmd); - QString execCMDWithOutput(QString cmd, unsigned long timeout = 2000); + QString execCMDWithOutput(QString cmd, unsigned long waitTime = 2000); void delay(unsigned int msec); + ClientType getClientType(); public slots: void processOutput(QString output); + void setClientType(Util::ClientType clientType); private: bool isRequiringOutput; QString* requiredOutput; QTime timeStamp; + ClientType clientType; signals: void refreshOutput(const QString& output); void write(QString data); diff --git a/lang/en_US.ts b/lang/en_US.ts index 1cb5ab4..b881d0e 100644 --- a/lang/en_US.ts +++ b/lang/en_US.ts @@ -138,6 +138,145 @@ + + MF_trailerDecoderDialog + + + Trailer Decoder + + + + + Blocks + + + + + 4 + + + + + 16 + + + + + Trailer Data: +(like "FF0780" or "FF 07 80") + + + + + Or set bits manually + + + + + Cx0 + + + + + Cx1 + + + + + Cx2 + + + + + Cx3 + + + + + Data Block Permission: + + + + + Block0 + + + + + Block1 + + + + + Block2 + + + + + + Read + + + + + + Write + + + + + Increase + + + + + Decrease/Transfer/Restore + + + + + Trailer Block Permission: + + + + + KeyA + + + + + Access Bits + + + + + KeyB + + + + + Reference: +MF1S70YYX_V1 Product data sheet +Rev. 3.2 — 23 November 2017 + + + + + Note:the Access Bits usually contains 4 bytes(8 hex symbols), but only the first 3 bytes matters. You can set the 4th byte randomly. + + + + + Invalid! +It could make the whole sector blocked irreversibly! + + + + + Valid + + + MainWindow @@ -151,534 +290,554 @@ - + Refresh - + Connect - + Disconnect - + Mifare - - >> + + Select Trailer - - << - - - - - F - - - - + Card Type - + MINI + 320 - + 1K - - - - - 2K - - - - - 4K - - - - - File - - - - - - Load + 1024 - - Save - - - - - - Data - - - - - Key - - - - - Attack - - - - - Card Info - - - - - Check Default - - - - - Nested - - - - - Hardnested - - - - - Read/Write - - - - - Block: - - - - - Key: - - - - - Key Type: - - - - - A - - - - - B - - - - - Data: - - - - - Normal(Require Password) - - - - - - Read Block - - - - - - Write Block - - - - - - - Read All - - - - - - Write All - - - - - Dump - - - - - Restore - - - - - Chinese Magic Card(Without Password) - - - - - Lock UFUID Card - - - - - - About UID Card - - - - - Set Parameter - - - - - Wipe - - - - - - Simulate - - - - - Load from data above + 2K + 2048 - + 4K + 4096 + + + + + File + + + + + + Load + + + + + + Save + + + + + + Data + + + + + Key + + + + + Attack + + + + + Card Info + + + + + Check Default + + + + + Nested + + + + + Hardnested + + + + + Read/Write + + + + + Block: + + + + + Key: + + + + + Key Type: + + + + + Snoop + + + + + List Data + + + + + Data: + + + + + Normal(Require Password) + + + + + Dump + + + + + Restore + + + + + Chinese Magic Card(Without Password) + + + + + Lock UFUID Card + + + + + + About UID Card + + + + + Set Parameter + + + + + Wipe + + + + + + Simulate + + + + + Clear - - + + Select All + + + + + KeyBlocks->Key + + + + + KeyBlocks<-Key + + + + + Fill Keys + + + + + Trailer Decoder + + + + + Set Fonts + + + + + + Read One + + + + + + Write One + + + + + + + Read Selected + + + + + + + Write Selected + + + + + Sniff - - List Sniff Data - - - - + RawCommand - - + + History: - + ClearHistory - + Send - + ClearOutput - - - - - - - - - - - + + + + + + + + + + + Info - + Plz choose a port first - + Connected - - - + + + Not Connected - + Binary Data Files(*.bin *.dump);;Text Data Files(*.txt *.eml);;All Files(*.*) - - - + + + Failed to open - - When Changeing card type, the data and keys in this app will be cleard. - - - - + Continue? - + + Check Update + + + + + Some of the data and key will be cleared. + + + + Plz select the font of data widget and key widget - + Data must consists of 32 Hex symbols(Whitespace is allowed) - - + + Key must consists of 12 Hex symbols(Whitespace is allowed) - + Plz select the data file: - + Plz select the key file: - + Binary Key Files(*.bin *.dump);;Binary Data Files(*.bin *.dump);;All Files(*.*) - + Plz select the location to save data file: - + Binary Data Files(*.bin *.dump);;Text Data Files(*.txt *.eml) - - - + + + Failed to save to - + Plz select the location to save key file: - + Binary Key Files(*.bin *.dump) - + Normally, the Block 0 of a typical Mifare card, which contains the UID, is locked during the manufacture. Users cannot write anything to Block 0 or set a new UID to a normal Mifare card. - + Chinese Magic Cards(aka UID Cards) are some special cards whose Block 0 are writeable. And you can change UID by writing to it. - + There are two versions of Chinese Magic Cards, the Gen1 and the Gen2. - + Gen1: - + also called UID card in China. It responses to some backdoor commands so you can access any blocks without password. The Proxmark3 has a bunch of related commands(csetblk, cgetblk, ...) to deal with this type of card, and my GUI also support these commands. - + Gen2: - + doesn't response to the backdoor commands, which means that a reader cannot detect whether it is a Chinese Magic Card or not by sending backdoor commands. - + There are some types of Chinese Magic Card Gen2. - + CUID Card: - + the Block 0 is writeable, you can write to this block repeatedly by normal wrbl command. - + (hf mf wrbl 0 A FFFFFFFFFFFF <the data you want to write>) - + FUID Card: - + you can only write to Block 0 once. After that, it seems like a typical Mifare card(Block 0 cannot be written to). - + (some readers might try changing the Block 0, which could detect the CUID Card. In that case, you should use FUID card.) - + UFUID Card: - + It behaves like a CUID card(or UID card? I'm not sure) before you send some special command to lock it. Once it is locked, you cannot change its Block 0(just like a typical Mifare card). - + Seemingly, these Chinese Magic Cards are more easily to be compromised by Nested Attack(it takes little time to get an unknown key). - + Plz select the trace file: - + Trace Files(*.trc);;All Files(*.*) - + Plz select the location to save trace file: - + Trace Files(*.trc) - - + + Idle - - + + Sec - + Blk - + KeyA - + KeyB - + HW Version: - + PM3: - + State: - + Running @@ -686,28 +845,31 @@ Mifare - - + Success! - - - - - + + + + Info - - + + Plz provide at least one known key + + + + + Failed! - + Failed to read card. diff --git a/lang/zh_CN.ts b/lang/zh_CN.ts index 3ae33f8..316cf77 100644 --- a/lang/zh_CN.ts +++ b/lang/zh_CN.ts @@ -8,10 +8,6 @@ Hardnested Attack Hardnested攻击 - - Known Key: - 已知Key: - Known Block: @@ -40,17 +36,9 @@ Target Block: 目标块: - - Target Block: - 目标块: - MF_Sim_simDialog - - Dialog - 对话框 - Simulate @@ -126,17 +114,9 @@ Generate random nonces instead of sequential nonces. Standard reader attack won't work with this option, only moebius attack works 生成随机nonce而不是顺序的nonce,这种情况下PM3将不对读卡器进行标准攻击,只进行moebius攻击 - - Generate random nonces instead of sequential nonces. Standard reader attack won't work with this option, only moebius attack works. - 生成随机nonce而不是顺序的nonce,这种情况下PM3将不对读卡器进行标准攻击,只进行moebius攻击 - MF_UID_parameterDialog - - Dialog - 对话框 - Set Parameter @@ -157,9 +137,148 @@ SAK: + + + MF_trailerDecoderDialog - The parameter will not change if you leave it empty. - 如果留空,则对应参数将保持不变 + + Trailer Decoder + Trailer解码 + + + + Blocks + 块大小 + + + + 4 + + + + + 16 + + + + + Trailer Data: +(like "FF0780" or "FF 07 80") + 输入Access Bits +(形如“FF0780”或“FF 07 80”) + + + + Or set bits manually + 手动设置访问情况: + + + + Cx0 + + + + + Cx1 + + + + + Cx2 + + + + + Cx3 + + + + + Data Block Permission: + 数据Block访问权限: + + + + Block0 + + + + + Block1 + + + + + Block2 + + + + + + Read + + + + + + Write + + + + + Increase + 增加 + + + + Decrease/Transfer/Restore + 减少/从缓冲区写入/读入至缓冲区 + + + + Trailer Block Permission: + Trailer访问权限: + + + + KeyA + + + + + Access Bits + Access Bits + + + + KeyB + + + + + Reference: +MF1S70YYX_V1 Product data sheet +Rev. 3.2 — 23 November 2017 + 参考资料: +MF1S70YYX_V1 Product data sheet +Rev. 3.2 — 23 November 2017 + + + + Note:the Access Bits usually contains 4 bytes(8 hex symbols), but only the first 3 bytes matters. You can set the 4th byte randomly. + 注意:Access Bits一般包含4个字节(8个16进制字符),但只有前3个字节决定访问情况,最后一个字节可任意设置。 + + + + Invalid! +It could make the whole sector blocked irreversibly! + 无效! +可能导致整个扇区被不可逆转地锁定! + + + + Valid + 有效 @@ -175,555 +294,554 @@ 路径: - + Refresh 刷新端口 - + Connect 连接 - + Disconnect 断开 - + Mifare Mifare(IC)卡 - - >> - + + Select Trailer + 选中密码块 - - << - - - - - F - - - - + Card Type 卡类型 - + MINI + 320 - + 1K + 1024 - + 2K + 2048 - + 4K + 4096 - + File 文件 - - + + Load 加载 - - + + Save 保存 - - + + Data - + Key - + Attack 破解 - + Card Info 读卡片信息 - + Check Default 验证默认密码 - + Nested Nested攻击 - + Hardnested Hardested攻击 - + Read/Write 读/写 - + Block: - + Key: - + Key Type: Key类型: - - A - + + Snoop + 嗅探(Snoop) - - B - + + List Data + 列出嗅探数据 - + Data: - + Normal(Require Password) 普通卡(需要密码) - - - Read Block - 读单个块 - - - - - Write Block - 写单个块 - - - - - - Read All - 读所有块 - - - - - Write All - 写所有块 - - - + Dump Dump命令 - + Restore Restore命令 - + Chinese Magic Card(Without Password) UID卡(不需要密码) - + Lock UFUID Card 锁定UFUID卡 - - + + About UID Card 关于UID卡 - + Set Parameter 设置卡参数 - + Wipe 擦除 - - + + Simulate 模拟 - - Load from data above - 从上方数据导入 - - - - + + Clear 清空 - - + + Select All + 全选 + + + + KeyBlocks->Key + 密码区->密码 + + + + KeyBlocks<-Key + 密码区<-密码 + + + + Fill Keys + 填充密码 + + + + Trailer Decoder + Trailer解码 + + + + Set Fonts + 设置字体 + + + + + Read One + 读取单个区 + + + + + Write One + 写入单个区 + + + + + + Read Selected + 读取选中块 + + + + + + Write Selected + 写入选中块 + + + + Sniff 嗅探 - - List Sniff Data - 列出嗅探数据 - - - + RawCommand 原始命令 - - + + History: 命令历史: - + ClearHistory 清空历史 - + Send 发送 - + ClearOutput 清空输出 - - - - - - - - - - - + + + + + + + + + + + Info 信息 - + Plz choose a port first 请先选择端口 - + Connected 已连接 - - - + + + Not Connected 未连接 - When Changeing card type, the data and keys in this app will be cleard. -Continue? - 更改卡容量后,窗口中的data和key会被清空\n要继续吗? - - - Plz choose the data file: - 请选择data文件: - - - + Binary Data Files(*.bin *.dump);;Text Data Files(*.txt *.eml);;All Files(*.*) 二进制Data文件(*.bin *.dump);;文本Data文件(*.txt *.eml);;所有文件(*.*) - - - + + + Failed to open 无法打开 - Plz choose the key file: - 请选择key文件: - - - Binary Key Files(*.bin *.dump);;All Files(*.*) - 二进制Key文件(*.bin *.dump);;所有文件(*.*) - - - Save data to - 保存数据至 - - - - When Changeing card type, the data and keys in this app will be cleard. - 卡片容量改变后,上方的所有Data和Key会被清空。 - - - + Continue? 确定? - + + Check Update + 检查更新 + + + + Some of the data and key will be cleared. + 部分数据和密码将被清除 + + + Plz select the font of data widget and key widget 请选择Data窗口和Key窗口的字体 - + Data must consists of 32 Hex symbols(Whitespace is allowed) Data必须由32个十六进制字符组成(中间可含有空格) - - + + Key must consists of 12 Hex symbols(Whitespace is allowed) Key必须由12个十六进制字符组成(中间可含有空格) - + Plz select the data file: 请选择data文件: - + Plz select the key file: 请选择key文件: - + Binary Key Files(*.bin *.dump);;Binary Data Files(*.bin *.dump);;All Files(*.*) 二进制Key文件(*.bin *.dump)二进制Data文件(*.bin *.dump);;所有文件(*.*) - + Plz select the location to save data file: 请选择文件保存的位置: - + Binary Data Files(*.bin *.dump);;Text Data Files(*.txt *.eml) 二进制Data文件(*.bin *.dump);;文本Data文件(*.txt *.eml) - - - + + + Failed to save to 无法保存至 - + Plz select the location to save key file: 请选择key文件保存的位置: - + Binary Key Files(*.bin *.dump) 二进制Key文件(*.bin *.dump) - + Normally, the Block 0 of a typical Mifare card, which contains the UID, is locked during the manufacture. Users cannot write anything to Block 0 or set a new UID to a normal Mifare card. 普通Mifare卡的Block0无法写入,UID也不能更改 - + Chinese Magic Cards(aka UID Cards) are some special cards whose Block 0 are writeable. And you can change UID by writing to it. UID卡(在国外叫Chinese Magic Card)的Block0可写,UID可变 - + There are two versions of Chinese Magic Cards, the Gen1 and the Gen2. 国外把UID卡分为Chinese Magic Card Gen1和Gen2 - + Gen1: - + also called UID card in China. It responses to some backdoor commands so you can access any blocks without password. The Proxmark3 has a bunch of related commands(csetblk, cgetblk, ...) to deal with this type of card, and my GUI also support these commands. 指通常所说的UID卡,可以通过后门指令直接读写块而无需密码,在PM3和此GUI中有特殊命令处理这类卡片 - + Gen2: - + doesn't response to the backdoor commands, which means that a reader cannot detect whether it is a Chinese Magic Card or not by sending backdoor commands. 这个叫法在国内比较罕见,在国外指CUID/FUID/UFUID这类对后门指令不响应的卡(防火墙卡) - + There are some types of Chinese Magic Card Gen2. 以下是Gen2卡的详细介绍 - + CUID Card: CUID卡: - + the Block 0 is writeable, you can write to this block repeatedly by normal wrbl command. 可通过普通的写块命令来写Block0,可重复擦写 - + (hf mf wrbl 0 A FFFFFFFFFFFF <the data you want to write>) (hf mf wrbl 0 A FFFFFFFFFFFF <待写入数据>) - + FUID Card: FUID卡: - + you can only write to Block 0 once. After that, it seems like a typical Mifare card(Block 0 cannot be written to). Block0只能写入一次 - + (some readers might try changing the Block 0, which could detect the CUID Card. In that case, you should use FUID card.) (更高级的穿防火墙卡,可以过一些能识别出CUID卡的读卡器) - + UFUID Card: UFUID卡: - + It behaves like a CUID card(or UID card? I'm not sure) before you send some special command to lock it. Once it is locked, you cannot change its Block 0(just like a typical Mifare card). 锁卡前和普通UID/CUID卡一样可以反复读写Block0,用特殊命令锁卡后就和FUID卡一样了 - + Seemingly, these Chinese Magic Cards are more easily to be compromised by Nested Attack(it takes little time to get an unknown key). 所有UID卡都似乎更容易被Nested攻击破解 - + Plz select the trace file: 请选择trace文件: - + Trace Files(*.trc);;All Files(*.*) Trace文件(*.trc);;所有文件(*.*) - + Plz select the location to save trace file: 请选择trace文件保存的位置: - + Trace Files(*.trc) Trace文件(*.trc) - - + + Idle 空闲 - - + + Sec - + Blk - + KeyA - + KeyB - + HW Version: 固件版本: - + PM3: 连接状态: - + State: 运行状态: - + Running 运行中 @@ -731,32 +849,31 @@ Continue? Mifare - info - 信息: - - - - + Success! 成功! - - - - - + + + + Info 信息 - - + + Plz provide at least one known key + 请至少提供一个已知密码 + + + + Failed! 失败! - + Failed to read card. 读卡失败。 diff --git a/main.cpp b/main.cpp index 5053128..999eb0b 100644 --- a/main.cpp +++ b/main.cpp @@ -11,7 +11,8 @@ int main(int argc, char *argv[]) QApplication a(argc, argv); MainWindow w; QSettings* settings = new QSettings("GUIsettings.ini", QSettings::IniFormat); - QVariant lang = settings->value("lang", "null"); + settings->beginGroup("lang"); + QVariant lang = settings->value("language", "null"); if(lang == "null") { #ifdef Q_OS_WIN @@ -44,12 +45,13 @@ int main(int argc, char *argv[]) if(translator->load(lang.toString())) { a.installTranslator(translator); - settings->setValue("lang", lang); + settings->setValue("language", lang); } else { QMessageBox::information(&w, "Error", "Can't load " + lang.toString() + " as translation file."); } + settings->endGroup(); delete settings; w.initUI(); w.show(); diff --git a/module/mifare.cpp b/module/mifare.cpp index 912e599..ad34915 100644 --- a/module/mifare.cpp +++ b/module/mifare.cpp @@ -1,5 +1,72 @@ #include "mifare.h" +const Mifare::CardType Mifare::card_mini = +{ + 0, + 5, + 20, + {4, 4, 4, 4, 4}, + {0, 4, 8, 12, 16} +}; +const Mifare::CardType Mifare::card_1k = +{ + 1, + 16, + 64, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60} +}; +const Mifare::CardType Mifare::card_2k = +{ + 2, + 32, + 128, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124} +}; +const Mifare::CardType Mifare::card_4k = +{ + 4, + 40, + 256, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 16, 16, 16, 16, 16, 16, 16, 16}, + {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, 144, 160, 176, 192, 208, 224, 240} +}; + +const Mifare::AccessType Mifare::dataCondition[8][4] = +{ + {ACC_KEY_AB, ACC_KEY_AB, ACC_KEY_AB, ACC_KEY_AB}, + {ACC_KEY_AB, ACC_NEVER, ACC_NEVER, ACC_NEVER}, + {ACC_KEY_AB, ACC_KEY_B, ACC_NEVER, ACC_NEVER}, + {ACC_KEY_AB, ACC_KEY_B, ACC_KEY_B, ACC_KEY_AB}, + {ACC_KEY_AB, ACC_NEVER, ACC_NEVER, ACC_KEY_AB}, + {ACC_KEY_B, ACC_KEY_B, ACC_NEVER, ACC_NEVER}, + {ACC_KEY_B, ACC_NEVER, ACC_NEVER, ACC_NEVER}, + {ACC_NEVER, ACC_NEVER, ACC_NEVER, ACC_NEVER}, +}; +const Mifare::AccessType Mifare::trailerReadCondition[8][3] = +{ + {ACC_NEVER, ACC_KEY_A, ACC_KEY_A}, + {ACC_NEVER, ACC_KEY_A, ACC_KEY_A}, + {ACC_NEVER, ACC_KEY_AB, ACC_NEVER}, + {ACC_NEVER, ACC_KEY_AB, ACC_NEVER}, + {ACC_NEVER, ACC_KEY_A, ACC_KEY_A}, + {ACC_NEVER, ACC_KEY_AB, ACC_NEVER}, + {ACC_NEVER, ACC_KEY_AB, ACC_NEVER}, + {ACC_NEVER, ACC_KEY_AB, ACC_NEVER}, +}; +const Mifare::AccessType Mifare::trailerWriteCondition[8][3] = +{ + {ACC_KEY_A, ACC_NEVER, ACC_KEY_A}, + {ACC_NEVER, ACC_NEVER, ACC_NEVER}, + {ACC_KEY_B, ACC_NEVER, ACC_KEY_B}, + {ACC_NEVER, ACC_NEVER, ACC_NEVER}, + {ACC_KEY_A, ACC_KEY_A, ACC_KEY_A}, + {ACC_KEY_B, ACC_KEY_B, ACC_KEY_B}, + {ACC_NEVER, ACC_KEY_B, ACC_NEVER}, + {ACC_NEVER, ACC_NEVER, ACC_NEVER}, +}; + Mifare::Mifare(Ui::MainWindow *ui, Util *addr, QWidget *parent): QObject(parent) { this->parent = parent; @@ -9,42 +76,45 @@ Mifare::Mifare(Ui::MainWindow *ui, Util *addr, QWidget *parent): QObject(parent) keyAList = new QStringList(); keyBList = new QStringList(); dataList = new QStringList(); - data_clearKey(); // fill with blank Qstring - data_clearData(); // fill with blank Qstring - dataPattern = new QRegExp("([0-9a-fA-F]{2} ){15}[0-9a-fA-F]{2}"); - chkKeyPattern = new QRegExp("\\|\\d{3}\\|.+\\|.+\\|"); - nestedKeyPattern = new QRegExp("\\|\\d{3}\\|.+\\|.+\\|.+\\|.+\\|"); + data_clearKey(); // fill with blank QString + data_clearData(); // fill with blank QString + dataPattern = new QRegularExpression("([0-9a-fA-F]{2} ){15}[0-9a-fA-F]{2}"); + keyPattern_res = new QRegularExpression("\\|\\d{3}\\|.+?\\|.+?\\|.+?\\|.+?\\|"); + keyPattern = new QRegularExpression("\\|\\d{3}\\|.+?\\|.+?\\|"); } QString Mifare::info(bool isRequiringOutput) { - if(isRequiringOutput) + if(util->getClientType() == Util::CLIENTTYPE_OFFICIAL || util->getClientType() == Util::CLIENTTYPE_ICEMAN) { - QString result = util->execCMDWithOutput("hf 14a info", 500); - qDebug() << result << result.indexOf(QRegExp(ui->MF_RW_dataEdit->text()), 0); - result.replace("UID :", "|"); - result.replace("ATQA :", "|"); - result.replace("SAK :", "|"); - result.replace("TYPE :", "|"); - QStringList lis = result.split("|"); - if(lis.length() > 4) + if(isRequiringOutput) { - qDebug() << lis[1] + lis[2] + lis[3]; - return lis[1] + lis[2] + lis[3]; + QString result = util->execCMDWithOutput("hf 14a info", 500); + result.replace("UID :", "|||"); + result.replace("ATQA :", "|||"); + result.replace("SAK :", "|||"); + result.replace("TYPE :", "|||"); + QStringList lis = result.split("|||"); + if(lis.length() > 4) + { + qDebug() << lis[1] + lis[2] + lis[3]; + return lis[1] + lis[2] + lis[3]; + } + else + return ""; } else + { + util->execCMD("hf 14a info"); + ui->funcTab->setCurrentIndex(1); return ""; - } - else - { - util->execCMD("hf 14a info"); - ui->funcTab->setCurrentIndex(1); - return ""; + } } } void Mifare::chk() { + QRegularExpressionMatch reMatch; QString result = util->execCMDWithOutput( "hf mf chk *" + QString::number(cardType.type) @@ -53,50 +123,129 @@ void Mifare::chk() qDebug() << result; int offset = 0; - QString tmp, tmp2; - for(int i = 0; i < cardType.sectors; i++) + QString data; + if(util->getClientType() == Util::CLIENTTYPE_OFFICIAL) { - offset = chkKeyPattern->indexIn(result, offset); -// offset = result.indexOf(*chkKeyPattern, offset); - tmp = result.mid(offset, 39).toUpper(); - offset += 39; - qDebug() << tmp << offset; - tmp2 = tmp.mid(7, 12).trimmed(); - if(tmp2 != "?") - keyAList->replace(i, tmp2); - tmp2 = tmp.mid(24, 12).trimmed(); - if(tmp2 != "?") - keyBList->replace(i, tmp2); + for(int i = 0; i < cardType.sector_size; i++) + { + reMatch = keyPattern->match(result, offset); + offset = reMatch.capturedStart(); + if(reMatch.hasMatch()) + { + data = reMatch.captured().toUpper(); + offset += data.length(); + QStringList cells = data.remove(" ").split("|"); + if(!cells[2].contains("?")) + { + keyAList->replace(i, cells[2]); + } + if(!cells[3].contains("?")) + { + keyBList->replace(i, cells[3]); + } + } + } + } + else if(util->getClientType() == Util::CLIENTTYPE_ICEMAN) + { + for(int i = 0; i < cardType.sector_size; i++) + { + reMatch = keyPattern_res->match(result, offset); + offset = reMatch.capturedStart(); + if(reMatch.hasMatch()) + { + data = reMatch.captured().toUpper(); + offset += data.length(); + QStringList cells = data.remove(" ").split("|"); + if(cells[3] == "1") + { + keyAList->replace(i, cells[2]); + } + if(cells[5] == "1") + { + keyBList->replace(i, cells[4]); + } + } + } + } data_syncWithKeyWidget(); } void Mifare::nested() { - QString result = util->execCMDWithOutput( + QRegularExpressionMatch reMatch; + QString result; + int offset = 0; + QString data; + if(util->getClientType() == Util::CLIENTTYPE_OFFICIAL) + { + result = util->execCMDWithOutput( + "hf mf nested " + + QString::number(cardType.type) + + " *", 10000); + } + else if(util->getClientType() == Util::CLIENTTYPE_ICEMAN) + { + QString knownKeyInfo = ""; + for(int i = 0; i < cardType.sector_size; i++) + { + if(data_isKeyValid(keyAList->at(i))) + { + knownKeyInfo = " " + QString::number(i * 4) + " A " + keyAList->at(i); + break; + } + } + if(knownKeyInfo == "") + { + for(int i = 0; i < cardType.sector_size; i++) + { + if(data_isKeyValid(keyBList->at(i))) + { + knownKeyInfo = " " + QString::number(i * 4) + " B " + keyBList->at(i); + break; + } + } + } + if(knownKeyInfo != "") + { + result = util->execCMDWithOutput( "hf mf nested " + QString::number(cardType.type) - + " *"); + + knownKeyInfo, 10000); + } + else + { + QMessageBox::information(parent, tr("Info"), tr("Plz provide at least one known key")); + } - int offset = 0; - QString tmp; - for(int i = 0; i < cardType.sectors; i++) + } + for(int i = 0; i < cardType.sector_size; i++) { - offset = nestedKeyPattern->indexIn(result, offset); -// offset = result.indexOf(*nestedKeyPattern, offset); - tmp = result.mid(offset, 47).toUpper(); - offset += 47; - if(tmp.at(23) == '1') - keyAList->replace(i, tmp.mid(7, 12).trimmed()); - if(tmp.at(44) == '1') - keyBList->replace(i, tmp.mid(28, 12).trimmed()); + reMatch = keyPattern_res->match(result, offset); + offset = reMatch.capturedStart(); + if(reMatch.hasMatch()) + { + data = reMatch.captured().toUpper(); + offset += data.length(); + QStringList cells = data.remove(" ").split("|"); + if(cells[3] == "1") + { + keyAList->replace(i, cells[2]); + } + if(cells[5] == "1") + { + keyBList->replace(i, cells[4]); + } + } } data_syncWithKeyWidget(); + } void Mifare::hardnested() { - MF_Attack_hardnestedDialog dialog(cardType.blocks); + MF_Attack_hardnestedDialog dialog(cardType.block_size); connect(&dialog, &MF_Attack_hardnestedDialog::sendCMD, util, &Util::execCMD); if(dialog.exec() == QDialog::Accepted) ui->funcTab->setCurrentIndex(1); @@ -108,313 +257,337 @@ void Mifare::sniff() ui->funcTab->setCurrentIndex(1); } +void Mifare::snoop() +{ + util->execCMD("hf 14a snoop"); + ui->funcTab->setCurrentIndex(1); +} + void Mifare::list() { util->execCMD("hf list mf"); ui->funcTab->setCurrentIndex(1); } -void Mifare::read() +QString Mifare::_readblk(int blockId, KeyType keyType, const QString& key, TargetType targetType, int waitTime) { - int waitTime = 300; - int currblk = ui->MF_RW_blockBox->currentText().toInt(); - QString result = util->execCMDWithOutput( + QString data; + QString result; + bool isKeyBlock = (blockId < 128 && ((blockId + 1) % 4 == 0)) || ((blockId + 1) % 16 == 0); + + if(util->getClientType() == Util::CLIENTTYPE_OFFICIAL || util->getClientType() == Util::CLIENTTYPE_ICEMAN) + { + if(targetType == TARGET_MIFARE) + { + if(!data_isKeyValid(key)) + { + return ""; + } + // use the given key type to read the target block + result = util->execCMDWithOutput( "hf mf rdbl " - + QString::number(currblk) + + QString::number(blockId) + " " - + ui->MF_RW_keyTypeBox->currentText() + + (char)keyType + " " - + ui->MF_RW_keyEdit->text(), + + key, waitTime); - if(result.indexOf("isOk:01") != -1) - { - result = result.mid(dataPattern->indexIn(result), 47).toUpper(); - if((currblk < 128 && ((currblk + 1) % 4 == 0)) || ((currblk + 1) % 8 == 0)) // process key block - { - if(ui->MF_RW_keyTypeBox->currentText() == "A") + if(result.indexOf("isOk:01") != -1) { - for(int i = 0; i < 6; i++) + data = dataPattern->match(result).captured().toUpper(); + data.remove(" "); + // when the target block is a key block and the given key type is KeyA, try to check whether the KeyB is valid(by Access Bits) + // if the given key type is KeyB, it will never get the KeyA from the key block + if(isKeyBlock && keyType == KEY_A) // in this case, the Access Bits is always accessible { - result = result.replace(i * 3, 2, ui->MF_RW_keyEdit->text().mid(i * 2, 2)); + data.replace(0, 12, key); + QList ACBits = data_getACBits(data.mid(12, 8)); + if(ACBits[3] == 2 || ACBits[3] == 3 || ACBits[3] == 5 || ACBits[3] == 6 || ACBits[3] == 7) // in these cases, the KeyB cannot be read by KeyA + { + data.replace(20, 12, "????????????"); + } } - ui->MF_RW_dataEdit->setText(result); - QString tmpKey = result.right(18).replace(" ", ""); - result = util->execCMDWithOutput( - "hf mf rdbl " - + ui->MF_RW_keyTypeBox->currentText() - + " B " - + tmpKey, - waitTime); - if(result.indexOf("isOk:01") == -1) + else if(isKeyBlock && keyType == KEY_B) { - result = ui->MF_RW_dataEdit->text(); - result = result.replace(30, 17, "?? ?? ?? ?? ?? ??"); - ui->MF_RW_dataEdit->setText(result); + data.replace(20, 12, key);; + data.replace(0, 12, "????????????"); // fill the keyA part with ? } } else - { - for(int i = 0; i < 6; i++) - { - result = result.replace( - 30 + i * 3, - 2, - ui->MF_RW_keyEdit->text().mid(i * 2, 2)); - } - result = result.replace(0, 18, "?? ?? ?? ?? ?? ?? "); - ui->MF_RW_dataEdit->setText(result); - } + data = ""; } - else - { - ui->MF_RW_dataEdit->setText(result); - } - } -} - -void Mifare::readAll() -{ - QString result; - bool isKeyAValid; - bool isKeyBValid; - const int waitTime = 150; - - QString tmp; - int offset = 0; - for(int i = 0; i < cardType.sectors; i++) - { - result = ""; - isKeyAValid = false; - isKeyBValid = false; - - // check keys and read the first block of each sector - if(data_isKeyValid(keyAList->at(i))) + else if(targetType == TARGET_UID) { result = util->execCMDWithOutput( - "hf mf rdsc " - + QString::number(i) - + " A " - + keyAList->at(i), - waitTime); - qDebug() << result; - offset = result.indexOf("isOk:01"); - if(offset != -1) - { - isKeyAValid = true; - for(int j = 0; j < cardType.blk[i]; j++) - { - offset = dataPattern->indexIn(result, offset); -// offset = result.indexOf(*dataPattern, offset); - tmp = result.mid(offset, 47).toUpper(); - offset += 47; - qDebug() << tmp; - tmp.replace(" ", ""); - dataList->replace(cardType.blks[i] + j, tmp); - data_syncWithDataWidget(false, cardType.blks[i] + j); - } - } - } - if(data_isKeyValid(keyBList->at(i))) - { - result = util->execCMDWithOutput( - "hf mf rdsc " - + QString::number(i) - + " B " - + keyBList->at(i), - waitTime); - qDebug() << result; - offset = result.indexOf("isOk:01"); - if(offset != -1) - { - isKeyBValid = true; - for(int j = 0; j < cardType.blk[i]; j++) - { - offset = dataPattern->indexIn(result, offset); -// offset = result.indexOf(*dataPattern, offset); - tmp = result.mid(offset, 47).toUpper(); - offset += 47; - qDebug() << tmp; - tmp.replace(" ", ""); - dataList->replace(cardType.blks[i] + j, tmp); - data_syncWithDataWidget(false, cardType.blks[i] + j); - } - } - } - - if(isKeyAValid || isKeyBValid) - { - - // fill the MF_dataWidget with the known valid key - // - // check whether the MF_dataWidget contains the valid key, - // and fill MF_keyWidget(when you only have KeyA but the ReadBlock output - // contains the KeyB) - // - // the structure is not symmetric, since you cannot get KeyA from output - // this program will only process the provided KeyA(in MF_keyWidget) - result = dataList->at(cardType.blks[i] + cardType.blk[i] - 1); - if(isKeyAValid) - { - result.replace(0, 12, keyAList->at(i)); - } - else - { - result = result.replace(0, 12, "????????????"); - } - dataList->replace(cardType.blks[i] + cardType.blk[i] - 1, result); - - if(isKeyBValid) - { - result.replace(20, 12, keyBList->at(i)); - dataList->replace(cardType.blks[i] + cardType.blk[i] - 1, result); - data_syncWithDataWidget(false, cardType.blks[i] + cardType.blk[i] - 1); - } - else // now isKeyAValid == true, the output might contains the KeyB - { - QString tmpKey = - dataList->at(cardType.blks[i] + cardType.blk[i] - 1).right(12); - result = util->execCMDWithOutput( - "hf mf rdbl " - + QString::number(cardType.blks[i] + cardType.blk[i] - 1) - + " B " - + tmpKey, - waitTime); - if(result.indexOf("isOk:01") != -1) - { - keyBList->replace(i, tmpKey); - data_syncWithKeyWidget(false, i, false); - } - else - { - result = dataList->at(cardType.blks[i] + cardType.blk[i] - 1); - result = result.replace(20, 12, "????????????"); - dataList->replace(cardType.blks[i] + cardType.blk[i] - 1, result); - } - } - data_syncWithDataWidget(false, cardType.blks[i] + cardType.blk[i] - 1); - } - } -} - -void Mifare::write() -{ - int waitTime = 300; - QString result = util->execCMDWithOutput( - "hf mf wrbl " + ui->MF_RW_blockBox->currentText() + " " + - ui->MF_RW_keyTypeBox->currentText() + " " + - ui->MF_RW_keyEdit->text() + " " + - ui->MF_RW_dataEdit->text().replace(" ", ""), - waitTime); - if(result.indexOf("isOk:01") != -1) - { - QMessageBox::information(parent, tr("Info"), tr("Success!")); - } - else - { - QMessageBox::information(parent, tr("Info"), tr("Failed!")); - } -} - -void Mifare::writeAll() -{ - const int waitTime = 300; - QString result; - for(int i = 0; i < cardType.sectors; i++) - { - for(int j = 0; j < cardType.blk[i]; j++) - { - result = ""; // if the KeyA is invalid and the result is not empty, the - // KeyB will not be tested. - if(data_isDataValid(dataList->at(cardType.blks[i] + j)) != DATA_NOSPACE || dataList->at(cardType.blks[i] + j).contains('?')) - continue; - if(data_isKeyValid(keyAList->at(i))) - { - result = util->execCMDWithOutput( - "hf mf wrbl " + - QString::number(cardType.blks[i] + j) - + " A " - + keyAList->at(i) - + " " - + dataList->at(cardType.blks[i] + j), - waitTime); - } - qDebug() << i << j << result.indexOf("isOk:01") << data_isKeyValid(keyBList->at(i)); - if(result.indexOf("isOk:01") == -1 && data_isKeyValid(keyBList->at(i))) - { - result = util->execCMDWithOutput( - "hf mf wrbl " - + QString::number(cardType.blks[i] + j) - + " B " - + keyBList->at(i) - + " " - + dataList->at(cardType.blks[i] + j), - waitTime); - } - } - } -} - -void Mifare::readC() -{ - int waitTime = 300; - int currblk = ui->MF_RW_blockBox->currentText().toInt(); - QString result = util->execCMDWithOutput( "hf mf cgetblk " - + QString::number(currblk), + + QString::number(blockId), waitTime); - if(result.indexOf("No chinese") == -1) + if(result.indexOf("Chinese magic") != -1) + { + data = dataPattern->match(result).captured().toUpper(); + data.remove(" "); + } + else + data = ""; + } + else if(targetType == TARGET_EMULATOR) + { + result = util->execCMDWithOutput( + "hf mf eget " + + QString::number(blockId), + 150); + data = dataPattern->match(result).captured().toUpper(); + data.remove(" "); + } + } + return data; +} + +QStringList Mifare::_readsec(int sectorId, KeyType keyType, const QString& key, TargetType targetType, int waitTime) +{ + QStringList data; + QString result, tmp; + QRegularExpressionMatch reMatch; + int offset = -1; + + for(int i = 0; i < cardType.blk[sectorId]; i++) + { + data.append(""); + } + + if(util->getClientType() == Util::CLIENTTYPE_OFFICIAL || util->getClientType() == Util::CLIENTTYPE_ICEMAN) + { + // try to read all blocks together + if(targetType == TARGET_MIFARE) + { + if(!data_isKeyValid(key)) + { + return data; + } + result = util->execCMDWithOutput( + "hf mf rdsc " + + QString::number(sectorId) + + " " + + (char)keyType + + " " + + key, + waitTime); + offset = result.indexOf("isOk:01"); + } + else if(targetType == TARGET_UID) + { + result = util->execCMDWithOutput( + "hf mf cgetsc " + + QString::number(sectorId), + waitTime); + offset = result.indexOf("Chinese magic"); + } + if(offset != -1) + { + for(int i = 0; i < cardType.blk[sectorId]; i++) + { + reMatch = dataPattern->match(result, offset); + offset = reMatch.capturedStart(); + if(reMatch.hasMatch()) + { + tmp = reMatch.captured().toUpper(); + offset += tmp.length(); + tmp.remove(" "); + data[i] = tmp; + } + } + } + // if failed, try to read them seperately. + // (when one of the block cannot be read, the rdsc will return nothing, so you need to read the rest of blocks manually) + else if(targetType != TARGET_UID) // if the targetType is Chinese Magic Card, then the result implies the backdoor command is invalid. + { + for(int i = 0; i < cardType.blk[sectorId]; i++) + data[i] = _readblk(cardType.blks[sectorId] + i, keyType, key, targetType, waitTime); + } + + //process trailer(like _readblk()) + QString trailer = data[cardType.blk[sectorId] - 1]; + if(trailer != "" && targetType == TARGET_MIFARE) + { + if(keyType == KEY_A) // in this case, the Access Bits is always accessible + { + trailer.replace(0, 12, key); + QList ACBits = data_getACBits(trailer.mid(12, 8)); + if(ACBits[3] == 2 || ACBits[3] == 3 || ACBits[3] == 5 || ACBits[3] == 6 || ACBits[3] == 7) // in these cases, the KeyB cannot be read by KeyA + { + trailer.replace(20, 12, "????????????"); + } + } + else if(keyType == KEY_B) + { + trailer.replace(20, 12, key);; + trailer.replace(0, 12, "????????????"); // fill the keyA part with ? + } + data[cardType.blk[sectorId] - 1] = trailer; + } + } + return data; +} + +void Mifare::readOne(TargetType targetType) +{ + int blockId = ui->MF_RW_blockBox->currentText().toInt(); + Mifare::KeyType keyType = (Mifare::KeyType)(ui->MF_RW_keyTypeBox->currentData().toInt()); + QString result = _readblk(blockId, keyType, ui->MF_RW_keyEdit->text().toUpper(), targetType); + if(result != "") { - result = result.mid(dataPattern->indexIn(result), 47).toUpper(); ui->MF_RW_dataEdit->setText(result); } + else + { + ui->MF_RW_dataEdit->setText(tr("Failed!")); + } } -void Mifare::readAllC() +void Mifare::readSelected(TargetType targetType) { - QString result; - const int waitTime = 150; - - QString tmp; - int offset = 0; - for(int i = 0; i < cardType.sectors; i++) + QStringList data, dataA, dataB; + QString trailerA, trailerB; + QList selectedSectors; + QList selectedBlocks; + for(int i = 0; i < cardType.block_size; i++) + { + if(ui->MF_dataWidget->item(i, 1)->checkState() == Qt::Checked) + selectedBlocks.append(i); + } + + for(int i = 0; i < cardType.sector_size; i++) + { + selectedSectors.append(false); + } + for(int item : selectedBlocks) + { + selectedSectors[data_b2s(item)] = true; + } + + for(int i = 0; i < cardType.sector_size; i++) { - result = util->execCMDWithOutput( - "hf mf cgetsc " - + QString::number(i), - waitTime); - qDebug() << result; - if(result.indexOf("No chinese") == -1) { - offset = 0; + if(!selectedSectors[i]) + { + continue; + } for(int j = 0; j < cardType.blk[i]; j++) { - offset = dataPattern->indexIn(result, offset); -// offset = result.indexOf(*dataPattern, offset); - tmp = result.mid(offset, 47).toUpper(); - offset += 47; - qDebug() << tmp; - tmp.replace(" ", ""); - dataList->replace(cardType.blks[i] + j, tmp); - data_syncWithDataWidget(false, cardType.blks[i] + j); + // dataA is always filled with "" because of the _readsec() + data.append(""); + dataB.append(""); } - keyAList->replace(i, dataList->at(cardType.blks[i] + cardType.blk[i] - 1).left(12)); - keyBList->replace(i, dataList->at(cardType.blks[i] + cardType.blk[i] - 1).right(12)); - data_syncWithKeyWidget(false, i, true); - data_syncWithKeyWidget(false, i, false); + + dataA = _readsec(i, Mifare::KEY_A, keyAList->at(i), targetType); + + // in other situations, the key doesn't matters + if(targetType == TARGET_MIFARE && (dataA.contains("") || dataA[cardType.blk[i] - 1].right(12) == "????????????")) + dataB = _readsec(i, Mifare::KEY_B, keyBList->at(i), targetType); + + for(int j = 0; j < cardType.blk[i]; j++) + { + if(dataA[j] != "") + data[j] = dataA[j]; + else + data[j] = dataB[j]; + } + + // process trailer block seperately + trailerA = dataA[cardType.blk[i] - 1]; + trailerB = dataB[cardType.blk[i] - 1]; + if(trailerA != "" && trailerB != "") + { + QString ACbits = trailerA.mid(12, 8); + QString key_A = trailerA.left(12); + QString key_B = trailerA.at(31) != '?' ? trailerA.right(12) : trailerB.right(12); + data[cardType.blk[i] - 1] = key_A + ACbits + key_B; + } + + for(int j = 0; j < cardType.blk[i]; j++) + { + if(selectedBlocks.contains(cardType.blks[i] + j)) + { + dataList->replace(cardType.blks[i] + j, data[j]); + data_syncWithDataWidget(false, cardType.blks[i] + j); + } + } + + if(selectedBlocks.contains(cardType.blks[i] + cardType.blk[i] - 1)) + { + // data widget has been updated, so this is just a temporary varient. + if(data[cardType.blk[i] - 1] == "") + data[cardType.blk[i] - 1] = "????????????????????????????????"; + + // doesn't replace the existing key. + if(!data_isKeyValid(keyAList->at(i))) + keyAList->replace(i, data[cardType.blk[i] - 1].left(12)); + if(!data_isKeyValid(keyBList->at(i))) + keyBList->replace(i, data[cardType.blk[i] - 1].right(12)); + data_syncWithKeyWidget(false, i, KEY_A); + data_syncWithKeyWidget(false, i, KEY_B); + } + } } } -void Mifare::writeC() +bool Mifare::_writeblk(int blockId, KeyType keyType, const QString& key, const QString& data, TargetType targetType, int waitTime) { - int waitTime = 150; - QString result = util->execCMDWithOutput( - "hf mf csetblk " - + ui->MF_RW_blockBox->currentText() + QString result; + QString input = data.toUpper(); + input.remove(" "); + + if(data_isDataValid(input) != DATA_NOSPACE) + return false; + + if(util->getClientType() == Util::CLIENTTYPE_OFFICIAL || util->getClientType() == Util::CLIENTTYPE_ICEMAN) + { + if(targetType == TARGET_MIFARE) + { + if(!data_isKeyValid(key)) + return false; + result = util->execCMDWithOutput( + "hf mf wrbl " + + QString::number(blockId) + " " - + ui->MF_RW_dataEdit->text().replace(" ", ""), + + (char)keyType + + " " + + key + + " " + + input, waitTime); - if(result.indexOf("No chinese") == -1) + return (result.indexOf("isOk:01") != -1); + } + else if(targetType == TARGET_UID) + { + result = util->execCMDWithOutput( + "hf mf csetblk " + + QString::number(blockId) + + " " + + input, + waitTime); + return (result.indexOf("Chinese magic") != -1); + } + else if(targetType == TARGET_EMULATOR) + { + util->execCMD( + "hf mf eset " + + QString::number(blockId) + + " " + + input); + util->delay(5); + return true; + } + } +} + +void Mifare::writeOne(TargetType targetType) +{ + int blockId = ui->MF_RW_blockBox->currentText().toInt(); + Mifare::KeyType keyType = (Mifare::KeyType)(ui->MF_RW_keyTypeBox->currentData().toInt()); + bool isSuccessful = _writeblk(blockId, keyType, ui->MF_RW_keyEdit->text().toUpper(), ui->MF_RW_dataEdit->text(), targetType); + if(isSuccessful) { QMessageBox::information(parent, tr("Info"), tr("Success!")); } @@ -424,25 +597,40 @@ void Mifare::writeC() } } -void Mifare::writeAllC() +QList Mifare::writeSelected(TargetType targetType) { - const int waitTime = 150; - QString result; - for(int i = 0; i < cardType.sectors; i++) + QList failedBlocks; + QList selectedBlocks; + for(int i = 0; i < cardType.block_size; i++) { - for(int j = 0; j < cardType.blk[i]; j++) + if(ui->MF_dataWidget->item(i, 1)->checkState() == Qt::Checked) + selectedBlocks.append(i); + } + for(int item : selectedBlocks) + { + bool result = false; + if(targetType == TARGET_MIFARE) { - result = ""; - if(data_isDataValid(dataList->at(cardType.blks[i] + j)) != DATA_NOSPACE || dataList->at(cardType.blks[i] + j).contains('?')) - continue; - result = util->execCMDWithOutput( - "hf mf csetblk " - + QString::number(cardType.blks[i] + j) - + " " - + dataList->at(cardType.blks[i] + j), - waitTime); + result = _writeblk(item, KEY_A, keyAList->at(data_b2s(item)), dataList->at(item), TARGET_MIFARE); + if(!result) + { + result = _writeblk(item, KEY_B, keyBList->at(data_b2s(item)), dataList->at(item), TARGET_MIFARE); + } + if(!result) + { + result = _writeblk(item, KEY_A, "FFFFFFFFFFFF", dataList->at(item), TARGET_MIFARE); + } + } + else // key doesn't matter when writing to Chinese Magic Card and Emulator Memory + { + result = _writeblk(item, KEY_A, "FFFFFFFFFFFF", dataList->at(item), targetType); + } + if(!result) + { + failedBlocks.append(item); } } + return failedBlocks; } void Mifare::dump() @@ -494,69 +682,6 @@ void Mifare::lockC() util->execCMD("hf 14a raw 52"); } -void Mifare::writeAllE() -{ - const int waitTime = 200; - QString result; - for(int i = 0; i < cardType.sectors; i++) - { - for(int j = 0; j < cardType.blk[i]; j++) - { - result = ""; - if(data_isDataValid(dataList->at(cardType.blks[i] + j)) != DATA_NOSPACE || dataList->at(cardType.blks[i] + j).contains('?')) - continue; - result = util->execCMDWithOutput( - "hf mf eset " - + QString::number(cardType.blks[i] + j) - + " " - + dataList->at(cardType.blks[i] + j), - waitTime); - } - } - util->execCMDWithOutput("hf mf eget", waitTime); // to refresh output buffer; -} - -void Mifare::readAllE() -{ - QString result; - const int waitTime = 200; - - QString tmp; - int offset = 0; - for(int i = 0; i < cardType.sectors; i++) - { - offset = 0; - for(int j = 0; j < cardType.blk[i]; j++) - { - - qDebug() << "**********" ; - result = util->execCMDWithOutput( - "hf mf eget " - + QString::number(cardType.blks[i] + j), - waitTime); - qDebug() << result ; - - offset = dataPattern->indexIn(result); -// offset = result.indexOf(*dataPattern, offset); // When I find the data position in this way, the Regex might fail to match. - - tmp = result.mid(offset, 47).toUpper(); - qDebug() << tmp << offset; - qDebug() << "**********" ; - - if(offset == -1) - continue; - tmp.replace(" ", ""); - dataList->replace(cardType.blks[i] + j, tmp); - data_syncWithDataWidget(false, cardType.blks[i] + j); - } - keyAList->replace(i, dataList->at(cardType.blks[i] + cardType.blk[i] - 1).left(12)); - keyBList->replace(i, dataList->at(cardType.blks[i] + cardType.blk[i] - 1).right(12)); - data_syncWithKeyWidget(false, i, true); - data_syncWithKeyWidget(false, i, false); - - } -} - void Mifare::wipeE() { util->execCMD("hf mf eclr"); @@ -584,10 +709,11 @@ void Mifare::saveSniff(const QString& file) void Mifare::data_syncWithDataWidget(bool syncAll, int block) { + ui->MF_dataWidget->blockSignals(true); QString tmp; if(syncAll) { - for(int i = 0; i < cardType.blocks; i++) + for(int i = 0; i < cardType.block_size; i++) { tmp = ""; if(dataList->at(i) != "") @@ -616,13 +742,15 @@ void Mifare::data_syncWithDataWidget(bool syncAll, int block) } ui->MF_dataWidget->item(block, 2)->setText(tmp); } + ui->MF_dataWidget->blockSignals(false); } -void Mifare::data_syncWithKeyWidget(bool syncAll, int sector, bool isKeyA) +void Mifare::data_syncWithKeyWidget(bool syncAll, int sector, KeyType keyType) { + ui->MF_keyWidget->blockSignals(true); if(syncAll) { - for(int i = 0; i < cardType.sectors; i++) + for(int i = 0; i < cardType.sector_size; i++) { ui->MF_keyWidget->item(i, 1)->setText(keyAList->at(i)); ui->MF_keyWidget->item(i, 2)->setText(keyBList->at(i)); @@ -630,28 +758,59 @@ void Mifare::data_syncWithKeyWidget(bool syncAll, int sector, bool isKeyA) } else { - if(isKeyA) + if(keyType == KEY_A) ui->MF_keyWidget->item(sector, 1)->setText(keyAList->at(sector)); else ui->MF_keyWidget->item(sector, 2)->setText(keyBList->at(sector)); } + ui->MF_keyWidget->blockSignals(false); } -void Mifare::data_clearData() +void Mifare::data_clearData(bool clearAll) { - dataList->clear(); - for(int i = 0; i < cardType.blocks; i++) - dataList->append(""); -} - -void Mifare::data_clearKey() -{ - keyAList->clear(); - keyBList->clear(); - for(int i = 0; i < cardType.sectors; i++) + if(clearAll) { - keyAList->append(""); - keyBList->append(""); + dataList->clear(); + } + + int delta = cardType.block_size - dataList->length() ; + if(delta >= 0) + { + for(int i = 0; i < delta; i++) + dataList->append(""); + } + else if(delta < 0) + { + for(int i = 0; i < -delta; i++) + + dataList->removeLast(); + } +} + +void Mifare::data_clearKey(bool clearAll) +{ + if(clearAll) + { + keyAList->clear(); + keyBList->clear(); + } + + int delta = cardType.sector_size - keyAList->length() ; + if(delta >= 0) + { + for(int i = 0; i < delta; i++) + { + keyAList->append(""); + keyBList->append(""); + } + } + else if(delta < 0) + { + for(int i = 0; i < -delta; i++) + { + keyAList->removeLast(); + keyBList->removeLast(); + } } } @@ -667,8 +826,7 @@ bool Mifare::data_isKeyValid(const QString &key) return true; } -Mifare::DataType -Mifare::data_isDataValid(QString data) // "?" will not been processd there +Mifare::DataType Mifare::data_isDataValid(const QString& data) // "?" will not been processd there { if(data.length() == 47) { @@ -717,8 +875,8 @@ void Mifare::setCardType(int type) cardType = card_2k; else if(type == 4) cardType = card_4k; - data_clearKey(); - data_clearData(); + data_clearKey(false); + data_clearData(false); } } @@ -730,7 +888,7 @@ bool Mifare::data_loadDataFile(const QString &filename) QByteArray buff; buff = file.read(10000); bool isBin = false; - for(int i = 0; i < cardType.blocks * 16; i++) // Detect the file type + for(int i = 0; i < cardType.block_size * 16; i++) // Detect the file type { // qDebug() << (unsigned char)buff[i]; if(!((buff[i] >= 'A' && buff[i] <= 'F') || (buff[i] >= 'a' && buff[i] <= 'f') || (buff[i] >= '0' && buff[i] <= '9') || buff[i] == '\n' || buff[i] == '\r')) @@ -741,9 +899,9 @@ bool Mifare::data_loadDataFile(const QString &filename) } if(isBin) { - if(file.size() < cardType.blocks * 16) + if(file.size() < cardType.block_size * 16) return false; - for(int i = 0; i < cardType.blocks; i++) + for(int i = 0; i < cardType.block_size; i++) { QString tmp = bin2text(buff, i, 16); dataList->replace(i, tmp.toUpper()); @@ -751,9 +909,9 @@ bool Mifare::data_loadDataFile(const QString &filename) } else { - QString tmp = buff.left(cardType.blocks * 34); + QString tmp = buff.left(cardType.block_size * 34); QStringList tmpList = tmp.split("\r\n"); - for(int i = 0; i < cardType.blocks; i++) + for(int i = 0; i < cardType.block_size; i++) { dataList->replace(i, tmpList[i].toUpper()); qDebug() << tmpList[i]; @@ -776,10 +934,10 @@ bool Mifare::data_loadKeyFile(const QString &filename) { QByteArray buff; buff = file.read(10000); - bool isKey = file.size() <= cardType.sectors * 14; + bool isKey = file.size() <= cardType.sector_size * 14; if(isKey) { - for(int i = 0; i < cardType.sectors; i++) + for(int i = 0; i < cardType.sector_size; i++) { QString tmp = bin2text(buff, i, 12); keyAList->replace(i, tmp.left(12).toUpper()); @@ -788,7 +946,7 @@ bool Mifare::data_loadKeyFile(const QString &filename) } else { - for(int i = 0; i < cardType.sectors; i++) + for(int i = 0; i < cardType.sector_size; i++) { int blk = cardType.blks[i] + cardType.blk[i] - 1; QString tmp = bin2text(buff, blk, 16); @@ -833,11 +991,11 @@ bool Mifare::data_saveDataFile(const QString &filename, bool isBin) QChar tmp; if(isBin) { - for(int i = 0; i < cardType.blocks; i++) + for(int i = 0; i < cardType.block_size; i++) { for(int j = 0; j < 16; j++) { - unsigned char Byt[2]; + unsigned char Byt[2] = {0x0, 0x0}; for(int k = 0; k < 2; k++) { tmp = dataList->at(i).at(j * 2 + k).toUpper(); @@ -852,7 +1010,7 @@ bool Mifare::data_saveDataFile(const QString &filename, bool isBin) } else { - for(int i = 0; i < cardType.blocks; i++) + for(int i = 0; i < cardType.block_size; i++) { buff += dataList->at(i); buff += "\r\n"; @@ -877,11 +1035,11 @@ bool Mifare::data_saveKeyFile(const QString &filename, bool isBin) QChar tmp; if(isBin) { - for(int i = 0; i < cardType.sectors; i++) + for(int i = 0; i < cardType.sector_size; i++) { for(int j = 0; j < 6; j++) { - unsigned char Byt[2]; + unsigned char Byt[2] = {0x0, 0x0}; for(int k = 0; k < 2; k++) { tmp = keyAList->at(i).at(j * 2 + k).toUpper(); @@ -894,7 +1052,7 @@ bool Mifare::data_saveKeyFile(const QString &filename, bool isBin) } for(int j = 0; j < 6; j++) { - unsigned char Byt[2]; + unsigned char Byt[2] = {0x0, 0x0}; for(int k = 0; k < 2; k++) { tmp = keyBList->at(i).at(j * 2 + k).toUpper(); @@ -922,7 +1080,7 @@ bool Mifare::data_saveKeyFile(const QString &filename, bool isBin) void Mifare::data_key2Data() { - for(int i = 0; i < cardType.sectors; i++) + for(int i = 0; i < cardType.sector_size; i++) { QString tmp = ""; if(data_isKeyValid(keyAList->at(i))) @@ -947,7 +1105,7 @@ void Mifare::data_key2Data() void Mifare::data_data2Key() { - for(int i = 0; i < cardType.sectors; i++) + for(int i = 0; i < cardType.sector_size; i++) { if(dataList->at(cardType.blks[i] + cardType.blk[i] - 1) == "") { @@ -968,10 +1126,64 @@ void Mifare::data_setData(int block, const QString &data) dataList->replace(block, data); } -void Mifare::data_setKey(int sector, bool isKeyA, const QString &key) +void Mifare::data_setKey(int sector, KeyType keyType, const QString &key) { - if(isKeyA) + if(keyType == KEY_A) keyAList->replace(sector, key); else keyBList->replace(sector, key); } + +void Mifare::data_fillKeys() +{ + for(int i = 0; i < cardType.sector_size; i++) + { + if(!data_isKeyValid(keyAList->at(i))) + { + keyAList->replace(i, "FFFFFFFFFFFF"); + } + if(!data_isKeyValid(keyBList->at(i))) + { + keyBList->replace(i, "FFFFFFFFFFFF"); + } + } + data_syncWithKeyWidget(); +} + +int Mifare::data_b2s(int block) +{ + if(block >= 0 && block < 128) + return block / 4; + else if(block < 256) + return (block - 128) / 16 + 32; + else + return -1; +} + +QList Mifare::data_getACBits(const QString& text) //return empty QList if the text is invalid +{ + QString input = text; + QList result; + input.remove(" "); + if(input.length() < 6) + { + return result; + } + input = input.left(6); + quint32 val = input.toUInt(nullptr, 16); + quint8 halfBytes[6]; + for(int i = 0; i < 6; i++) + { + halfBytes[i] = (val >> ((5 - i) * 4)) & 0xf; + } + qDebug() << val; + if((~halfBytes[0] & 0xf) == halfBytes[5] && (~halfBytes[1] & 0xf) == halfBytes[2] && (~halfBytes[3] & 0xf) == halfBytes[4]) + { + for(int i = 0; i < 4; i++) + { + result.append((((halfBytes[4] >> i) & 1) << 2) | (((halfBytes[5] >> i) & 1) << 1) | (((halfBytes[2] >> i) & 1) << 0)); + } + } + return result; +} + diff --git a/module/mifare.h b/module/mifare.h index 4d804b6..3223f67 100644 --- a/module/mifare.h +++ b/module/mifare.h @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include class Mifare : public QObject { @@ -17,18 +17,11 @@ class Mifare : public QObject public: explicit Mifare(Ui::MainWindow *ui, Util *addr, QWidget *parent = nullptr); - QString info(bool isRequiringOutput = false); - void chk(); - void nested(); - void hardnested(); - void sniff(); - void list(); - void read(); - void readAll(); - void write(); - void writeAll(); - void dump(); - void restore(); + enum KeyType + { + KEY_A = 'A', + KEY_B = 'B', + }; enum DataType { @@ -39,79 +32,82 @@ public: struct CardType { - int type; - int sectors; - int blocks; - int blk[40]; - int blks[40]; + quint8 type; + quint8 sector_size; + quint16 block_size; + quint8 blk[40]; + quint8 blks[40]; }; - const CardType card_mini = + enum AccessType { - 0, - 5, - 20, - {4, 4, 4, 4, 4}, - {0, 4, 8, 12, 16} - }; - const CardType card_1k = - { - 1, - 16, - 64, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60} - }; - const CardType card_2k = - { - 2, - 32, - 128, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124} - }; - const CardType card_4k = - { - 4, - 40, - 256, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 16, 16, 16, 16, 16, 16, 16, 16}, - {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, 144, 160, 176, 192, 208, 224, 240} + ACC_NEVER = 0, + ACC_KEY_A = 1, + ACC_KEY_B = 2, + ACC_KEY_AB = 3, }; - void data_clearData(); - void data_clearKey(); - bool data_isKeyValid(const QString &key); - Mifare::DataType data_isDataValid(QString data); + enum TargetType + { + TARGET_MIFARE, + TARGET_UID, + TARGET_EMULATOR, + }; + + static const CardType card_mini; + static const CardType card_1k; + static const CardType card_2k; + static const CardType card_4k; + + static const AccessType dataCondition[8][4]; + static const AccessType trailerReadCondition[8][3]; + static const AccessType trailerWriteCondition[8][3]; + + QString info(bool isRequiringOutput = false); + void chk(); + void nested(); + void hardnested(); + void sniff(); + void snoop(); + void list(); + void readOne(TargetType targetType = TARGET_MIFARE); + void readSelected(TargetType targetType = TARGET_MIFARE); + void writeOne(TargetType targetType = TARGET_MIFARE); + QList writeSelected(TargetType targetType = TARGET_MIFARE); + void dump(); + void restore(); + + void data_clearData(bool clearAll = true); + void data_clearKey(bool clearAll = true); + static bool data_isKeyValid(const QString& key); + static Mifare::DataType data_isDataValid(const QString& data); void data_syncWithDataWidget(bool syncAll = true, int block = 0); - void data_syncWithKeyWidget(bool syncAll = true, int sector = 0, bool isKeyA = true); + void data_syncWithKeyWidget(bool syncAll = true, int sector = 0, KeyType keyType = KEY_A); CardType cardType; Mifare::CardType getCardType(); void setCardType(int type); - void writeAllC(); - void writeC(); - void readAllC(); - void readC(); void wipeC(); void setParameterC(); - bool data_loadDataFile(const QString &filename); - bool data_loadKeyFile(const QString &filename); + bool data_loadDataFile(const QString& filename); + bool data_loadKeyFile(const QString& filename); bool data_saveDataFile(const QString& filename, bool isBin); - bool data_saveKeyFile(const QString &filename, bool isBin); + bool data_saveKeyFile(const QString& filename, bool isBin); void data_key2Data(); void data_data2Key(); - void data_setData(int block, const QString &data); - void data_setKey(int sector, bool isKeyA, const QString &key); + void data_setData(int block, const QString& data); + void data_setKey(int sector, KeyType keyType, const QString& key); void lockC(); - void writeAllE(); - void readAllE(); void wipeE(); void simulate(); void loadSniff(const QString& file); void saveSniff(const QString& file); + void data_fillKeys(); + + static QList data_getACBits(const QString &text); + static int data_b2s(int block); public slots: signals: @@ -123,11 +119,14 @@ private: QStringList* keyAList; QStringList* keyBList; QStringList* dataList; - QRegExp* dataPattern; - QRegExp* chkKeyPattern; - QRegExp* nestedKeyPattern; - QRegExp* UIDPattern; - QString bin2text(const QByteArray &buff, int start, int length); + QRegularExpression* dataPattern; + QRegularExpression* keyPattern_res; + QRegularExpression* keyPattern; + QString bin2text(const QByteArray& buff, int start, int length); + + QString _readblk(int blockId, KeyType keyType, const QString &key, TargetType targetType = TARGET_MIFARE, int waitTime = 300); + QStringList _readsec(int sectorId, KeyType keyType, const QString &key, TargetType targetType = TARGET_MIFARE, int waitTime = 300); + bool _writeblk(int blockId, KeyType keyType, const QString &key, const QString &data, TargetType targetType = TARGET_MIFARE, int waitTime = 300); }; #endif // MIFARE_H diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 7259805..fc350cf 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -6,19 +6,23 @@ MainWindow::MainWindow(QWidget *parent): , ui(new Ui::MainWindow) { ui->setupUi(this); -// ui->MF_simGroupBox->setVisible(false); // developing... -// ui->MF_sniffGroupBox->setVisible(false); // developing... myInfo = new QAction("wh201906", this); + checkUpdate = new QAction(tr("Check Update"), this); connect(myInfo, &QAction::triggered, [ = ]() { QDesktopServices::openUrl(QUrl("https://github.com/wh201906")); }); + connect(checkUpdate, &QAction::triggered, [ = ]() + { + QDesktopServices::openUrl(QUrl("https://github.com/wh201906/Proxmark3GUI/releases")); + }); this->addAction(myInfo); + this->addAction(checkUpdate); + + settings = new QSettings("GUIsettings.ini", QSettings::IniFormat); pm3Thread = new QThread(this); pm3 = new PM3Process(pm3Thread); -// pm3->moveToThread(pm3Thread); -// pm3->init(); pm3Thread->start(); pm3state = false; @@ -77,6 +81,7 @@ void MainWindow::on_PM3_connectButton_clicked() QMessageBox::information(NULL, tr("Info"), tr("Plz choose a port first"), QMessageBox::Ok); else { + saveClientPath(ui->PM3_pathEdit->text()); emit connectPM3(ui->PM3_pathEdit->text(), port); } } @@ -92,6 +97,7 @@ void MainWindow::onPM3StateChanged(bool st, QString info) } else { + setStatusBar(PM3VersionBar, ""); setStatusBar(connectStatusBar, tr("Not Connected")); } } @@ -176,12 +182,22 @@ void MainWindow::MF_onTypeChanged(int id, bool st) qDebug() << id << typeBtnGroup->checkedId(); if(!st) { - int result = QMessageBox::question(this, tr("Info"), tr("When Changeing card type, the data and keys in this app will be cleard.") + "\n" + tr("Continue?"), QMessageBox::Yes | QMessageBox::No); + int result; + if(id > typeBtnGroup->checkedId()) // id is specified in uiInit() with a proper order, so I can compare the size by id. + { + result = QMessageBox::question(this, tr("Info"), tr("Some of the data and key will be cleared.") + "\n" + tr("Continue?"), QMessageBox::Yes | QMessageBox::No); + } + else + { + result = QMessageBox::Yes; + } if(result == QMessageBox::Yes) { qDebug() << "Yes"; mifare->setCardType(typeBtnGroup->checkedId()); MF_widgetReset(); + mifare->data_syncWithDataWidget(); + mifare->data_syncWithKeyWidget(); } else { @@ -192,16 +208,93 @@ void MainWindow::MF_onTypeChanged(int id, bool st) typeBtnGroup->blockSignals(false); } -void MainWindow::on_MF_data2KeyBotton_clicked() +void MainWindow::on_MF_selectAllBox_stateChanged(int arg1) +{ + ui->MF_dataWidget->blockSignals(true); + ui->MF_selectAllBox->blockSignals(true); + ui->MF_selectTrailerBox->blockSignals(true); + if(arg1 == Qt::PartiallyChecked) + { + ui->MF_selectAllBox->setTristate(false); + ui->MF_selectAllBox->setCheckState(Qt::Checked); + } + for(int i = 0; i < mifare->cardType.block_size; i++) + { + ui->MF_dataWidget->item(i, 1)->setCheckState(ui->MF_selectAllBox->checkState()); + } + for(int i = 0; i < mifare->cardType.sector_size; i++) + { + ui->MF_dataWidget->item(mifare->cardType.blks[i], 0)->setCheckState(ui->MF_selectAllBox->checkState()); + } + ui->MF_selectTrailerBox->setCheckState(ui->MF_selectAllBox->checkState()); + ui->MF_dataWidget->blockSignals(false); + ui->MF_selectAllBox->blockSignals(false); + ui->MF_selectTrailerBox->blockSignals(false); +} + + +void MainWindow::on_MF_selectTrailerBox_stateChanged(int arg1) +{ + int selectedSubBlocks = 0; + + ui->MF_dataWidget->blockSignals(true); + ui->MF_selectAllBox->blockSignals(true); + ui->MF_selectTrailerBox->blockSignals(true); + if(arg1 == Qt::PartiallyChecked) + { + ui->MF_selectTrailerBox->setTristate(false); + ui->MF_selectTrailerBox->setCheckState(Qt::Checked); + } + for(int i = 0; i < mifare->cardType.sector_size; i++) + { + ui->MF_dataWidget->item(mifare->cardType.blks[i] + mifare->cardType.blk[i] - 1, 1)->setCheckState(ui->MF_selectTrailerBox->checkState()); + selectedSubBlocks = 0; + for(int j = 0; j < mifare->cardType.blk[i]; j++) + { + if(ui->MF_dataWidget->item(j + mifare->cardType.blks[i], 1)->checkState() == Qt::Checked) + selectedSubBlocks++; + } + if(selectedSubBlocks == 0) + { + ui->MF_dataWidget->item(mifare->cardType.blks[i], 0)->setCheckState(Qt::Unchecked); + } + else if(selectedSubBlocks == mifare->cardType.blk[i]) + { + ui->MF_dataWidget->item(mifare->cardType.blks[i], 0)->setCheckState(Qt::Checked); + } + else + { + ui->MF_dataWidget->item(mifare->cardType.blks[i], 0)->setCheckState(Qt::PartiallyChecked); + } + } + + ui->MF_dataWidget->blockSignals(false); + ui->MF_selectAllBox->blockSignals(false); + ui->MF_selectTrailerBox->blockSignals(false); +} + + +void MainWindow::on_MF_data2KeyButton_clicked() { mifare->data_data2Key(); } -void MainWindow::on_MF_key2DataBotton_clicked() +void MainWindow::on_MF_key2DataButton_clicked() { mifare->data_key2Data(); } +void MainWindow::on_MF_fillKeysButton_clicked() +{ + mifare->data_fillKeys(); +} + +void MainWindow::on_MF_trailerDecoderButton_clicked() +{ + decDialog = new MF_trailerDecoderDialog(this); + decDialog->show(); +} + void MainWindow::on_MF_fontButton_clicked() { bool isOK = false; @@ -216,8 +309,100 @@ void MainWindow::on_MF_fontButton_clicked() void MainWindow::on_MF_dataWidget_itemChanged(QTableWidgetItem *item) { + ui->MF_dataWidget->blockSignals(true); + ui->MF_selectAllBox->blockSignals(true); + ui->MF_selectTrailerBox->blockSignals(true); + if(item->column() == 0) + { + int selectedSectors = 0; + for(int i = 0; i < mifare->cardType.blk[Mifare::data_b2s(item->row())]; i++) + { + ui->MF_dataWidget->item(i + item->row(), 1)->setCheckState(item->checkState()); + qDebug() << i << mifare->cardType.blk[item->row()] << i + item->row() << ui->MF_dataWidget->item(i + item->row(), 1)->text(); + } + for(int i = 0; i < mifare->cardType.sector_size; i++) + { + if(ui->MF_dataWidget->item(mifare->cardType.blks[i], 0)->checkState() == Qt::Checked) + { + selectedSectors++; + } + } + if(selectedSectors == 0) + { + ui->MF_selectAllBox->setCheckState(Qt::Unchecked); + ui->MF_selectTrailerBox->setCheckState(Qt::Unchecked); + } + else if(selectedSectors == mifare->cardType.sector_size) + { + ui->MF_selectAllBox->setCheckState(Qt::Checked); + ui->MF_selectTrailerBox->setCheckState(Qt::Checked); + } + else + { + ui->MF_selectAllBox->setCheckState(Qt::PartiallyChecked); + ui->MF_selectTrailerBox->setCheckState(Qt::PartiallyChecked); + } + } + else if(item->column() == 1) + { + int selectedSubBlocks = 0; + int selectedBlocks = 0; + int selectedTrailers = 0; - if(item->column() == 2) + for(int i = 0; i < mifare->cardType.block_size; i++) + { + if(ui->MF_dataWidget->item(i, 1)->checkState() == Qt::Checked) + selectedBlocks++; + } + for(int i = 0; i < mifare->cardType.blk[Mifare::data_b2s(item->row())]; i++) + { + if(ui->MF_dataWidget->item(i + mifare->cardType.blks[Mifare::data_b2s(item->row())], 1)->checkState() == Qt::Checked) + selectedSubBlocks++; + } + for(int i = 0; i < mifare->cardType.sector_size; i++) + { + int targetBlock = mifare->cardType.blks[i] + mifare->cardType.blk[i] - 1; + if(ui->MF_dataWidget->item(targetBlock, 1)->checkState() == Qt::Checked) + selectedTrailers++; + } + if(selectedBlocks == 0) + { + ui->MF_selectAllBox->setCheckState(Qt::Unchecked); + } + else if(selectedBlocks == mifare->cardType.block_size) + { + ui->MF_selectAllBox->setCheckState(Qt::Checked); + } + else + { + ui->MF_selectAllBox->setCheckState(Qt::PartiallyChecked); + } + if(selectedSubBlocks == 0) + { + ui->MF_dataWidget->item(mifare->cardType.blks[Mifare::data_b2s(item->row())], 0)->setCheckState(Qt::Unchecked); + } + else if(selectedSubBlocks == mifare->cardType.blk[Mifare::data_b2s(item->row())]) + { + ui->MF_dataWidget->item(mifare->cardType.blks[Mifare::data_b2s(item->row())], 0)->setCheckState(Qt::Checked); + } + else + { + ui->MF_dataWidget->item(mifare->cardType.blks[Mifare::data_b2s(item->row())], 0)->setCheckState(Qt::PartiallyChecked); + } + if(selectedTrailers == 0) + { + ui->MF_selectTrailerBox->setCheckState(Qt::Unchecked); + } + else if(selectedTrailers == mifare->cardType.sector_size) + { + ui->MF_selectTrailerBox->setCheckState(Qt::Checked); + } + else + { + ui->MF_selectTrailerBox->setCheckState(Qt::PartiallyChecked); + } + } + else if(item->column() == 2) { QString data = item->text().replace(" ", "").toUpper(); if(data == "" || mifare->data_isDataValid(data) == Mifare::DATA_NOSPACE) @@ -230,6 +415,9 @@ void MainWindow::on_MF_dataWidget_itemChanged(QTableWidgetItem *item) } mifare->data_syncWithDataWidget(false, item->row()); } + ui->MF_dataWidget->blockSignals(false); + ui->MF_selectAllBox->blockSignals(false); + ui->MF_selectTrailerBox->blockSignals(false); } void MainWindow::on_MF_keyWidget_itemChanged(QTableWidgetItem *item) @@ -239,26 +427,26 @@ void MainWindow::on_MF_keyWidget_itemChanged(QTableWidgetItem *item) QString key = item->text().replace(" ", "").toUpper(); if(key == "" || mifare->data_isKeyValid(key)) { - mifare->data_setKey(item->row(), true, key); + mifare->data_setKey(item->row(), Mifare::KEY_A, key); } else { QMessageBox::information(this, tr("Info"), tr("Key must consists of 12 Hex symbols(Whitespace is allowed)")); } - mifare->data_syncWithKeyWidget(false, item->row(), true); + mifare->data_syncWithKeyWidget(false, item->row(), Mifare::KEY_A); } else if(item->column() == 2) { - QString key = item->text().replace(" ", ""); + QString key = item->text().replace(" ", "").toUpper(); if(key == "" || mifare->data_isKeyValid(key)) { - mifare->data_setKey(item->row(), false, key); + mifare->data_setKey(item->row(), Mifare::KEY_B, key); } else { QMessageBox::information(this, tr("Info"), tr("Key must consists of 12 Hex symbols(Whitespace is allowed)")); } - mifare->data_syncWithKeyWidget(false, item->row(), false); + mifare->data_syncWithKeyWidget(false, item->row(), Mifare::KEY_B); } } @@ -369,31 +557,32 @@ void MainWindow::on_MF_Attack_hardnestedButton_clicked() mifare->hardnested(); } -void MainWindow::on_MF_RW_readAllButton_clicked() +void MainWindow::on_MF_RW_readSelectedButton_clicked() { setState(false); - mifare->readAll(); + mifare->readSelected(Mifare::TARGET_MIFARE); setState(true); } void MainWindow::on_MF_RW_readBlockButton_clicked() { setState(false); - mifare->read(); + mifare->readOne(Mifare::TARGET_MIFARE); setState(true); } void MainWindow::on_MF_RW_writeBlockButton_clicked() { setState(false); - mifare->write(); + mifare->writeOne(); setState(true); } -void MainWindow::on_MF_RW_writeAllButton_clicked() +void MainWindow::on_MF_RW_writeSelectedButton_clicked() { + QList failedBlocks; setState(false); - mifare->writeAll(); + failedBlocks = mifare->writeSelected(Mifare::TARGET_MIFARE); setState(true); } @@ -407,31 +596,32 @@ void MainWindow::on_MF_RW_restoreButton_clicked() mifare->restore(); } -void MainWindow::on_MF_UID_readAllButton_clicked() +void MainWindow::on_MF_UID_readSelectedButton_clicked() { setState(false); - mifare->readAllC(); + mifare->readSelected(Mifare::TARGET_UID); setState(true); } void MainWindow::on_MF_UID_readBlockButton_clicked() { setState(false); - mifare->readC(); + mifare->readOne(Mifare::TARGET_UID); setState(true); } -void MainWindow::on_MF_UID_writeAllButton_clicked() +void MainWindow::on_MF_UID_writeSelectedButton_clicked() { + QList failedBlocks; setState(false); - mifare->writeAllC(); + failedBlocks = mifare->writeSelected(Mifare::TARGET_UID); setState(true); } void MainWindow::on_MF_UID_writeBlockButton_clicked() { setState(false); - mifare->writeC(); + mifare->writeOne(Mifare::TARGET_UID); setState(true); } @@ -473,17 +663,18 @@ void MainWindow::on_MF_UID_lockButton_clicked() mifare->lockC(); } -void MainWindow::on_MF_Sim_loadDataButton_clicked() +void MainWindow::on_MF_Sim_readSelectedButton_clicked() { setState(false); - mifare->writeAllE(); + mifare->readSelected(Mifare::TARGET_EMULATOR); setState(true); } -void MainWindow::on_MF_Sim_writeAllButton_clicked() +void MainWindow::on_MF_Sim_writeSelectedButton_clicked() { + QList failedBlocks; setState(false); - mifare->readAllE(); + failedBlocks = mifare->writeSelected(Mifare::TARGET_EMULATOR); setState(true); } @@ -548,6 +739,13 @@ void MainWindow::on_MF_Sniff_sniffButton_clicked() setState(true); } +void MainWindow::on_MF_Sniff_snoopButton_clicked() +{ + setState(false); + mifare->snoop(); + setState(true); +} + void MainWindow::on_MF_Sniff_listButton_clicked() { mifare->list(); @@ -555,16 +753,22 @@ void MainWindow::on_MF_Sniff_listButton_clicked() void MainWindow::MF_widgetReset() { - int secs = mifare->cardType.sectors; - int blks = mifare->cardType.blocks; + int secs = mifare->cardType.sector_size; + int blks = mifare->cardType.block_size; ui->MF_RW_blockBox->clear(); ui->MF_keyWidget->setRowCount(secs); ui->MF_dataWidget->setRowCount(blks); + ui->MF_dataWidget->blockSignals(true); + ui->MF_keyWidget->blockSignals(true); + ui->MF_selectAllBox->blockSignals(true); + ui->MF_selectTrailerBox->blockSignals(true); + for(int i = 0; i < blks; i++) { setTableItem(ui->MF_dataWidget, i, 0, ""); setTableItem(ui->MF_dataWidget, i, 1, QString::number(i)); + ui->MF_dataWidget->item(i, 1)->setCheckState(Qt::Checked); setTableItem(ui->MF_dataWidget, i, 2, ""); ui->MF_RW_blockBox->addItem(QString::number(i)); } @@ -575,7 +779,15 @@ void MainWindow::MF_widgetReset() setTableItem(ui->MF_keyWidget, i, 1, ""); setTableItem(ui->MF_keyWidget, i, 2, ""); setTableItem(ui->MF_dataWidget, mifare->cardType.blks[i], 0, QString::number(i)); + ui->MF_dataWidget->item(mifare->cardType.blks[i], 0)->setCheckState(Qt::Checked); } + ui->MF_selectAllBox->setCheckState(Qt::Checked); + ui->MF_selectTrailerBox->setCheckState(Qt::Checked); + + ui->MF_dataWidget->blockSignals(false); + ui->MF_keyWidget->blockSignals(false); + ui->MF_selectAllBox->blockSignals(false); + ui->MF_selectTrailerBox->blockSignals(false); } // ************************************************ @@ -601,8 +813,8 @@ void MainWindow::uiInit() ui->MF_dataWidget->setHorizontalHeaderItem(1, new QTableWidgetItem(tr("Blk"))); ui->MF_dataWidget->setHorizontalHeaderItem(2, new QTableWidgetItem(tr("Data"))); ui->MF_dataWidget->verticalHeader()->setVisible(false); - ui->MF_dataWidget->setColumnWidth(0, 35); - ui->MF_dataWidget->setColumnWidth(1, 35); + ui->MF_dataWidget->setColumnWidth(0, 55); + ui->MF_dataWidget->setColumnWidth(1, 55); ui->MF_dataWidget->setColumnWidth(2, 430); ui->MF_keyWidget->setColumnCount(3); @@ -625,6 +837,35 @@ void MainWindow::uiInit() ui->MF_keyWidget->installEventFilter(this); ui->MF_dataWidget->installEventFilter(this); + settings->beginGroup("UI_grpbox_preference"); + + QStringList boxNames = settings->allKeys(); + QGroupBox* boxptr; + foreach(QString name, boxNames) + { + boxptr = this->findChild(name); + if(boxptr == nullptr) + continue; + if(settings->value(name, true).toBool()) + { + boxptr->setMaximumHeight(16777215); + boxptr->setChecked(true); + } + else + { + boxptr->setMaximumHeight(20); + boxptr->setChecked(false); + } + } + settings->endGroup(); + + settings->beginGroup("Client_Path"); + ui->PM3_pathEdit->setText(settings->value("path", "proxmark3").toString()); + settings->endGroup(); + + ui->MF_RW_keyTypeBox->addItem("A", Mifare::KEY_A); + ui->MF_RW_keyTypeBox->addItem("B", Mifare::KEY_B); + on_Raw_CMDHistoryBox_stateChanged(Qt::Unchecked); on_PM3_refreshPortButton_clicked(); } @@ -632,6 +873,7 @@ void MainWindow::uiInit() void MainWindow::signalInit() { connect(pm3, &PM3Process::newOutput, util, &Util::processOutput); + connect(pm3, &PM3Process::changeClientType, util, &Util::setClientType); connect(util, &Util::refreshOutput, this, &MainWindow::refreshOutput); connect(this, &MainWindow::connectPM3, pm3, &PM3Process::connectPM3); @@ -639,6 +881,14 @@ void MainWindow::signalInit() connect(this, &MainWindow::killPM3, pm3, &PM3Process::kill); connect(util, &Util::write, pm3, &PM3Process::write); + + connect(ui->MF_typeGroupBox, &QGroupBox::clicked, this, &MainWindow::on_GroupBox_clicked); + connect(ui->MF_fileGroupBox, &QGroupBox::clicked, this, &MainWindow::on_GroupBox_clicked); + connect(ui->MF_RWGroupBox, &QGroupBox::clicked, this, &MainWindow::on_GroupBox_clicked); + connect(ui->MF_normalGroupBox, &QGroupBox::clicked, this, &MainWindow::on_GroupBox_clicked); + connect(ui->MF_UIDGroupBox, &QGroupBox::clicked, this, &MainWindow::on_GroupBox_clicked); + connect(ui->MF_simGroupBox, &QGroupBox::clicked, this, &MainWindow::on_GroupBox_clicked); + connect(ui->MF_sniffGroupBox, &QGroupBox::clicked, this, &MainWindow::on_GroupBox_clicked); } void MainWindow::setStatusBar(QLabel* target, const QString & text) @@ -718,8 +968,28 @@ void MainWindow::setState(bool st) ui->Raw_sendCMDButton->setEnabled(st); } +void MainWindow::on_GroupBox_clicked(bool checked) +{ + QGroupBox* box = dynamic_cast(sender()); + + settings->beginGroup("UI_grpbox_preference"); + if(checked) + { + box->setMaximumHeight(16777215); + settings->setValue(box->objectName(), true); + } + else + { + box->setMaximumHeight(20); + settings->setValue(box->objectName(), false); + } + settings->endGroup(); +} + +void MainWindow::saveClientPath(const QString& path) +{ + settings->beginGroup("Client_Path"); + settings->setValue("path", path); + settings->endGroup(); +} // *********************************************** - - - - diff --git a/ui/mainwindow.h b/ui/mainwindow.h index 087fc5d..873f6d4 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -16,10 +16,14 @@ #include #include #include +#include +#include +#include #include "common/pm3process.h" #include "module/mifare.h" #include "common/util.h" +#include "ui/mf_trailerdecoderdialog.h" QT_BEGIN_NAMESPACE namespace Ui @@ -73,7 +77,7 @@ private slots: void on_MF_Sniff_listButton_clicked(); - void on_MF_RW_readAllButton_clicked(); + void on_MF_RW_readSelectedButton_clicked(); void on_MF_RW_readBlockButton_clicked(); @@ -81,18 +85,18 @@ private slots: void on_MF_Attack_infoButton_clicked(); - void on_MF_RW_writeAllButton_clicked(); + void on_MF_RW_writeSelectedButton_clicked(); void on_MF_RW_dumpButton_clicked(); void on_MF_RW_restoreButton_clicked(); - void on_MF_UID_readAllButton_clicked(); + void on_MF_UID_readSelectedButton_clicked(); void on_MF_UID_readBlockButton_clicked(); - void on_MF_UID_writeAllButton_clicked(); + void on_MF_UID_writeSelectedButton_clicked(); void on_MF_UID_writeBlockButton_clicked(); @@ -100,9 +104,9 @@ private slots: void on_MF_File_saveButton_clicked(); - void on_MF_data2KeyBotton_clicked(); + void on_MF_data2KeyButton_clicked(); - void on_MF_key2DataBotton_clicked(); + void on_MF_key2DataButton_clicked(); void on_MF_dataWidget_itemChanged(QTableWidgetItem *item); @@ -120,9 +124,9 @@ private slots: void on_MF_UID_lockButton_clicked(); - void on_MF_Sim_loadDataButton_clicked(); + void on_MF_Sim_readSelectedButton_clicked(); - void on_MF_Sim_writeAllButton_clicked(); + void on_MF_Sim_writeSelectedButton_clicked(); void on_MF_Sim_clearButton_clicked(); @@ -132,6 +136,18 @@ private slots: void on_MF_Sniff_saveButton_clicked(); + void on_GroupBox_clicked(bool checked); + + void on_MF_selectAllBox_stateChanged(int arg1); + + void on_MF_fillKeysButton_clicked(); + + void on_MF_Sniff_snoopButton_clicked(); + + void on_MF_trailerDecoderButton_clicked(); + + void on_MF_selectTrailerBox_stateChanged(int arg1); + private: Ui::MainWindow* ui; QButtonGroup* typeBtnGroup; @@ -139,6 +155,8 @@ private: QLabel* programStatusBar; QLabel* PM3VersionBar; QAction* myInfo; + QAction* checkUpdate; + QSettings* settings; void uiInit(); @@ -149,11 +167,14 @@ private: Mifare* mifare; Util* util; + MF_trailerDecoderDialog* decDialog; + void signalInit(); void MF_widgetReset(); void setTableItem(QTableWidget *widget, int row, int column, const QString &text); void setState(bool st); + void saveClientPath(const QString &path); signals: void connectPM3(const QString path, const QString port); void killPM3(); diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 8e90e42..edd93f4 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -6,13 +6,13 @@ 0 0 - 870 + 970 770 - 870 + 970 770 @@ -58,11 +58,7 @@ - - - ../pm3/win64/proxmark3 - - + @@ -76,6 +72,12 @@ + + + 40 + 0 + + Refresh @@ -83,6 +85,12 @@ + + + 40 + 0 + + Connect @@ -90,6 +98,12 @@ + + + 40 + 0 + + Disconnect @@ -105,6 +119,9 @@ 0 + + 0 + Mifare @@ -180,84 +197,97 @@ - - - - 0 - 0 - - - - - 20 - 20 - - - - - 20 - 20 - - + - >> + Select All - - - - 0 - 0 - + + + Select Trailer - + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + 20 20 - + + + + + - 20 - 20 + 40 + 0 - << + KeyBlocks->Key + + + + + + + + 40 + 0 + + + + KeyBlocks<-Key + + + + + + + + 40 + 0 + + + + Fill Keys + + + + + + + + 40 + 0 + + + + Trailer Decoder - - - 20 - 20 - - - 20 - 20 + 40 + 0 - - - 20 - 20 - - - - - Courier - 75 - true - - - F + Set Fonts @@ -317,7 +347,7 @@ Card Type - false + true @@ -328,15 +358,27 @@ + + + 0 + 0 + + - MINI + MINI + + + 0 + 0 + + - 1K + 1K true @@ -345,15 +387,27 @@ + + + 0 + 0 + + - 2K + 2K + + + 0 + 0 + + - 4K + 4K @@ -365,6 +419,9 @@ File + + true + 5 @@ -436,6 +493,9 @@ Attack + + true + 5 @@ -458,6 +518,12 @@ + + + 40 + 0 + + Card Info @@ -480,7 +546,7 @@ - 0 + 40 0 @@ -491,6 +557,12 @@ + + + 40 + 0 + + Hardnested @@ -519,6 +591,9 @@ Read/Write + + true + @@ -586,7 +661,7 @@ - 35 + 45 0 @@ -596,16 +671,6 @@ 16777215 - - - A - - - - - B - - @@ -635,6 +700,9 @@ Normal(Require Password) + + true + 5 @@ -647,29 +715,53 @@ + + + 40 + 0 + + - Read Block + Read One + + + 40 + 0 + + - Write Block + Write One - + + + + 0 + 0 + + - Read All + Read Selected - + + + + 0 + 0 + + - Write All + Write Selected @@ -700,6 +792,12 @@ 0 + + + 40 + 0 + + Restore @@ -713,6 +811,9 @@ Chinese Magic Card(Without Password) + + true + 5 @@ -722,6 +823,12 @@ + + + 0 + 0 + + Lock UFUID Card @@ -729,6 +836,12 @@ + + + 0 + 0 + + About UID Card @@ -736,29 +849,53 @@ + + + 40 + 0 + + - Read Block + Read One + + + 40 + 0 + + - Write Block + Write One - + + + + 0 + 0 + + - Read All + Read Selected - + + + + 0 + 0 + + - Write All + Write Selected @@ -770,6 +907,12 @@ 0 + + + 0 + 0 + + Set Parameter @@ -818,6 +961,9 @@ Simulate + + true + 5 @@ -842,16 +988,28 @@ - + + + + 0 + 0 + + - Load from data above + Write Selected - + + + + 0 + 0 + + - Read All + Read Selected @@ -870,6 +1028,12 @@ + + + 40 + 0 + + Simulate @@ -896,6 +1060,9 @@ Sniff + + true + 5 @@ -930,9 +1097,28 @@ - + + + + 40 + 0 + + - List Sniff Data + Snoop + + + + + + + + 40 + 0 + + + + List Data diff --git a/ui/mf_trailerdecoderdialog.cpp b/ui/mf_trailerdecoderdialog.cpp new file mode 100644 index 0000000..3b09a16 --- /dev/null +++ b/ui/mf_trailerdecoderdialog.cpp @@ -0,0 +1,154 @@ +#include "mf_trailerdecoderdialog.h" +#include "ui_mf_trailerdecoderdialog.h" + +MF_trailerDecoderDialog::MF_trailerDecoderDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::MF_trailerDecoderDialog) +{ + ui->setupUi(this); + QRegularExpression re("(([0-9a-fA-F]{2} ){0,4})|([0-9a-fA-F]{0,8})"); + validator = new QRegularExpressionValidator(this); + validator->setRegularExpression(re); + ui->accessBitsEdit->setValidator(validator); + + sizeGroup = new QButtonGroup(this); + sizeGroup->addButton(ui->size4Button, 4); + sizeGroup->addButton(ui->size16Button, 16); + connect(sizeGroup, QOverload::of(&QButtonGroup::buttonToggled), this, &MF_trailerDecoderDialog::on_blockSizeChanged); + connect(ui->C0Box, &QSpinBox::textChanged, this, &MF_trailerDecoderDialog::on_boxChanged); + connect(ui->C1Box, &QSpinBox::textChanged, this, &MF_trailerDecoderDialog::on_boxChanged); + connect(ui->C2Box, &QSpinBox::textChanged, this, &MF_trailerDecoderDialog::on_boxChanged); + connect(ui->C3Box, &QSpinBox::textChanged, this, &MF_trailerDecoderDialog::on_boxChanged); + + ui->dataBlockWidget->setRowCount(3); + ui->dataBlockWidget->setColumnCount(4); + ui->trailerBlockWidget->setRowCount(2); + ui->trailerBlockWidget->setColumnCount(3); +} + +MF_trailerDecoderDialog::~MF_trailerDecoderDialog() +{ + delete ui; +} +void MF_trailerDecoderDialog::on_accessBitsEdit_textChanged(const QString &arg1) +{ + ui->C0Box->blockSignals(true); + ui->C1Box->blockSignals(true); + ui->C2Box->blockSignals(true); + ui->C3Box->blockSignals(true); + QList ACBits = Mifare::data_getACBits(arg1); + if(ACBits.size() == 0) + { + ui->isAccessBitsValidLabel->setStyleSheet("color:rgb(200, 0, 0)"); + ui->isAccessBitsValidLabel->setText(tr("Invalid!\nIt could make the whole sector blocked irreversibly!")); + } + else + { + ui->C0Box->setValue(ACBits[0]); + ui->C1Box->setValue(ACBits[1]); + ui->C2Box->setValue(ACBits[2]); + ui->C3Box->setValue(ACBits[3]); + ui->isAccessBitsValidLabel->setStyleSheet("color:rgb(0, 200, 0)"); + ui->isAccessBitsValidLabel->setText(tr("Valid")); + bool isKeyBReadable = ACBits[3] == 0 || ACBits[3] == 1 || ACBits[3] == 4; + for(int j = 0; j < 3; j++) + { + setTableItem(ui->trailerBlockWidget, 0, j, Mifare::trailerReadCondition[ACBits[3]][j]); + setTableItem(ui->trailerBlockWidget, 1, j, Mifare::trailerWriteCondition[ACBits[3]][j]); + } + for(int i = 0; i < 3; i++) + { + for(int j = 0; j < 4; j++) + { + Mifare::AccessType type = Mifare::dataCondition[ACBits[i]][j]; + if(type == Mifare::ACC_KEY_B && isKeyBReadable) + { + type = Mifare::ACC_NEVER; + } + else if(type == Mifare::ACC_KEY_AB && isKeyBReadable) + { + type = Mifare::ACC_KEY_A; + } + setTableItem(ui->dataBlockWidget, i, j, type); + } + } + } + ui->C0Box->blockSignals(false); + ui->C1Box->blockSignals(false); + ui->C2Box->blockSignals(false); + ui->C3Box->blockSignals(false); +} + +void MF_trailerDecoderDialog::on_blockSizeChanged(int id, bool st) +{ + if(st) + { + if(id == 4) + { + ui->dataBlockWidget->verticalHeaderItem(0)->setText("Block0"); + ui->dataBlockWidget->verticalHeaderItem(1)->setText("Block1"); + ui->dataBlockWidget->verticalHeaderItem(2)->setText("Block2"); + } + else if(id == 16) + { + ui->dataBlockWidget->verticalHeaderItem(0)->setText("Block0~4"); + ui->dataBlockWidget->verticalHeaderItem(1)->setText("Block5~9"); + ui->dataBlockWidget->verticalHeaderItem(2)->setText("Block10~14"); + } + + } +} + +void MF_trailerDecoderDialog::setTableItem(QTableWidget* widget, int row, int column, Mifare::AccessType accessType) +{ + if(widget->item(row, column) == nullptr) + widget->setItem(row, column, new QTableWidgetItem()); + QString text; + if(accessType == Mifare::ACC_NEVER) + { + text = "X"; + } + else if(accessType == Mifare::ACC_KEY_A) + { + text = "KeyA"; + } + else if(accessType == Mifare::ACC_KEY_B) + { + text = "KeyB"; + } + else if(accessType == Mifare::ACC_KEY_AB) + { + text = "KeyA+B"; + } + widget->item(row, column)->setText(text); +} + +void MF_trailerDecoderDialog::on_boxChanged(const QString &arg1) +{ + quint8 ACBits[4]; + ACBits[0] = ui->C0Box->value(); + ACBits[1] = ui->C1Box->value(); + ACBits[2] = ui->C2Box->value(); + ACBits[3] = ui->C3Box->value(); + quint8 halfBytes[6] = {0, 0, 0, 0, 0, 0}; + for(int i = 0; i < 4; i++) + { + halfBytes[2] |= (((ACBits[i] >> 0) & 1) << i); + halfBytes[5] |= (((ACBits[i] >> 1) & 1) << i); + halfBytes[4] |= (((ACBits[i] >> 2) & 1) << i); + } + halfBytes[0] = (~halfBytes[5]) & 0xf; + halfBytes[1] = (~halfBytes[2]) & 0xf; + halfBytes[3] = (~halfBytes[4]) & 0xf; + + QString result; + for(int i = 0; i < 3; i++) + { + result += QString::number(halfBytes[i * 2], 16); + result += QString::number(halfBytes[i * 2 + 1], 16); + result += " "; + } + result = result.toUpper(); + ui->accessBitsEdit->setText(result); + +} diff --git a/ui/mf_trailerdecoderdialog.h b/ui/mf_trailerdecoderdialog.h new file mode 100644 index 0000000..93a6abd --- /dev/null +++ b/ui/mf_trailerdecoderdialog.h @@ -0,0 +1,39 @@ +#ifndef MF_TRAILERDECODERDIALOG_H +#define MF_TRAILERDECODERDIALOG_H + +#include +#include +#include +#include +#include +#include "../module/mifare.h" + +namespace Ui +{ +class MF_trailerDecoderDialog; +} + +class MF_trailerDecoderDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MF_trailerDecoderDialog(QWidget *parent = nullptr); + ~MF_trailerDecoderDialog(); + +private slots: + + void on_accessBitsEdit_textChanged(const QString &arg1); + + void on_blockSizeChanged(int id, bool st); + + void on_boxChanged(const QString &arg1); +private: + Ui::MF_trailerDecoderDialog *ui; + QRegularExpressionValidator* validator; + QButtonGroup* sizeGroup; + void setTableItem(QTableWidget *widget, int row, int column, Mifare::AccessType accessType); + +}; + +#endif // MF_TRAILERDECODERDIALOG_H diff --git a/ui/mf_trailerdecoderdialog.ui b/ui/mf_trailerdecoderdialog.ui new file mode 100644 index 0000000..00bd86a --- /dev/null +++ b/ui/mf_trailerdecoderdialog.ui @@ -0,0 +1,402 @@ + + + MF_trailerDecoderDialog + + + + 0 + 0 + 534 + 507 + + + + Trailer Decoder + + + + + + + + Blocks + + + + + + 4 + + + true + + + + + + + 16 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + Trailer Data: +(like "FF0780" or "FF 07 80") + + + + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 0 + + + + + + + + + + + + Or set bits manually + + + + + + + + + 0 + + + + + Cx0 + + + + + + + 7 + + + + + + + + + 0 + + + + + Cx1 + + + + + + + 7 + + + + + + + + + 0 + + + + + Cx2 + + + + + + + 7 + + + + + + + + + 0 + + + + + Cx3 + + + + + + + 7 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 0 + + + + + + + + + + + + Data Block Permission: + + + + + + + + 0 + 4 + + + + QAbstractItemView::NoEditTriggers + + + + Block0 + + + + + Block1 + + + + + Block2 + + + + + Read + + + + + Write + + + + + Increase + + + + + Decrease/Transfer/Restore + + + + + + + + Trailer Block Permission: + + + + + + + + 0 + 3 + + + + QAbstractItemView::NoEditTriggers + + + + Read + + + + + Write + + + + + KeyA + + + + + Access Bits + + + + + KeyB + + + + + + + + + + Reference: +MF1S70YYX_V1 Product data sheet +Rev. 3.2 — 23 November 2017 + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + Note:the Access Bits usually contains 4 bytes(8 hex symbols), but only the first 3 bytes matters. You can set the 4th byte randomly. + + + true + + + + + + + + + buttonBox + accepted() + MF_trailerDecoderDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MF_trailerDecoderDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +