This is a geoguesser exploit that chooses the hidden location everytime 100% accuracy.
- Install Tampermonkey extension on google chrome
- Create a new script
- Copy and paste the script from this repository into tampermonkey script
- Enable the script
- Press 1 and reload the page for the guess to be made!
I couldn't beat my friends in geoguesser so I wanted to find out how to win everytime. As I thought about the fact that geoguesser is a web application which means that every time you load into the game it must make some request to the server for information. So, I looked at my trusty chrome developer tools and checked the network tab. To my surprise I found a request that had the following:
It seemed like geoguesser was using the google maps API for peopole to play the game. The response included a bunch of numbers that seemed to be coordinates for the location. Knowing this I could manually already know where to put the marker for the map by typing it up on google maps. But I wanted to do more, I want it to be fully automatic.
So I tried making a proxy using an express server. This approach would you a MITM proxy to intercept the incoming requests from geoguesser and my computer. However, I was not able to do it. Instead I used userscripts. They are basically small pieces of code that are executed by the web browser. I used one of the most popular userscripts Tampermonkey. To intercept the calls with userscripts I had to use XMLHttpRequeset. It allowed me to retrieve data from the website without having to do a full page refresh.
So using the request before here is how I got the exact coordinates:
let session = {
"token": "",
"lat": 0,
"lng": 0,
"timedOut": false
}
var originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
console.log(method);
console.log(url);
// Intercepting Google Maps API for coordinates
if (url.startsWith('https://maps.googleapis.com/$rpc/google.internal.maps.mapsjs.v1.MapsJsInternalService/GetMetadata')) {
this.addEventListener('load', function () {
let interceptedResult = this.responseText;
const pattern = /-?\d+\.\d+,-?\d+\.\d+/g;
let match = interceptedResult.match(pattern)[0];
let split = match.split(",");
let lat = Number.parseFloat(split[0]);
let lng = Number.parseFloat(split[1]);
session.lat = lat;
session.lng = lng;
session.token = extractGameIdFromUrl();
session.lat = lat;
session.lng = lng;
});
}
// Call the original open function
return originalOpen.apply(this, arguments);
};
The code snippet above declares a global variable called session that will be used throughout the game. Then, when geoguesser calls the Google Maps API we will decode the message from the exact coordinates we want and store it in out session object.
The next step that was challenging was understanding how to place the marker. I found out that whenever geoguesser wants to make a guess it uses the following API request:
It also needs a payload of:
{token: "qfI6G8S6uWzl6qeo", lat: -3.1279349737285282, lng: 46.60165472021482, timedOut: false}
From this I basically just had to find out how to do the following:
- Get the game ID
- Send a POST request with the API route with the payload from before
- Include auth in my request
To find the game ID I just realised that it's part of the url for every game you play:
function extractGameIdFromUrl() {
const urlPath = window.location.pathname;
const pathSegments = urlPath.split("/"); // Split the URL path by '/'
// Assuming the game ID is always the last segment of the URL path after '/game/'
const gameIdIndex = pathSegments.findIndex(segment => segment === "game") + 1;
// Check if the game ID index is valid and return the game ID
if (gameIdIndex > 0 && gameIdIndex < pathSegments.length) {
return pathSegments[gameIdIndex];
}
return null; // Return null if the game ID is not found
}
This function just extracts the game ID from the URL
Then for placing the marker:
function placeMarker() {
const { token, lat, lng } = session; // Extracting the required information from your object
// URL of the GeoGuessr API endpoint for placing a marker (this is a placeholder)
const endpointUrl = `https://www.geoguessr.com/api/v3/games/${token}`;
// Create a new instance of XMLHttpRequest
var xhr = new XMLHttpRequest();
// Open a new connection, using the POST request on the URL endpoint
xhr.open("POST", endpointUrl, true);
xhr.withCredentials = true; // To include cookies in cross-origin requests
// Set up the request headers as needed
xhr.setRequestHeader("Content-Type", "application/json");
// Include additional headers as necessary, e.g., for authentication
// Define what happens on successful data submission
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
// Parse the JSON response
var response = JSON.parse(xhr.responseText);
console.log('Marker placed successfully:', response);
// Handle the response data here
} else {
// Handle errors here
console.error('Error placing marker:', xhr.statusText);
}
};
// Define what happens in case of an error
xhr.onerror = function () {
console.error('Request failed');
};
let payload = JSON.stringify(session);
// Send the request with the JSON body
xhr.send(payload);
}
I used XMLHttpRequeset again to make the post request. I just used the token I found before and put it in the API url. Then, to ensure It contained the necessary cookies I set xhr.withCredentials = true;
which includes the necessary headers for authentication.