1 #include "common/PythonManager.h"
2 #include "common/CrashHandler.h"
3 #include "CutterApplication.h"
4 #include "plugins/PluginManager.h"
5 #include "CutterConfig.h"
6 #include "common/Decompiler.h"
7 #include "common/ResourcePaths.h"
8 
9 #include <QApplication>
10 #include <QFileOpenEvent>
11 #include <QEvent>
12 #include <QMenu>
13 #include <QMessageBox>
14 #include <QCommandLineParser>
15 #include <QTextCodec>
16 #include <QStringList>
17 #include <QProcess>
18 #include <QPluginLoader>
19 #include <QDir>
20 #include <QTranslator>
21 #include <QLibraryInfo>
22 #include <QFontDatabase>
23 #ifdef Q_OS_WIN
24 #include <QtNetwork/QtNetwork>
25 #endif // Q_OS_WIN
26 
27 #include <cstdlib>
28 
29 #if CUTTER_R2GHIDRA_STATIC
30 #include <R2GhidraDecompiler.h>
31 #endif
32 
CutterApplication(int & argc,char ** argv)33 CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc, argv)
34 {
35     // Setup application information
36     setApplicationVersion(CUTTER_VERSION_FULL);
37     setWindowIcon(QIcon(":/img/r2cutter.svg"));
38     setAttribute(Qt::AA_UseHighDpiPixmaps);
39     setLayoutDirection(Qt::LeftToRight);
40 
41     // WARN!!! Put initialization code below this line. Code above this line is mandatory to be run First
42 
43 #ifdef Q_OS_WIN
44     // Hack to force Cutter load internet connection related DLL's
45     QSslSocket s;
46     s.sslConfiguration();
47 #endif // Q_OS_WIN
48 
49     // Load translations
50     if (!loadTranslations()) {
51         qWarning() << "Cannot load translations";
52     }
53 
54     // Load fonts
55     int ret = QFontDatabase::addApplicationFont(":/fonts/Anonymous Pro.ttf");
56     if (ret == -1) {
57         qWarning() << "Cannot load Anonymous Pro font.";
58     }
59 
60     ret = QFontDatabase::addApplicationFont(":/fonts/Inconsolata-Regular.ttf");
61     if (ret == -1) {
62         qWarning() << "Cannot load Incosolata-Regular font.";
63     }
64 
65 
66     // Set QString codec to UTF-8
67     QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
68 #if QT_VERSION < QT_VERSION_CHECK(5,0,0)
69     QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
70     QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
71 #endif
72 
73     if (!parseCommandLineOptions()) {
74         std::exit(1);
75     }
76 
77     // Check r2 version
78     QString r2version = r_core_version();
79     QString localVersion = "" R2_GITTAP;
80     if (localVersion != "" && r2version != "" && r2version != localVersion) {
81         QMessageBox msg;
82         msg.setIcon(QMessageBox::Critical);
83         msg.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
84         msg.setWindowTitle(QObject::tr("Version mismatch!"));
85         msg.setText(QString(
86                         QObject::tr("The version used to compile Cutter (%1) does not match the binary version of radare2 (%2). This could result in unexpected behaviour. Are you sure you want to continue?")).arg(
87                         localVersion, r2version));
88         if (msg.exec() == QMessageBox::No) {
89             std::exit(1);
90         }
91     }
92 
93 #ifdef CUTTER_ENABLE_PYTHON
94     // Init python
95     if (!clOptions.pythonHome.isEmpty()) {
96         Python()->setPythonHome(clOptions.pythonHome);
97     }
98     Python()->initialize();
99 #endif
100 
101 #ifdef Q_OS_WIN
102     // Redefine r_sys_prefix() behaviour
103     qputenv("R_ALT_SRC_DIR", "1");
104 #endif
105 
106     Core()->initialize(clOptions.enableR2Plugins);
107     Core()->setSettings();
108     Config()->loadInitial();
109     Core()->loadCutterRC();
110 
111     Config()->setOutputRedirectionEnabled(clOptions.outputRedirectionEnabled);
112 
113     if (R2DecDecompiler::isAvailable()) {
114         Core()->registerDecompiler(new R2DecDecompiler(Core()));
115     }
116 
117 #if CUTTER_R2GHIDRA_STATIC
118     Core()->registerDecompiler(new R2GhidraDecompiler(Core()));
119 #endif
120 
121     Plugins()->loadPlugins(clOptions.enableCutterPlugins);
122 
123     for (auto &plugin : Plugins()->getPlugins()) {
124         plugin->registerDecompilers();
125     }
126 
127     mainWindow = new MainWindow();
128     installEventFilter(mainWindow);
129 
130     // set up context menu shortcut display fix
131 #if QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
132     setStyle(new CutterProxyStyle());
133 #endif // QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
134 
135     if (clOptions.args.empty()) {
136         // check if this is the first execution of Cutter in this computer
137         // Note: the execution after the preferences been reset, will be considered as first-execution
138         if (Config()->isFirstExecution()) {
139             mainWindow->displayWelcomeDialog();
140         }
141         mainWindow->displayNewFileDialog();
142     } else { // filename specified as positional argument
143         bool askOptions = clOptions.analLevel != AutomaticAnalysisLevel::Ask;
144         mainWindow->openNewFile(clOptions.fileOpenOptions, askOptions);
145     }
146 
147 #if 0
148 #ifdef APPIMAGE
149     {
150         auto appdir = QDir(QCoreApplication::applicationDirPath()); // appdir/bin
151         appdir.cdUp(); // appdir
152 
153         auto sleighHome = appdir;
154         sleighHome.cd("share/radare2/plugins/r2ghidra_sleigh"); // appdir/share/radare2/plugins/r2ghidra_sleigh
155         Core()->setConfig("r2ghidra.sleighhome", sleighHome.absolutePath());
156 
157         auto r2decHome = appdir;
158         r2decHome.cd("share/radare2/plugins/r2dec-js"); // appdir/share/radare2/plugins/r2dec-js
159         qputenv("R2DEC_HOME", r2decHome.absolutePath().toLocal8Bit());
160     }
161 #endif
162 
163 #ifdef Q_OS_MACOS
164     {
165         auto r2prefix = QDir(QCoreApplication::applicationDirPath()); // Contents/MacOS
166         r2prefix.cdUp(); // Contents
167         r2prefix.cd("Resources/r2"); // Contents/Resources/r2
168 
169         auto sleighHome = r2prefix;
170         sleighHome.cd("share/radare2/plugins/r2ghidra_sleigh"); // Contents/Resources/r2/share/radare2/plugins/r2ghidra_sleigh
171         Core()->setConfig("r2ghidra.sleighhome", sleighHome.absolutePath());
172 
173         auto r2decHome = r2prefix;
174         r2decHome.cd("share/radare2/plugins/r2dec-js"); // Contents/Resources/r2/share/radare2/plugins/r2dec-js
175         qputenv("R2DEC_HOME", r2decHome.absolutePath().toLocal8Bit());
176     }
177 #endif
178 
179 #ifdef CUTTER_APPVEYOR_R2DEC
180     qputenv("R2DEC_HOME", "lib\\plugins\\r2dec-js");
181 #endif
182 #ifdef Q_OS_WIN
183     {
184         auto sleighHome = QDir(QCoreApplication::applicationDirPath());
185         sleighHome.cd("lib/plugins/r2ghidra_sleigh");
186         Core()->setConfig("r2ghidra.sleighhome", sleighHome.absolutePath());
187     }
188 #endif
189 #endif
190 }
191 
~CutterApplication()192 CutterApplication::~CutterApplication()
193 {
194     Plugins()->destroyPlugins();
195     delete mainWindow;
196 #ifdef CUTTER_ENABLE_PYTHON
197     Python()->shutdown();
198 #endif
199 }
200 
launchNewInstance(const QStringList & args)201 void CutterApplication::launchNewInstance(const QStringList &args)
202 {
203     QProcess process(this);
204     process.setEnvironment(QProcess::systemEnvironment());
205     QStringList allArgs;
206     if (!clOptions.enableCutterPlugins) {
207         allArgs.push_back("--no-cutter-plugins");
208     }
209     if (!clOptions.enableR2Plugins) {
210         allArgs.push_back("--no-r2-plugins");
211     }
212     allArgs.append(args);
213     process.startDetached(qApp->applicationFilePath(), allArgs);
214 }
215 
event(QEvent * e)216 bool CutterApplication::event(QEvent *e)
217 {
218     if (e->type() == QEvent::FileOpen) {
219         QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(e);
220         if (openEvent) {
221             if (m_FileAlreadyDropped) {
222                 // We already dropped a file in macOS, let's spawn another instance
223                 // (Like the File -> Open)
224                 QString fileName = openEvent->file();
225                 launchNewInstance({fileName});
226             } else {
227                 QString fileName = openEvent->file();
228                 m_FileAlreadyDropped = true;
229                 mainWindow->closeNewFileDialog();
230                 InitialOptions options;
231                 options.filename = fileName;
232                 mainWindow->openNewFile(options);
233             }
234         }
235     }
236     return QApplication::event(e);
237 }
238 
loadTranslations()239 bool CutterApplication::loadTranslations()
240 {
241     const QString &language = Config()->getCurrLocale().bcp47Name();
242     if (language == QStringLiteral("en") || language.startsWith(QStringLiteral("en-"))) {
243         return true;
244     }
245     const auto &allLocales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript,
246                                                       QLocale::AnyCountry);
247 
248     bool cutterTrLoaded = false;
249 
250     for (const QLocale &it : allLocales) {
251         const QString &langPrefix = it.bcp47Name();
252         if (langPrefix == language) {
253             QApplication::setLayoutDirection(it.textDirection());
254             QLocale::setDefault(it);
255 
256             QTranslator *trCutter = new QTranslator;
257             QTranslator *trQtBase = new QTranslator;
258             QTranslator *trQt = new QTranslator;
259 
260             const QStringList &cutterTrPaths = Cutter::getTranslationsDirectories();
261 
262             for (const auto &trPath : cutterTrPaths) {
263                 if (trCutter && trCutter->load(it, QLatin1String("cutter"), QLatin1String("_"), trPath)) {
264                     installTranslator(trCutter);
265                     cutterTrLoaded = true;
266                     trCutter = nullptr;
267                 }
268                 if (trQt && trQt->load(it, "qt", "_", trPath)) {
269                     installTranslator(trQt);
270                     trQt = nullptr;
271                 }
272 
273                 if (trQtBase && trQtBase->load(it, "qtbase", "_", trPath)) {
274                     installTranslator(trQtBase);
275                     trQtBase = nullptr;
276                 }
277             }
278 
279             if (trCutter) {
280                 delete trCutter;
281             }
282             if (trQt) {
283                 delete trQt;
284             }
285             if (trQtBase) {
286                 delete trQtBase;
287             }
288             return true;
289         }
290     }
291     if (!cutterTrLoaded) {
292         qWarning() << "Cannot load Cutter's translation for " << language;
293     }
294     return false;
295 }
296 
parseCommandLineOptions()297 bool CutterApplication::parseCommandLineOptions()
298 {
299     // Keep this function in sync with documentation
300 
301     QCommandLineParser cmd_parser;
302     cmd_parser.setApplicationDescription(
303         QObject::tr("A Qt and C++ GUI for radare2 reverse engineering framework"));
304     cmd_parser.addHelpOption();
305     cmd_parser.addVersionOption();
306     cmd_parser.addPositionalArgument("filename", QObject::tr("Filename to open."));
307 
308     QCommandLineOption analOption({"A", "analysis"},
309                                   QObject::tr("Automatically open file and optionally start analysis. "
310                                               "Needs filename to be specified. May be a value between 0 and 2:"
311                                               " 0 = no analysis, 1 = aaa, 2 = aaaa (experimental)"),
312                                   QObject::tr("level"));
313     cmd_parser.addOption(analOption);
314 
315     QCommandLineOption formatOption({"F", "format"},
316                                     QObject::tr("Force using a specific file format (bin plugin)"),
317                                     QObject::tr("name"));
318     cmd_parser.addOption(formatOption);
319 
320     QCommandLineOption baddrOption({"B", "base"},
321                                    QObject::tr("Load binary at a specific base address"),
322                                    QObject::tr("base address"));
323     cmd_parser.addOption(baddrOption);
324 
325     QCommandLineOption scriptOption("i",
326                                     QObject::tr("Run script file"),
327                                     QObject::tr("file"));
328     cmd_parser.addOption(scriptOption);
329 
330     QCommandLineOption writeModeOption({"w", "writemode"},
331                                        QObject::tr("Open file in write mode"));
332     cmd_parser.addOption(writeModeOption);
333 
334 
335     QCommandLineOption pythonHomeOption("pythonhome",
336                                         QObject::tr("PYTHONHOME to use for embedded python interpreter"),
337                                         "PYTHONHOME");
338     cmd_parser.addOption(pythonHomeOption);
339 
340     QCommandLineOption disableRedirectOption("no-output-redirect",
341                                              QObject::tr("Disable output redirection."
342                                                          " Some of the output in console widget will not be visible."
343                                                          " Use this option when debuging a crash or freeze and output "
344                                                          " redirection is causing some messages to be lost."));
345     cmd_parser.addOption(disableRedirectOption);
346 
347     QCommandLineOption disablePlugins("no-plugins",
348                                       QObject::tr("Do not load plugins"));
349     cmd_parser.addOption(disablePlugins);
350 
351     QCommandLineOption disableCutterPlugins("no-cutter-plugins",
352                                             QObject::tr("Do not load Cutter plugins"));
353     cmd_parser.addOption(disableCutterPlugins);
354 
355     QCommandLineOption disableR2Plugins("no-r2-plugins",
356                                         QObject::tr("Do not load radare2 plugins"));
357     cmd_parser.addOption(disableR2Plugins);
358 
359     cmd_parser.process(*this);
360 
361     CutterCommandLineOptions opts;
362     opts.args = cmd_parser.positionalArguments();
363 
364     if (cmd_parser.isSet(analOption)) {
365         bool analLevelSpecified = false;
366         int analLevel = cmd_parser.value(analOption).toInt(&analLevelSpecified);
367 
368         if (!analLevelSpecified || analLevel < 0 || analLevel > 2) {
369             fprintf(stderr, "%s\n",
370                     QObject::tr("Invalid Analysis Level. May be a value between 0 and 2.").toLocal8Bit().constData());
371             return false;
372         }
373         switch (analLevel) {
374         case 0:
375             opts.analLevel = AutomaticAnalysisLevel::None;
376             break;
377         case 1:
378             opts.analLevel = AutomaticAnalysisLevel::AAA;
379             break;
380         case 2:
381             opts.analLevel = AutomaticAnalysisLevel::AAAA;
382             break;
383         }
384     }
385 
386     if (opts.args.empty() && opts.analLevel != AutomaticAnalysisLevel::Ask) {
387         fprintf(stderr, "%s\n",
388                 QObject::tr("Filename must be specified to start analysis automatically.").toLocal8Bit().constData());
389         return false;
390     }
391 
392     InitialOptions options;
393     if (!opts.args.isEmpty()) {
394         opts.fileOpenOptions.filename = opts.args[0];
395         opts.fileOpenOptions.forceBinPlugin = cmd_parser.value(formatOption);
396         if (cmd_parser.isSet(baddrOption)) {
397             bool ok;
398             RVA baddr = cmd_parser.value(baddrOption).toULongLong(&ok, 0);
399             if (ok) {
400                 options.binLoadAddr = baddr;
401             }
402         }
403         switch (opts.analLevel) {
404         case AutomaticAnalysisLevel::Ask:
405             break;
406         case AutomaticAnalysisLevel::None:
407             opts.fileOpenOptions.analCmd = {};
408             break;
409         case AutomaticAnalysisLevel::AAA:
410             opts.fileOpenOptions.analCmd = { {"aaa", "Auto analysis"} };
411             break;
412         case AutomaticAnalysisLevel::AAAA:
413             opts.fileOpenOptions.analCmd = { {"aaaa", "Auto analysis (experimental)"} };
414             break;
415         }
416         opts.fileOpenOptions.script = cmd_parser.value(scriptOption);
417 
418         opts.fileOpenOptions.writeEnabled = cmd_parser.isSet(writeModeOption);
419     }
420 
421     if (cmd_parser.isSet(pythonHomeOption)) {
422         opts.pythonHome = cmd_parser.value(pythonHomeOption);
423     }
424 
425     opts.outputRedirectionEnabled = !cmd_parser.isSet(disableRedirectOption);
426     if (cmd_parser.isSet(disablePlugins)) {
427         opts.enableCutterPlugins = false;
428         opts.enableR2Plugins = false;
429     }
430 
431     if (cmd_parser.isSet(disableCutterPlugins)) {
432         opts.enableCutterPlugins = false;
433     }
434 
435     if (cmd_parser.isSet(disableR2Plugins)) {
436         opts.enableR2Plugins = false;
437     }
438 
439     this->clOptions = opts;
440     return true;
441 }
442 
443 
polish(QWidget * widget)444 void CutterProxyStyle::polish(QWidget *widget)
445 {
446     QProxyStyle::polish(widget);
447 #if QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
448     // HACK: This is the only way I've found to force Qt (5.10 and newer) to
449     //       display shortcuts in context menus on all platforms. It's ugly,
450     //       but it gets the job done.
451     if (auto menu = qobject_cast<QMenu *>(widget)) {
452         const auto &actions = menu->actions();
453         for (auto action : actions) {
454             action->setShortcutVisibleInContextMenu(true);
455         }
456     }
457 #endif // QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
458 }
459