forked from Vector35/debugger
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support installing WinDbg and making a TTD recording directly from wi…
…thin the debugger. Fix Vector35#552
- Loading branch information
Showing
8 changed files
with
422 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import urllib.request | ||
import xml.dom.minidom | ||
import zipfile | ||
import tempfile | ||
import binaryninja | ||
from binaryninja.settings import Settings | ||
import os | ||
|
||
|
||
def check_install_ok(path): | ||
if not os.path.exists(os.path.join(path, 'amd64', 'dbgeng.dll')): | ||
return False | ||
|
||
if not os.path.exists(os.path.join(path, 'amd64', 'dbghelp.dll')): | ||
return False | ||
|
||
if not os.path.exists(os.path.join(path, 'amd64', 'dbgmodel.dll')): | ||
return False | ||
|
||
if not os.path.exists(os.path.join(path, 'amd64', 'dbgcore.dll')): | ||
return False | ||
|
||
if not os.path.exists(os.path.join(path, 'amd64', 'ttd', 'TTD.exe')): | ||
return False | ||
|
||
if not os.path.exists(os.path.join(path, 'amd64', 'ttd', 'TTDRecord.dll')): | ||
return False | ||
|
||
return True | ||
|
||
|
||
def install_windbg(): | ||
ttd_url = 'https://aka.ms/windbg/download' | ||
print('Downloading appinstaller from: %s...' % ttd_url) | ||
try: | ||
local_file, _ = urllib.request.urlretrieve(ttd_url) | ||
except Exception as e: | ||
print('Failed to download appinstaller file from %s' % ttd_url) | ||
print(e) | ||
return | ||
print('Successfully downloaded appinstaller') | ||
|
||
xml_doc = xml.dom.minidom.parse(local_file) | ||
try: | ||
msix_url = xml_doc.getElementsByTagName('MainBundle')[0].attributes['Uri'].value | ||
except Exception as e: | ||
print('Failed to parse XML') | ||
print(e) | ||
return | ||
|
||
print('Downloading MSIX bundle from: %s...' % msix_url) | ||
try: | ||
msix_file, _ = urllib.request.urlretrieve(msix_url) | ||
except Exception as e: | ||
print('Failed to download MSIX bundle from %s' % msix_url) | ||
print(e) | ||
return | ||
print('Successfully downloaded MSIX bundle') | ||
|
||
zip_file = zipfile.ZipFile(msix_file) | ||
temp_dir = tempfile.mkdtemp() | ||
inner_msix = zip_file.extract('windbg_win7-x64.msix', temp_dir) | ||
print('Extracted windbg_win7-x64 to %s' % inner_msix) | ||
|
||
install_target = os.path.join(binaryninja.user_directory(), 'windbg') | ||
print('Installing to: %s' % install_target) | ||
|
||
inner_zip = zipfile.ZipFile(inner_msix) | ||
inner_zip.extractall(install_target) | ||
|
||
if check_install_ok(install_target): | ||
print('WinDbg/TTD installed to %s!' % install_target) | ||
else: | ||
print('The WinDbg/TTD installation appears to be successful, but important files are missing from %s, ' | ||
'and the TTD recording may not work properly.' % install_target) | ||
return | ||
|
||
x64dbgEngPath = os.path.join(install_target, 'amd64') | ||
if Settings().set_string("debugger.x64dbgEngPath", x64dbgEngPath): | ||
print('Please restart Binary Ninja to make the changes take effect!') | ||
else: | ||
print('Failed to set debugger.x64dbgEngPath to %s, the WinDbg/TTD installation is not being used' % (x64dbgEngPath)) | ||
|
||
|
||
install_windbg() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
/* | ||
Copyright 2020-2024 Vector 35 Inc. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
#include "ttdrecord.h" | ||
#include "uicontext.h" | ||
#include "qfiledialog.h" | ||
#include "fmt/format.h" | ||
|
||
using namespace BinaryNinjaDebuggerAPI; | ||
using namespace BinaryNinja; | ||
using namespace std; | ||
|
||
TTDRecordDialog::TTDRecordDialog(QWidget* parent, BinaryView* data) : | ||
QDialog() | ||
{ | ||
if (data) | ||
m_controller = DebuggerController::GetController(data); | ||
|
||
setWindowTitle("TTD Record"); | ||
setAttribute(Qt::WA_DeleteOnClose); | ||
|
||
setModal(true); | ||
QVBoxLayout* layout = new QVBoxLayout; | ||
layout->setSpacing(0); | ||
|
||
m_pathEntry = new QLineEdit(this); | ||
m_pathEntry->setMinimumWidth(800); | ||
m_argumentsEntry = new QLineEdit(this); | ||
m_workingDirectoryEntry = new QLineEdit(this); | ||
m_outputDirectory = new QLineEdit(this); | ||
m_launchWithoutTracing = new QCheckBox(this); | ||
|
||
auto* pathSelector = new QPushButton("...", this); | ||
pathSelector->setMaximumWidth(30); | ||
connect(pathSelector, &QPushButton::clicked, [&]() { | ||
auto fileName = QFileDialog::getOpenFileName(this, "Select Executable Path", m_pathEntry->text()); | ||
if (!fileName.isEmpty()) | ||
m_pathEntry->setText(fileName); | ||
}); | ||
|
||
auto* workingDirSelector = new QPushButton("...", this); | ||
workingDirSelector->setMaximumWidth(30); | ||
connect(workingDirSelector, &QPushButton::clicked, [&]() { | ||
auto pathName = QFileDialog::getExistingDirectory(this, "Specify Working Directory", | ||
m_workingDirectoryEntry->text(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); | ||
if (!pathName.isEmpty()) | ||
m_workingDirectoryEntry->setText(pathName); | ||
}); | ||
|
||
auto* outputDirSelector = new QPushButton("...", this); | ||
outputDirSelector->setMaximumWidth(30); | ||
connect(outputDirSelector, &QPushButton::clicked, [&]() { | ||
auto pathName = QFileDialog::getExistingDirectory(this, "Specify Trace Output Directory", | ||
m_workingDirectoryEntry->text(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); | ||
if (!pathName.isEmpty()) | ||
m_workingDirectoryEntry->setText(pathName); | ||
}); | ||
|
||
auto pathEntryLayout = new QHBoxLayout; | ||
pathEntryLayout->addWidget(m_pathEntry); | ||
pathEntryLayout->addWidget(pathSelector); | ||
|
||
auto workingDirLayout = new QHBoxLayout; | ||
workingDirLayout->addWidget(m_workingDirectoryEntry); | ||
workingDirLayout->addWidget(workingDirSelector); | ||
|
||
auto outputLayout = new QHBoxLayout; | ||
outputLayout->addWidget(m_outputDirectory); | ||
outputLayout->addWidget(outputDirSelector); | ||
|
||
QVBoxLayout* contentLayout = new QVBoxLayout; | ||
contentLayout->setSpacing(10); | ||
contentLayout->addWidget(new QLabel("Executable Path")); | ||
contentLayout->addLayout(pathEntryLayout); | ||
contentLayout->addWidget(new QLabel("Working Directory")); | ||
contentLayout->addLayout(workingDirLayout); | ||
contentLayout->addWidget(new QLabel("Command Line Arguments")); | ||
contentLayout->addWidget(m_argumentsEntry); | ||
contentLayout->addWidget(new QLabel("Trace Output Directory")); | ||
contentLayout->addLayout(outputLayout); | ||
contentLayout->addWidget(new QLabel("Start application With Recording Off")); | ||
contentLayout->addWidget(m_launchWithoutTracing); | ||
|
||
QHBoxLayout* buttonLayout = new QHBoxLayout; | ||
buttonLayout->setContentsMargins(0, 0, 0, 0); | ||
|
||
QPushButton* cancelButton = new QPushButton("Cancel"); | ||
connect(cancelButton, &QPushButton::clicked, [&]() { reject(); }); | ||
QPushButton* acceptButton = new QPushButton("Record"); | ||
connect(acceptButton, &QPushButton::clicked, [&]() { apply(); }); | ||
acceptButton->setDefault(true); | ||
|
||
buttonLayout->addStretch(1); | ||
buttonLayout->addWidget(cancelButton); | ||
buttonLayout->addWidget(acceptButton); | ||
|
||
layout->addLayout(contentLayout); | ||
layout->addStretch(1); | ||
layout->addSpacing(10); | ||
layout->addLayout(buttonLayout); | ||
setLayout(layout); | ||
|
||
if (m_controller) | ||
{ | ||
m_pathEntry->setText(QString::fromStdString(m_controller->GetExecutablePath())); | ||
m_argumentsEntry->setText(QString::fromStdString(m_controller->GetCommandLineArguments())); | ||
m_workingDirectoryEntry->setText(QString::fromStdString(m_controller->GetWorkingDirectory())); | ||
m_outputDirectory->setText(QString::fromStdString(m_controller->GetWorkingDirectory())); | ||
} | ||
m_launchWithoutTracing->setChecked(false); | ||
|
||
setFixedSize(QDialog::sizeHint()); | ||
|
||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); | ||
} | ||
|
||
|
||
void TTDRecordDialog::apply() | ||
{ | ||
DoTTDTrace(); | ||
|
||
accept(); | ||
} | ||
|
||
|
||
static bool IsValidDbgEngTTDPaths(const std::string& path) | ||
{ | ||
if (path.empty()) | ||
return false; | ||
|
||
auto enginePath = filesystem::path(path); | ||
if (!filesystem::exists(enginePath)) | ||
return false; | ||
|
||
if (!filesystem::exists(enginePath / "TTD.exe")) | ||
return false; | ||
|
||
if (!filesystem::exists(enginePath / "TTDRecord.dll")) | ||
return false; | ||
|
||
return true; | ||
} | ||
|
||
|
||
std::string TTDRecordDialog::GetTTDRecorderPath() | ||
{ | ||
std::string path = Settings::Instance()->Get<string>("debugger.x64dbgEngPath"); | ||
if (!path.empty()) | ||
{ | ||
// If the user has specified the path in the setting, then check it for validity. If it is valid, then use it; | ||
// if it is invalid, fail the operation -- do not fallback to the default one | ||
auto userTTDPath = filesystem::path(path) / "TTD"; | ||
if (IsValidDbgEngTTDPaths(userTTDPath.string())) | ||
return userTTDPath.string(); | ||
else | ||
return ""; | ||
} | ||
|
||
std::string pluginRoot; | ||
if (getenv("BN_STANDALONE_DEBUGGER") != nullptr) | ||
pluginRoot = GetUserPluginDirectory(); | ||
else | ||
pluginRoot = GetBundledPluginDirectory(); | ||
|
||
// If the user does not specify a path (the default case), find the one from the plugins/dbgeng/arch | ||
auto TTDRecorderRoot = filesystem::path(pluginRoot) / "dbgeng" / "amd64" / "TTD"; | ||
if (IsValidDbgEngTTDPaths(TTDRecorderRoot.string())) | ||
return TTDRecorderRoot.string(); | ||
|
||
return ""; | ||
} | ||
|
||
|
||
void TTDRecordDialog::DoTTDTrace() | ||
{ | ||
auto ttdPath = GetTTDRecorderPath(); | ||
if (ttdPath.empty()) | ||
{ | ||
LogWarn("The debugger cannot find the path for the TTD recorder. " | ||
"If you have set debugger.x64dbgEngPath, check if it valid"); | ||
return; | ||
} | ||
LogDebug("TTD Recorder in path %s", ttdPath.c_str()); | ||
|
||
auto ttdRecorder = fmt::format("\"{}\\TTD.exe\"", ttdPath); | ||
auto ttdCommandLine = fmt::format("-accepteula -out \"{}\" {} -launch \"{}\" {}", | ||
m_outputDirectory->text().toStdString(), | ||
m_launchWithoutTracing->isChecked() ? "-tracingOff -recordMode Manual" : "", | ||
m_pathEntry->text().toStdString(), | ||
m_argumentsEntry->text().toStdString()); | ||
LogWarn("TTD tracer cmd: %s %s", ttdRecorder.c_str(), ttdCommandLine.c_str()); | ||
|
||
HINSTANCE ret = ShellExecuteA( | ||
NULL, | ||
"runas", | ||
ttdRecorder.c_str(), | ||
ttdCommandLine.c_str(), | ||
m_workingDirectoryEntry->text().toStdString().c_str(), | ||
SW_NORMAL | ||
); | ||
|
||
if ((INT_PTR)ret < 32) | ||
LogWarn("TTD recording failed: %d", ret); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
Copyright 2020-2024 Vector 35 Inc. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <QDialog> | ||
#include <QPushButton> | ||
#include <QLineEdit> | ||
#include <QComboBox> | ||
#include <QFormLayout> | ||
#include <QCheckBox> | ||
#include "inttypes.h" | ||
#include "binaryninjaapi.h" | ||
#include "viewframe.h" | ||
#include "fontsettings.h" | ||
#include "debuggerapi.h" | ||
|
||
using namespace BinaryNinjaDebuggerAPI; | ||
|
||
class TTDRecordDialog : public QDialog | ||
{ | ||
Q_OBJECT | ||
|
||
private: | ||
DbgRef<DebuggerController> m_controller = nullptr; | ||
QLineEdit* m_pathEntry; | ||
QLineEdit* m_workingDirectoryEntry; | ||
QLineEdit* m_argumentsEntry; | ||
QLineEdit* m_outputDirectory; | ||
QCheckBox* m_launchWithoutTracing; | ||
|
||
public: | ||
TTDRecordDialog(QWidget* parent, BinaryView* data); | ||
void DoTTDTrace(); | ||
std::string GetTTDRecorderPath(); | ||
|
||
private Q_SLOTS: | ||
void apply(); | ||
}; |
Oops, something went wrong.