Skip to content

Commit

Permalink
[Olly, x64dbg] fixed issue #41
Browse files Browse the repository at this point in the history
[Olly, x64dbg] don't put extra lines endings to stdout/stderr
[IDA] now we could navigate to an error line when click at the debugger's script error line
[IDA] log outputs of the debuggee were made read only
  • Loading branch information
a1ext committed Jul 17, 2018
1 parent 85d3339 commit 9210747
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 70 deletions.
73 changes: 65 additions & 8 deletions labeless_ida/labeless_ida.cpp
Expand Up @@ -85,6 +85,7 @@
#include "jedi.h"
#include "jedicompletionworker.h"
#include "pausenotificationlistener.h"
#include "textedit.h"


namespace {
Expand Down Expand Up @@ -208,6 +209,9 @@ static const QString kDisablePauseNotifAction = QObject::tr("Disable pause notif
static const uint32 kPauseNotificationCursorColor = 0x305500;
static const WORD kDefaultPauseNotificationPort = 12344;

static const QString kLogItemActionLoad = "load";
static const QString kLogItemActionNavigate = "nav";

typedef QHash<ea_t, std::string> EA2CommentHash;

bool isUtf8StringValid(const char* const s, size_t len)
Expand Down Expand Up @@ -628,6 +632,34 @@ bool parseBackendId(const ::qstring& qid, std::string& result)
return true;
}

QString supplyStdErrWithNavigationLinks(const LogItem& logItem)
{
static const QString kErrorNavigatorFmt = QObject::tr("<a href=\"%1/%2/%3\" title=\"Click to navigate\">%4</a>");
const QRegExp reTracebackFilePosition = QRegExp("^\\s*File \"<string>\", line (\\d+), in (.+)$", Qt::CaseInsensitive);

auto items = logItem.textStdErr.split("\n");
QStringList rvList;
foreach (QString line, items)
{
const bool matches = reTracebackFilePosition.exactMatch(line);
line = line.replace("\r", "").replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\\s", "&nbsp;"); // kludge
if (!matches)
{
rvList << line;
}
else
{
rvList << kErrorNavigatorFmt
.arg(kLogItemActionNavigate)
.arg(logItem.number)
.arg(reTracebackFilePosition.cap(1))
.arg(line);
}
}

return rvList.join("<br>");
}

} // anonymous

compat::TWidget* Labeless::m_EditorTForm;
Expand Down Expand Up @@ -3045,10 +3077,11 @@ void Labeless::addLogItem(LogItem& logItem)

m_LogItems.insert(logItem.number, logItem);

static const QString stdOutFmt = tr("<a href=\"%1\" title=\"Click to load into editors\">request #%2</a> %3<br>");
static const QString stdOutFmt = tr("<a href=\"%1/%2\" title=\"Click to load into editors\">request #%3</a> %4<br>");

m_PyOllyView->prependStdoutLog("-------------------------------------------------------------------<br>", true);
m_PyOllyView->prependStdoutLog(stdOutFmt
.arg(kLogItemActionLoad)
.arg(logItem.number)
.arg(logItem.number)
.arg(logItem.textStdErr.isEmpty() ? QString::null : tr("(has std_err |&gt;)")), true);
Expand All @@ -3060,13 +3093,14 @@ void Labeless::addLogItem(LogItem& logItem)

m_PyOllyView->prependStderrLog("-------------------------------------------------------------------<br>", true);
m_PyOllyView->prependStderrLog(stdOutFmt
.arg(kLogItemActionLoad)
.arg(logItem.number)
.arg(logItem.number)
.arg(logItem.textStdOut.isEmpty() ? QString::null : tr("(has std_out &lt;|)")), true);
if (!logItem.textStdErr.isEmpty())
{
m_PyOllyView->prependStderrLog("<br>", true);
m_PyOllyView->prependStderrLog(logItem.textStdErr);
m_PyOllyView->prependStderrLog(supplyStdErrWithNavigationLinks(logItem), true);
}
}

Expand All @@ -3076,21 +3110,44 @@ void Labeless::onLogAnchorClicked(const QString& value)
return;

bool ok = false;
const uint64_t number = value.toULongLong(&ok);
QStringList items = value.split("/", QString::SkipEmptyParts);
if (items.length() < 2)
return;

const bool isLoad = items[0] == kLogItemActionLoad;
const bool isNavigate = items[0] == kLogItemActionNavigate;
if (!isLoad && !isNavigate)
return;

if (isNavigate && items.length() < 3)
return;

const uint64_t number = items[1].toULongLong(&ok);
if (!ok || !m_LogItems.contains(number))
return;

const LogItem& li = m_LogItems[number];

if (li.idaScript.isEmpty() && li.ollyScript.isEmpty())
return;
if (isLoad)
{
if (li.idaScript.isEmpty() && li.ollyScript.isEmpty())
return;

if (ASKBTN_YES != ASK_YN(ASKBTN_NO, "Do you want to load IDA & Olly scripts related to that request into editors?\n"
if (ASKBTN_YES != ASK_YN(ASKBTN_NO, "Do you want to load IDA & Olly scripts related to that request into editors?\n"
"All changes made will be lost!"))
return;

m_PyOllyView->setIDAScript(li.idaScript);
m_PyOllyView->setOllyScript(li.ollyScript);
return;
}

// navigate
const auto lineNumber = items[2].toLong(&ok);
if (!ok || lineNumber <= 0)
return;

m_PyOllyView->setIDAScript(li.idaScript);
m_PyOllyView->setOllyScript(li.ollyScript);
m_PyOllyView->jumpAndSelectLine(false, lineNumber - 1);
}

void Labeless::onClearLogsRequested()
Expand Down
31 changes: 31 additions & 0 deletions labeless_ida/pyollyview.cpp
Expand Up @@ -166,6 +166,19 @@ void PyOllyView::setUpGUI()

CHECKED_CONNECT(connect(pb, SIGNAL(clicked()), this, SLOT(onFastActionRequested())));
}

auto cornerWidget = new QWidget();
auto cornerVBoxLayout = new QVBoxLayout();

auto logTabBarLabel = new QLabel(tr("Debugger's "));
logTabBarLabel->setAlignment(Qt::AlignVCenter);
cornerVBoxLayout->addWidget(logTabBarLabel, 1);
cornerVBoxLayout->setContentsMargins(0, 0, 0, 4);

cornerWidget->setLayout(cornerVBoxLayout);
m_UI->tbLogPane->setCornerWidget(cornerWidget, Qt::TopLeftCorner);
m_UI->teIDAScript->setProperty("placeholderText", tr("Put IDA script here...")); // workaround for Qt 4, it doesn't have placeholder text for QPlainTextEdit
m_UI->teOllyScript->setProperty("placeholderText", tr("Put Debugger's script here..."));
}

void PyOllyView::setUpConnections()
Expand Down Expand Up @@ -245,6 +258,16 @@ QString PyOllyView::getIDAScript(bool html) const
return m_UI->teIDAScript->toPlainText();
}

TextEdit* PyOllyView::idaScriptWidget() const
{
return m_UI->teIDAScript;
}

TextEdit* PyOllyView::ollyScriptWidget() const
{
return m_UI->teOllyScript;
}

void PyOllyView::onColorSchemeChanged()
{
const int index = m_UI->cbColorScheme->currentIndex();
Expand All @@ -261,6 +284,14 @@ void PyOllyView::onColorSchemeChanged()
GlobalSettingsManger::instance().setValue(GSK_ColorScheme, sel);
}

void PyOllyView::jumpAndSelectLine(bool isIDA, int line)
{
TextEdit* te = isIDA ? m_UI->teIDAScript : m_UI->teOllyScript;

te->selectLine(line);
te->setFocus();
}

bool PyOllyView::eventFilter(QObject* obj, QEvent* event)
{
if (event->type() != QEvent::MouseButtonPress)
Expand Down
6 changes: 6 additions & 0 deletions labeless_ida/pyollyview.h
Expand Up @@ -25,6 +25,8 @@ class PyOllyView;
}
QT_END_NAMESPACE

class TextEdit;

class PyOllyView : public QWidget
{
Q_OBJECT
Expand All @@ -39,11 +41,15 @@ class PyOllyView : public QWidget
QString getOllyScript(bool html = false) const;
QString getIDAScript(bool html = false) const;

TextEdit* idaScriptWidget() const;
TextEdit* ollyScriptWidget() const;

public slots:
void enableRunScriptButton(bool enabled);
void setOllyScript(const QString& text);
void setIDAScript(const QString& text);
void onColorSchemeChanged();
void jumpAndSelectLine(bool isIDA, int line);

private slots:
void onScriptEditContextMenuRequested(const QPoint& pt);
Expand Down
11 changes: 11 additions & 0 deletions labeless_ida/textedit.cpp
Expand Up @@ -341,6 +341,17 @@ int TextEdit::lineNumberAreaWidth()
return space;
}

void TextEdit::selectLine(int line)
{
auto block = document()->findBlockByLineNumber(line);
if (!block.isValid())
return;

QTextCursor cursor(block);
cursor.select(QTextCursor::LineUnderCursor);
setTextCursor(cursor);
}

void TextEdit::updateLineNumberAreaWidth(int /* newBlockCount */)
{
setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
Expand Down
1 change: 1 addition & 0 deletions labeless_ida/textedit.h
Expand Up @@ -43,6 +43,7 @@ class TextEdit : public QPlainTextEdit
public slots:
void colorSchemeChanged();
void onAutoCompleteFinished(QSharedPointer<jedi::Result> r);
void selectLine(int line);

private slots:
void insertCompletion(const QString& completion);
Expand Down
54 changes: 12 additions & 42 deletions labeless_ida/ui/pyollyview.ui
Expand Up @@ -17,16 +17,7 @@
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<property name="margin">
<number>3</number>
</property>
<item>
Expand Down Expand Up @@ -166,16 +157,7 @@
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<property name="margin">
<number>3</number>
</property>
<item>
Expand Down Expand Up @@ -299,16 +281,7 @@
<string>stdout</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<property name="margin">
<number>0</number>
</property>
<item>
Expand All @@ -319,8 +292,11 @@
<property name="undoRedoEnabled">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
Expand All @@ -331,16 +307,7 @@
<string>stderr</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<property name="margin">
<number>0</number>
</property>
<item>
Expand All @@ -351,8 +318,11 @@
<property name="undoRedoEnabled">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
Expand Down
22 changes: 16 additions & 6 deletions labeless_olly/labeless.cpp
Expand Up @@ -190,7 +190,7 @@ static PyObject* stdOutHandler(PyObject* self, PyObject* arg)

auto& cd = Labeless::instance().clientData();
recursive_lock_guard lock(cd.stdOutLock);
cd.stdOut << str << "\n";
cd.stdOut << str;

Py_RETURN_NONE;
}
Expand Down Expand Up @@ -455,12 +455,22 @@ bool pySerializeObjectToJson(PyObject* o, std::string& rv)
(PyDict_Contains(pyModuleDict, pyLLstr) == 1) &&
(pyLL = PyDict_GetItem(pyModuleDict, pyLLstr)) &&
(pySerializeResultFn = PyObject_GetAttrString(pyLL, kLabelessSerializeResultFuncName.c_str())) &&
(pyArgs = PyTuple_Pack(1, o)) &&
(result = PyObject_Call(pySerializeResultFn, pyArgs, NULL)) &&
PyString_Check(result))
(pyArgs = PyTuple_Pack(1, o)))
{
rv = PyString_AsString(result);
isOk = true;
result = PyObject_Call(pySerializeResultFn, pyArgs, NULL);
if (!result)
{
PyErr_Print();
}
else if (!PyString_Check(result))
{
PySys_WriteStderr("[!] Unable to serialize `__result__`, check that it serializes to JSON without errors\n");
}
else
{
rv = PyString_AsString(result);
isOk = true;
}
}

Py_XDECREF(result);
Expand Down

0 comments on commit 9210747

Please sign in to comment.