Build a debugger in 5 minutes (3/5)
Let’s tap into the internals of native processes with our own custom-made debugger.
Communicate with the remote agent
Before we can do something cool with our injected code, we need to be able to communicate with it. The code we injected is our agent, and we will communicate with it by sending and receiving JSON objects.
Let’s first add a TextArea to our GUI so we can display messages from the script. Also, a button to tell the agent to do something should come handy. But before we do that, we’ll need another import so we can position these new elements:
import QtQuick.Layouts 1.1
Just add it below the other imports.
Next, replace your “TableView” object with the following:
RowLayout {
anchors.fill: parent
spacing: 0
TableView {
id: processes
Layout.fillHeight: true
TableViewColumn {
role: "smallIcon"
width: 16
delegate: Image {
source: styleData.value
fillMode: Image.Pad
}
}
TableViewColumn { role: "pid"; title: "Pid"; width: 50 }
TableViewColumn { role: "name"; title: "Name";
width: 100 }
model: processModel
onActivated: {
Frida.localSystem.inject(script,
processModel.get(currentRow).pid);
}
}
TextArea {
id: messages
Layout.fillWidth: true
Layout.fillHeight: true
readOnly: true
}
Button {
Layout.alignment: Qt.AlignBottom
text: "Request Status"
onClicked: {
script.post({name: "request-status"});
}
}
}
We basically put our “TableView” inside a “RowLayout”, with a “TextArea” next to it, followed by a “Button”. The button will post a message to the agent when clicked. (Technically it will post a message to all instances of the script, in case you injected it into multiple processes. It’s also possible to post to one specific instance, but that’s material for a future blog post.)
So that covers displaying messages and posting them, but we also need to be able to receive them. Let’s update the “Script” object so it looks like this:
Script {
id: script
url: Qt.resolvedUrl("./agent.js")
onMessage: {
messages.append(JSON.stringify(object) + "\n");
}
}
Great. Now that we can post a message to the agent, we should modify its code so it’s able to act on that message. Append the following code to “agent.js”:
function onStanza(stanza) {
if (stanza.name === "request-status") {
var threads = [];
Process.enumerateThreads({
onMatch: function (thread) {
threads.push(thread);
},
onComplete: function () {
send({
name: "status",
payload: threads
});
}
});
}
recv(onStanza);
}
recv(onStanza);
send({
name: "hello",
payload: {
threadId: Process.getCurrentThreadId()
}
});
Let’s take it for a test-ride:
That concludes the third minute of our five-minute tutorial.