#include #include #include #include #include #include #include #include #include #include #include "common/TempConfig.h" #include "common/BasicInstructionHighlighter.h" #include "common/Configuration.h" #include "common/AsyncTask.h" #include "common/R2Task.h" #include "common/Json.h" #include "core/Cutter.h" #include "Decompiler.h" #include "r_asm.h" #include "r_cmd.h" #include "sdb.h" Q_GLOBAL_STATIC(CutterCore, uniqueInstance) #define R_JSON_KEY(name) static const QString name = QStringLiteral(#name) namespace RJsonKey { R_JSON_KEY(addr); R_JSON_KEY(addr_end); R_JSON_KEY(arrow); R_JSON_KEY(baddr); R_JSON_KEY(bind); R_JSON_KEY(blocks); R_JSON_KEY(blocksize); R_JSON_KEY(bytes); R_JSON_KEY(calltype); R_JSON_KEY(cc); R_JSON_KEY(classname); R_JSON_KEY(code); R_JSON_KEY(comment); R_JSON_KEY(comments); R_JSON_KEY(cost); R_JSON_KEY(data); R_JSON_KEY(description); R_JSON_KEY(ebbs); R_JSON_KEY(edges); R_JSON_KEY(enabled); R_JSON_KEY(entropy); R_JSON_KEY(fcn_addr); R_JSON_KEY(fcn_name); R_JSON_KEY(fields); R_JSON_KEY(file); R_JSON_KEY(flags); R_JSON_KEY(flagname); R_JSON_KEY(format); R_JSON_KEY(from); R_JSON_KEY(functions); R_JSON_KEY(graph); R_JSON_KEY(haddr); R_JSON_KEY(hw); R_JSON_KEY(in_functions); R_JSON_KEY(index); R_JSON_KEY(jump); R_JSON_KEY(laddr); R_JSON_KEY(lang); R_JSON_KEY(len); R_JSON_KEY(length); R_JSON_KEY(license); R_JSON_KEY(methods); R_JSON_KEY(name); R_JSON_KEY(realname); R_JSON_KEY(nargs); R_JSON_KEY(nbbs); R_JSON_KEY(nlocals); R_JSON_KEY(offset); R_JSON_KEY(opcode); R_JSON_KEY(opcodes); R_JSON_KEY(ordinal); R_JSON_KEY(libname); R_JSON_KEY(outdegree); R_JSON_KEY(paddr); R_JSON_KEY(path); R_JSON_KEY(perm); R_JSON_KEY(pid); R_JSON_KEY(plt); R_JSON_KEY(prot); R_JSON_KEY(ref); R_JSON_KEY(refs); R_JSON_KEY(reg); R_JSON_KEY(rwx); R_JSON_KEY(section); R_JSON_KEY(sections); R_JSON_KEY(size); R_JSON_KEY(stackframe); R_JSON_KEY(status); R_JSON_KEY(string); R_JSON_KEY(strings); R_JSON_KEY(symbols); R_JSON_KEY(text); R_JSON_KEY(to); R_JSON_KEY(trace); R_JSON_KEY(type); R_JSON_KEY(uid); R_JSON_KEY(vaddr); R_JSON_KEY(value); R_JSON_KEY(vsize); } #undef R_JSON_KEY static void updateOwnedCharPtr(char *&variable, const QString &newValue) { auto data = newValue.toUtf8(); R_FREE(variable) variable = strdup(data.data()); } RCoreLocked::RCoreLocked(CutterCore *core) : core(core) { core->coreMutex.lock(); assert(core->coreLockDepth >= 0); core->coreLockDepth++; if (core->coreLockDepth == 1) { assert(core->coreBed); r_cons_sleep_end(core->coreBed); core->coreBed = nullptr; } } RCoreLocked::~RCoreLocked() { assert(core->coreLockDepth > 0); core->coreLockDepth--; if (core->coreLockDepth == 0) { core->coreBed = r_cons_sleep_begin(); } core->coreMutex.unlock(); } RCoreLocked::operator RCore *() const { return core->core_; } RCore *RCoreLocked::operator->() const { return core->core_; } #define CORE_LOCK() RCoreLocked core(this) static void cutterREventCallback(REvent *, int type, void *user, void *data) { auto core = reinterpret_cast(user); core->handleREvent(type, data); } CutterCore::CutterCore(QObject *parent) : QObject(parent), coreMutex(QMutex::Recursive) { } CutterCore *CutterCore::instance() { return uniqueInstance; } void CutterCore::initialize() { r_cons_new(); // initialize console core_ = r_core_new(); r_core_task_sync_begin(&core_->tasks); coreBed = r_cons_sleep_begin(); CORE_LOCK(); r_event_hook(core_->anal->ev, R_EVENT_ALL, cutterREventCallback, this); #if defined(APPIMAGE) || defined(MACOS_R2_BUNDLED) auto prefix = QDir(QCoreApplication::applicationDirPath()); #ifdef APPIMAGE // Executable is in appdir/bin prefix.cdUp(); qInfo() << "Setting r2 prefix =" << prefix.absolutePath() << " for AppImage."; #else // MACOS_R2_BUNDLED // Executable is in Contents/MacOS, prefix is Contents/Resources/r2 prefix.cdUp(); prefix.cd("Resources"); prefix.cd("r2"); qInfo() << "Setting r2 prefix =" << prefix.absolutePath() << " for macOS Application Bundle."; #endif setConfig("dir.prefix", prefix.absolutePath()); auto pluginsDir = prefix; if (pluginsDir.cd("share/radare2/plugins")) { qInfo() << "Setting r2 plugins dir =" << pluginsDir.absolutePath(); setConfig("dir.plugins", pluginsDir.absolutePath()); } else { qInfo() << "r2 plugins dir =" << pluginsDir.absolutePath() << "does not exist!"; } #endif r_core_loadlibs(this->core_, R_CORE_LOADLIBS_ALL, NULL); // IMPLICIT r_bin_iobind (core_->bin, core_->io); // Otherwise r2 may ask the user for input and Cutter would freeze setConfig("scr.interactive", false); // Initialize graph node highlighter bbHighlighter = new BasicBlockHighlighter(); // Initialize Async tasks manager asyncTaskManager = new AsyncTaskManager(this); } CutterCore::~CutterCore() { delete bbHighlighter; r_cons_sleep_end(coreBed); r_core_task_sync_end(&core_->tasks); r_core_free(this->core_); r_cons_free(); } RCoreLocked CutterCore::core() { return RCoreLocked(this); } QVector CutterCore::getCutterRCDirectories() const { QVector result; result.push_back(QDir::home()); QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppConfigLocation); for (auto &location : locations) { result.push_back(QDir(location)); } return result; } void CutterCore::loadCutterRC() { CORE_LOCK(); const auto result = getCutterRCDirectories(); for(auto &dir : result){ if(!dir.exists())continue; auto cutterRCFileInfo = QFileInfo(dir, ".cutterrc"); auto path = cutterRCFileInfo.absoluteFilePath(); if (!cutterRCFileInfo.isFile()) { continue; } qInfo() << "Loading initialization file from" << path; r_core_cmd_file(core, path.toUtf8().constData()); } } QList CutterCore::sdbList(QString path) { CORE_LOCK(); QList list = QList(); Sdb *root = sdb_ns_path(core->sdb, path.toUtf8().constData(), 0); if (root) { void *vsi; ls_iter_t *iter; ls_foreach(root->ns, iter, vsi) { SdbNs *nsi = (SdbNs *)vsi; list << nsi->name; } } return list; } using SdbListPtr = std::unique_ptr; static SdbListPtr makeSdbListPtr(SdbList *list) { return {list, ls_free}; } QList CutterCore::sdbListKeys(QString path) { CORE_LOCK(); QList list = QList(); Sdb *root = sdb_ns_path(core->sdb, path.toUtf8().constData(), 0); if (root) { void *vsi; ls_iter_t *iter; SdbListPtr l = makeSdbListPtr(sdb_foreach_list(root, false)); ls_foreach(l, iter, vsi) { SdbKv *nsi = (SdbKv *)vsi; list << reinterpret_cast(nsi->base.key); } } return list; } QString CutterCore::sdbGet(QString path, QString key) { CORE_LOCK(); Sdb *db = sdb_ns_path(core->sdb, path.toUtf8().constData(), 0); if (db) { const char *val = sdb_const_get(db, key.toUtf8().constData(), 0); if (val && *val) return val; } return QString(); } bool CutterCore::sdbSet(QString path, QString key, QString val) { CORE_LOCK(); Sdb *db = sdb_ns_path(core->sdb, path.toUtf8().constData(), 1); if (!db) return false; return sdb_set(db, key.toUtf8().constData(), val.toUtf8().constData(), 0); } QString CutterCore::sanitizeStringForCommand(QString s) { static const QRegularExpression regexp(";|@"); return s.replace(regexp, QStringLiteral("_")); } QString CutterCore::cmd(const char *str) { CORE_LOCK(); RVA offset = core->offset; char *res = r_core_cmd_str(core, str); QString o = QString(res ? res : ""); r_mem_free(res); if (offset != core->offset) { updateSeek(); } return o; } bool CutterCore::isRedirectableDebugee() { if (!currentlyDebugging || currentlyAttachedToPID != -1) { return false; } // We are only able to redirect locally debugged unix processes QJsonArray openFilesArray = cmdj("oj").array();; for (QJsonValue value : openFilesArray) { QJsonObject openFile = value.toObject(); QString URI = openFile["uri"].toString(); if (URI.contains("ptrace") | URI.contains("mach")) { return true; } } return false; } bool CutterCore::isDebugTaskInProgress() { if (!debugTask.isNull()) { return true; } return false; } bool CutterCore::asyncCmdEsil(const char *command, QSharedPointer &task) { asyncCmd(command, task); if (task.isNull()) { return false; } connect(task.data(), &R2Task::finished, task.data(), [this, task] () { QString res = task.data()->getResult(); if (res.contains(QStringLiteral("[ESIL] Stopped execution in an invalid instruction"))) { msgBox.showMessage("Stopped when attempted to run an invalid instruction. You can disable this in Preferences"); } }); return true; } bool CutterCore::asyncCmd(const char *str, QSharedPointer &task) { if (!task.isNull()) { return false; } CORE_LOCK(); RVA offset = core->offset; task = QSharedPointer(new R2Task(str, true)); connect(task.data(), &R2Task::finished, task.data(), [this, offset, task] () { CORE_LOCK(); if (offset != core->offset) { updateSeek(); } }); return true; } QString CutterCore::cmdRawAt(const char *cmd, RVA address) { QString res; RVA oldOffset = getOffset(); seekSilent(address); res = cmdRaw(cmd); seekSilent(oldOffset); return res; } QString CutterCore::cmdRaw(const char *cmd) { QString res; CORE_LOCK(); r_cons_push (); // r_cmd_call does not return the output of the command r_cmd_call(core->rcmd, cmd); // we grab the output straight from r_cons res = r_cons_get_buffer(); // cleaning up r_cons_pop (); r_cons_echo (NULL); return res; } QJsonDocument CutterCore::cmdj(const char *str) { char *res; { CORE_LOCK(); res = r_core_cmd_str(core, str); } QJsonDocument doc = parseJson(res, str); r_mem_free(res); return doc; } QString CutterCore::cmdTask(const QString &str) { R2Task task(str); task.startTask(); task.joinTask(); return task.getResult(); } QJsonDocument CutterCore::cmdjTask(const QString &str) { R2Task task(str); task.startTask(); task.joinTask(); return parseJson(task.getResultRaw(), str); } QJsonDocument CutterCore::parseJson(const char *res, const char *cmd) { QByteArray json(res); if (json.isEmpty()) { return QJsonDocument(); } QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(json, &jsonError); if (jsonError.error != QJsonParseError::NoError) { if (cmd) { eprintf("Failed to parse JSON for command \"%s\": %s\n", cmd, jsonError.errorString().toLocal8Bit().constData()); } else { eprintf("Failed to parse JSON: %s\n", jsonError.errorString().toLocal8Bit().constData()); } const int MAX_JSON_DUMP_SIZE = 8 * 1024; if (json.length() > MAX_JSON_DUMP_SIZE) { int originalSize = json.length(); json.resize(MAX_JSON_DUMP_SIZE); eprintf("%d bytes total: %s ...\n", originalSize, json.constData()); } else { eprintf("%s\n", json.constData()); } } return doc; } QStringList CutterCore::autocomplete(const QString &cmd, RLinePromptType promptType, size_t limit) { RLineBuffer buf; int c = snprintf(buf.data, sizeof(buf.data), "%s", cmd.toUtf8().constData()); if (c < 0) { return {}; } buf.index = buf.length = std::min((int)(sizeof(buf.data) - 1), c); RLineCompletion completion; r_line_completion_init(&completion, limit); r_core_autocomplete(core(), &completion, &buf, promptType); QStringList r; r.reserve(r_pvector_len(&completion.args)); for (size_t i = 0; i < r_pvector_len(&completion.args); i++) { r.push_back(QString::fromUtf8(reinterpret_cast(r_pvector_at(&completion.args, i)))); } r_line_completion_fini(&completion); return r; } /** * @brief CutterCore::loadFile * Load initial file. TODO Maybe use the "o" commands? * @param path File path * @param baddr Base (RBin) address * @param mapaddr Map address * @param perms * @param va * @param loadbin Load RBin information * @param forceBinPlugin * @return */ bool CutterCore::loadFile(QString path, ut64 baddr, ut64 mapaddr, int perms, int va, bool loadbin, const QString &forceBinPlugin) { CORE_LOCK(); RCoreFile *f; r_config_set_i(core->config, "io.va", va); f = r_core_file_open(core, path.toUtf8().constData(), perms, mapaddr); if (!f) { eprintf("r_core_file_open failed\n"); return false; } if (!forceBinPlugin.isNull()) { r_bin_force_plugin(r_core_get_bin(core), forceBinPlugin.toUtf8().constData()); } if (loadbin && va) { if (!r_core_bin_load(core, path.toUtf8().constData(), baddr)) { eprintf("CANNOT GET RBIN INFO\n"); } #if HAVE_MULTIPLE_RBIN_FILES_INSIDE_SELECT_WHICH_ONE if (!r_core_file_open(core, path.toUtf8(), R_IO_READ | (rw ? R_IO_WRITE : 0, mapaddr))) { eprintf("Cannot open file\n"); } else { // load RBin information // XXX only for sub-bins r_core_bin_load(core, path.toUtf8(), baddr); r_bin_select_idx(core->bin, NULL, idx); } #endif } else { // Not loading RBin info coz va = false } auto iod = core->io ? core->io->desc : NULL; auto debug = core->file && iod && (core->file->fd == iod->fd) && iod->plugin && \ iod->plugin->isdbg; if (!debug && r_flag_get (core->flags, "entry0")) { r_core_cmd0 (core, "s entry0"); } if (perms & R_PERM_W) { r_core_cmd0 (core, "omfg+w"); } ut64 hashLimit = getConfigut64("cfg.hashlimit"); r_bin_file_compute_hashes(core->bin, hashLimit); fflush(stdout); return true; } bool CutterCore::tryFile(QString path, bool rw) { CORE_LOCK(); RCoreFile *cf; int flags = R_PERM_R; if (rw) flags = R_PERM_RW; cf = r_core_file_open(core, path.toUtf8().constData(), flags, 0LL); if (!cf) { return false; } r_core_file_close (core, cf); return true; } /** * @brief Maps a file using r2 API * @param path Path to file * @param mapaddr Map Address * @return bool */ bool CutterCore::mapFile(QString path, RVA mapaddr) { CORE_LOCK(); RVA addr = mapaddr != RVA_INVALID ? mapaddr : 0; ut64 baddr = Core()->getFileInfo().object()["bin"].toObject()["baddr"].toVariant().toULongLong(); if (r_core_file_open(core, path.toUtf8().constData(), R_PERM_RX, addr)) { r_core_bin_load(core, path.toUtf8().constData(), baddr); } else { return false; } return true; } void CutterCore::renameFunction(const QString &oldName, const QString &newName) { cmdRaw("afn " + newName + " " + oldName); emit functionRenamed(oldName, newName); } void CutterCore::delFunction(RVA addr) { cmdRaw("af- " + RAddressString(addr)); emit functionsChanged(); } void CutterCore::renameFlag(QString old_name, QString new_name) { cmdRaw("fr " + old_name + " " + new_name); emit flagsChanged(); } void CutterCore::delFlag(RVA addr) { cmdRawAt("f-", addr); emit flagsChanged(); } void CutterCore::delFlag(const QString &name) { cmdRaw("f-" + name); emit flagsChanged(); } QString CutterCore::getInstructionBytes(RVA addr) { return cmdj("aoj @ " + RAddressString(addr)).array().first().toObject()[RJsonKey::bytes].toString(); } QString CutterCore::getInstructionOpcode(RVA addr) { return cmdj("aoj @ " + RAddressString(addr)).array().first().toObject()[RJsonKey::opcode].toString(); } void CutterCore::editInstruction(RVA addr, const QString &inst) { cmdRawAt(QString("wa %1").arg(inst), addr); emit instructionChanged(addr); } void CutterCore::nopInstruction(RVA addr) { cmdRawAt("wao nop", addr); emit instructionChanged(addr); } void CutterCore::jmpReverse(RVA addr) { cmdRawAt("wao recj", addr); emit instructionChanged(addr); } void CutterCore::editBytes(RVA addr, const QString &bytes) { cmdRawAt(QString("wx %1").arg(bytes), addr); emit instructionChanged(addr); } void CutterCore::editBytesEndian(RVA addr, const QString &bytes) { cmdRawAt(QString("wv %1").arg(bytes), addr); emit stackChanged(); } void CutterCore::setToCode(RVA addr) { cmdRawAt("Cd-", addr); emit instructionChanged(addr); } void CutterCore::setAsString(RVA addr, int size, StringTypeFormats type) { if(RVA_INVALID == addr) { return; } QString command; switch(type) { case StringTypeFormats::None: { command = "Cs"; break; } case StringTypeFormats::ASCII_LATIN1: { command = "Csa"; break; } case StringTypeFormats::UTF8: { command = "Cs8"; break; } default: return; } seekAndShow(addr); cmdRawAt(QString("%1 %2").arg(command).arg(size), addr); emit instructionChanged(addr); } void CutterCore::removeString(RVA addr) { cmdRawAt("Cs-", addr); emit instructionChanged(addr); } QString CutterCore::getString(RVA addr) { return cmdRawAt("ps", addr); } void CutterCore::setToData(RVA addr, int size, int repeat) { if (size <= 0 || repeat <= 0) { return; } cmdRawAt("Cd-", addr); cmdRawAt(QString("Cd %1 %2").arg(size).arg(repeat), addr); emit instructionChanged(addr); } int CutterCore::sizeofDataMeta(RVA addr) { bool ok; int size = cmdRawAt("Cd.", addr).toInt(&ok); return (ok ? size : 0); } void CutterCore::setComment(RVA addr, const QString &cmt) { cmdRawAt(QString("CCu base64:%1").arg(QString(cmt.toLocal8Bit().toBase64())), addr); emit commentsChanged(); } void CutterCore::delComment(RVA addr) { cmdRawAt("CC-", addr); emit commentsChanged(); } /** * @brief Gets the comment present at a specific address * @param addr The address to be checked * @return String containing comment */ QString CutterCore::getCommentAt(RVA addr) { return Core()->cmdRawAt("CC.", addr); } void CutterCore::setImmediateBase(const QString &r2BaseName, RVA offset) { if (offset == RVA_INVALID) { offset = getOffset(); } this->cmdRawAt(QString("ahi %1").arg(r2BaseName), offset); emit instructionChanged(offset); } void CutterCore::setCurrentBits(int bits, RVA offset) { if (offset == RVA_INVALID) { offset = getOffset(); } this->cmdRawAt(QString("ahb %1").arg(bits), offset); emit instructionChanged(offset); } void CutterCore::applyStructureOffset(const QString &structureOffset, RVA offset) { if (offset == RVA_INVALID) { offset = getOffset(); } this->cmdRawAt("aht " + structureOffset, offset); emit instructionChanged(offset); } void CutterCore::seekSilent(ut64 offset) { CORE_LOCK(); if (offset == RVA_INVALID) { return; } r_core_seek(core, offset, true); } void CutterCore::seek(ut64 offset) { // Slower than using the API, but the API is not complete // which means we either have to duplicate code from radare2 // here, or refactor radare2 API. CORE_LOCK(); if (offset == RVA_INVALID) { return; } // use cmd and not cmdRaw to make sure seekChanged is emitted cmd(QString("s %1").arg(offset)); // cmd already does emit seekChanged(core_->offset); } void CutterCore::showMemoryWidget() { emit showMemoryWidgetRequested(); } void CutterCore::seekAndShow(ut64 offset) { seek(offset); showMemoryWidget(); } void CutterCore::seekAndShow(QString offset) { seek(offset); showMemoryWidget(); } void CutterCore::seek(QString thing) { cmdRaw(QString("s %1").arg(thing)); } void CutterCore::seekPrev() { // Use cmd because cmdRaw does not work with seek history cmd("s-"); } void CutterCore::seekNext() { // Use cmd because cmdRaw does not work with seek history cmd("s+"); } void CutterCore::updateSeek() { emit seekChanged(getOffset()); } RVA CutterCore::prevOpAddr(RVA startAddr, int count) { CORE_LOCK(); bool ok; RVA offset = cmdRawAt(QString("/O %1").arg(count), startAddr).toULongLong(&ok, 16); return ok ? offset : startAddr - count; } RVA CutterCore::nextOpAddr(RVA startAddr, int count) { CORE_LOCK(); QJsonArray array = Core()->cmdj("pdj " + QString::number(count + 1) + "@" + QString::number( startAddr)).array(); if (array.isEmpty()) { return startAddr + 1; } QJsonValue instValue = array.last(); if (!instValue.isObject()) { return startAddr + 1; } bool ok; RVA offset = instValue.toObject()[RJsonKey::offset].toVariant().toULongLong(&ok); if (!ok) { return startAddr + 1; } return offset; } RVA CutterCore::getOffset() { return core_->offset; } ut64 CutterCore::math(const QString &expr) { CORE_LOCK(); return r_num_math(core ? core->num : NULL, expr.toUtf8().constData()); } ut64 CutterCore::num(const QString &expr) { CORE_LOCK(); return r_num_get(core ? core->num : NULL, expr.toUtf8().constData()); } QString CutterCore::itoa(ut64 num, int rdx) { return QString::number(num, rdx); } void CutterCore::setConfig(const char *k, const QString &v) { CORE_LOCK(); r_config_set(core->config, k, v.toUtf8().constData()); } void CutterCore::setConfig(const char *k, int v) { CORE_LOCK(); r_config_set_i(core->config, k, static_cast(v)); } void CutterCore::setConfig(const char *k, bool v) { CORE_LOCK(); r_config_set_i(core->config, k, v ? 1 : 0); } int CutterCore::getConfigi(const char *k) { CORE_LOCK(); return static_cast(r_config_get_i(core->config, k)); } ut64 CutterCore::getConfigut64(const char *k) { CORE_LOCK(); return r_config_get_i(core->config, k); } bool CutterCore::getConfigb(const char *k) { CORE_LOCK(); return r_config_get_i(core->config, k) != 0; } QString CutterCore::getConfigDescription(const char *k) { CORE_LOCK(); RConfigNode *node = r_config_node_get (core->config, k); return QString(node->desc); } void CutterCore::triggerRefreshAll() { emit refreshAll(); } void CutterCore::triggerAsmOptionsChanged() { emit asmOptionsChanged(); } void CutterCore::triggerGraphOptionsChanged() { emit graphOptionsChanged(); } void CutterCore::message(const QString &msg, bool debug) { if (msg.isEmpty()) return; if (debug) { qDebug() << msg; emit newDebugMessage(msg); return; } emit newMessage(msg); } QString CutterCore::getConfig(const char *k) { CORE_LOCK(); return QString(r_config_get(core->config, k)); } void CutterCore::setConfig(const char *k, const QVariant &v) { switch (v.type()) { case QVariant::Type::Bool: setConfig(k, v.toBool()); break; case QVariant::Type::Int: setConfig(k, v.toInt()); break; default: setConfig(k, v.toString()); break; } } void CutterCore::setCPU(QString arch, QString cpu, int bits) { if (arch != nullptr) { setConfig("asm.arch", arch); } if (cpu != nullptr) { setConfig("asm.cpu", cpu); } setConfig("asm.bits", bits); } void CutterCore::setEndianness(bool big) { setConfig("cfg.bigendian", big); } QByteArray CutterCore::assemble(const QString &code) { CORE_LOCK(); RAsmCode *ac = r_asm_massemble(core->assembler, code.toUtf8().constData()); QByteArray res; if (ac && ac->bytes) { res = QByteArray(reinterpret_cast(ac->bytes), ac->len); } r_asm_code_free(ac); return res; } QString CutterCore::disassemble(const QByteArray &data) { CORE_LOCK(); RAsmCode *ac = r_asm_mdisassemble(core->assembler, reinterpret_cast(data.constData()), data.length()); QString code; if (ac && ac->assembly) { code = QString::fromUtf8(ac->assembly); } r_asm_code_free(ac); return code; } QString CutterCore::disassembleSingleInstruction(RVA addr) { return cmdRawAt("pi 1", addr).simplified(); } RAnalFunction *CutterCore::functionIn(ut64 addr) { CORE_LOCK(); RList *fcns = r_anal_get_functions_in (core->anal, addr); RAnalFunction *fcn = !r_list_empty(fcns) ? reinterpret_cast(r_list_first(fcns)) : nullptr; r_list_free(fcns); return fcn; } RAnalFunction *CutterCore::functionAt(ut64 addr) { CORE_LOCK(); return r_anal_get_function_at(core->anal, addr); } /** * @brief finds the start address of a function in a given address * @param addr - an address which belongs to a function * @returns if function exists, return its start address. Otherwise return RVA_INVALID */ RVA CutterCore::getFunctionStart(RVA addr) { CORE_LOCK(); RAnalFunction *fcn = Core()->functionIn(addr); return fcn ? fcn->addr : RVA_INVALID; } /** * @brief finds the end address of a function in a given address * @param addr - an address which belongs to a function * @returns if function exists, return its end address. Otherwise return RVA_INVALID */ RVA CutterCore::getFunctionEnd(RVA addr) { CORE_LOCK(); RAnalFunction *fcn = Core()->functionIn(addr); return fcn ? fcn->addr : RVA_INVALID; } /** * @brief finds the last instruction of a function in a given address * @param addr - an address which belongs to a function * @returns if function exists, return the address of its last instruction. Otherwise return RVA_INVALID */ RVA CutterCore::getLastFunctionInstruction(RVA addr) { CORE_LOCK(); RAnalFunction *fcn = Core()->functionIn(addr); if (!fcn) { return RVA_INVALID; } RAnalBlock *lastBB = (RAnalBlock *)r_list_last(fcn->bbs); return lastBB ? lastBB->addr + r_anal_bb_offset_inst(lastBB, lastBB->ninstr-1) : RVA_INVALID; } QString CutterCore::cmdFunctionAt(QString addr) { QString ret; // Use cmd because cmdRaw would not work with grep ret = cmd(QString("fd @ %1~[0]").arg(addr)); return ret.trimmed(); } QString CutterCore::cmdFunctionAt(RVA addr) { return cmdFunctionAt(QString::number(addr)); } void CutterCore::cmdEsil(const char *command) { // use cmd and not cmdRaw because of unexpected commands QString res = cmd(command); if (res.contains(QStringLiteral("[ESIL] Stopped execution in an invalid instruction"))) { msgBox.showMessage("Stopped when attempted to run an invalid instruction. You can disable this in Preferences"); } } QString CutterCore::createFunctionAt(RVA addr) { QString ret = cmdRaw(QString("af %1").arg(addr)); emit functionsChanged(); return ret; } QString CutterCore::createFunctionAt(RVA addr, QString name) { static const QRegularExpression regExp("[^a-zA-Z0-9_]"); name.remove(regExp); QString ret = cmdRawAt(QString("af %1").arg(name), addr); emit functionsChanged(); return ret; } QJsonDocument CutterCore::getRegistersInfo() { return cmdj("aeafj"); } RVA CutterCore::getOffsetJump(RVA addr) { bool ok; RVA value = cmdj("aoj @" + QString::number( addr)).array().first().toObject().value(RJsonKey::jump).toVariant().toULongLong(&ok); if (!ok) { return RVA_INVALID; } return value; } QList CutterCore::getDecompilers() { return decompilers; } Decompiler *CutterCore::getDecompilerById(const QString &id) { for (Decompiler *dec : decompilers) { if (dec->getId() == id) { return dec; } } return nullptr; } bool CutterCore::registerDecompiler(Decompiler *decompiler) { if (getDecompilerById(decompiler->getId())) { return false; } decompiler->setParent(this); decompilers.push_back(decompiler); return true; } QJsonDocument CutterCore::getFileInfo() { return cmdj("ij"); } QJsonDocument CutterCore::getFileVersionInfo() { return cmdj("iVj"); } QJsonDocument CutterCore::getSignatureInfo() { return cmdj("iCj"); } // Utility function to check if a telescoped item exists and add it with prefixes to the desc static inline const QString appendVar(QString &dst, const QString val, const QString prepend_val, const QString append_val) { if (!val.isEmpty()) { dst += prepend_val + val + append_val; } return val; } RefDescription CutterCore::formatRefDesc(QJsonObject refItem) { RefDescription desc; // Ignore empty refs and refs that only contain addr if (refItem.size() <= 1) { return desc; } QString str = refItem["string"].toVariant().toString(); if (!str.isEmpty()) { desc.ref = str; desc.refColor = ConfigColor("comment"); } else { QString type, string; do { desc.ref += " ->"; appendVar(desc.ref, refItem["reg"].toVariant().toString(), " @", ""); appendVar(desc.ref, refItem["mapname"].toVariant().toString(), " (", ")"); appendVar(desc.ref, refItem["section"].toVariant().toString(), " (", ")"); appendVar(desc.ref, refItem["func"].toVariant().toString(), " ", ""); type = appendVar(desc.ref, refItem["type"].toVariant().toString(), " ", ""); appendVar(desc.ref, refItem["perms"].toVariant().toString(), " ", ""); appendVar(desc.ref, refItem["asm"].toVariant().toString(), " \"", "\""); string = appendVar(desc.ref, refItem["string"].toVariant().toString(), " ", ""); if (!string.isNull()) { // There is no point in adding ascii and addr info after a string break; } if (!refItem["value"].isNull()) { appendVar(desc.ref, RAddressString(refItem["value"].toVariant().toULongLong()), " ", ""); } refItem = refItem["ref"].toObject(); } while (!refItem.empty()); // Set the ref's color according to the last item type if (type == "ascii" || !string.isEmpty()) { desc.refColor = ConfigColor("comment"); } else if (type == "program") { desc.refColor = ConfigColor("fname"); } else if (type == "library") { desc.refColor = ConfigColor("floc"); } else if (type == "stack") { desc.refColor = ConfigColor("offset"); } } return desc; } QList CutterCore::getRegisterRefs(int depth) { QList ret; if (!currentlyDebugging) { return ret; } QJsonObject registers = cmdj("drj").object(); for (const QString &key : registers.keys()) { QJsonObject reg; reg["value"] = registers.value(key); reg["ref"] = getAddrRefs(registers.value(key).toVariant().toULongLong(), depth); reg["name"] = key; ret.append(reg); } return ret; } QList CutterCore::getStack(int size, int depth) { QList stack; if (!currentlyDebugging) { return stack; } CORE_LOCK(); bool ret; RVA addr = cmdRaw("dr SP").toULongLong(&ret, 16); if (!ret) { return stack; } int base = core->anal->bits; for (int i = 0; i < size; i += base / 8) { if ((base == 32 && addr + i >= UT32_MAX) || (base == 16 && addr + i >= UT16_MAX)) { break; } stack.append(getAddrRefs(addr + i, depth)); } return stack; } QJsonObject CutterCore::getAddrRefs(RVA addr, int depth) { QJsonObject json; if (depth < 1 || addr == UT64_MAX) { return json; } CORE_LOCK(); int bits = core->assembler->bits; QByteArray buf = QByteArray(); ut64 type = r_core_anal_address(core, addr); json["addr"] = QString::number(addr); // Search for the section the addr is in, avoid duplication for heap/stack with type if(!(type & R_ANAL_ADDR_TYPE_HEAP || type & R_ANAL_ADDR_TYPE_STACK)) { // Attempt to find the address within a map RDebugMap *map = r_debug_map_get(core->dbg, addr); if (map && map->name && map->name[0]) { json["mapname"] = map->name; } RBinSection *sect = r_bin_get_section_at(r_bin_cur_object (core->bin), addr, true); if (sect && sect->name[0]) { json["section"] = sect->name; } } // Check if the address points to a register RFlagItem *fi = r_flag_get_i(core->flags, addr); if (fi) { RRegItem *r = r_reg_get(core->dbg->reg, fi->name, -1); if (r) { json["reg"] = r->name; } } // Attempt to find the address within a function RAnalFunction *fcn = r_anal_get_fcn_in(core->anal, addr, 0); if (fcn) { json["fcn"] = fcn->name; } // Update type and permission information if (type != 0) { if (type & R_ANAL_ADDR_TYPE_HEAP) { json["type"] = "heap"; } else if (type & R_ANAL_ADDR_TYPE_STACK) { json["type"] = "stack"; } else if (type & R_ANAL_ADDR_TYPE_PROGRAM) { json["type"] = "program"; } else if (type & R_ANAL_ADDR_TYPE_LIBRARY) { json["type"] = "library"; } else if (type & R_ANAL_ADDR_TYPE_ASCII) { json["type"] = "ascii"; } else if (type & R_ANAL_ADDR_TYPE_SEQUENCE) { json["type"] = "sequence"; } QString perms = ""; if (type & R_ANAL_ADDR_TYPE_READ) { perms += "r"; } if (type & R_ANAL_ADDR_TYPE_WRITE) { perms += "w"; } if (type & R_ANAL_ADDR_TYPE_EXEC) { RAsmOp op; buf.resize(32); perms += "x"; // Instruction disassembly r_io_read_at(core->io, addr, (unsigned char*)buf.data(), buf.size()); r_asm_set_pc(core->assembler, addr); r_asm_disassemble(core->assembler, &op, (unsigned char*)buf.data(), buf.size()); json["asm"] = r_asm_op_get_asm(&op); } if (!perms.isEmpty()) { json["perms"] = perms; } } // Try to telescope further if depth permits it if ((type & R_ANAL_ADDR_TYPE_READ) && !(type & R_ANAL_ADDR_TYPE_EXEC)) { buf.resize(64); ut32 *n32 = (ut32 *)buf.data(); ut64 *n64 = (ut64 *)buf.data(); r_io_read_at(core->io, addr, (unsigned char*)buf.data(), buf.size()); ut64 n = (bits == 64)? *n64: *n32; // The value of the next address will serve as an indication that there's more to // telescope if we have reached the depth limit json["value"] = QString::number(n); if (depth && n != addr) { // Make sure we aren't telescoping the same address QJsonObject ref = getAddrRefs(n, depth - 1); if (!ref.empty() && !ref["type"].isNull()) { // If the dereference of the current pointer is an ascii character we // might have a string in this address if (ref["type"].toString().contains("ascii")) { buf.resize(128); r_io_read_at(core->io, addr, (unsigned char*)buf.data(), buf.size()); QString strVal = QString(buf); // Indicate that the string is longer than the printed value if (strVal.size() == buf.size()) { strVal += "..."; } json["string"] = strVal; } json["ref"] = ref; } } } return json; } QJsonDocument CutterCore::getProcessThreads(int pid) { if (-1 == pid) { // Return threads list of the currently debugged PID return cmdj("dptj"); } else { return cmdj("dptj " + QString::number(pid)); } } QJsonDocument CutterCore::getChildProcesses(int pid) { // Return the currently debugged process and it's children if (-1 == pid) { return cmdj("dpj"); } // Return the given pid and it's child processes return cmdj("dpj " + QString::number(pid)); } QJsonDocument CutterCore::getRegisterValues() { return cmdj("drj"); } QList CutterCore::getVariables(RVA at) { QList ret; QJsonObject varsObject = cmdj(QString("afvj @ %1").arg(at)).object(); auto addVars = [&](VariableDescription::RefType refType, const QJsonArray &array) { for (const QJsonValue &varValue : array) { QJsonObject varObject = varValue.toObject(); VariableDescription desc; desc.refType = refType; desc.name = varObject["name"].toString(); desc.type = varObject["type"].toString(); ret << desc; } }; addVars(VariableDescription::RefType::SP, varsObject["sp"].toArray()); addVars(VariableDescription::RefType::BP, varsObject["bp"].toArray()); addVars(VariableDescription::RefType::Reg, varsObject["reg"].toArray()); return ret; } QJsonObject CutterCore::getRegisterJson() { QJsonArray registerRefArray = cmdj("drrj").array(); QJsonObject registerJson; for (const QJsonValue &value : registerRefArray) { QJsonObject regRefObject = value.toObject(); QJsonObject registers; registers.insert(RJsonKey::value, regRefObject[RJsonKey::value]); registers.insert(RJsonKey::ref, regRefObject[RJsonKey::ref]); registerJson.insert(regRefObject[RJsonKey::reg].toString(), registers); } return registerJson; } QString CutterCore::getRegisterName(QString registerRole) { return cmdRaw("drn " + registerRole).trimmed(); } RVA CutterCore::getProgramCounterValue() { bool ok; if (currentlyDebugging) { // Use cmd because cmdRaw would not work with inner command backticked // TODO: Risky command due to changes in API, search for something safer RVA addr = cmd("dr?`drn PC`").toULongLong(&ok, 16); if (ok) { return addr; } } return RVA_INVALID; } void CutterCore::setRegister(QString regName, QString regValue) { cmdRaw(QString("dr %1=%2").arg(regName).arg(regValue)); emit registersChanged(); emit refreshCodeViews(); } void CutterCore::setCurrentDebugThread(int tid) { if (!asyncCmd("dpt=" + QString::number(tid), debugTask)) { return; } emit debugTaskStateChanged(); connect(debugTask.data(), &R2Task::finished, this, [this] () { debugTask.clear(); emit registersChanged(); emit refreshCodeViews(); emit stackChanged(); syncAndSeekProgramCounter(); emit switchedThread(); emit debugTaskStateChanged(); }); debugTask->startTask(); } void CutterCore::setCurrentDebugProcess(int pid) { if (!currentlyDebugging || !asyncCmd("dp=" + QString::number(pid), debugTask)) { return; } emit debugTaskStateChanged(); connect(debugTask.data(), &R2Task::finished, this, [this] () { debugTask.clear(); emit registersChanged(); emit refreshCodeViews(); emit stackChanged(); emit flagsChanged(); syncAndSeekProgramCounter(); emit switchedProcess(); emit debugTaskStateChanged(); }); debugTask->startTask(); } void CutterCore::startDebug() { if (!currentlyDebugging) { offsetPriorDebugging = getOffset(); } currentlyOpenFile = getConfig("file.path"); if (!asyncCmd("ood", debugTask)) { return; } emit debugTaskStateChanged(); connect(debugTask.data(), &R2Task::finished, this, [this] () { if (debugTaskDialog) { delete debugTaskDialog; } debugTask.clear(); emit registersChanged(); if (!currentlyDebugging) { setConfig("asm.flags", false); currentlyDebugging = true; emit toggleDebugView(); emit refreshCodeViews(); } emit codeRebased(); emit stackChanged(); emit debugTaskStateChanged(); }); debugTaskDialog = new R2TaskDialog(debugTask); debugTaskDialog->setBreakOnClose(true); debugTaskDialog->setAttribute(Qt::WA_DeleteOnClose); debugTaskDialog->setDesc(tr("Starting native debug...")); debugTaskDialog->show(); debugTask->startTask(); } void CutterCore::startEmulation() { if (!currentlyDebugging) { offsetPriorDebugging = getOffset(); } // clear registers, init esil state, stack, progcounter at current seek asyncCmd("aei; aeim; aeip", debugTask); emit debugTaskStateChanged(); connect(debugTask.data(), &R2Task::finished, this, [this] () { if (debugTaskDialog) { delete debugTaskDialog; } debugTask.clear(); if (!currentlyDebugging || !currentlyEmulating) { // prevent register flags from appearing during debug/emul setConfig("asm.flags", false); // allows to view self-modifying code changes or other binary changes setConfig("io.cache", true); currentlyDebugging = true; currentlyEmulating = true; emit toggleDebugView(); } emit registersChanged(); emit stackChanged(); emit codeRebased(); emit refreshCodeViews(); emit debugTaskStateChanged(); }); debugTaskDialog = new R2TaskDialog(debugTask); debugTaskDialog->setBreakOnClose(true); debugTaskDialog->setAttribute(Qt::WA_DeleteOnClose); debugTaskDialog->setDesc(tr("Starting emulation...")); debugTaskDialog->show(); debugTask->startTask(); } void CutterCore::attachRemote(const QString &uri) { if (!currentlyDebugging) { offsetPriorDebugging = getOffset(); } // connect to a debugger with the given plugin asyncCmd("e cfg.debug = true; oodf " + uri, debugTask); emit debugTaskStateChanged(); connect(debugTask.data(), &R2Task::finished, this, [this, uri] () { if (debugTaskDialog) { delete debugTaskDialog; } debugTask.clear(); // Check if we actually connected bool connected = false; QJsonArray openFilesArray = getOpenedFiles(); for (QJsonValue value : openFilesArray) { QJsonObject openFile = value.toObject(); QString fileUri= openFile["uri"].toString(); if (!fileUri.compare(uri)) { connected = true; } } // Use cmd because cmdRaw would not with inner command backticked QString programCounterValue = cmd("dr?`drn PC`").trimmed(); seekAndShow(programCounterValue); if (!connected) { emit attachedRemote(false); emit debugTaskStateChanged(); return; } emit registersChanged(); if (!currentlyDebugging || !currentlyEmulating) { // prevent register flags from appearing during debug/emul setConfig("asm.flags", false); currentlyDebugging = true; emit toggleDebugView(); } emit codeRebased(); emit attachedRemote(true); emit debugTaskStateChanged(); }); debugTaskDialog = new R2TaskDialog(debugTask); debugTaskDialog->setBreakOnClose(true); debugTaskDialog->setAttribute(Qt::WA_DeleteOnClose); debugTaskDialog->setDesc(tr("Connecting to: ") + uri); debugTaskDialog->show(); debugTask->startTask(); } void CutterCore::attachDebug(int pid) { if (!currentlyDebugging) { offsetPriorDebugging = getOffset(); } // attach to process with dbg plugin asyncCmd("e cfg.debug = true; oodf dbg://" + QString::number(pid), debugTask); emit debugTaskStateChanged(); connect(debugTask.data(), &R2Task::finished, this, [this, pid] () { if (debugTaskDialog) { delete debugTaskDialog; } debugTask.clear(); syncAndSeekProgramCounter(); if (!currentlyDebugging || !currentlyEmulating) { // prevent register flags from appearing during debug/emul setConfig("asm.flags", false); currentlyDebugging = true; currentlyOpenFile = getConfig("file.path"); currentlyAttachedToPID = pid; emit toggleDebugView(); } emit codeRebased(); emit debugTaskStateChanged(); }); debugTaskDialog = new R2TaskDialog(debugTask); debugTaskDialog->setBreakOnClose(true); debugTaskDialog->setAttribute(Qt::WA_DeleteOnClose); debugTaskDialog->setDesc(tr("Attaching to process (") + QString::number(pid) + ")..."); debugTaskDialog->show(); debugTask->startTask(); } void CutterCore::suspendDebug() { debugTask->breakTask(); } void CutterCore::stopDebug() { if (!currentlyDebugging) { return; } if (!debugTask.isNull()) { suspendDebug(); } currentlyDebugging = false; emit debugTaskStateChanged(); if (currentlyEmulating) { cmdEsil("aeim-; aei-; wcr; .ar-"); currentlyEmulating = false; } else if (currentlyAttachedToPID != -1) { // Use cmd because cmdRaw would not work with command concatenation cmd(QString("dp- %1; o %2; .ar-").arg( QString::number(currentlyAttachedToPID), currentlyOpenFile)); currentlyAttachedToPID = -1; } else { QString ptraceFiles = ""; // close ptrace file descriptors left open QJsonArray openFilesArray = cmdj("oj").array();; for (QJsonValue value : openFilesArray) { QJsonObject openFile = value.toObject(); QString URI = openFile["uri"].toString(); if (URI.contains("ptrace")) { ptraceFiles += "o-" + QString::number(openFile["fd"].toInt()) + ";"; } } // Use cmd because cmdRaw would not work with command concatenation cmd("doc" + ptraceFiles); } syncAndSeekProgramCounter(); setConfig("asm.flags", true); setConfig("io.cache", false); emit codeRebased(); emit toggleDebugView(); offsetPriorDebugging = getOffset(); emit debugTaskStateChanged(); } void CutterCore::syncAndSeekProgramCounter() { // Use cmd because cmdRaw would not work with inner command backticked QString programCounterValue = cmd("dr?`drn PC`").trimmed(); seekAndShow(programCounterValue); emit registersChanged(); } void CutterCore::continueDebug() { if (!currentlyDebugging) { return; } if (currentlyEmulating) { if (!asyncCmdEsil("aec", debugTask)) { return; } } else { if (!asyncCmd("dc", debugTask)) { return; } } emit debugTaskStateChanged(); connect(debugTask.data(), &R2Task::finished, this, [this] () { debugTask.clear(); syncAndSeekProgramCounter(); emit registersChanged(); emit refreshCodeViews(); emit debugTaskStateChanged(); }); debugTask->startTask(); } void CutterCore::continueUntilDebug(QString offset) { if (!currentlyDebugging) { return; } if (currentlyEmulating) { if (!asyncCmdEsil("aecu " + offset, debugTask)) { return; } } else { if (!asyncCmd("dcu " + offset, debugTask)) { return; } } emit debugTaskStateChanged(); connect(debugTask.data(), &R2Task::finished, this, [this] () { debugTask.clear(); syncAndSeekProgramCounter(); emit registersChanged(); emit stackChanged(); emit refreshCodeViews(); emit debugTaskStateChanged(); }); debugTask->startTask(); } void CutterCore::continueUntilCall() { if (!currentlyDebugging) { return; } if (currentlyEmulating) { if (!asyncCmdEsil("aecc", debugTask)) { return; } } else { if (!asyncCmd("dcc", debugTask)) { return; } } emit debugTaskStateChanged(); connect(debugTask.data(), &R2Task::finished, this, [this] () { debugTask.clear(); syncAndSeekProgramCounter(); emit debugTaskStateChanged(); }); debugTask->startTask(); } void CutterCore::continueUntilSyscall() { if (!currentlyDebugging) { return; } if (currentlyEmulating) { if (!asyncCmdEsil("aecs", debugTask)) { return; } } else { if (!asyncCmd("dcs", debugTask)) { return; } } emit debugTaskStateChanged(); connect(debugTask.data(), &R2Task::finished, this, [this] () { debugTask.clear(); syncAndSeekProgramCounter(); emit debugTaskStateChanged(); }); debugTask->startTask(); } void CutterCore::stepDebug() { if (!currentlyDebugging) { return; } if (currentlyEmulating) { if (!asyncCmdEsil("aes", debugTask)) { return; } } else { if (!asyncCmd("ds", debugTask)) { return; } } emit debugTaskStateChanged(); connect(debugTask.data(), &R2Task::finished, this, [this] () { debugTask.clear(); syncAndSeekProgramCounter(); emit debugTaskStateChanged(); }); debugTask->startTask(); } void CutterCore::stepOverDebug() { if (!currentlyDebugging) { return; } if (currentlyEmulating) { if (!asyncCmdEsil("aeso", debugTask)) { return; } } else { if (!asyncCmd("dso", debugTask)) { return; } } emit debugTaskStateChanged(); connect(debugTask.data(), &R2Task::finished, this, [this] () { debugTask.clear(); syncAndSeekProgramCounter(); emit debugTaskStateChanged(); }); debugTask->startTask(); } void CutterCore::stepOutDebug() { if (!currentlyDebugging) { return; } emit debugTaskStateChanged(); if (!asyncCmd("dsf", debugTask)) { return; } connect(debugTask.data(), &R2Task::finished, this, [this] () { debugTask.clear(); syncAndSeekProgramCounter(); emit debugTaskStateChanged(); }); debugTask->startTask(); } QStringList CutterCore::getDebugPlugins() { QStringList plugins; QJsonArray pluginArray = cmdj("dLj").array(); for (const QJsonValue &value : pluginArray) { QJsonObject pluginObject = value.toObject(); QString plugin = pluginObject[RJsonKey::name].toString(); plugins << plugin; } return plugins; } QString CutterCore::getActiveDebugPlugin() { return getConfig("dbg.backend"); } void CutterCore::setDebugPlugin(QString plugin) { setConfig("dbg.backend", plugin); } void CutterCore::toggleBreakpoint(RVA addr) { cmdRaw(QString("dbs %1").arg(addr)); emit instructionChanged(addr); emit breakpointsChanged(); } void CutterCore::toggleBreakpoint(QString addr) { cmdRaw("dbs " + addr); emit instructionChanged(addr.toULongLong()); emit breakpointsChanged(); } void CutterCore::addBreakpoint(QString addr) { cmdRaw("db " + addr); emit instructionChanged(addr.toULongLong()); emit breakpointsChanged(); } void CutterCore::addBreakpoint(const BreakpointDescription &config) { CORE_LOCK(); RBreakpointItem *breakpoint = nullptr; int watchpoint_prot = 0; if (config.hw) { watchpoint_prot = config.permission & ~(R_BP_PROT_EXEC); } auto address = config.addr; char *module = nullptr; QByteArray moduleNameData; if (config.type == BreakpointDescription::Named) { address = Core()->math(config.positionExpression); } else if (config.type == BreakpointDescription::Module) { address = 0; moduleNameData = config.positionExpression.toUtf8(); module = moduleNameData.data(); } breakpoint = r_debug_bp_add(core->dbg, address, (config.hw && watchpoint_prot == 0), watchpoint_prot, watchpoint_prot, module, config.moduleDelta); if (!breakpoint) { QMessageBox::critical(nullptr, tr("Breakpoint error"), tr("Failed to create breakpoint")); return; } if (config.type == BreakpointDescription::Named) { updateOwnedCharPtr(breakpoint->expr, config.positionExpression); } if (config.hw) { breakpoint->size = config.size; } if (config.type == BreakpointDescription::Named) { updateOwnedCharPtr(breakpoint->name, config.positionExpression); } int index = std::find(core->dbg->bp->bps_idx, core->dbg->bp->bps_idx + core->dbg->bp->bps_idx_count, breakpoint) - core->dbg->bp->bps_idx; breakpoint->enabled = config.enabled; if (config.trace) { setBreakpointTrace(index, config.trace); } if (!config.condition.isEmpty()) { updateOwnedCharPtr(breakpoint->cond, config.condition); } if (!config.command.isEmpty()) { updateOwnedCharPtr(breakpoint->data, config.command); } emit instructionChanged(breakpoint->addr); emit breakpointsChanged(); } void CutterCore::updateBreakpoint(int index, const BreakpointDescription &config) { CORE_LOCK(); if (auto bp = r_bp_get_index(core->dbg->bp, index)) { r_bp_del(core->dbg->bp, bp->addr); } // Delete by index currently buggy, // required for breakpoints with non address based position //r_bp_del_index(core->dbg->bp, index); addBreakpoint(config); } void CutterCore::delBreakpoint(RVA addr) { cmdRaw("db- " + RAddressString(addr)); emit instructionChanged(addr); emit breakpointsChanged(); } void CutterCore::delAllBreakpoints() { cmdRaw("db-*"); emit refreshCodeViews(); } void CutterCore::enableBreakpoint(RVA addr) { cmdRaw("dbe " + RAddressString(addr)); emit instructionChanged(addr); emit breakpointsChanged(); } void CutterCore::disableBreakpoint(RVA addr) { cmdRaw("dbd " + RAddressString(addr)); emit instructionChanged(addr); emit breakpointsChanged(); } void CutterCore::setBreakpointTrace(int index, bool enabled) { if (enabled) { cmdRaw(QString("dbite %1").arg(index)); } else { cmdRaw(QString("dbitd %1").arg(index)); } } static BreakpointDescription breakpointDescriptionFromR2(int index, r_bp_item_t *bpi) { BreakpointDescription bp; bp.addr = bpi->addr; bp.index = index; bp.size = bpi->size; if (bpi->expr) { bp.positionExpression = bpi->expr; bp.type = BreakpointDescription::Named; } bp.name = bpi->name; bp.permission = bpi->perm; bp.command = bpi->data; bp.condition = bpi->cond; bp.hw = bpi->hw; bp.trace = bpi->trace; bp.enabled = bpi->enabled; return bp; } int CutterCore::breakpointIndexAt(RVA addr) { CORE_LOCK(); return r_bp_get_index_at(core->dbg->bp, addr); } BreakpointDescription CutterCore::getBreakpointAt(RVA addr) { CORE_LOCK(); int index = breakpointIndexAt(addr); auto bp = r_bp_get_index(core->dbg->bp, index); if (bp) { return breakpointDescriptionFromR2(index, bp); } return BreakpointDescription(); } QList CutterCore::getBreakpoints() { CORE_LOCK(); QList ret; //TODO: use higher level API, don't touch r2 bps_idx directly for (int i = 0; i < core->dbg->bp->bps_idx_count; i++) { if (auto bpi = core->dbg->bp->bps_idx[i]) { ret.push_back(breakpointDescriptionFromR2(i, bpi)); } } return ret; } QList CutterCore::getBreakpointsAddresses() { QList bpAddresses; for (const BreakpointDescription &bp : getBreakpoints()) { bpAddresses << bp.addr; } return bpAddresses; } QList CutterCore::getBreakpointsInFunction(RVA funcAddr) { QList allBreakpoints = getBreakpointsAddresses(); QList functionBreakpoints; // Use std manipulations to take only the breakpoints that belong to this function std::copy_if(allBreakpoints.begin(), allBreakpoints.end(), std::back_inserter(functionBreakpoints), [this, funcAddr](RVA BPadd) { return getFunctionStart(BPadd) == funcAddr; }); return functionBreakpoints; } bool CutterCore::isBreakpoint(const QList &breakpoints, RVA addr) { return breakpoints.contains(addr); } QJsonDocument CutterCore::getBacktrace() { return cmdj("dbtj"); } QList CutterCore::getAllProcesses() { QList ret; QJsonArray processArray = cmdj("dplj").array(); for (const QJsonValue &value : processArray) { QJsonObject procObject = value.toObject(); ProcessDescription proc; proc.pid = procObject[RJsonKey::pid].toInt(); proc.uid = procObject[RJsonKey::uid].toInt(); proc.status = procObject[RJsonKey::status].toString(); proc.path = procObject[RJsonKey::path].toString(); ret << proc; } return ret; } QList CutterCore::getMemoryMap() { QList ret; QJsonArray memoryMapArray = cmdj("dmj").array(); for (const QJsonValue &value : memoryMapArray) { QJsonObject memMapObject = value.toObject(); MemoryMapDescription memMap; memMap.name = memMapObject[RJsonKey::name].toString(); memMap.fileName = memMapObject[RJsonKey::file].toString(); memMap.addrStart = memMapObject[RJsonKey::addr].toVariant().toULongLong(); memMap.addrEnd = memMapObject[RJsonKey::addr_end].toVariant().toULongLong(); memMap.type = memMapObject[RJsonKey::type].toString(); memMap.permission = memMapObject[RJsonKey::perm].toString(); ret << memMap; } return ret; } QStringList CutterCore::getStats() { QStringList stats; cmdRaw("fs functions"); // The cmd coomand is frequently used in this function because // cmdRaw would not work with grep stats << cmd("f~?").trimmed(); QString imps = cmd("ii~?").trimmed(); stats << imps; cmdRaw("fs symbols"); stats << cmd("f~?").trimmed(); cmdRaw("fs strings"); stats << cmd("f~?").trimmed(); cmdRaw("fs relocs"); stats << cmd("f~?").trimmed(); cmdRaw("fs sections"); stats << cmd("f~?").trimmed(); cmdRaw("fs *"); stats << cmd("f~?").trimmed(); return stats; } void CutterCore::setGraphEmpty(bool empty) { emptyGraph = empty; } bool CutterCore::isGraphEmpty() { return emptyGraph; } void CutterCore::getOpcodes() { this->opcodes = cmdList("?O"); this->regs = cmdList("drp~[1]"); } void CutterCore::setSettings() { setConfig("scr.interactive", false); setConfig("hex.pairs", false); setConfig("asm.xrefs", false); setConfig("asm.tabs.once", true); setConfig("asm.flags.middle", 2); setConfig("anal.hasnext", false); setConfig("asm.lines.call", false); setConfig("cfg.fortunes.tts", false); // Colors setConfig("scr.color", COLOR_MODE_DISABLED); // Don't show hits setConfig("search.flags", false); } QList CutterCore::getSeekHistory() { CORE_LOCK(); QList ret; QJsonArray jsonArray = cmdj("sj").array(); for (const QJsonValue &value : jsonArray) ret << value.toVariant().toULongLong(); return ret; } QStringList CutterCore::getAsmPluginNames() { CORE_LOCK(); RListIter *it; QStringList ret; RAsmPlugin *ap; CutterRListForeach(core->assembler->plugins, it, RAsmPlugin, ap) { ret << ap->name; } return ret; } QStringList CutterCore::getAnalPluginNames() { CORE_LOCK(); RListIter *it; QStringList ret; RAnalPlugin *ap; CutterRListForeach(core->anal->plugins, it, RAnalPlugin, ap) { ret << ap->name; } return ret; } QStringList CutterCore::getProjectNames() { CORE_LOCK(); QStringList ret; QJsonArray jsonArray = cmdj("Pj").array(); for (const QJsonValue &value : jsonArray) ret.append(value.toString()); return ret; } QList CutterCore::getRBinPluginDescriptions(const QString &type) { QList ret; QJsonObject jsonRoot = cmdj("iLj").object(); for (const QString &key : jsonRoot.keys()) { if (!type.isNull() && key != type) continue; QJsonArray pluginArray = jsonRoot[key].toArray(); for (const QJsonValue &pluginValue : pluginArray) { QJsonObject pluginObject = pluginValue.toObject(); RBinPluginDescription desc; desc.name = pluginObject[RJsonKey::name].toString(); desc.description = pluginObject[RJsonKey::description].toString(); desc.license = pluginObject[RJsonKey::license].toString(); desc.type = key; ret.append(desc); } } return ret; } QList CutterCore::getRIOPluginDescriptions() { QList ret; QJsonArray plugins = cmdj("oLj").object()["io_plugins"].toArray(); for (const QJsonValue &pluginValue : plugins) { QJsonObject pluginObject = pluginValue.toObject(); RIOPluginDescription plugin; plugin.name = pluginObject["name"].toString(); plugin.description = pluginObject["description"].toString(); plugin.license = pluginObject["license"].toString(); plugin.permissions = pluginObject["permissions"].toString(); for (const auto &uri : pluginObject["uris"].toArray()) { plugin.uris << uri.toString(); } ret << plugin; } return ret; } QList CutterCore::getRCorePluginDescriptions() { QList ret; QJsonArray plugins = cmdj("Lsj").array(); for (const QJsonValue &pluginValue : plugins) { QJsonObject pluginObject = pluginValue.toObject(); RCorePluginDescription plugin; plugin.name = pluginObject["Name"].toString(); plugin.description = pluginObject["Description"].toString(); ret << plugin; } return ret; } QList CutterCore::getRAsmPluginDescriptions() { CORE_LOCK(); RListIter *it; QList ret; RAsmPlugin *ap; CutterRListForeach(core->assembler->plugins, it, RAsmPlugin, ap) { RAsmPluginDescription plugin; plugin.name = ap->name; plugin.architecture = ap->arch; plugin.author = ap->author; plugin.version = ap->version; plugin.cpus = ap->cpus; plugin.description = ap->desc; plugin.license = ap->license; ret << plugin; } return ret; } QList CutterCore::getAllFunctions() { CORE_LOCK(); QList funcList; funcList.reserve(r_list_length(core->anal->fcns)); RListIter *iter; RAnalFunction *fcn; CutterRListForeach (core->anal->fcns, iter, RAnalFunction, fcn) { FunctionDescription function; function.offset = fcn->addr; function.linearSize = r_anal_function_linear_size(fcn); function.nargs = r_anal_var_count(core->anal, fcn, 'b', 1) + r_anal_var_count(core->anal, fcn, 'r', 1) + r_anal_var_count(core->anal, fcn, 's', 1); function.nlocals = r_anal_var_count(core->anal, fcn, 'b', 0) + r_anal_var_count(core->anal, fcn, 'r', 0) + r_anal_var_count(core->anal, fcn, 's', 0); function.nbbs = r_list_length (fcn->bbs); function.calltype = fcn->cc ? QString::fromUtf8(fcn->cc) : QString(); function.name = fcn->name ? QString::fromUtf8(fcn->name) : QString(); function.edges = r_anal_function_count_edges(fcn, nullptr); function.stackframe = fcn->maxstack; funcList.append(function); } return funcList; } QList CutterCore::getAllImports() { CORE_LOCK(); QList ret; QJsonArray importsArray = cmdj("iij").array(); for (const QJsonValue &value : importsArray) { QJsonObject importObject = value.toObject(); ImportDescription import; import.plt = importObject[RJsonKey::plt].toVariant().toULongLong(); import.ordinal = importObject[RJsonKey::ordinal].toInt(); import.bind = importObject[RJsonKey::bind].toString(); import.type = importObject[RJsonKey::type].toString(); import.libname = importObject[RJsonKey::libname].toString(); import.name = importObject[RJsonKey::name].toString(); ret << import; } return ret; } QList CutterCore::getAllExports() { CORE_LOCK(); QList ret; QJsonArray exportsArray = cmdj("iEj").array(); for (const QJsonValue &value : exportsArray) { QJsonObject exportObject = value.toObject(); ExportDescription exp; exp.vaddr = exportObject[RJsonKey::vaddr].toVariant().toULongLong(); exp.paddr = exportObject[RJsonKey::paddr].toVariant().toULongLong(); exp.size = exportObject[RJsonKey::size].toVariant().toULongLong(); exp.type = exportObject[RJsonKey::type].toString(); exp.name = exportObject[RJsonKey::name].toString(); exp.flag_name = exportObject[RJsonKey::flagname].toString(); ret << exp; } return ret; } QList CutterCore::getAllSymbols() { CORE_LOCK(); RListIter *it; QList ret; RBinSymbol *bs; if (core && core->bin && core->bin->cur && core->bin->cur->o) { CutterRListForeach(core->bin->cur->o->symbols, it, RBinSymbol, bs) { QString type = QString(bs->bind) + " " + QString(bs->type); SymbolDescription symbol; symbol.vaddr = bs->vaddr; symbol.name = QString(bs->name); symbol.bind = QString(bs->bind); symbol.type = QString(bs->type); ret << symbol; } /* list entrypoints as symbols too */ int n = 0; RBinAddr *entry; CutterRListForeach(core->bin->cur->o->entries, it, RBinAddr, entry) { SymbolDescription symbol; symbol.vaddr = entry->vaddr; symbol.name = QString("entry") + QString::number(n++); symbol.bind.clear(); symbol.type = "entry"; ret << symbol; } } return ret; } QList CutterCore::getAllHeaders() { CORE_LOCK(); QList ret; QJsonArray headersArray = cmdj("ihj").array(); for (const QJsonValue &value : headersArray) { QJsonObject headerObject = value.toObject(); HeaderDescription header; header.vaddr = headerObject[RJsonKey::vaddr].toVariant().toULongLong(); header.paddr = headerObject[RJsonKey::paddr].toVariant().toULongLong(); header.value = headerObject[RJsonKey::comment].toString(); header.name = headerObject[RJsonKey::name].toString(); ret << header; } return ret; } QList CutterCore::getAllZignatures() { CORE_LOCK(); QList zignatures; QJsonArray zignaturesArray = cmdj("zj").array(); for (const QJsonValue &value : zignaturesArray) { QJsonObject zignatureObject = value.toObject(); ZignatureDescription zignature; zignature.name = zignatureObject[RJsonKey::name].toString(); zignature.bytes = zignatureObject[RJsonKey::bytes].toString(); zignature.offset = zignatureObject[RJsonKey::offset].toVariant().toULongLong(); for (const QJsonValue &ref : zignatureObject[RJsonKey::refs].toArray()) { zignature.refs << ref.toString(); } QJsonObject graphObject = zignatureObject[RJsonKey::graph].toObject(); zignature.cc = graphObject[RJsonKey::cc].toVariant().toULongLong(); zignature.nbbs = graphObject[RJsonKey::nbbs].toVariant().toULongLong(); zignature.edges = graphObject[RJsonKey::edges].toVariant().toULongLong(); zignature.ebbs = graphObject[RJsonKey::ebbs].toVariant().toULongLong(); zignatures << zignature; } return zignatures; } QList CutterCore::getAllComments(const QString &filterType) { CORE_LOCK(); QList ret; QJsonArray commentsArray = cmdj("CCj").array(); for (const QJsonValue &value : commentsArray) { QJsonObject commentObject = value.toObject(); QString type = commentObject[RJsonKey::type].toString(); if (type != filterType) continue; CommentDescription comment; comment.offset = commentObject[RJsonKey::offset].toVariant().toULongLong(); comment.name = commentObject[RJsonKey::name].toString(); ret << comment; } return ret; } QList CutterCore::getAllRelocs() { CORE_LOCK(); QList ret; if (core && core->bin && core->bin->cur && core->bin->cur->o) { auto relocs = core->bin->cur->o->relocs; RBIter iter; RBinReloc *br; r_rbtree_foreach (relocs, iter, br, RBinReloc, vrb) { RelocDescription reloc; reloc.vaddr = br->vaddr; reloc.paddr = br->paddr; reloc.type = (br->additive ? "ADD_" : "SET_") + QString::number(br->type); if (br->import) reloc.name = br->import->name; else reloc.name = QString("reloc_%1").arg(QString::number(br->vaddr, 16)); ret << reloc; } } return ret; } QList CutterCore::getAllStrings() { return parseStringsJson(cmdjTask("izzj")); } QList CutterCore::parseStringsJson(const QJsonDocument &doc) { QList ret; QJsonArray stringsArray = doc.array(); for (const QJsonValue &value : stringsArray) { QJsonObject stringObject = value.toObject(); StringDescription string; string.string = stringObject[RJsonKey::string].toString(); string.vaddr = stringObject[RJsonKey::vaddr].toVariant().toULongLong(); string.type = stringObject[RJsonKey::type].toString(); string.size = stringObject[RJsonKey::size].toVariant().toUInt(); string.length = stringObject[RJsonKey::length].toVariant().toUInt(); string.section = stringObject[RJsonKey::section].toString(); ret << string; } return ret; } QList CutterCore::getAllFlagspaces() { CORE_LOCK(); QList ret; QJsonArray flagspacesArray = cmdj("fsj").array(); for (const QJsonValue &value : flagspacesArray) { QJsonObject flagspaceObject = value.toObject(); FlagspaceDescription flagspace; flagspace.name = flagspaceObject[RJsonKey::name].toString(); ret << flagspace; } return ret; } QList CutterCore::getAllFlags(QString flagspace) { CORE_LOCK(); QList ret; if (!flagspace.isEmpty()) cmdRaw("fs " + flagspace); else cmdRaw("fs *"); QJsonArray flagsArray = cmdj("fj").array(); for (const QJsonValue &value : flagsArray) { QJsonObject flagObject = value.toObject(); FlagDescription flag; flag.offset = flagObject[RJsonKey::offset].toVariant().toULongLong(); flag.size = flagObject[RJsonKey::size].toVariant().toULongLong(); flag.name = flagObject[RJsonKey::name].toString(); flag.realname = flagObject[RJsonKey::realname].toString(); ret << flag; } return ret; } QList CutterCore::getAllSections() { CORE_LOCK(); QList sections; QJsonDocument sectionsDoc = cmdj("iSj entropy"); QJsonObject sectionsObj = sectionsDoc.object(); QJsonArray sectionsArray = sectionsObj[RJsonKey::sections].toArray(); for (const QJsonValue &value : sectionsArray) { QJsonObject sectionObject = value.toObject(); QString name = sectionObject[RJsonKey::name].toString(); if (name.isEmpty()) continue; SectionDescription section; section.name = name; section.vaddr = sectionObject[RJsonKey::vaddr].toVariant().toULongLong(); section.vsize = sectionObject[RJsonKey::vsize].toVariant().toULongLong(); section.paddr = sectionObject[RJsonKey::paddr].toVariant().toULongLong(); section.size = sectionObject[RJsonKey::size].toVariant().toULongLong(); section.perm = sectionObject[RJsonKey::perm].toString(); section.entropy = sectionObject[RJsonKey::entropy].toString(); sections << section; } return sections; } QStringList CutterCore::getSectionList() { CORE_LOCK(); QStringList ret; QJsonArray sectionsArray = cmdj("iSj").array(); for (const QJsonValue &value : sectionsArray) { ret << value.toObject()[RJsonKey::name].toString(); } return ret; } QList CutterCore::getAllSegments() { CORE_LOCK(); QList ret; QJsonArray segments = cmdj("iSSj").array(); for (const QJsonValue &value : segments) { QJsonObject segmentObject = value.toObject(); QString name = segmentObject[RJsonKey::name].toString(); if (name.isEmpty()) continue; SegmentDescription segment; segment.name = name; segment.vaddr = segmentObject[RJsonKey::vaddr].toVariant().toULongLong(); segment.paddr = segmentObject[RJsonKey::paddr].toVariant().toULongLong(); segment.size = segmentObject[RJsonKey::size].toVariant().toULongLong(); segment.vsize = segmentObject[RJsonKey::vsize].toVariant().toULongLong(); segment.perm = segmentObject[RJsonKey::perm].toString(); ret << segment; } return ret; } QList CutterCore::getAllEntrypoint() { CORE_LOCK(); QList ret; QJsonArray entrypointsArray = cmdj("iej").array(); for (const QJsonValue &value : entrypointsArray) { QJsonObject entrypointObject = value.toObject(); EntrypointDescription entrypoint; entrypoint.vaddr = entrypointObject[RJsonKey::vaddr].toVariant().toULongLong(); entrypoint.paddr = entrypointObject[RJsonKey::paddr].toVariant().toULongLong(); entrypoint.baddr = entrypointObject[RJsonKey::baddr].toVariant().toULongLong(); entrypoint.laddr = entrypointObject[RJsonKey::laddr].toVariant().toULongLong(); entrypoint.haddr = entrypointObject[RJsonKey::haddr].toVariant().toULongLong(); entrypoint.type = entrypointObject[RJsonKey::type].toString(); ret << entrypoint; } return ret; } QList CutterCore::getAllClassesFromBin() { CORE_LOCK(); QList ret; QJsonArray classesArray = cmdj("icj").array(); for (const QJsonValue &value : classesArray) { QJsonObject classObject = value.toObject(); BinClassDescription cls; cls.name = classObject[RJsonKey::classname].toString(); cls.addr = classObject[RJsonKey::addr].toVariant().toULongLong(); cls.index = classObject[RJsonKey::index].toVariant().toULongLong(); for (const QJsonValue &value2 : classObject[RJsonKey::methods].toArray()) { QJsonObject methObject = value2.toObject(); BinClassMethodDescription meth; meth.name = methObject[RJsonKey::name].toString(); meth.addr = methObject[RJsonKey::addr].toVariant().toULongLong(); cls.methods << meth; } for (const QJsonValue &value2 : classObject[RJsonKey::fields].toArray()) { QJsonObject fieldObject = value2.toObject(); BinClassFieldDescription field; field.name = fieldObject[RJsonKey::name].toString(); field.addr = fieldObject[RJsonKey::addr].toVariant().toULongLong(); cls.fields << field; } ret << cls; } return ret; } QList CutterCore::getAllClassesFromFlags() { static const QRegularExpression classFlagRegExp("^class\\.(.*)$"); static const QRegularExpression methodFlagRegExp("^method\\.([^\\.]*)\\.(.*)$"); CORE_LOCK(); QList ret; QMap classesCache; QJsonArray flagsArray = cmdj("fj@F:classes").array(); for (const QJsonValue &value : flagsArray) { QJsonObject flagObject = value.toObject(); QString flagName = flagObject[RJsonKey::name].toString(); QRegularExpressionMatch match = classFlagRegExp.match(flagName); if (match.hasMatch()) { QString className = match.captured(1); BinClassDescription *desc = nullptr; auto it = classesCache.find(className); if (it == classesCache.end()) { BinClassDescription cls = {}; ret << cls; desc = &ret.last(); classesCache[className] = desc; } else { desc = it.value(); } desc->name = match.captured(1); desc->addr = flagObject[RJsonKey::offset].toVariant().toULongLong(); desc->index = RVA_INVALID; continue; } match = methodFlagRegExp.match(flagName); if (match.hasMatch()) { QString className = match.captured(1); BinClassDescription *classDesc = nullptr; auto it = classesCache.find(className); if (it == classesCache.end()) { // add a new stub class, will be replaced if class flag comes after it BinClassDescription cls; cls.name = tr("Unknown (%1)").arg(className); cls.addr = RVA_INVALID; cls.index = 0; ret << cls; classDesc = &ret.last(); classesCache[className] = classDesc; } else { classDesc = it.value(); } BinClassMethodDescription meth; meth.name = match.captured(2); meth.addr = flagObject[RJsonKey::offset].toVariant().toULongLong(); classDesc->methods << meth; continue; } } return ret; } QList CutterCore::getAllAnalClasses(bool sorted) { CORE_LOCK(); QList ret; SdbListPtr l = makeSdbListPtr(r_anal_class_get_all(core->anal, sorted)); if (!l) { return ret; } ret.reserve(static_cast(l->length)); SdbListIter *it; void *entry; ls_foreach(l, it, entry) { auto kv = reinterpret_cast(entry); ret.append(QString::fromUtf8(reinterpret_cast(kv->base.key))); } return ret; } QList CutterCore::getAnalClassMethods(const QString &cls) { CORE_LOCK(); QList ret; RVector *meths = r_anal_class_method_get_all(core->anal, cls.toUtf8().constData()); if (!meths) { return ret; } ret.reserve(static_cast(meths->len)); RAnalMethod *meth; CutterRVectorForeach(meths, meth, RAnalMethod) { AnalMethodDescription desc; desc.name = QString::fromUtf8(meth->name); desc.addr = meth->addr; desc.vtableOffset = meth->vtable_offset; ret.append(desc); } r_vector_free(meths); return ret; } QList CutterCore::getAnalClassBaseClasses(const QString &cls) { CORE_LOCK(); QList ret; RVector *bases = r_anal_class_base_get_all(core->anal, cls.toUtf8().constData()); if (!bases) { return ret; } ret.reserve(static_cast(bases->len)); RAnalBaseClass *base; CutterRVectorForeach(bases, base, RAnalBaseClass) { AnalBaseClassDescription desc; desc.id = QString::fromUtf8(base->id); desc.offset = base->offset; desc.className = QString::fromUtf8(base->class_name); ret.append(desc); } r_vector_free(bases); return ret; } QList CutterCore::getAnalClassVTables(const QString &cls) { CORE_LOCK(); QList acVtables; RVector *vtables = r_anal_class_vtable_get_all(core->anal, cls.toUtf8().constData()); if (!vtables) { return acVtables; } acVtables.reserve(static_cast(vtables->len)); RAnalVTable *vtable; CutterRVectorForeach(vtables, vtable, RAnalVTable) { AnalVTableDescription desc; desc.id = QString::fromUtf8(vtable->id); desc.offset = vtable->offset; desc.addr = vtable->addr; acVtables.append(desc); } r_vector_free(vtables); return acVtables; } void CutterCore::createNewClass(const QString &cls) { CORE_LOCK(); r_anal_class_create(core->anal, cls.toUtf8().constData()); } void CutterCore::renameClass(const QString &oldName, const QString &newName) { CORE_LOCK(); r_anal_class_rename(core->anal, oldName.toUtf8().constData(), newName.toUtf8().constData()); } void CutterCore::deleteClass(const QString &cls) { CORE_LOCK(); r_anal_class_delete(core->anal, cls.toUtf8().constData()); } bool CutterCore::getAnalMethod(const QString &cls, const QString &meth, AnalMethodDescription *desc) { CORE_LOCK(); RAnalMethod analMeth; if (r_anal_class_method_get(core->anal, cls.toUtf8().constData(), meth.toUtf8().constData(), &analMeth) != R_ANAL_CLASS_ERR_SUCCESS) { return false; } desc->name = QString::fromUtf8(analMeth.name); desc->addr = analMeth.addr; desc->vtableOffset = analMeth.vtable_offset; r_anal_class_method_fini(&analMeth); return true; } void CutterCore::setAnalMethod(const QString &className, const AnalMethodDescription &meth) { CORE_LOCK(); RAnalMethod analMeth; analMeth.name = strdup (meth.name.toUtf8().constData()); analMeth.addr = meth.addr; analMeth.vtable_offset = meth.vtableOffset; r_anal_class_method_set(core->anal, className.toUtf8().constData(), &analMeth); r_anal_class_method_fini(&analMeth); } void CutterCore::renameAnalMethod(const QString &className, const QString &oldMethodName, const QString &newMethodName) { CORE_LOCK(); r_anal_class_method_rename(core->anal, className.toUtf8().constData(), oldMethodName.toUtf8().constData(), newMethodName.toUtf8().constData()); } QList CutterCore::getAllResources() { CORE_LOCK(); QList resources; QJsonArray resourcesArray = cmdj("iRj").array(); for (const QJsonValue &value : resourcesArray) { QJsonObject resourceObject = value.toObject(); ResourcesDescription res; res.name = resourceObject[RJsonKey::name].toInt(); res.vaddr = resourceObject[RJsonKey::vaddr].toVariant().toULongLong(); res.index = resourceObject[RJsonKey::index].toVariant().toULongLong(); res.type = resourceObject[RJsonKey::type].toString(); res.size = resourceObject[RJsonKey::size].toVariant().toULongLong(); res.lang = resourceObject[RJsonKey::lang].toString(); resources << res; } return resources; } QList CutterCore::getAllVTables() { CORE_LOCK(); QList vtables; QJsonArray vTablesArray = cmdj("avj").array(); for (const QJsonValue &vTableValue : vTablesArray) { QJsonObject vTableObject = vTableValue.toObject(); VTableDescription res; res.addr = vTableObject[RJsonKey::offset].toVariant().toULongLong(); QJsonArray methodArray = vTableObject[RJsonKey::methods].toArray(); for (const QJsonValue &methodValue : methodArray) { QJsonObject methodObject = methodValue.toObject(); BinClassMethodDescription method; method.addr = methodObject[RJsonKey::offset].toVariant().toULongLong(); method.name = methodObject[RJsonKey::name].toString(); res.methods << method; } vtables << res; } return vtables; } QList CutterCore::getAllTypes() { QList types; types.append(getAllPrimitiveTypes()); types.append(getAllUnions()); types.append(getAllStructs()); types.append(getAllEnums()); types.append(getAllTypedefs()); return types; } QList CutterCore::getAllPrimitiveTypes() { CORE_LOCK(); QList primitiveTypes; QJsonArray typesArray = cmdj("tj").array(); for (const QJsonValue &value : typesArray) { QJsonObject typeObject = value.toObject(); TypeDescription exp; exp.type = typeObject[RJsonKey::type].toString(); exp.size = typeObject[RJsonKey::size].toVariant().toULongLong(); exp.format = typeObject[RJsonKey::format].toString(); exp.category = tr("Primitive"); primitiveTypes << exp; } return primitiveTypes; } QList CutterCore::getAllUnions() { CORE_LOCK(); QList unions; QJsonArray typesArray = cmdj("tuj").array(); for (const QJsonValue value: typesArray) { QJsonObject typeObject = value.toObject(); TypeDescription exp; exp.type = typeObject[RJsonKey::type].toString(); exp.size = typeObject[RJsonKey::size].toVariant().toULongLong(); exp.category = "Union"; unions << exp; } return unions; } QList CutterCore::getAllStructs() { CORE_LOCK(); QList structs; QJsonArray typesArray = cmdj("tsj").array(); for (const QJsonValue value: typesArray) { QJsonObject typeObject = value.toObject(); TypeDescription exp; exp.type = typeObject[RJsonKey::type].toString(); exp.size = typeObject[RJsonKey::size].toVariant().toULongLong(); exp.category = "Struct"; structs << exp; } return structs; } QList CutterCore::getAllEnums() { CORE_LOCK(); QList enums; QJsonObject typesObject = cmdj("tej").object(); for (QString key: typesObject.keys()) { TypeDescription exp; exp.type = key; exp.size = 0; exp.category = "Enum"; enums << exp; } return enums; } QList CutterCore::getAllTypedefs() { CORE_LOCK(); QList typeDefs; QJsonObject typesObject = cmdj("ttj").object(); for (QString key: typesObject.keys()) { TypeDescription exp; exp.type = key; exp.size = 0; exp.category = "Typedef"; typeDefs << exp; } return typeDefs; } QString CutterCore::addTypes(const char *str) { CORE_LOCK(); char *error_msg = nullptr; char *parsed = r_parse_c_string(core->anal, str, &error_msg); QString error; if (!parsed) { if (error_msg) { error = error_msg; r_mem_free(error_msg); } return error; } r_anal_save_parsed_type(core->anal, parsed); r_mem_free(parsed); if (error_msg) { error = error_msg; r_mem_free(error_msg); } return error; } QString CutterCore::getTypeAsC(QString name, QString category) { CORE_LOCK(); QString output = "Failed to fetch the output."; if (name.isEmpty() || category.isEmpty()) { return output; } QString typeName = sanitizeStringForCommand(name); if (category == "Struct") { output = cmdRaw(QString("tsc %1").arg(typeName)); } else if (category == "Union") { output = cmdRaw(QString("tuc %1").arg(typeName)); } else if(category == "Enum") { output = cmdRaw(QString("tec %1").arg(typeName)); } else if(category == "Typedef") { output = cmdRaw(QString("ttc %1").arg(typeName)); } return output; } bool CutterCore::isAddressMapped(RVA addr) { // If value returned by "om. @ addr" is empty means that address is not mapped return !Core()->cmdRawAt(QString("om."), addr).isEmpty(); } QList CutterCore::getAllSearch(QString search_for, QString space) { CORE_LOCK(); QList searchRef; QJsonArray searchArray = cmdj(space + QString(" ") + search_for).array(); if (space == "/Rj") { for (const QJsonValue &value : searchArray) { QJsonObject searchObject = value.toObject(); SearchDescription exp; exp.code.clear(); for (const QJsonValue &value2 : searchObject[RJsonKey::opcodes].toArray()) { QJsonObject gadget = value2.toObject(); exp.code += gadget[RJsonKey::opcode].toString() + "; "; } exp.offset = searchObject[RJsonKey::opcodes].toArray().first().toObject()[RJsonKey::offset].toVariant().toULongLong(); exp.size = searchObject[RJsonKey::size].toVariant().toULongLong(); searchRef << exp; } } else { for (const QJsonValue &value : searchArray) { QJsonObject searchObject = value.toObject(); SearchDescription exp; exp.offset = searchObject[RJsonKey::offset].toVariant().toULongLong(); exp.size = searchObject[RJsonKey::len].toVariant().toULongLong(); exp.code = searchObject[RJsonKey::code].toString(); exp.data = searchObject[RJsonKey::data].toString(); searchRef << exp; } } return searchRef; } BlockStatistics CutterCore::getBlockStatistics(unsigned int blocksCount) { BlockStatistics blockStats; if (blocksCount == 0) { blockStats.from = blockStats.to = blockStats.blocksize = 0; return blockStats; } QJsonObject statsObj = cmdj("p-j " + QString::number(blocksCount)).object(); blockStats.from = statsObj[RJsonKey::from].toVariant().toULongLong(); blockStats.to = statsObj[RJsonKey::to].toVariant().toULongLong(); blockStats.blocksize = statsObj[RJsonKey::blocksize].toVariant().toULongLong(); QJsonArray blocksArray = statsObj[RJsonKey::blocks].toArray(); for (const QJsonValue &value : blocksArray) { QJsonObject blockObj = value.toObject(); BlockDescription block; block.addr = blockObj[RJsonKey::offset].toVariant().toULongLong(); block.size = blockObj[RJsonKey::size].toVariant().toULongLong(); block.flags = blockObj[RJsonKey::flags].toInt(0); block.functions = blockObj[RJsonKey::functions].toInt(0); block.inFunctions = blockObj[RJsonKey::in_functions].toInt(0); block.comments = blockObj[RJsonKey::comments].toInt(0); block.symbols = blockObj[RJsonKey::symbols].toInt(0); block.strings = blockObj[RJsonKey::strings].toInt(0); block.rwx = 0; QString rwxStr = blockObj[RJsonKey::rwx].toString(); if (rwxStr.length() == 3) { if (rwxStr[0] == 'r') { block.rwx |= (1 << 0); } if (rwxStr[1] == 'w') { block.rwx |= (1 << 1); } if (rwxStr[2] == 'x') { block.rwx |= (1 << 2); } } blockStats.blocks << block; } return blockStats; } QList CutterCore::getXRefs(RVA addr, bool to, bool whole_function, const QString &filterType) { QList xrefList = QList(); QJsonArray xrefsArray; if (to) { xrefsArray = cmdj("axtj@" + QString::number(addr)).array(); } else { xrefsArray = cmdj("axfj@" + QString::number(addr)).array(); } for (const QJsonValue &value : xrefsArray) { QJsonObject xrefObject = value.toObject(); XrefDescription xref; xref.type = xrefObject[RJsonKey::type].toString(); if (!filterType.isNull() && filterType != xref.type) continue; xref.from = xrefObject[RJsonKey::from].toVariant().toULongLong(); if (!to) { xref.from_str = RAddressString(xref.from); } else { QString fcn = xrefObject[RJsonKey::fcn_name].toString(); if (!fcn.isEmpty()) { RVA fcnAddr = xrefObject[RJsonKey::fcn_addr].toVariant().toULongLong(); xref.from_str = fcn + " + 0x" + QString::number(xref.from - fcnAddr, 16); } else { xref.from_str = RAddressString(xref.from); } } if (!whole_function && !to && xref.from != addr) { continue; } if (to && !xrefObject.contains(RJsonKey::to)) { xref.to = addr; } else { xref.to = xrefObject[RJsonKey::to].toVariant().toULongLong(); } xref.to_str = Core()->cmdRaw(QString("fd %1").arg(xref.to)).trimmed(); xrefList << xref; } return xrefList; } void CutterCore::addFlag(RVA offset, QString name, RVA size) { name = sanitizeStringForCommand(name); cmdRawAt(QString("f %1 %2").arg(name).arg(size), offset); emit flagsChanged(); } /** * @brief Gets all the flags present at a specific address * @param addr The address to be checked * @return String containing all the flags which are comma-separated */ QString CutterCore::listFlagsAsStringAt(RVA addr) { CORE_LOCK(); char *flagList = r_flag_get_liststr (core->flags, addr); QString result(flagList); r_mem_free(flagList); return result; } QString CutterCore::nearestFlag(RVA offset, RVA *flagOffsetOut) { auto r = cmdj(QString("fdj @") + QString::number(offset)).object(); QString name = r.value("name").toString(); if (flagOffsetOut) { int queryOffset = r.value("offset").toInt(0); *flagOffsetOut = offset + static_cast(-queryOffset); } return name; } void CutterCore::handleREvent(int type, void *data) { switch (type) { case R_EVENT_CLASS_NEW: { auto ev = reinterpret_cast(data); emit classNew(QString::fromUtf8(ev->name)); break; } case R_EVENT_CLASS_DEL: { auto ev = reinterpret_cast(data); emit classDeleted(QString::fromUtf8(ev->name)); break; } case R_EVENT_CLASS_RENAME: { auto ev = reinterpret_cast(data); emit classRenamed(QString::fromUtf8(ev->name_old), QString::fromUtf8(ev->name_new)); break; } case R_EVENT_CLASS_ATTR_SET: { auto ev = reinterpret_cast(data); emit classAttrsChanged(QString::fromUtf8(ev->attr.class_name)); break; } case R_EVENT_CLASS_ATTR_DEL: { auto ev = reinterpret_cast(data); emit classAttrsChanged(QString::fromUtf8(ev->class_name)); break; } case R_EVENT_CLASS_ATTR_RENAME: { auto ev = reinterpret_cast(data); emit classAttrsChanged(QString::fromUtf8(ev->attr.class_name)); break; } case R_EVENT_DEBUG_PROCESS_FINISHED: { auto ev = reinterpret_cast(data); emit debugProcessFinished(ev->pid); break; } default: break; } } void CutterCore::triggerFlagsChanged() { emit flagsChanged(); } void CutterCore::triggerVarsChanged() { emit varsChanged(); } void CutterCore::triggerFunctionRenamed(const QString &prevName, const QString &newName) { emit functionRenamed(prevName, newName); } void CutterCore::loadPDB(const QString &file) { cmdRaw("idp " + sanitizeStringForCommand(file)); } void CutterCore::openProject(const QString &name) { cmdRaw("Po " + name); QString notes = QString::fromUtf8(QByteArray::fromBase64(cmdRaw("Pnj").toUtf8())); } void CutterCore::saveProject(const QString &name) { const QString &rv = cmdRaw("Ps " + name.trimmed()).trimmed(); const bool ok = rv == name.trimmed(); cmdRaw(QString("Pnj %1").arg(QString(notes.toUtf8().toBase64()))); emit projectSaved(ok, name); } void CutterCore::deleteProject(const QString &name) { cmdRaw("Pd " + name); } bool CutterCore::isProjectNameValid(const QString &name) { // see is_valid_project_name() in libr/core/project. QString pattern(R"(^[a-zA-Z0-9\\\._:-]{1,}$)"); // The below construct mimics the behaviour of QRegexP::exactMatch(), which was here before static const QRegularExpression regexp("\\A(?:" + pattern + ")\\z"); return regexp.match(name).hasMatch() && !name.endsWith(".zip") ; } QList CutterCore::disassembleLines(RVA offset, int lines) { QJsonArray array = cmdj(QString("pdJ ") + QString::number(lines) + QString(" @ ") + QString::number( offset)).array(); QList r; for (const QJsonValueRef &value : array) { QJsonObject object = value.toObject(); DisassemblyLine line; line.offset = object[RJsonKey::offset].toVariant().toULongLong(); line.text = ansiEscapeToHtml(object[RJsonKey::text].toString()); const auto& arrow = object[RJsonKey::arrow]; line.arrow = arrow.isNull() ? RVA_INVALID : arrow.toVariant().toULongLong(); r << line; } return r; } /** * @brief return hexdump of from an by a given formats * @param address - the address from which to print the hexdump * @param size - number of bytes to print * @param format - the type of hexdump (qwords, words. decimal, etc) */ QString CutterCore::hexdump(RVA address, int size, HexdumpFormats format) { QString command = "px"; switch (format) { case HexdumpFormats::Normal: break; case HexdumpFormats::Half: command += "h"; break; case HexdumpFormats::Word: command += "w"; break; case HexdumpFormats::Quad: command += "q"; break; case HexdumpFormats::Signed: command += "d"; break; case HexdumpFormats::Octal: command += "o"; break; } return cmdRawAt(QString("%1 %2") .arg(command) .arg(size), address); } QByteArray CutterCore::hexStringToBytes(const QString &hex) { QByteArray hexChars = hex.toUtf8(); QByteArray bytes; bytes.reserve(hexChars.length() / 2); int size = r_hex_str2bin(hexChars.constData(), reinterpret_cast(bytes.data())); bytes.resize(size); return bytes; } QString CutterCore::bytesToHexString(const QByteArray &bytes) { QByteArray hex; hex.resize(bytes.length() * 2); r_hex_bin2str(reinterpret_cast(bytes.constData()), bytes.size(), hex.data()); return QString::fromUtf8(hex); } void CutterCore::loadScript(const QString &scriptname) { { CORE_LOCK(); r_core_cmd_file(core, scriptname.toUtf8().constData()); } triggerRefreshAll(); } QString CutterCore::getVersionInformation() { int i; QString versionInfo; struct vcs_t { const char *name; const char *(*callback)(); } vcs[] = { { "r_anal", &r_anal_version }, { "r_lib", &r_lib_version }, { "r_egg", &r_egg_version }, { "r_asm", &r_asm_version }, { "r_bin", &r_bin_version }, { "r_cons", &r_cons_version }, { "r_flag", &r_flag_version }, { "r_core", &r_core_version }, { "r_crypto", &r_crypto_version }, { "r_bp", &r_bp_version }, { "r_debug", &r_debug_version }, { "r_hash", &r_hash_version }, { "r_fs", &r_fs_version }, { "r_io", &r_io_version }, #if !USE_LIB_MAGIC { "r_magic", &r_magic_version }, #endif { "r_parse", &r_parse_version }, { "r_reg", &r_reg_version }, { "r_sign", &r_sign_version }, { "r_search", &r_search_version }, { "r_syscall", &r_syscall_version }, { "r_util", &r_util_version }, /* ... */ {NULL, NULL} }; versionInfo.append(QString("%1 r2\n").arg(R2_GITTAP)); for (i = 0; vcs[i].name; i++) { struct vcs_t *v = &vcs[i]; const char *name = v->callback (); versionInfo.append(QString("%1 %2\n").arg(name, v->name)); } return versionInfo; } QJsonArray CutterCore::getOpenedFiles() { QJsonDocument files = cmdj("oj"); return files.array(); } QList CutterCore::getColorThemes() { QList r; QJsonDocument themes = cmdj("ecoj"); for (const QJsonValue &s : themes.array()) { r << s.toString(); } return r; } QString CutterCore::ansiEscapeToHtml(const QString &text) { int len; char *html = r_cons_html_filter(text.toUtf8().constData(), &len); if (!html) { return QString(); } QString r = QString::fromUtf8(html, len); r_mem_free(html); return r; } BasicBlockHighlighter* CutterCore::getBBHighlighter() { return bbHighlighter; } BasicInstructionHighlighter* CutterCore::getBIHighlighter() { return &biHighlighter; } void CutterCore::setIOCache(bool enabled) { setConfig("io.cache", enabled); this->iocache = enabled; emit ioCacheChanged(enabled); } bool CutterCore::isIOCacheEnabled() const { return iocache; } void CutterCore::commitWriteCache() { if (!isWriteModeEnabled()) { setWriteMode (true); cmdRaw("wci"); cmdRaw("oo"); } else { cmdRaw("wci"); } } // Enable or disable write-mode. Avoid unecessary changes if not need. void CutterCore::setWriteMode(bool enabled) { bool writeModeState = isWriteModeEnabled(); if (writeModeState == enabled) { // New mode is the same as current. Do nothing. return; } // Change from read-only to write-mode if (enabled && !writeModeState) { cmdRaw("oo+"); // Change from write-mode to read-only } else { cmdRaw("oo"); } writeModeChanged (enabled); } bool CutterCore::isWriteModeEnabled() { using namespace std; QJsonArray ans = cmdj("oj").array(); return find_if(begin(ans), end(ans), [](const QJsonValue &v) { return v.toObject().value("raised").toBool(); })->toObject().value("writable").toBool(); } /** * @brief get a compact disassembly preview for tooltips * @param address - the address from which to print the disassembly * @param num_of_lines - number of instructions to print */ QStringList CutterCore::getDisassemblyPreview(RVA address, int num_of_lines) { QList disassemblyLines; { // temporarily simplify the disasm output to get it colorful and simple to read TempConfig tempConfig; tempConfig .set("scr.color", COLOR_MODE_16M) .set("asm.lines", false) .set("asm.var", false) .set("asm.comments", false) .set("asm.bytes", false) .set("asm.lines.fcn", false) .set("asm.lines.out", false) .set("asm.lines.bb", false) .set("asm.bb.line", false); disassemblyLines = disassembleLines(address, num_of_lines + 1); } QStringList disasmPreview; for (const DisassemblyLine &line : disassemblyLines) { disasmPreview << line.text; if (disasmPreview.length() >= num_of_lines) { disasmPreview << "..."; break; } } if (!disasmPreview.isEmpty()) { return disasmPreview; } else { return QStringList(); } } /** * @brief get a compact hexdump preview for tooltips * @param address - the address from which to print the hexdump * @param size - number of bytes to print */ QString CutterCore::getHexdumpPreview(RVA address, int size) { // temporarily simplify the disasm output to get it colorful and simple to read TempConfig tempConfig; tempConfig .set("scr.color", COLOR_MODE_16M) .set("asm.offset", true) .set("hex.header", false) .set("hex.cols", 16); return ansiEscapeToHtml(hexdump(address, size, HexdumpFormats::Normal)).replace(QLatin1Char('\n'), "
"); } QByteArray CutterCore::ioRead(RVA addr, int len) { CORE_LOCK(); QByteArray array; if (len <= 0) return array; /* Zero-copy */ array.resize(len); if (!r_io_read_at(core->io, addr, (uint8_t *)array.data(), len)) { qWarning() << "Can't read data" << addr << len; array.fill(0xff); } return array; }