Search code examples
c++linuxwxwidgetschild-processptrace

Linux Process GUI freezes when attached


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);

Solution

  • 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.