diff --git a/Proxmark3GUI.pro b/Proxmark3GUI.pro index b5635cf..926190c 100644 --- a/Proxmark3GUI.pro +++ b/Proxmark3GUI.pro @@ -17,22 +17,36 @@ DEFINES += QT_DEPRECATED_WARNINGS SOURCES += \ main.cpp \ - mainwindow.cpp \ - pm3process.cpp + common/pm3process.cpp \ + common/util.cpp \ + module/mifare.cpp \ + ui/mf_uid_parameterdialog.cpp \ + ui/mainwindow.cpp \ + ui/mf_attack_hardnesteddialog.cpp \ HEADERS += \ - mainwindow.h \ - pm3process.h + common/pm3process.h \ + common/util.h \ + module/mifare.h \ + ui/mf_uid_parameterdialog.h \ + ui/mainwindow.h \ + ui/mf_attack_hardnesteddialog.h \ FORMS += \ - mainwindow.ui + ui/mf_uid_parameterdialog.ui \ + ui/mainwindow.ui \ + ui/mf_attack_hardnesteddialog.ui + +TRANSLATIONS += \ + lang/zh_CN.ts \ + lang/en_US.ts # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target -VERSION = 0.0.1 +VERSION = 0.1 QMAKE_TARGET_PRODUCT = "Proxmark3GUI" QMAKE_TARGET_DESCRIPTION = "Proxmark3GUI" QMAKE_TARGET_COMPANY = "wh201906" diff --git a/README.md b/README.md index 7492642..6e6477b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,35 @@ # Proxmark3GUI -A GUI for Proxmark3 client +A GUI for [Proxmark3](https://github.com/Proxmark/proxmark3) client + +[中文](README/README.zh_CN.md) + +*** + +## Features + ++ Support raw commands of Proxmark3 client ++ Have a friendly UI to test Mifare cards ++ Easy to edit Mifare data files ++ Support binary(.bin .dump) files and text(.eml) files ++ ... + +*** + +## Previews +![nested_attack](README/mf_nested_attack.gif) + +![mf_load_file](README/mf_load_file.gif) + +![mf_edit_file](README/mf_edit_file.gif) + +![raw_command](README/raw_command.gif) *** -Update Log: +## Update Log: + +### V0.1 ++ Able to deal with Mifare card and related files -## V0.0.1 +### V0.0.1 + a dumb version with a useless GUI and a serial choose box. \ No newline at end of file diff --git a/README/README.zh_CN.md b/README/README.zh_CN.md new file mode 100644 index 0000000..9e18da8 --- /dev/null +++ b/README/README.zh_CN.md @@ -0,0 +1,35 @@ +# 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 ++ 支持处理Mifare卡片及相关数据文件 + +### V0.0.1 ++ 一个带串口选择框的实验版本 \ No newline at end of file diff --git a/README/mf_edit_file.gif b/README/mf_edit_file.gif new file mode 100644 index 0000000..25c5de3 Binary files /dev/null and b/README/mf_edit_file.gif differ diff --git a/README/mf_load_file.gif b/README/mf_load_file.gif new file mode 100644 index 0000000..f8dc95e Binary files /dev/null and b/README/mf_load_file.gif differ diff --git a/README/mf_nested_attack.gif b/README/mf_nested_attack.gif new file mode 100644 index 0000000..0ac5bc4 Binary files /dev/null and b/README/mf_nested_attack.gif differ diff --git a/README/raw_command.gif b/README/raw_command.gif new file mode 100644 index 0000000..0a8c67b Binary files /dev/null and b/README/raw_command.gif differ diff --git a/common/pm3process.cpp b/common/pm3process.cpp new file mode 100644 index 0000000..6ea2492 --- /dev/null +++ b/common/pm3process.cpp @@ -0,0 +1,102 @@ +#include "pm3process.h" + +PM3Process::PM3Process(QThread* thread, QObject* parent): QProcess(parent) +{ + moveToThread(thread); + setProcessChannelMode(PM3Process::MergedChannels); + isRequiringOutput = false; + requiredOutput = new QString(); + serialListener = new QTimer(); // if using new QTimer(this), the debug output will show "Cannot create children for a parent that is in a different thread." + serialListener->moveToThread(this->thread());// I've tried many ways to creat a QTimer instance, but all of the instances are in the main thread(UI thread), so I have to move it manually + serialListener->setInterval(1000); + serialListener->setTimerType(Qt::VeryCoarseTimer); + connect(serialListener, &QTimer::timeout, this, &PM3Process::onTimeout); + connect(this, &PM3Process::readyRead, this, &PM3Process::onReadyRead); +} + +void PM3Process::connectPM3(const QString path, const QString port) +{ + 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)) + ; + setRequiringOutput(false); + QString result = *requiredOutput; + if(result.indexOf("os: ") != -1) // make sure the PM3 is connected + { + result = result.mid(result.indexOf("os: ")); + result = result.left(result.indexOf("\r\n")); + result = result.mid(3, result.lastIndexOf(" ") - 3); + emit PM3StatedChanged(true, result); + setSerialListener(port, true); + } + else + kill(); + } +} + +void PM3Process::setRequiringOutput(bool st) +{ + isRequiringOutput = st; + if(isRequiringOutput) + requiredOutput->clear(); +} + +bool PM3Process::waitForReadyRead(int msecs) +{ + return QProcess::waitForReadyRead(msecs); +} + +void PM3Process::setSerialListener(const QString& name, bool state) +{ + if(state) + { + portInfo = new QSerialPortInfo(name); + serialListener->start(); + qDebug() << serialListener->thread(); + } + else + { + serialListener->stop(); + delete portInfo; + } +} + +void PM3Process::onTimeout() //when the proxmark3 client is unexpectedly terminated or the PM3 hardware is removed, the isBusy() will return false(only tested on Windows); +{ +// qDebug()<isBusy(); + if(!portInfo->isBusy()) + { + kill(); + emit PM3StatedChanged(false); + setSerialListener("", false); + } +} + +void PM3Process::testThread() +{ + qDebug() << "PM3:" << QThread::currentThread(); +} + + +qint64 PM3Process::write(QString data) +{ + return QProcess::write(data.toLatin1()); +} + +void PM3Process::onReadyRead() +{ + QString out = readAll(); + if(isRequiringOutput) + requiredOutput->append(out); + if(out != "") + { +// qDebug() << "PM3Process::onReadyRead:" << out; + emit newOutput(out); + + } +} diff --git a/common/pm3process.h b/common/pm3process.h new file mode 100644 index 0000000..17cee5f --- /dev/null +++ b/common/pm3process.h @@ -0,0 +1,39 @@ +#ifndef PM3PROCESS_H +#define PM3PROCESS_H + +#include +#include +#include +#include +#include +#include +#include + +class PM3Process : public QProcess +{ + Q_OBJECT +public: + explicit PM3Process(QThread* thread, QObject* parent = nullptr); + bool waitForReadyRead(int msecs = 2000); + + void testThread(); + +public slots: + void connectPM3(const QString path, const QString port); + void setSerialListener(const QString &name, bool state); + qint64 write(QString data); +private slots: + void onTimeout(); + void onReadyRead(); +private: + bool isRequiringOutput; + QString* requiredOutput; // It only works in this class now + void setRequiringOutput(bool st);// It only works in this class now + QTimer* serialListener; + QSerialPortInfo* portInfo; +signals: + void PM3StatedChanged(bool st, QString info = ""); + void newOutput(QString output); +}; + +#endif // PM3PROCESS_H diff --git a/common/util.cpp b/common/util.cpp new file mode 100644 index 0000000..f7f7997 --- /dev/null +++ b/common/util.cpp @@ -0,0 +1,52 @@ +#include "util.h" + +Util::Util(QObject *parent) : QObject(parent) +{ + isRequiringOutput = false; + requiredOutput = new QString(); + timeStamp = QTime::currentTime(); +} + +void Util::processOutput(QString output) +{ +// qDebug() << "Util::processOutput:" << output; + if(isRequiringOutput) + { + requiredOutput->append(output); + timeStamp = QTime::currentTime(); + } + emit refreshOutput(output); +} + +void Util::execCMD(QString cmd) +{ + qDebug() << cmd; + emit write(cmd + "\r\n"); +} + +QString Util::execCMDWithOutput(QString cmd, unsigned long timeout) +{ + QTime currTime = QTime::currentTime(); + QTime targetTime = QTime::currentTime().addMSecs(timeout); + isRequiringOutput = true; + requiredOutput->clear(); + execCMD(cmd); + while( QTime::currentTime() < targetTime) + { + QApplication::processEvents(); + if(timeStamp > currTime) + { + currTime = timeStamp; + targetTime = timeStamp.addMSecs(timeout); + } + } + isRequiringOutput = false; + return *requiredOutput; +} + +void Util::delay(unsigned int msec) +{ + QTime timer = QTime::currentTime().addMSecs(msec); + while( QTime::currentTime() < timer ) + QApplication::processEvents(QEventLoop::AllEvents, 100); +} diff --git a/common/util.h b/common/util.h new file mode 100644 index 0000000..3b29a69 --- /dev/null +++ b/common/util.h @@ -0,0 +1,33 @@ +#ifndef UTIL_H +#define UTIL_H + +#include +#include +#include +#include +#include +#include +#include + +class Util : public QObject +{ + Q_OBJECT +public: + explicit Util(QObject *parent = nullptr); + + void execCMD(QString cmd); + QString execCMDWithOutput(QString cmd, unsigned long timeout = 2000); + void delay(unsigned int msec); +public slots: + void processOutput(QString output); + +private: + bool isRequiringOutput; + QString* requiredOutput; + QTime timeStamp; +signals: + void refreshOutput(const QString& output); + void write(QString data); +}; + +#endif // UTIL_H diff --git a/lang/en_US.ts b/lang/en_US.ts new file mode 100644 index 0000000..729872e --- /dev/null +++ b/lang/en_US.ts @@ -0,0 +1,608 @@ + + + + + MF_Attack_hardnestedDialog + + + Hardnested Attack + + + + + Known Block: + + + + + + Block: + + + + + + A + + + + + + B + + + + + Target Block: + + + + + MF_UID_parameterDialog + + + Set Parameter + + + + + UID: + + + + + ATQA: + + + + + SAK: + + + + + MainWindow + + + Proxmark3GUI + + + + + Path: + + + + + Refresh + + + + + Connect + + + + + Disconnect + + + + + Mifare + + + + + >> + + + + + << + + + + + F + + + + + Card Type + + + + + MINI + + + + + 1K + + + + + 2K + + + + + 4K + + + + + File + + + + + + Load + + + + + + Save + + + + + + Data + + + + + Key + + + + + Attack + + + + + Card Info + + + + + Check Default + + + + + Nested + + + + + Hardnested + + + + + Read/Write + + + + + Block: + + + + + Key: + + + + + Key Type: + + + + + A + + + + + B + + + + + 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 + + + + + + Clear + + + + + + 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? + + + + + 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). + + + + + + Idle + + + + + + Sec + + + + + Blk + + + + + KeyA + + + + + KeyB + + + + + HW Version: + + + + + PM3: + + + + + State: + + + + + Running + + + + + Mifare + + + + Success! + + + + + + + + + Info + + + + + + Failed! + + + + + Failed to read card. + + + + diff --git a/lang/zh_CN.ts b/lang/zh_CN.ts new file mode 100644 index 0000000..921ff20 --- /dev/null +++ b/lang/zh_CN.ts @@ -0,0 +1,649 @@ + + + + + MF_Attack_hardnestedDialog + + + Hardnested Attack + Hardnested攻击 + + + Known Key: + 已知Key: + + + + Known Block: + 已知块: + + + + + Block: + 块: + + + + + A + + + + + + B + + + + + Target Block: + 目标块: + + + Target Block: + 目标块: + + + + MF_UID_parameterDialog + + Dialog + 对话框 + + + + Set Parameter + 设置卡参数 + + + + UID: + + + + + ATQA: + + + + + SAK: + + + + The parameter will not change if you leave it empty. + 如果留空,则对应参数将保持不变 + + + + MainWindow + + + Proxmark3GUI + + + + + Path: + 路径: + + + + Refresh + 刷新端口 + + + + Connect + 连接 + + + + Disconnect + 断开 + + + + Mifare + Mifare(IC)卡 + + + + >> + + + + + << + + + + + F + + + + + Card Type + 卡类型 + + + + MINI + + + + + 1K + + + + + 2K + + + + + 4K + + + + + File + 文件 + + + + + Load + 加载 + + + + + Save + 保存 + + + + + Data + + + + + Key + + + + + Attack + 破解 + + + + Card Info + 读卡片信息 + + + + Check Default + 验证默认密码 + + + + Nested + Nested攻击 + + + + Hardnested + Hardested攻击 + + + + Read/Write + 读/写 + + + + Block: + + + + + Key: + + + + + Key Type: + Key类型: + + + + A + + + + + B + + + + + 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 + 清空 + + + + + 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? + 确定? + + + + 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攻击破解 + + + + + Idle + 空闲 + + + + + Sec + + + + + Blk + + + + + KeyA + + + + + KeyB + + + + + HW Version: + 固件版本: + + + + PM3: + 连接状态: + + + + State: + 运行状态: + + + + Running + 运行中 + + + + Mifare + + info + 信息: + + + + + Success! + 成功! + + + + + + + + Info + 信息 + + + + + Failed! + 失败! + + + + Failed to read card. + 读卡失败。 + + + diff --git a/main.cpp b/main.cpp index aff48df..5053128 100644 --- a/main.cpp +++ b/main.cpp @@ -1,11 +1,57 @@ -#include "mainwindow.h" +#include "ui/mainwindow.h" #include +#include +#include +#include +#include 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"); + if(lang == "null") + { +#ifdef Q_OS_WIN + lang = "lang/en_US.qm"; +#else + lang = "lang/en_US.ts"; +#endif + QStringList langList; + langList.append("English"); + langList.append("简体中文"); + QString seletedText = QInputDialog::getItem(&w, "", "Choose a language:", langList, 0, false); + if(seletedText == "English") + { +#ifdef Q_OS_WIN + lang = "lang/en_US.qm"; +#else + lang = "lang/en_US.ts"; +#endif + } + else if(seletedText == "简体中文") + { +#ifdef Q_OS_WIN + lang = "lang/zh_CN.qm"; +#else + lang = "lang/zh_CN.ts"; +#endif + } + } + QTranslator* translator = new QTranslator(&w); + if(translator->load(lang.toString())) + { + a.installTranslator(translator); + settings->setValue("lang", lang); + } + else + { + QMessageBox::information(&w, "Error", "Can't load " + lang.toString() + " as translation file."); + } + delete settings; + w.initUI(); w.show(); return a.exec(); } diff --git a/mainwindow.cpp b/mainwindow.cpp deleted file mode 100644 index 3f9c961..0000000 --- a/mainwindow.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" - -MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent) - , ui(new Ui::MainWindow) -{ - ui->setupUi(this); - pm3=new PM3Process; - connect(pm3,&PM3Process::readyRead,this,&MainWindow::refresh); - connect(ui->commandEdit,&QLineEdit::editingFinished,this,&MainWindow::sendMSG); - ui->portBox->addItem(""); - foreach(QString port,pm3->findPort()) - { - ui->portBox->addItem(port); - } -} - -MainWindow::~MainWindow() -{ - delete ui; -} - - -void MainWindow::on_connectButton_clicked() -{ - QString port=ui->portBox->currentText(); - if(port=="") - QMessageBox::information(NULL, "Info", "Plz choose a port first", QMessageBox::Ok); - else - qDebug()<start(ui->PM3PathEdit->text(),port); -} - -void MainWindow::on_sendButton_clicked() -{ - qDebug()<<(ui->commandEdit->text().toLocal8Bit()); - pm3->write((ui->commandEdit->text()+"\r\n").toLocal8Bit()); - pm3->waitForBytesWritten(3000); -} - -void MainWindow::refresh() -{ - QByteArray btay=pm3->readLine(); - while(btay!="") - { - qDebug()<outputEdit->insertPlainText(btay); - btay=pm3->readLine(); - } - ui->outputEdit->moveCursor(QTextCursor::End); -} - -void MainWindow::sendMSG() -{ - if(ui->commandEdit->hasFocus()) - on_sendButton_clicked(); -} - -void MainWindow::on_disconnectButton_clicked() -{ - pm3->kill(); -} - -void MainWindow::on_clearButton_clicked() -{ - ui->outputEdit->clear(); -} diff --git a/mainwindow.h b/mainwindow.h deleted file mode 100644 index 469e98c..0000000 --- a/mainwindow.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include -#include -#include -#include -#include "pm3process.h" - -QT_BEGIN_NAMESPACE -namespace Ui { class MainWindow; } -QT_END_NAMESPACE - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - MainWindow(QWidget *parent = nullptr); - ~MainWindow(); - -public slots: - void refresh(); -private slots: - void on_connectButton_clicked(); - - void on_sendButton_clicked(); - - void on_disconnectButton_clicked(); - - void on_clearButton_clicked(); - - void sendMSG(); -private: - Ui::MainWindow *ui; - PM3Process* pm3; -}; -#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui deleted file mode 100644 index b807653..0000000 --- a/mainwindow.ui +++ /dev/null @@ -1,146 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 450 - 310 - - - - - 450 - 300 - - - - Proxmark3GUI - - - - - 0 - 0 - - - - - - - QLayout::SetMaximumSize - - - - - Path: - - - - - - - proxmark3 - - - - - - - - - - Connect - - - - - - - Disconnect - - - - - - - - - - 0 - 0 - - - - 1 - - - - Tab 1 - - - - - RawCommand - - - - - - Qt::ScrollBarAlwaysOn - - - QAbstractScrollArea::AdjustIgnored - - - true - - - - - - - QLayout::SetMaximumSize - - - - - - - - Send - - - - - - - ClearOutput - - - - - - - - - - - - - - - 0 - 0 - 450 - 22 - - - - - - - - diff --git a/module/mifare.cpp b/module/mifare.cpp new file mode 100644 index 0000000..5bd6ddd --- /dev/null +++ b/module/mifare.cpp @@ -0,0 +1,855 @@ +#include "mifare.h" + +Mifare::Mifare(Ui::MainWindow *ui, Util *addr, QWidget *parent) : QObject(parent) +{ + this->parent = parent; + util = addr; + this->ui = ui; + cardType = card_1k; + 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}\\|.+\\|.+\\|.+\\|.+\\|"); +} + + + +QString Mifare::info(bool isRequiringOutput) +{ + if(isRequiringOutput) + { + 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) + { + 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 ""; + } +} + +void Mifare::chk() +{ + QString result = util->execCMDWithOutput("hf mf chk *" + QString::number(cardType.type) + " ?", 1000 + cardType.type * 1000); + qDebug() << result; + + int offset = 0; + QString tmp, tmp2; + for(int i = 0; i < cardType.sectors; i++) + { + 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); + } + data_syncWithKeyWidget(); +} + +void Mifare::nested() +{ + QString result = util->execCMDWithOutput("hf mf nested " + QString::number(cardType.type) + " *"); + + int offset = 0; + QString tmp; + for(int i = 0; i < cardType.sectors; i++) + { + 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()); + } + data_syncWithKeyWidget(); +} + +void Mifare::hardnested() +{ + MF_Attack_hardnestedDialog dialog(cardType.blocks); + connect(&dialog, &MF_Attack_hardnestedDialog::sendCMD, util, &Util::execCMD); + if(dialog.exec() == QDialog::Accepted) + ui->funcTab->setCurrentIndex(1); +} + +void Mifare::sniff() +{ + util->execCMD("hf mf sniff"); + ui->funcTab->setCurrentIndex(1); +} + +void Mifare::list() +{ + util->execCMD("hf list mf"); + ui->funcTab->setCurrentIndex(1); +} + +void Mifare::read() +{ + int waitTime = 300; + int currblk = ui->MF_RW_blockBox->currentText().toInt(); + QString result = util->execCMDWithOutput("hf mf rdbl " + + QString::number(currblk) + + " " + + ui->MF_RW_keyTypeBox->currentText() + + " " + + ui->MF_RW_keyEdit->text(), waitTime); + if(result.indexOf("isOk:01") != -1) + { + result = result.mid(result.indexOf(*dataPattern, 0), 47).toUpper(); + if((currblk < 128 && ((currblk + 1) % 4 == 0)) || ((currblk + 1) % 8 == 0)) // process key block + { + if(ui->MF_RW_keyTypeBox->currentText() == "A") + { + for(int i = 0; i < 6; i++) + { + result = result.replace(i * 3, 2, ui->MF_RW_keyEdit->text().mid(i * 2, 2)); + } + 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) + { + result = ui->MF_RW_dataEdit->text(); + result = result.replace(30, 17, "?? ?? ?? ?? ?? ??"); + ui->MF_RW_dataEdit->setText(result); + } + } + 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); + } + } + 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))) + { + 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 = 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 = 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), waitTime); + if(result.indexOf("No chinese") == -1) + { + result = result.mid(result.indexOf(*dataPattern, 0), 47).toUpper(); + ui->MF_RW_dataEdit->setText(result); + } +} + +void Mifare::readAllC() +{ + QString result; + const int waitTime = 150; + + QString tmp; + int offset = 0; + for(int i = 0; i < cardType.sectors; i++) + { + result = util->execCMDWithOutput("hf mf cgetsc " + + QString::number(i), waitTime); + qDebug() << result; + if(result.indexOf("No chinese") == -1) + { + offset = 0; + for(int j = 0; j < cardType.blk[i]; j++) + { + 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); + } + 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::writeC() +{ + int waitTime = 150; + QString result = util->execCMDWithOutput("hf mf csetblk " + + ui->MF_RW_blockBox->currentText() + + " " + + ui->MF_RW_dataEdit->text().replace(" ", ""), waitTime); + if(result.indexOf("No chinese") == -1) + { + QMessageBox::information(parent, tr("Info"), tr("Success!")); + } + else + { + QMessageBox::information(parent, tr("Info"), tr("Failed!")); + } +} + +void Mifare::writeAllC() +{ + const int waitTime = 150; + 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; + result = util->execCMDWithOutput("hf mf csetblk " + + QString::number(cardType.blks[i] + j) + + " " + + dataList->at(cardType.blks[i] + j), waitTime); + } + } +} + +void Mifare::wipeC() +{ + util->execCMD("hf mf cwipe " + + QString::number(cardType.type) + + " f"); + ui->funcTab->setCurrentIndex(1); +} + +void Mifare::setParameterC() +{ + QString result = info(true); + if(result == "") + QMessageBox::information(parent, tr("Info"), tr("Failed to read card.")); + else + { + QStringList lis = result.split("\r\n"); + lis[0].replace(" ", ""); + lis[1].replace(" ", ""); + lis[2].replace(" ", ""); + MF_UID_parameterDialog dialog(lis[0].toUpper(), lis[1].toUpper(), lis[2].mid(0, 2).toUpper()); + connect(&dialog, &MF_UID_parameterDialog::sendCMD, util, &Util::execCMD); + if(dialog.exec() == QDialog::Accepted) + ui->funcTab->setCurrentIndex(1); + } +} + +void Mifare::lockC() +{ + util->execCMD("hf 14a raw -pa -b7 40"); + util->execCMD("hf 14a raw -pa 43"); + util->execCMD("hf 14a raw -pa E0 00 39 F7"); + util->execCMD("hf 14a raw -pa E1 00 E1 EE"); + util->execCMD("hf 14a raw -pa 85 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 18 47"); + util->execCMD("hf 14a raw 52"); +} + +void Mifare::dump() +{ + util->execCMD("hf mf dump"); + ui->funcTab->setCurrentIndex(1); +} + +void Mifare::restore() +{ + util->execCMD("hf mf restore"); + ui->funcTab->setCurrentIndex(1); +} + +void Mifare::data_syncWithDataWidget(bool syncAll, int block) +{ + QString tmp; + if(syncAll) + { + for(int i = 0; i < cardType.blocks; i++) + { + tmp = ""; + if(dataList->at(i) != "") + { + tmp += dataList->at(i).mid(0, 2); + for(int j = 1; j < 16; j++) + { + tmp += " "; + tmp += dataList->at(i).mid(j * 2, 2); + } + } + ui->MF_dataWidget->item(i, 2)->setText(tmp); + } + } + else + { + tmp = ""; + if(dataList->at(block) != "") + { + tmp += dataList->at(block).mid(0, 2); + for(int j = 1; j < 16; j++) + { + tmp += " "; + tmp += dataList->at(block).mid(j * 2, 2); + } + } + ui->MF_dataWidget->item(block, 2)->setText(tmp); + } +} + +void Mifare::data_syncWithKeyWidget(bool syncAll, int sector, bool isKeyA) +{ + if(syncAll) + { + for(int i = 0; i < cardType.sectors; i++) + { + ui->MF_keyWidget->item(i, 1)->setText(keyAList->at(i)); + ui->MF_keyWidget->item(i, 2)->setText(keyBList->at(i)); + } + } + else + { + if(isKeyA) + ui->MF_keyWidget->item(sector, 1)->setText(keyAList->at(sector)); + else + ui->MF_keyWidget->item(sector, 2)->setText(keyBList->at(sector)); + } +} + +void Mifare::data_clearData() +{ + 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++) + { + keyAList->append(""); + keyBList->append(""); + } +} + +bool Mifare::data_isKeyValid(const QString& key) +{ + if(key.length() != 12) + return false; + for(int i = 0; i < 12; i++) + { + if(!((key[i] >= '0' && key[i] <= '9') || (key[i] >= 'A' && key[i] <= 'F'))) + return false; + } + return true; +} + +Mifare::DataType Mifare::data_isDataValid(QString data) // "?" will not been processd there +{ + if(data.length() == 47) + { + for(int i = 0; i < 47; i++) + { + if(i % 3 != 0) + { + if(!((data[i] >= '0' && data[i] <= '9') || (data[i] >= 'A' && data[i] <= 'F') || data[i] == '?')) + return DATA_INVALID; + } + else + { + if(data[i] != ' ') + return DATA_INVALID; + } + } + return DATA_WITHSPACE; + } + else if(data.length() == 32) + { + for(int i = 0; i < 32; i++) + { + if(!((data[i] >= '0' && data[i] <= '9') || (data[i] >= 'A' && data[i] <= 'F') || data[i] == '?')) + return DATA_INVALID; + } + return DATA_NOSPACE; + } + else + return DATA_INVALID; +} + +Mifare::CardType Mifare::getCardType() +{ + return cardType; +} + +void Mifare::setCardType(int type) +{ + if(type == 0 || type == 1 || type == 2 || type == 4) + { + if(type == 0) + cardType = card_mini; + else if(type == 1) + cardType = card_1k; + else if(type == 2) + cardType = card_2k; + else if(type == 4) + cardType = card_4k; + data_clearKey(); + data_clearData(); + } +} + +bool Mifare::data_loadDataFile(const QString& filename) +{ + QFile file(filename, this); + if(file.open(QIODevice::ReadOnly)) + { + QByteArray buff; + buff = file.read(10000); + bool isBin = false; + for(int i = 0; i < cardType.blocks * 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')) + { + isBin = true; + break; + } + } + if(isBin) + { + if(file.size() < cardType.blocks * 16) + return false; + for(int i = 0; i < cardType.blocks; i++) + { + QString tmp = bin2text(buff, i, 16); + dataList->replace(i, tmp.toUpper()); + } + } + else + { + QString tmp = buff.left(cardType.blocks * 34); + QStringList tmpList = tmp.split("\r\n"); + for(int i = 0; i < cardType.blocks; i++) + { + dataList->replace(i, tmpList[i].toUpper()); + qDebug() << tmpList[i]; + } + } + file.close(); + data_syncWithDataWidget(); + return true; + } + else + { + return false; + } +} + +bool Mifare::data_loadKeyFile(const QString& filename) +{ + QFile file(filename, this); + if(file.open(QIODevice::ReadOnly)) + { + QByteArray buff; + buff = file.read(10000); + bool isKey = file.size() <= cardType.sectors * 14; + if(isKey) + { + for(int i = 0; i < cardType.sectors; i++) + { + QString tmp = bin2text(buff, i, 12); + keyAList->replace(i, tmp.left(12).toUpper()); + keyBList->replace(i, tmp.right(12).toUpper()); + } + } + else + { + for(int i = 0; i < cardType.sectors; i++) + { + int blk = cardType.blks[i] + cardType.blk[i] - 1; + QString tmp = bin2text(buff, blk, 16); + keyAList->replace(i, tmp.left(12).toUpper()); + keyBList->replace(i, tmp.right(12).toUpper()); + } + } + file.close(); + data_syncWithKeyWidget(); + return true; + } + else + { + return false; + } +} + +QString Mifare::bin2text(const QByteArray& buff, int i, int length) +{ + QString ret = ""; + char LByte, RByte; + char map[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + for(int j = 0; j < length; j++) + { + LByte = map[(unsigned char)buff[i * length + j] >> 4]; + RByte = map[(unsigned char)buff[i * length + j] & 0xF]; + ret += LByte; + ret += RByte; + } + qDebug() << ret; + return ret; +} + +bool Mifare::data_saveDataFile(const QString& filename, bool isBin) +{ + QFile file(filename, this); + if(file.open(QIODevice::WriteOnly)) + { + QByteArray buff; + QChar tmp; + if(isBin) + { + for(int i = 0; i < cardType.blocks; i++) + { + for(int j = 0; j < 16; j++) + { + unsigned char Byt[2]; + for(int k = 0; k < 2; k++) + { + tmp = dataList->at(i).at(j * 2 + k).toUpper(); + if(tmp >= '0' && tmp <= '9') + Byt[k] = tmp.toLatin1() - '0'; + else if(tmp >= 'A' && tmp <= 'F') + Byt[k] = tmp.toLatin1() - 'A' + 10; + } + buff += (Byt[0] << 4) | Byt[1]; + } + } + } + else + { + for(int i = 0; i < cardType.blocks; i++) + { + buff += dataList->at(i); + buff += "\r\n"; + } + } + bool ret = file.write(buff) != -1; + file.close(); + return ret; + } + else + { + return false; + } +} + +bool Mifare::data_saveKeyFile(const QString& filename, bool isBin) +{ + QFile file(filename, this); + if(file.open(QIODevice::WriteOnly)) + { + QByteArray buff; + QChar tmp; + if(isBin) + { + for(int i = 0; i < cardType.sectors; i++) + { + for(int j = 0; j < 6; j++) + { + unsigned char Byt[2]; + for(int k = 0; k < 2; k++) + { + tmp = keyAList->at(i).at(j * 2 + k).toUpper(); + if(tmp >= '0' && tmp <= '9') + Byt[k] = tmp.toLatin1() - '0'; + else if(tmp >= 'A' && tmp <= 'F') + Byt[k] = tmp.toLatin1() - 'A' + 10; + } + buff += (Byt[0] << 4) | Byt[1]; + } + for(int j = 0; j < 6; j++) + { + unsigned char Byt[2]; + for(int k = 0; k < 2; k++) + { + tmp = keyBList->at(i).at(j * 2 + k).toUpper(); + if(tmp >= '0' && tmp <= '9') + Byt[k] = tmp.toLatin1() - '0'; + else if(tmp >= 'A' && tmp <= 'F') + Byt[k] = tmp.toLatin1() - 'A' + 10; + } + buff += (Byt[0] << 4) | Byt[1]; + } + } + } + else + { + + } + bool ret = file.write(buff) != -1; + file.close(); + return ret; + } + else + { + return false; + } +} + +void Mifare::data_key2Data() +{ + for(int i = 0; i < cardType.sectors; i++) + { + QString tmp = ""; + if(data_isKeyValid(keyAList->at(i))) + tmp += keyAList->at(i); + else + tmp += "????????????"; + + if(dataList->at(cardType.blks[i] + cardType.blk[i] - 1) == "") + tmp += "FF078069"; // default control bytes + else + tmp += dataList->at(cardType.blks[i] + cardType.blk[i] - 1).mid(12, 8); + + if(data_isKeyValid(keyBList->at(i))) + tmp += keyBList->at(i); + else + tmp += "????????????"; + + dataList->replace(cardType.blks[i] + cardType.blk[i] - 1, tmp); + data_syncWithDataWidget(); + } +} + +void Mifare::data_data2Key() +{ + for(int i = 0; i < cardType.sectors; i++) + { + if(dataList->at(cardType.blks[i] + cardType.blk[i] - 1) == "") + { + keyAList->replace(i, "????????????"); + keyBList->replace(i, "????????????"); + } + else + { + 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(); + } +} + +void Mifare::data_setData(int block, const QString& data) +{ + dataList->replace(block, data); +} + +void Mifare::data_setKey(int sector, bool isKeyA, const QString& key) +{ + if(isKeyA) + keyAList->replace(sector, key); + else + keyBList->replace(sector, key); +} diff --git a/module/mifare.h b/module/mifare.h new file mode 100644 index 0000000..8f31ead --- /dev/null +++ b/module/mifare.h @@ -0,0 +1,126 @@ +#ifndef MIFARE_H +#define MIFARE_H + +#include "common/util.h" +#include "ui_mainwindow.h" +#include "ui/mf_attack_hardnesteddialog.h" +#include "ui/mf_uid_parameterdialog.h" +#include +#include +#include +#include +#include +class Mifare : public QObject +{ + Q_OBJECT +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 DataType + { + DATA_INVALID, + DATA_WITHSPACE, + DATA_NOSPACE, + }; + + struct CardType + { + int type; + int sectors; + int blocks; + int blk[40]; + int blks[40]; + }; + + const CardType card_mini = + { + 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} + }; + + void data_clearData(); + void data_clearKey(); + bool data_isKeyValid(const QString &key); + Mifare::DataType data_isDataValid(QString data); + void data_syncWithDataWidget(bool syncAll = true, int block = 0); + void data_syncWithKeyWidget(bool syncAll = true, int sector = 0, bool isKeyA = true); + + 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_saveDataFile(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 lockC(); +public slots: +signals: + +private: + QWidget* parent; + Ui::MainWindow *ui; + Util* util; + + QStringList* keyAList; + QStringList* keyBList; + QStringList* dataList; + QRegExp* dataPattern; + QRegExp* chkKeyPattern; + QRegExp* nestedKeyPattern; + QRegExp* UIDPattern; + QString bin2text(const QByteArray &buff, int start, int length); +}; + +#endif // MIFARE_H diff --git a/pm3process.cpp b/pm3process.cpp deleted file mode 100644 index 59e6ca7..0000000 --- a/pm3process.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "pm3process.h" - -PM3Process::PM3Process(QObject* parent): QProcess(parent) -{ - setProcessChannelMode(PM3Process::MergedChannels); -} - -QStringList PM3Process::findPort() -{ - QSerialPort serial; - QStringList retList; - foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) - { - serial.setPort(info); - if(serial.open(QIODevice::ReadWrite)) - { - retList< -#include -#include -#include -#include - -class PM3Process : public QProcess -{ - Q_OBJECT -public: - explicit PM3Process(QObject* parent=nullptr); - bool start(const QString path, const QString port); - QStringList findPort(); -}; - -#endif // PM3PROCESS_H diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp new file mode 100644 index 0000000..600aec1 --- /dev/null +++ b/ui/mainwindow.cpp @@ -0,0 +1,653 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(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); + connect(myInfo, &QAction::triggered, [ = ]() { + QDesktopServices::openUrl(QUrl("https://github.com/wh201906")); + }); + this->addAction(myInfo); + + pm3Thread = new QThread(this); + pm3 = new PM3Process(pm3Thread); +// pm3->moveToThread(pm3Thread); +// pm3->init(); + pm3Thread->start(); + pm3state = false; + + util = new Util(this); + mifare = new Mifare(ui, util, this); + +} + +MainWindow::~MainWindow() +{ + delete ui; + emit killPM3(); + pm3Thread->exit(0); + pm3Thread->wait(5000); + delete pm3; + delete pm3Thread; +} + +void MainWindow::initUI() // will be called by main.app +{ + ui->retranslateUi(this); + uiInit(); + signalInit(); + setState(false); +} + +// ******************** basic functions ******************** + +void MainWindow::on_PM3_refreshPortButton_clicked() +{ + ui->PM3_portBox->clear(); + ui->PM3_portBox->addItem(""); + QSerialPort serial; + QStringList serialList; + foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) + { + qDebug() << info.isBusy() << info.isNull() << info.portName(); + serial.setPort(info); + + if(serial.open(QIODevice::ReadWrite)) + { + serialList << info.portName(); + serial.close(); + } + } + foreach(QString port, serialList) + { + ui->PM3_portBox->addItem(port); + } +} + +void MainWindow::on_PM3_connectButton_clicked() +{ + qDebug() << "Main:" << QThread::currentThread(); + QString port = ui->PM3_portBox->currentText(); + if(port == "") + QMessageBox::information(NULL, tr("Info"), tr("Plz choose a port first"), QMessageBox::Ok); + else + { + emit connectPM3(ui->PM3_pathEdit->text(), port); + } +} + +void MainWindow::onPM3StateChanged(bool st, QString info) +{ + pm3state = st; + setState(st); + if(st == true) + { + setStatusBar(PM3VersionBar, info); + setStatusBar(connectStatusBar, tr("Connected")); + } + else + { + setStatusBar(connectStatusBar, tr("Not Connected")); + } +} + +void MainWindow::on_PM3_disconnectButton_clicked() +{ + pm3state = false; + setState(false); + emit killPM3(); + emit setSerialListener("", false); + setStatusBar(connectStatusBar, tr("Not Connected")); +} + +void MainWindow::refreshOutput(const QString& output) +{ +// qDebug() << "MainWindow::refresh:" << output; + ui->Raw_outputEdit->insertPlainText(output); + ui->Raw_outputEdit->moveCursor(QTextCursor::End); +} + +void MainWindow::refreshCMD(const QString& cmd) +{ + ui->Raw_CMDEdit->setText(cmd); + if(cmd != "" && (ui->Raw_CMDHistoryWidget->count() == 0 || ui->Raw_CMDHistoryWidget->item(ui->Raw_CMDHistoryWidget->count() - 1)->text() != cmd)) + ui->Raw_CMDHistoryWidget->addItem(cmd); +} + +// ********************************************************* + +// ******************** raw command ******************** + +void MainWindow::on_Raw_sendCMDButton_clicked() +{ + util->execCMD(ui->Raw_CMDEdit->text()); + refreshCMD(ui->Raw_CMDEdit->text()); +} + +void MainWindow::on_Raw_clearOutputButton_clicked() +{ + ui->Raw_outputEdit->clear(); +} + +void MainWindow::on_Raw_CMDHistoryBox_stateChanged(int arg1) +{ + if(arg1 == Qt::Checked) + { + ui->Raw_CMDHistoryWidget->setVisible(true); + ui->Raw_clearHistoryButton->setVisible(true); + ui->Raw_CMDHistoryBox->setText(tr("History:")); + } + else + { + ui->Raw_CMDHistoryWidget->setVisible(false); + ui->Raw_clearHistoryButton->setVisible(false); + ui->Raw_CMDHistoryBox->setText(""); + } +} + +void MainWindow::on_Raw_clearHistoryButton_clicked() +{ + ui->Raw_CMDHistoryWidget->clear(); +} + +void MainWindow::on_Raw_CMDHistoryWidget_itemDoubleClicked(QListWidgetItem *item) +{ + ui->Raw_CMDEdit->setText(item->text()); + ui->Raw_CMDEdit->setFocus(); +} + +void MainWindow::sendMSG() // send command when pressing Enter +{ + if(ui->Raw_CMDEdit->hasFocus()) + on_Raw_sendCMDButton_clicked(); +} + +// ***************************************************** + +// ******************** mifare ******************** +void MainWindow::MF_onTypeChanged(int id, bool st) +{ + typeBtnGroup->blockSignals(true); + 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); + if(result == QMessageBox::Yes) + { + qDebug() << "Yes"; + mifare->setCardType(typeBtnGroup->checkedId()); + MF_widgetReset(); + } + else + { + qDebug() << "No"; + typeBtnGroup->button(id)->setChecked(true); + } + } + typeBtnGroup->blockSignals(false); +} + +void MainWindow::on_MF_data2KeyBotton_clicked() +{ + mifare->data_data2Key(); +} + +void MainWindow::on_MF_key2DataBotton_clicked() +{ + mifare->data_key2Data(); +} + +void MainWindow::on_MF_fontButton_clicked() +{ + bool isOK = false; + QFont font = QFontDialog::getFont(&isOK, ui->MF_keyWidget->font(), this, tr("Plz select the font of data widget and key widget")); + + if(isOK) + { + ui->MF_keyWidget->setFont(font); + ui->MF_dataWidget->setFont(font); + } +} + +void MainWindow::on_MF_dataWidget_itemChanged(QTableWidgetItem *item) +{ + + if(item->column() == 2) + { + QString data = item->text().replace(" ", "").toUpper(); + if(data == "" || mifare->data_isDataValid(data) == Mifare::DATA_NOSPACE) + { + mifare->data_setData(item->row(), data); + } + else + { + QMessageBox::information(this, tr("Info"), tr("Data must consists of 32 Hex symbols(Whitespace is allowed)")); + } + mifare->data_syncWithDataWidget(false, item->row()); + } +} + +void MainWindow::on_MF_keyWidget_itemChanged(QTableWidgetItem *item) +{ + if(item->column() == 1) + { + QString key = item->text().replace(" ", "").toUpper(); + if(key == "" || mifare->data_isKeyValid(key)) + { + mifare->data_setKey(item->row(), true, 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); + } + else if(item->column() == 2) + { + QString key = item->text().replace(" ", ""); + if(key == "" || mifare->data_isKeyValid(key)) + { + mifare->data_setKey(item->row(), false, 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); + } +} + +void MainWindow::on_MF_File_loadButton_clicked() +{ + QString title = ""; + QString filename = ""; + if(ui->MF_File_dataBox->isChecked()) + { + title = tr("Plz select the data file:"); + filename = QFileDialog::getOpenFileName(this, title, "./", tr("Binary Data Files(*.bin *.dump);;Text Data Files(*.txt *.eml);;All Files(*.*)")); + qDebug() << filename; + if(filename != "") + { + if(!mifare->data_loadDataFile(filename)) + { + QMessageBox::information(this, tr("Info"), tr("Failed to open") + "\n" + filename); + } + } + } + else if(ui->MF_File_keyBox->isChecked()) + { + title = tr("Plz select the key file:"); + filename = QFileDialog::getOpenFileName(this, title, "./", tr("Binary Key Files(*.bin *.dump);;Binary Data Files(*.bin *.dump);;All Files(*.*)")); + qDebug() << filename; + if(filename != "") + { + if(!mifare->data_loadKeyFile(filename)) + { + QMessageBox::information(this, tr("Info"), tr("Failed to open") + "\n" + filename); + } + } + } + +} + +void MainWindow::on_MF_File_saveButton_clicked() +{ + + QString title = ""; + QString filename = ""; + QString selectedType = ""; + + if(ui->MF_File_dataBox->isChecked()) + { + title = tr("Plz select the location to save data file:"); + filename = QFileDialog::getSaveFileName(this, title, "./", tr("Binary Data Files(*.bin *.dump);;Text Data Files(*.txt *.eml)"), &selectedType); + qDebug() << filename; + if(filename != "") + { + if(!mifare->data_saveDataFile(filename, selectedType == "Binary Data Files(*.bin *.dump)")) + { + QMessageBox::information(this, tr("Info"), tr("Failed to save to") + "\n" + filename); + } + } + } + else if(ui->MF_File_keyBox->isChecked()) + { + title = tr("Plz select the location to save key file:"); + filename = QFileDialog::getSaveFileName(this, title, "./", tr("Binary Key Files(*.bin *.dump)"), &selectedType); + qDebug() << filename; + if(filename != "") + { + if(!mifare->data_saveKeyFile(filename, selectedType == "Binary Key Files(*.bin *.dump)")) + { + QMessageBox::information(this, tr("Info"), tr("Failed to save to") + "\n" + filename); + } + } + } + qDebug() << filename << selectedType; +} + +void MainWindow::on_MF_File_clearButton_clicked() +{ + if(ui->MF_File_keyBox->isChecked()) + { + mifare->data_clearKey(); + mifare->data_syncWithKeyWidget(); + } + else if(ui->MF_File_dataBox->isChecked()) + { + mifare->data_clearData(); + mifare->data_syncWithDataWidget(); + } +} + +void MainWindow::on_MF_Attack_infoButton_clicked() +{ + mifare->info(); +} + +void MainWindow::on_MF_Attack_chkButton_clicked() +{ + setState(false); + mifare->chk(); + setState(true); +} + +void MainWindow::on_MF_Attack_nestedButton_clicked() +{ + setState(false); + mifare->nested(); + setState(true); +} + +void MainWindow::on_MF_Attack_hardnestedButton_clicked() +{ + mifare->hardnested(); +} + +void MainWindow::on_MF_RW_readAllButton_clicked() +{ + setState(false); + mifare->readAll(); + setState(true); +} + +void MainWindow::on_MF_RW_readBlockButton_clicked() +{ + setState(false); + mifare->read(); + setState(true); +} + +void MainWindow::on_MF_RW_writeBlockButton_clicked() +{ + setState(false); + mifare->write(); + setState(true); +} + +void MainWindow::on_MF_RW_writeAllButton_clicked() +{ + setState(false); + mifare->writeAll(); + setState(true); +} + +void MainWindow::on_MF_RW_dumpButton_clicked() +{ + mifare->dump(); +} + +void MainWindow::on_MF_RW_restoreButton_clicked() +{ + mifare->restore(); +} + +void MainWindow::on_MF_UID_readAllButton_clicked() +{ + setState(false); + mifare->readAllC(); + setState(true); +} + +void MainWindow::on_MF_UID_readBlockButton_clicked() +{ + setState(false); + mifare->readC(); + setState(true); +} + +void MainWindow::on_MF_UID_writeAllButton_clicked() +{ + setState(false); + mifare->writeAllC(); + setState(true); +} + +void MainWindow::on_MF_UID_writeBlockButton_clicked() +{ + setState(false); + mifare->writeC(); + setState(true); +} + +void MainWindow::on_MF_UID_wipeButton_clicked() +{ + mifare->wipeC(); +} + +void MainWindow::on_MF_UID_aboutUIDButton_clicked() +{ + QString msg; + msg += tr(" 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.") + "\n"; + msg += tr(" Chinese Magic Cards(aka UID Cards) are some special cards whose Block 0 are writeable. And you can change UID by writing to it.") + "\n"; + msg += "\n"; + msg += tr("There are two versions of Chinese Magic Cards, the Gen1 and the Gen2.") + "\n"; + msg += tr(" Gen1:") + "\n" + tr(" 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.") + "\n"; + msg += tr(" Gen2:") + "\n" + tr(" 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.") + "\n"; + msg += "\n"; + msg += tr("There are some types of Chinese Magic Card Gen2.") + "\n"; + msg += tr(" CUID Card:") + "\n" + tr(" the Block 0 is writeable, you can write to this block repeatedly by normal wrbl command.") + "\n"; + msg += tr(" (hf mf wrbl 0 A FFFFFFFFFFFF )") + "\n"; + msg += tr(" FUID Card:") + "\n" + tr(" you can only write to Block 0 once. After that, it seems like a typical Mifare card(Block 0 cannot be written to).") + "\n"; + msg += tr(" (some readers might try changing the Block 0, which could detect the CUID Card. In that case, you should use FUID card.)") + "\n"; + msg += tr(" UFUID Card:") + "\n" + tr(" 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).") + "\n"; + msg += "\n"; + msg += tr(" Seemingly, these Chinese Magic Cards are more easily to be compromised by Nested Attack(it takes little time to get an unknown key).") + "\n"; + QMessageBox::information(this, tr("About UID Card"), msg); +} + +void MainWindow::on_MF_UID_setParaButton_clicked() +{ + setState(false); + mifare->setParameterC(); + setState(true); +} + +void MainWindow::on_MF_UID_lockButton_clicked() +{ + mifare->lockC(); +} + +void MainWindow::on_MF_Sniff_sniffButton_clicked() +{ + setState(false); + mifare->sniff(); + setState(true); +} + +void MainWindow::on_MF_Sniff_listButton_clicked() +{ + mifare->list(); +} + +void MainWindow::MF_widgetReset() +{ + int secs = mifare->cardType.sectors; + int blks = mifare->cardType.blocks; + ui->MF_RW_blockBox->clear(); + ui->MF_keyWidget->setRowCount(secs); + ui->MF_dataWidget->setRowCount(blks); + + for(int i = 0; i < blks; i++) + { + setTableItem(ui->MF_dataWidget, i, 0, ""); + setTableItem(ui->MF_dataWidget, i, 1, QString::number(i)); + setTableItem(ui->MF_dataWidget, i, 2, ""); + ui->MF_RW_blockBox->addItem(QString::number(i)); + } + + for(int i = 0; i < secs; i++) + { + setTableItem(ui->MF_keyWidget, i, 0, QString::number(i)); + setTableItem(ui->MF_keyWidget, i, 1, ""); + setTableItem(ui->MF_keyWidget, i, 2, ""); + setTableItem(ui->MF_dataWidget, mifare->cardType.blks[i], 0, QString::number(i)); + } +} +// ************************************************ + + +// ******************** other ******************** + +void MainWindow::uiInit() +{ + connect(ui->Raw_CMDEdit, &QLineEdit::editingFinished, this, &MainWindow::sendMSG); + + connectStatusBar = new QLabel(this); + programStatusBar = new QLabel(this); + PM3VersionBar = new QLabel(this); + setStatusBar(connectStatusBar, tr("Not Connected")); + setStatusBar(programStatusBar, tr("Idle")); + setStatusBar(PM3VersionBar, ""); + ui->statusbar->addPermanentWidget(PM3VersionBar, 1); + ui->statusbar->addPermanentWidget(connectStatusBar, 1); + ui->statusbar->addPermanentWidget(programStatusBar, 1); + + ui->MF_dataWidget->setColumnCount(3); + ui->MF_dataWidget->setHorizontalHeaderItem(0, new QTableWidgetItem(tr("Sec"))); + 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(2, 430); + + ui->MF_keyWidget->setColumnCount(3); + ui->MF_keyWidget->setHorizontalHeaderItem(0, new QTableWidgetItem(tr("Sec"))); + ui->MF_keyWidget->setHorizontalHeaderItem(1, new QTableWidgetItem(tr("KeyA"))); + ui->MF_keyWidget->setHorizontalHeaderItem(2, new QTableWidgetItem(tr("KeyB"))); + ui->MF_keyWidget->verticalHeader()->setVisible(false); + ui->MF_keyWidget->setColumnWidth(0, 35); + ui->MF_keyWidget->setColumnWidth(1, 115); + ui->MF_keyWidget->setColumnWidth(2, 115); + + MF_widgetReset(); + typeBtnGroup = new QButtonGroup(this); + typeBtnGroup->addButton(ui->MF_Type_miniButton, 0); + typeBtnGroup->addButton(ui->MF_Type_1kButton, 1); + typeBtnGroup->addButton(ui->MF_Type_2kButton, 2); + typeBtnGroup->addButton(ui->MF_Type_4kButton, 4); + connect(typeBtnGroup, QOverload::of(&QButtonGroup::buttonToggled), this, &MainWindow::MF_onTypeChanged); + + ui->MF_keyWidget->installEventFilter(this); + ui->MF_dataWidget->installEventFilter(this); + + on_Raw_CMDHistoryBox_stateChanged(Qt::Unchecked); + on_PM3_refreshPortButton_clicked(); +} + +void MainWindow::signalInit() +{ + connect(pm3, &PM3Process::newOutput, util, &Util::processOutput); + connect(util, &Util::refreshOutput, this, &MainWindow::refreshOutput); + + connect(this, &MainWindow::connectPM3, pm3, &PM3Process::connectPM3); + connect(pm3, &PM3Process::PM3StatedChanged, this, &MainWindow::onPM3StateChanged); + connect(this, &MainWindow::killPM3, pm3, &PM3Process::kill); + + connect(util, &Util::write, pm3, &PM3Process::write); +} + +void MainWindow::setStatusBar(QLabel* target, const QString & text) +{ + if(target == PM3VersionBar) + target->setText(tr("HW Version:") + text); + else if(target == connectStatusBar) + target->setText(tr("PM3:") + text); + else if(target == programStatusBar) + target->setText(tr("State:") + text); +} + +void MainWindow::setTableItem(QTableWidget* widget, int row, int column, const QString& text) +{ + if(widget->item(row, column) == nullptr) + widget->setItem(row, column, new QTableWidgetItem()); + widget->item(row, column)->setText(text); +} + +bool MainWindow::eventFilter(QObject *watched, QEvent *event) // drag support +{ + if(event->type() == QEvent::DragEnter) + { + QDragEnterEvent* dragEvent = static_cast(event); + dragEvent->acceptProposedAction(); + return true; + } + else if(event->type() == QEvent::Drop) + { + QDropEvent* dropEvent = static_cast(event); + if(watched == ui->MF_keyWidget) + { + const QMimeData* mime = dropEvent->mimeData(); + if(mime->hasUrls()) + { + QList urls = mime->urls(); + if(urls.length() == 1) + { + mifare->data_loadKeyFile(urls[0].toLocalFile()); + return true; + } + } + } + else if(watched == ui->MF_dataWidget) + { + const QMimeData* mime = dropEvent->mimeData(); + if(mime->hasUrls()) + { + QList urls = mime->urls(); + if(urls.length() == 1) + { + mifare->data_loadDataFile(urls[0].toLocalFile()); + return true; + } + } + } + } + return QMainWindow::eventFilter(watched, event); +} + +void MainWindow::setState(bool st) +{ + if(!st && pm3state) + { + setStatusBar(programStatusBar, tr("Running")); + } + else + { + setStatusBar(programStatusBar, tr("Idle")); + } + ui->MF_attackGroupBox->setEnabled(st); + ui->MF_normalGroupBox->setEnabled(st); + ui->MF_UIDGroupBox->setEnabled(st); + ui->MF_simGroupBox->setEnabled(st); + ui->MF_sniffGroupBox->setEnabled(st); + ui->Raw_CMDEdit->setEnabled(st); + ui->Raw_sendCMDButton->setEnabled(st); +} + +// *********************************************** diff --git a/ui/mainwindow.h b/ui/mainwindow.h new file mode 100644 index 0000000..17dcf8f --- /dev/null +++ b/ui/mainwindow.h @@ -0,0 +1,149 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/pm3process.h" +#include "module/mifare.h" +#include "common/util.h" + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + + void initUI(); + bool eventFilter(QObject *watched, QEvent *event); +public slots: + void refreshOutput(const QString &output); + void refreshCMD(const QString &cmd); + void setStatusBar(QLabel* target, const QString & text); + void onPM3StateChanged(bool st, QString info); + void MF_onTypeChanged(int id, bool st); +private slots: + + void on_PM3_connectButton_clicked(); + + void on_Raw_sendCMDButton_clicked(); + + void on_PM3_disconnectButton_clicked(); + + void on_Raw_clearOutputButton_clicked(); + + void sendMSG(); + void on_PM3_refreshPortButton_clicked(); + + void on_Raw_CMDHistoryBox_stateChanged(int arg1); + + void on_Raw_clearHistoryButton_clicked(); + + void on_Raw_CMDHistoryWidget_itemDoubleClicked(QListWidgetItem *item); + + void on_MF_Attack_chkButton_clicked(); + + void on_MF_Attack_nestedButton_clicked(); + + void on_MF_Attack_hardnestedButton_clicked(); + + void on_MF_Sniff_sniffButton_clicked(); + + void on_MF_Sniff_listButton_clicked(); + + void on_MF_RW_readAllButton_clicked(); + + void on_MF_RW_readBlockButton_clicked(); + + void on_MF_RW_writeBlockButton_clicked(); + + void on_MF_Attack_infoButton_clicked(); + + void on_MF_RW_writeAllButton_clicked(); + + + void on_MF_RW_dumpButton_clicked(); + + void on_MF_RW_restoreButton_clicked(); + + void on_MF_UID_readAllButton_clicked(); + + void on_MF_UID_readBlockButton_clicked(); + + void on_MF_UID_writeAllButton_clicked(); + + void on_MF_UID_writeBlockButton_clicked(); + + void on_MF_File_loadButton_clicked(); + + void on_MF_File_saveButton_clicked(); + + void on_MF_data2KeyBotton_clicked(); + + void on_MF_key2DataBotton_clicked(); + + void on_MF_dataWidget_itemChanged(QTableWidgetItem *item); + + void on_MF_File_clearButton_clicked(); + + void on_MF_keyWidget_itemChanged(QTableWidgetItem *item); + + void on_MF_fontButton_clicked(); + + void on_MF_UID_wipeButton_clicked(); + + void on_MF_UID_aboutUIDButton_clicked(); + + void on_MF_UID_setParaButton_clicked(); + + void on_MF_UID_lockButton_clicked(); + +private: + Ui::MainWindow* ui; + QButtonGroup* typeBtnGroup; + QLabel* connectStatusBar; + QLabel* programStatusBar; + QLabel* PM3VersionBar; + QAction* myInfo; + + void uiInit(); + + PM3Process* pm3; + bool pm3state; + QThread* pm3Thread; + + Mifare* mifare; + Util* util; + + + void signalInit(); + void MF_widgetReset(); + void setTableItem(QTableWidget *widget, int row, int column, const QString &text); + void setState(bool st); +signals: + void connectPM3(const QString path, const QString port); + void killPM3(); + void setSerialListener(const QString &name, bool state); +}; +#endif // MAINWINDOW_H diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui new file mode 100644 index 0000000..9e0722d --- /dev/null +++ b/ui/mainwindow.ui @@ -0,0 +1,1108 @@ + + + MainWindow + + + + 0 + 0 + 870 + 770 + + + + + 870 + 770 + + + + Qt::ActionsContextMenu + + + Proxmark3GUI + + + + + 0 + 0 + + + + + 5 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + QLayout::SetMaximumSize + + + + + Path: + + + + + + + ../pm3/win64/proxmark3 + + + + + + + + 80 + 0 + + + + + + + + Refresh + + + + + + + Connect + + + + + + + Disconnect + + + + + + + + + + 0 + 0 + + + + 1 + + + + Mifare + + + + 5 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + 2 + + + + + + 2 + 0 + + + + + Consolas + 12 + + + + Qt::PreventContextMenu + + + true + + + QAbstractItemView::SingleSelection + + + 20 + + + 20 + + + + + + + 2 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 20 + 20 + + + + + 20 + 20 + + + + >> + + + + + + + + 0 + 0 + + + + + 20 + 20 + + + + + 20 + 20 + + + + << + + + + + + + + 20 + 20 + + + + + 20 + 20 + + + + + 20 + 20 + + + + + Courier + 75 + true + + + + F + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + 1 + 0 + + + + + Consolas + 12 + + + + Qt::PreventContextMenu + + + true + + + QAbstractItemView::SingleSelection + + + 20 + + + 20 + + + + + + + + + + + Card Type + + + false + + + + 5 + + + 5 + + + + + MINI + + + + + + + 1K + + + true + + + + + + + 2K + + + + + + + 4K + + + + + + + + + + File + + + + 5 + + + 5 + + + + + + 40 + 0 + + + + Load + + + + + + + + 40 + 0 + + + + Save + + + + + + + + 40 + 0 + + + + Clear + + + + + + + Data + + + true + + + + + + + Key + + + + + + + + + + Attack + + + + 5 + + + 5 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Card Info + + + + + + + + 0 + 0 + + + + Check Default + + + + + + + + 0 + 0 + + + + Nested + + + + + + + Hardnested + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Read/Write + + + + + + + + + + Block: + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + true + + + + + + -1 + + + + + + + Key: + + + + + + + + Courier + + + + FFFFFFFFFFFF + + + + + + + Key Type: + + + + + + + + 35 + 0 + + + + + 35 + 16777215 + + + + + A + + + + + B + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Normal(Require Password) + + + + 5 + + + 5 + + + 5 + + + + + Read Block + + + + + + + Write Block + + + + + + + Read All + + + + + + + Write All + + + + + + + + 0 + 0 + + + + + 40 + 0 + + + + Dump + + + + + + + + 0 + 0 + + + + Restore + + + + + + + + + + Chinese Magic Card(Without Password) + + + + 5 + + + 5 + + + + + Lock UFUID Card + + + + + + + About UID Card + + + + + + + Read Block + + + + + + + Write Block + + + + + + + Read All + + + + + + + Write All + + + + + + + + 0 + 0 + + + + Set Parameter + + + + + + + + 0 + 0 + + + + + 40 + 0 + + + + Wipe + + + + + + + + + + + + + Courier + + + + + + + + + + + + + Simulate + + + + 5 + + + 9 + + + 5 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Load from data above + + + + + + + Read All + + + + + + + + 40 + 0 + + + + Clear + + + + + + + Simulate + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Sniff + + + + 5 + + + 5 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 40 + 0 + + + + Sniff + + + + + + + List Sniff Data + + + + + + + + 40 + 0 + + + + Load + + + + + + + + 40 + 0 + + + + Save + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + RawCommand + + + + 5 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + 2 + 0 + + + + Qt::ScrollBarAlwaysOn + + + QAbstractScrollArea::AdjustIgnored + + + true + + + + + + + + + Qt::LeftToRight + + + History: + + + false + + + + + + + + 1 + 0 + + + + Qt::PreventContextMenu + + + + + + + + 0 + 0 + + + + Qt::LeftToRight + + + ClearHistory + + + + + + + + + + + QLayout::SetMaximumSize + + + + + + + + Send + + + + + + + ClearOutput + + + + + + + + + + + + + + true + + + + + + diff --git a/ui/mf_attack_hardnesteddialog.cpp b/ui/mf_attack_hardnesteddialog.cpp new file mode 100644 index 0000000..6896764 --- /dev/null +++ b/ui/mf_attack_hardnesteddialog.cpp @@ -0,0 +1,34 @@ +#include "mf_attack_hardnesteddialog.h" +#include "ui_mf_attack_hardnesteddialog.h" + +MF_Attack_hardnestedDialog::MF_Attack_hardnestedDialog(int blocks, QWidget *parent) : + QDialog(parent), + ui(new Ui::MF_Attack_hardnestedDialog) +{ + ui->setupUi(this); + for(int i = 0; i < blocks; i++) + { + ui->knownKeySectorBox->addItem(QString::number(i)); + ui->targetKeySectorBox->addItem(QString::number(i)); + } + +} + +MF_Attack_hardnestedDialog::~MF_Attack_hardnestedDialog() +{ + delete ui; +} + +void MF_Attack_hardnestedDialog::on_buttonBox_accepted() +{ + emit sendCMD("hf mf hardnested " + + ui->knownKeySectorBox->currentText() + + " " + + ui->knownKeyTypeBox->currentText() + + " " + + ui->knownKeyBox->text() + + " " + + ui->targetKeySectorBox->currentText() + + " " + + ui->targetKeyTypeBox->currentText()); +} diff --git a/ui/mf_attack_hardnesteddialog.h b/ui/mf_attack_hardnesteddialog.h new file mode 100644 index 0000000..0f13bf7 --- /dev/null +++ b/ui/mf_attack_hardnesteddialog.h @@ -0,0 +1,27 @@ +#ifndef MF_ATTACK_HARDNESTEDDIALOG_H +#define MF_ATTACK_HARDNESTEDDIALOG_H + +#include + +namespace Ui { +class MF_Attack_hardnestedDialog; +} + +class MF_Attack_hardnestedDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MF_Attack_hardnestedDialog(int blocks, QWidget *parent = nullptr); + ~MF_Attack_hardnestedDialog(); + + +private: + Ui::MF_Attack_hardnestedDialog *ui; +signals: + void sendCMD(QString cmd); +private slots: + void on_buttonBox_accepted(); +}; + +#endif // MF_ATTACK_HARDNESTEDDIALOG_H diff --git a/ui/mf_attack_hardnesteddialog.ui b/ui/mf_attack_hardnesteddialog.ui new file mode 100644 index 0000000..9463564 --- /dev/null +++ b/ui/mf_attack_hardnesteddialog.ui @@ -0,0 +1,228 @@ + + + MF_Attack_hardnestedDialog + + + + 0 + 0 + 287 + 173 + + + + Hardnested Attack + + + + + + Known Block: + + + + + + + + + Block: + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + true + + + + + + + + 35 + 0 + + + + + 35 + 16777215 + + + + + A + + + + + B + + + + + + + + + Courier + + + + FFFFFFFFFFFF + + + + + + + + + Target Block: + + + + + + + + + Block: + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + true + + + + + + + + 35 + 0 + + + + + 35 + 16777215 + + + + + A + + + + + B + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 31 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + MF_Attack_hardnestedDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MF_Attack_hardnestedDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/ui/mf_uid_parameterdialog.cpp b/ui/mf_uid_parameterdialog.cpp new file mode 100644 index 0000000..e30705d --- /dev/null +++ b/ui/mf_uid_parameterdialog.cpp @@ -0,0 +1,27 @@ +#include "mf_uid_parameterdialog.h" +#include "ui_mf_uid_parameterdialog.h" + +MF_UID_parameterDialog::MF_UID_parameterDialog(const QString& uid, const QString& atqa, const QString& sak, QWidget *parent) : + QDialog(parent), + ui(new Ui::MF_UID_parameterDialog) +{ + ui->setupUi(this); + ui->UIDLineEdit->setText(uid); + ui->ATQALineEdit->setText(atqa); + ui->SAKLineEdit->setText(sak); +} + +MF_UID_parameterDialog::~MF_UID_parameterDialog() +{ + delete ui; +} + +void MF_UID_parameterDialog::on_buttonBox_accepted() +{ + emit sendCMD("hf mf csetuid " + + ui->UIDLineEdit->text() + + " " + + ui->ATQALineEdit->text() + + " " + + ui->SAKLineEdit->text()); +} diff --git a/ui/mf_uid_parameterdialog.h b/ui/mf_uid_parameterdialog.h new file mode 100644 index 0000000..ec580cb --- /dev/null +++ b/ui/mf_uid_parameterdialog.h @@ -0,0 +1,26 @@ +#ifndef MF_UID_PARAMETERDIALOG_H +#define MF_UID_PARAMETERDIALOG_H + +#include + +namespace Ui { +class MF_UID_parameterDialog; +} + +class MF_UID_parameterDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MF_UID_parameterDialog(const QString& uid, const QString& atqa, const QString& sak, QWidget *parent = nullptr); + ~MF_UID_parameterDialog(); + +private: + Ui::MF_UID_parameterDialog *ui; +signals: + void sendCMD(QString cmd); +private slots: + void on_buttonBox_accepted(); +}; + +#endif // MF_UID_PARAMETERDIALOG_H diff --git a/ui/mf_uid_parameterdialog.ui b/ui/mf_uid_parameterdialog.ui new file mode 100644 index 0000000..15d5129 --- /dev/null +++ b/ui/mf_uid_parameterdialog.ui @@ -0,0 +1,111 @@ + + + MF_UID_parameterDialog + + + + 0 + 0 + 205 + 186 + + + + Set Parameter + + + + + + + + UID: + + + + + + + + + + ATQA: + + + + + + + + + + SAK: + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + MF_UID_parameterDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MF_UID_parameterDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +