Im trying to attach(linux)/.dll inject(windows) to a given process. The code succeeds but the image freezes. The attached process itself continues to work (I added a resume).
Ive done this previously but this is the first time I try with a process(game) that has a GUI.
Is it doable in Linux or I dont have the correct approach?
#include <wx/wx.h>
#include <wx/thread.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <string>
#include <dirent.h>
#include <libgen.h>
#include <fcntl.h>
#include <signal.h> // Add this include at the top of your file
// Define new event types
wxDEFINE_EVENT(EVT_ATTACH_SUCCESS, wxThreadEvent);
wxDEFINE_EVENT(EVT_DETACH_SUCCESS, wxThreadEvent);
class WorkerThread : public wxThread {
public:
WorkerThread(wxEvtHandler *parent) : wxThread(wxTHREAD_JOINABLE), m_parent(parent), targetPid(-1) {}
~WorkerThread() override {
if (targetPid != -1) {
if (ptrace(PTRACE_DETACH, targetPid, nullptr, nullptr) == -1) {
wxLogError("Failed to detach from process: %s", strerror(errno));
} else {
wxLogMessage("Successfully detached from process %ld", targetPid);
}
wxQueueEvent(m_parent, new wxThreadEvent(wxEVT_THREAD, EVT_DETACH_SUCCESS));
}
}
wxThread::ExitCode Entry() override {
std::string processName = "dura";
int pipefd[2];
if (pipe(pipefd) == -1) {
wxLogError("pipe failed: %s", strerror(errno));
return (wxThread::ExitCode)0;
}
pid_t childPid = fork();
if (childPid == -1) {
wxLogError("fork failed: %s", strerror(errno));
return (wxThread::ExitCode)0;
}
if (childPid == 0) { // Child process
close(pipefd[0]); // Close read end
dup2(pipefd[1], STDOUT_FILENO); // Redirect stdout to pipe
execlp("pgrep", "pgrep", processName.c_str(), nullptr);
wxLogError("execlp failed: %s", strerror(errno));
exit(EXIT_FAILURE);
} else { // Parent process
close(pipefd[1]); // Close write end
int status;
waitpid(childPid, &status, 0);
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { // Process found
char buf[32];
read(pipefd[0], buf, sizeof(buf) - 1);
pid_t targetPid = strtoul(buf, nullptr, 10);
if (ptrace(PTRACE_ATTACH, targetPid, nullptr, nullptr) == -1) {
wxLogError("Failed to attach to process: %s", strerror(errno));
return (wxThread::ExitCode)0;
} else {
this->targetPid = targetPid;
kill(targetPid, SIGCONT); // Add this line to send the SIGCONT signal
wxLogMessage("Successfully attached to process %ld", targetPid);
wxThreadEvent* event = new wxThreadEvent(wxEVT_THREAD, EVT_ATTACH_SUCCESS);
event->SetString("Successfully attached to process " + std::to_string(targetPid));
wxQueueEvent(m_parent, event);
}
// Wait for the process to stop
waitpid(targetPid, nullptr, 0);
while(!TestDestroy()) {
wxThread::This()->Sleep(1000); // 1 second sleep
}
} else if (WIFEXITED(status)) { // Process not found or error executing pgrep
wxLogError("Process %s not found or error executing pgrep.", processName.c_str());
}
}
return (wxThread::ExitCode)0;
}
private:
wxEvtHandler *m_parent;
pid_t targetPid;
};
class MyFrame : public wxFrame {
public:
MyFrame() : wxFrame(NULL, wxID_ANY, "Hello wxWidgets") {
m_panel = new wxPanel(this);
m_attachBtn = new wxButton(m_panel, wxID_ANY, "Attach");
m_detachBtn = new wxButton(m_panel, wxID_ANY, "Detach");
m_textCtrl = new wxTextCtrl(m_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE | wxTE_READONLY);
wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(m_attachBtn, 0, wxEXPAND | wxALL, 5);
sizer->Add(m_detachBtn, 0, wxEXPAND | wxALL, 5);
sizer->Add(m_textCtrl, 1, wxEXPAND | wxALL, 5);
m_panel->SetSizer(sizer);
Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MyFrame::OnAttach, this, m_attachBtn->GetId());
Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MyFrame::OnDetach, this, m_detachBtn->GetId());
Bind(EVT_ATTACH_SUCCESS, &MyFrame::OnAttachSuccess, this);
Bind(EVT_DETACH_SUCCESS, &MyFrame::OnDetachSuccess, this);
m_workerThread = nullptr;
}
void OnAttach(wxCommandEvent &event) {
m_workerThread = new WorkerThread(this);
if (m_workerThread->Run() != wxTHREAD_NO_ERROR) {
wxLogError("Could not create the worker thread!");
delete m_workerThread;
m_workerThread = nullptr;
}
m_textCtrl->AppendText("Attaching to process...\n");
}
void OnDetach(wxCommandEvent &event) {
if (m_workerThread) {
m_workerThread->Delete(); // Signals the thread to exit its main loop
m_workerThread->Wait(); // Waits for the thread to exit
delete m_workerThread;
m_workerThread = nullptr;
}
}
void OnAttachSuccess(wxThreadEvent &event) {
m_textCtrl->AppendText(event.GetString() + "\n");
}
void OnDetachSuccess(wxThreadEvent &event) {
m_textCtrl->AppendText("Successfully detached from process.\n");
}
private:
wxPanel *m_panel;
wxButton *m_attachBtn;
wxButton *m_detachBtn;
wxTextCtrl *m_textCtrl;
WorkerThread *m_workerThread;
};
class MyApp : public wxApp {
public:
bool OnInit() override {
MyFrame *frame = new MyFrame();
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
You're trying to resume the attached process by sending SIGCONT
.
The ptrace
manpage describes Signal-delivery-stop:
When a (possibly multithreaded) process receives any signal except SIGKILL, the kernel selects an arbitrary thread which handles the signal ... If the selected thread is traced, it enters signal-delivery-stop. At this point, the signal is not yet delivered to the process, and can be suppressed by the tracer. If the tracer doesn't suppress the signal, it passes the signal to the tracee in the next ptrace restart request. This second step of signal delivery is called signal injection in this manual page. Note that if the signal is blocked, signal-delivery-stop doesn't happen until the signal is unblocked, with the usual exception that SIGSTOP can't be blocked.
Signal-delivery-stop is observed by the tracer as waitpid(2) returning with WIFSTOPPED(status) true, with the signal returned by WSTOPSIG(status). If the signal is SIGTRAP, this may be a different kind of ptrace-stop; see the "Syscall-stops" and "execve" sections below for details.
So all you've done is queued up a signal to a process which is still stopped by the original PTRACE_ATTACH
. You should be able to confirm this by calling waitpid
after sending the signal, as described in the manpage.
If you just want the attached process to keep running without supervision, you should use PTRACE_CONT
instead.