Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions docs/guide/dbgeng-ttd.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ The WinDbg installation only needs to be done once.
## Record a TTD Trace

Once we have installed and configured WinDbg, we can start recording a TTD trace. There are two ways to do it, we can either
do it from within Binary Ninja, or do it from WinDbg. Doing it from Binary Ninja is more convenient, though it does not support
all types of recording supported by WinDbg (e.g., attach to a running process and start recroding).
do it from within Binary Ninja, or do it from WinDbg. Binary Ninja now supports both launching new processes and attaching to running processes for TTD recording.

### Record a TTD Trace in Binary Ninja

Expand All @@ -60,13 +59,17 @@ all types of recording supported by WinDbg (e.g., attach to a running process an

<img src="../../img/debugger/ttd_record.png" width="600px">

- In the "TTD Record" dialog, configure the recording as you wish:
- Executable Path: the path of the executable to trace
- Working Directory: the working directory to launch the executable in
- Command Line Arguments: the command line arguments to pass to the executable
- Trace Output Directory: the directory to write the trace. By default, it is equal to the working directory, but can be changed if necessary
- Click "Record". A UAC dialog will pop up to because the TTD recording requires Administrator privilege
- Accept the elevation. The program will be launched and recorded. Once it exits, find the trace file in the trace output directory
- In the "TTD Record" dialog, you can choose between two recording modes:
- **Launch new process**: Records a new instance of an executable from the beginning
- Executable Path: the path of the executable to trace
- Working Directory: the working directory to launch the executable in
- Command Line Arguments: the command line arguments to pass to the executable
- Start application With Recording Off: checkbox to manually control when tracing begins
- **Attach to running process**: Attaches to an already running process and starts recording
- Target Process: click "Select Process..." to choose from running processes
- Trace Output Directory: the directory to write the trace (applies to both modes)
- Click "Record". A UAC dialog will pop up because TTD recording requires Administrator privilege
- Accept the elevation. The program will be launched/attached and recorded. Once it exits, find the trace file in the trace output directory


### Record a TTD Trace in WinDbg
Expand Down
184 changes: 148 additions & 36 deletions ui/ttdrecord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@ limitations under the License.
#include "qfiledialog.h"
#include "fmt/format.h"
#include <QMessageBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFormLayout>
#include <QGroupBox>
#include <QRadioButton>
#include <QButtonGroup>
#include <QLabel>
#include "attachprocess.h"

using namespace BinaryNinjaDebuggerAPI;
using namespace BinaryNinja;
using namespace std;

TTDRecordDialog::TTDRecordDialog(QWidget* parent, BinaryView* data) :
QDialog()
QDialog(), m_selectedPid(0)
{
if (data)
m_controller = DebuggerController::GetController(data);
Expand All @@ -35,13 +43,31 @@ TTDRecordDialog::TTDRecordDialog(QWidget* parent, BinaryView* data) :

setModal(true);
QVBoxLayout* layout = new QVBoxLayout;
layout->setSpacing(0);

layout->setSpacing(10);

// Mode selection
m_launchModeRadio = new QRadioButton("Launch new process", this);
m_attachModeRadio = new QRadioButton("Attach to running process", this);
m_modeButtonGroup = new QButtonGroup(this);
m_modeButtonGroup->addButton(m_launchModeRadio, 0);
m_modeButtonGroup->addButton(m_attachModeRadio, 1);
m_launchModeRadio->setChecked(true);

connect(m_modeButtonGroup, QOverload<int>::of(&QButtonGroup::buttonClicked),
this, &TTDRecordDialog::onModeChanged);

QVBoxLayout* modeLayout = new QVBoxLayout;
modeLayout->addWidget(m_launchModeRadio);
modeLayout->addWidget(m_attachModeRadio);

// Launch mode group
m_launchGroup = new QGroupBox("Launch Settings", this);
QVBoxLayout* launchLayout = new QVBoxLayout;

m_pathEntry = new QLineEdit(this);
m_pathEntry->setMinimumWidth(800);
m_pathEntry->setMinimumWidth(600);
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);
Expand All @@ -61,6 +87,48 @@ TTDRecordDialog::TTDRecordDialog(QWidget* parent, BinaryView* data) :
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);

launchLayout->addWidget(new QLabel("Executable Path"));
launchLayout->addLayout(pathEntryLayout);
launchLayout->addWidget(new QLabel("Working Directory"));
launchLayout->addLayout(workingDirLayout);
launchLayout->addWidget(new QLabel("Command Line Arguments"));
launchLayout->addWidget(m_argumentsEntry);
launchLayout->addWidget(new QLabel("Start application With Recording Off"));
launchLayout->addWidget(m_launchWithoutTracing);

m_launchGroup->setLayout(launchLayout);

// Attach mode group
m_attachGroup = new QGroupBox("Attach Settings", this);
QVBoxLayout* attachLayout = new QVBoxLayout;

m_selectedProcessDisplay = new QLineEdit(this);
m_selectedProcessDisplay->setReadOnly(true);
m_selectedProcessDisplay->setPlaceholderText("No process selected");

m_selectProcessButton = new QPushButton("Select Process...", this);
connect(m_selectProcessButton, &QPushButton::clicked, this, &TTDRecordDialog::selectProcess);

QHBoxLayout* processLayout = new QHBoxLayout;
processLayout->addWidget(m_selectedProcessDisplay);
processLayout->addWidget(m_selectProcessButton);

attachLayout->addWidget(new QLabel("Target Process"));
attachLayout->addLayout(processLayout);

m_attachGroup->setLayout(attachLayout);
m_attachGroup->setEnabled(false);

// Common settings
m_outputDirectory = new QLineEdit(this);
auto* outputDirSelector = new QPushButton("...", this);
outputDirSelector->setMaximumWidth(30);
connect(outputDirSelector, &QPushButton::clicked, [&]() {
Expand All @@ -70,31 +138,11 @@ TTDRecordDialog::TTDRecordDialog(QWidget* parent, BinaryView* data) :
m_outputDirectory->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);

// Button layout
QHBoxLayout* buttonLayout = new QHBoxLayout;
buttonLayout->setContentsMargins(0, 0, 0, 0);

Expand All @@ -108,7 +156,12 @@ TTDRecordDialog::TTDRecordDialog(QWidget* parent, BinaryView* data) :
buttonLayout->addWidget(cancelButton);
buttonLayout->addWidget(acceptButton);

layout->addLayout(contentLayout);
// Main layout
layout->addLayout(modeLayout);
layout->addWidget(m_launchGroup);
layout->addWidget(m_attachGroup);
layout->addWidget(new QLabel("Trace Output Directory"));
layout->addLayout(outputLayout);
layout->addStretch(1);
layout->addSpacing(10);
layout->addLayout(buttonLayout);
Expand All @@ -123,16 +176,60 @@ TTDRecordDialog::TTDRecordDialog(QWidget* parent, BinaryView* data) :
}
m_launchWithoutTracing->setChecked(false);

setFixedSize(QDialog::sizeHint());

CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
}


void TTDRecordDialog::onModeChanged()
{
bool isLaunchMode = m_launchModeRadio->isChecked();
m_launchGroup->setEnabled(isLaunchMode);
m_attachGroup->setEnabled(!isLaunchMode);
}


void TTDRecordDialog::selectProcess()
{
AttachProcessDialog dialog(this, m_controller);
if (dialog.exec() == QDialog::Accepted)
{
m_selectedPid = dialog.GetSelectedPid();
if (m_selectedPid > 0)
{
m_selectedProcessDisplay->setText(QString("PID: %1").arg(m_selectedPid));
}
}
}


void TTDRecordDialog::apply()
{
// Validate output directory
if (m_outputDirectory->text().isEmpty())
{
QMessageBox::critical(this, "Invalid Configuration", "Please specify a trace output directory.");
return;
}

// Mode-specific validation
if (m_launchModeRadio->isChecked())
{
if (m_pathEntry->text().isEmpty())
{
QMessageBox::critical(this, "Invalid Configuration", "Please specify an executable path for launch mode.");
return;
}
}
else
{
if (m_selectedPid == 0)
{
QMessageBox::critical(this, "Invalid Configuration", "Please select a process to attach to.");
return;
}
}

DoTTDTrace();

accept();
}

Expand Down Expand Up @@ -197,11 +294,25 @@ void TTDRecordDialog::DoTTDTrace()
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());
std::string ttdCommandLine;

if (m_launchModeRadio->isChecked())
{
// Launch mode - existing functionality
ttdCommandLine = fmt::format("-accepteula -out \"{}\" {} -launch \"{}\" {}",
m_outputDirectory->text().toStdString(),
m_launchWithoutTracing->isChecked() ? "-tracingOff -recordMode Manual" : "",
m_pathEntry->text().toStdString(),
m_argumentsEntry->text().toStdString());
}
else
{
// Attach mode - new functionality
ttdCommandLine = fmt::format("-accepteula -out \"{}\" -attach {}",
m_outputDirectory->text().toStdString(),
m_selectedPid);
}

LogWarn("TTD tracer cmd: %s %s", ttdRecorder.c_str(), ttdCommandLine.c_str());

SHELLEXECUTEINFOA info = {0};
Expand All @@ -210,7 +321,8 @@ void TTDRecordDialog::DoTTDTrace()
info.lpVerb = "runas";
info.lpFile = ttdRecorder.c_str();
info.lpParameters = ttdCommandLine.c_str();
info.lpDirectory = m_workingDirectoryEntry->text().toStdString().c_str();
info.lpDirectory = m_launchModeRadio->isChecked() ?
m_workingDirectoryEntry->text().toStdString().c_str() : nullptr;
info.nShow = SW_NORMAL;
bool ret = ShellExecuteExA(&info);
if (ret == FALSE)
Expand Down
18 changes: 18 additions & 0 deletions ui/ttdrecord.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ limitations under the License.
#include <QComboBox>
#include <QFormLayout>
#include <QCheckBox>
#include <QRadioButton>
#include <QButtonGroup>
#include <QGroupBox>
#include "inttypes.h"
#include "binaryninjaapi.h"
#include "viewframe.h"
Expand All @@ -30,6 +33,9 @@ limitations under the License.

using namespace BinaryNinjaDebuggerAPI;

// Forward declare the attach process dialog
class AttachProcessDialog;

class TTDRecordDialog : public QDialog
{
Q_OBJECT
Expand All @@ -41,6 +47,16 @@ class TTDRecordDialog : public QDialog
QLineEdit* m_argumentsEntry;
QLineEdit* m_outputDirectory;
QCheckBox* m_launchWithoutTracing;

// New UI elements for attach mode
QRadioButton* m_launchModeRadio;
QRadioButton* m_attachModeRadio;
QButtonGroup* m_modeButtonGroup;
QGroupBox* m_launchGroup;
QGroupBox* m_attachGroup;
QPushButton* m_selectProcessButton;
QLineEdit* m_selectedProcessDisplay;
uint32_t m_selectedPid;

public:
TTDRecordDialog(QWidget* parent, BinaryView* data);
Expand All @@ -49,4 +65,6 @@ class TTDRecordDialog : public QDialog

private Q_SLOTS:
void apply();
void onModeChanged();
void selectProcess();
};