2026.05.31 13:43
yesterday, for the first time, I managed to get an AI to successfully hack a game

Yesterday, for the first time, I managed to get an AI to successfully hack a game at my request. Here is how we did it.
There is this online gaming website on the Internet, https://www.crazygames.com/ . I've been trying to hack these games for a long time, but I only succeeded with a few. Yesterday, I thought I'd take one of the games and have the AI hack it. I chose https://www.crazygames.com/game/nuts-puzzle-sort-by-color as my victim. I booted up a certain AI agent that uses various models under the hood (it chooses them itself), but usually Claude models. I played the game for a bit in Chrome, then saved all requests and responses from the Network tab in DevTools as a HAR file, gave this file to the agent, and told it to figure something out. It spent a long time tinkering, giving me snippets of JavaScript to run from the DevTools console, while I kept telling it what I saw in this or that tab. Finally, the AI wrote a piece of code for me that allows changing the number of coins in the game. This piece of code is tailored for a programmer who knows their way around a computer and understands what they are doing. This is how you use it:
First, I go to https://www.crazygames.com/game/nuts-puzzle-sort-by-color in the browser and play for a bit to get past the initial tutorial (I played enough to reach 120 points, meaning those coin things).
Then, in DevTools, I go to the "Console" tab and select which iframe I want to operate on from the dropdown. By default, the "top" iframe is selected; we need to choose "nuts-puzzle-sort-by-color.game-files.crazygames.com".
I paste this snippet into the console and run it.
It asks me how many points I have, and then searches if that number is sitting somewhere on the Unity heap. It states how many occurrences it found. I then have a choice: either overwrite all those locations with a new number, or not just yet. On the first run, it will probably find over a hundred occurrences, so it might be better not to overwrite them right away to avoid crashing the game. Next, I play a bit further so that the number of points changes, run the piece of code again, and tell it how many points I have now. It scans only those memory locations that were found previously, looking for the new number there. I keep doing this until it finds just a few locations (usually two runs are enough), then I tell it to overwrite, play for a moment (so that the number from the variable updates on the screen), and bam!
It works for me (tested in Chrome). I also used the same snippet (exactly the same one) to change the level number. I wonder if it would work in other Unity-based games too. I guess so? After all, searching for variables on the heap is searching for variables on the heap.
In any case, the fact that an AI hacked a game I told it to hack is a significant milestone.
(function() {
    // Check if we're in the right iframe
    if (typeof unityGameInstance === 'undefined') {
        console.error("❌ Wrong iframe! In DevTools console, click the dropdown at the top-left (shows 'top') and select the iframe with 'nuts-puzzle-sort-by-color.game-files.crazygames.com' or similar game URL. Then run this snippet again.");
        return;
    }
 
    let view = new DataView(unityGameInstance.Module.HEAPU8.buffer);
    let len = unityGameInstance.Module.HEAPU8.length;
 
    // If we have narrowed candidates from a previous run, offer to search within them
    if (window._coinCandidates && window._coinCandidates.length > 0) {
        let choice = prompt(
            "Found " + window._coinCandidates.length + " saved candidates from last run.\n\n" +
            "1 = Search NEW value only within saved candidates (to narrow down)\n" +
            "2 = Start fresh (full scan)\n" +
            "3 = Overwrite ALL saved candidates with a new value"
        );
 
        if (choice === "1") {
            let newVal = parseInt(prompt("What is the CURRENT coins value you see in the game?"));
            if (isNaN(newVal)) return console.log("Cancelled.");
            let stillMatch = window._coinCandidates.filter(off => view.getInt32(off, true) === newVal);
            console.log("🔍 " + stillMatch.length + " of " + window._coinCandidates.length + " candidates still have value " + newVal);
            window._coinCandidates = stillMatch;
            if (stillMatch.length === 0) {
                console.log("No matches. Run again and start fresh (option 2).");
                return;
            }
            if (stillMatch.length <= 10) {
                stillMatch.forEach(off => console.log("  offset " + off + " = " + view.getInt32(off, true)));
            }
            let action = prompt(stillMatch.length + " candidates remain.\n\n1 = Overwrite all with new value\n2 = Keep narrowing (play more, change coins, run again)");
            if (action === "1") {
                let setTo = parseInt(prompt("Set coins to:"));
                if (isNaN(setTo)) return console.log("Cancelled.");
                for (let off of stillMatch) view.setInt32(off, setTo, true);
                console.log("✅ Set " + setTo + " at " + stillMatch.length + " locations. Check in the game!");
            } else {
                console.log("👍 Saved " + stillMatch.length + " candidates. Play until coins change, then run again.");
            }
            return;
        } else if (choice === "3") {
            let setTo = parseInt(prompt("Set coins to:"));
            if (isNaN(setTo)) return console.log("Cancelled.");
            let count = 0;
            for (let off of window._coinCandidates) {
                view.setInt32(off, setTo, true);
                count++;
            }
            console.log("✅ Set " + setTo + " at " + count + " locations. Check in the game!");
            return;
        }
        // choice === "2" falls through to fresh scan
    }
 
    // Fresh scan
    let coins = parseInt(prompt("How many coins do you see in the game right now?"));
    if (isNaN(coins)) return console.log("Cancelled.");
 
    let found = [];
    for (let i = 0; i < len - 4; i += 4) {
        if (view.getInt32(i, true) === coins) {
            found.push(i);
        }
    }
    console.log("🔍 Found " + found.length + " locations with value " + coins);
 
    // Filter - close to a small number (1-20) which is likely current_win/level
    let narrow = found.filter(off => {
        for (let j = off - 64; j <= off + 64; j += 4) {
            if (j !== off && j >= 0 && j < len - 4) {
                let v = view.getInt32(j, true);
                if (v >= 1 && v <= 20) return true;
            }
        }
        return false;
    });
 
    let targets = narrow.length > 0 && narrow.length < 500 ? narrow : found;
    console.log("📋 After filtering: " + targets.length + " candidates" + (targets === narrow ? " (near small ints)" : " (unfiltered)"));
 
    window._coinCandidates = targets;
 
    let action = prompt(
        targets.length + " candidates found.\n\n" +
        "1 = Overwrite ALL with new value now\n" +
        "2 = Save candidates & narrow down later (play until coins change, then run again)"
    );
 
    if (action === "1") {
        let setTo = parseInt(prompt("Set coins to:"));
        if (isNaN(setTo)) return console.log("Cancelled.");
        for (let off of targets) view.setInt32(off, setTo, true);
        console.log("✅ Set " + setTo + " at " + targets.length + " locations. Check in the game!");
    } else {
        console.log("👍 Saved " + targets.length + " candidates in window._coinCandidates. Play until coins change, then run this snippet again.");
    }
})();


comments:

nickname:

enter digit "four": (this is a spam protection)

offensive comments or those I don't like will be deleted


back to homepage

RSS