Build a debugger in 5 minutes (5/5)

Let’s tap into the internals of native processes with our own custom-made debugger.

Ole André Vadla Ravnås
4 min readJun 22, 2014

Collect IP addresses

Let’s improve our agent to only report IP addresses to us, and only IPs we haven’t already seen.

Open “agent.js” and add the following just before the call to “Module.enumerateExports”:

var ips = {};

Next, replace the “send()” call after “Socket.peerAddress()” with the following:

if (!ips[address.ip]) {
ips[address.ip] = true;
send({
name: "new-ip-address",
payload: address.ip
});
}

This means that our agent now keeps track of IP addresses it’s seen, and only notifies us when a new one has been discovered. Sweet!

View diff

Resolve IP addresses to geo coordinates

In case you’re still wondering why we named our project “geoshark”, then this is the moment of truth. Open “geoshark.qml” and add some extra logic at the end of “onMessage”:

if (object.type === "send") {
var stanza = object.payload;
if (stanza.name === "new-ip-address") {
var ip = stanza.payload;
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
var location = JSON.parse(xhr.responseText);
messages.append("Resolved " + ip +
" to " + JSON.stringify(location) + "\n");
}
};
xhr.open("GET", "http://freegeoip.net/json/" + ip);
xhr.send();
}
}

Let’s take it for a ride:

View diff

Plot them on a map

It’s time to make our debugger awesome. We will use the Google Maps JavaScript SDK in a “WebView” and drop markers as IP locations are discovered.

Start by adding an import for the “WebView” itself:

import QtWebKit.experimental 1.0

Replace the “TextArea” object with a “ColumnLayout” that wraps it and adds a “WebView”:

ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
WebView {
id: map
Layout.fillWidth: true
Layout.fillHeight: true
url: Qt.resolvedUrl("./map.html")
function addMarker(ip, lat, lng) {
map.experimental.evaluateJavaScript(
"addMarker(\"" + ip + "\", " +
lat + ", " + lng + ");"
);
}
}
TextArea {
id: messages
Layout.fillWidth: true
height: 200
readOnly: true
}
}

Next we’ll wire up the geo logic to add a marker:

map.addMarker(ip, location.latitude, location.longitude);

Add this right after these two statements:

var location = JSON.parse(xhr.responseText);
messages.append("Resolved " + ip +
" to " + JSON.stringify(location) + "\n");

Finally, create a file “map.html” containing:

<!DOCTYPE html>
<html>
<head>
<meta name="viewport"
content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map-canvas { height: 100% }
</style>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY">
</script>
<script type="text/javascript">
var map = null;
var pending = [];
function initialize() {
var mapOptions = {
center: new google.maps.LatLng(
59.9155441,
10.7154132),
zoom: 3
};
map = new google.maps.Map(
document.getElementById("map-canvas"),
mapOptions);
pending.splice(0, pending.length).forEach(
function (f) { f(); }
);
}
function addMarker(ip, lat, lng) {
if (map === null) {
pending.push(function () {
addMarker(ip, lat, lng);
});
return;
}
var position = new google.maps.LatLng(lat, lng);
map.panTo(position);
var marker = new google.maps.Marker({
position: position,
title: ip,
animation: google.maps.Animation.DROP
});
marker.setMap(map);
}
google.maps.event.addDomListener(window, "load",
initialize);
</script>
</head>
<body>
<div id="map-canvas" />
</body>
</html>

Replace YOUR_API_KEY with a valid Google Maps JavaScript API key.

Now, launch a web browser and attach to it. Navigate to a website while watching the map:

View diff

Add remote debugging (bonus section)

Wouldn’t it be cool to be able to attach to processes on your iPhone or Android device? Or maybe another desktop machine? Let’s do it.

Start by adding a “DeviceListModel” just above our “ProcessListModel”:

DeviceListModel {
id: deviceModel
}

Next, let’s beef up our “ProcessListModel”:

ProcessListModel {
id: processModel
device: devices.currentRow !== -1 ?
deviceModel.get(devices.currentRow) : null
onError: {
errorDialog.text = message;
errorDialog.open();
}
}

So now we no longer hard-code the device to Frida.localSystem. Also, we throw in some error-handling in case there’s a problem listing processes on a remote device. We will add a devices “TableView” next. Replace your processes “TableView” with the following:

ColumnLayout {
Layout.fillHeight: true
spacing: 0
TableView {
id: devices
TableViewColumn {
role: "icon"
width: 16
delegate: Image {
source: styleData.value
fillMode: Image.Pad
}
}
TableViewColumn { role: "name"; title: "Name";
width: 100 }
model: deviceModel
}
Item {
width: processes.width
Layout.fillHeight: true
TableView {
id: processes
height: parent.height
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: {
deviceModel.get(devices.currentRow).inject(
script, processModel.get(currentRow).pid);
}
}
BusyIndicator {
anchors.centerIn: parent
running: processModel.isLoading
}
}
}

Because it may take slightly longer to enumerate processes remotely, we added a “BusyIndicator” overlayed on the processes “TableView”. Also, we use the currently selected device in our onActivated handler.

It’s showtime. You now have three options. Either install Frida on your jailbroken iOS device (instructions), rooted Android device (instructions), or run frida-server somewhere and create an SSH tunnel to it from localhost:27042. For iOS you’ll see your devices popping up as you plug in the USB cable, and for Android and remote devices you’ll have to make sure localhost:27042 is forwarded to the host where frida-server is running. Pick the device “Local TCP” in those cases.

Ok, let’s attach to the Yo app running on my iPhone, shall we?

Debugging, Yo!

View diff

That concludes the fifth minute of our five-minute tutorial. Now imagine what you could do in one hour! ;-)

Questions? Shoot me an email at oleavr [at] gmail [dot] com, or drop by #Frida on irc.freenode.net.

Previous part

--

--

Ole André Vadla Ravnås

Co-Founder @soundrop, reverse-engineer, author of Frida, oSpy, libmimic and JBLinux.