Skip to content

Instantly share code, notes, and snippets.

@oleavr
Last active July 6, 2021 19:04
Show Gist options
  • Select an option

  • Save oleavr/51066491b6961b608fb38fb1fb971dd3 to your computer and use it in GitHub Desktop.

Select an option

Save oleavr/51066491b6961b608fb38fb1fb971dd3 to your computer and use it in GitHub Desktop.

Revisions

  1. oleavr renamed this gist Aug 30, 2018. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. oleavr created this gist Aug 30, 2018.
    17 changes: 17 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,17 @@
    # Build

    npm install

    # Run

    $ frida QuakeSpasm --enable-jit -l _agent.js
    $ curl -s http://localhost:1337/stats | jq
    $ curl -s -X POST http://localhost:1337/attack | jq

    # Rebuild

    npm run build

    # Iterate

    npm run watch
    92 changes: 92 additions & 0 deletions agent.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,92 @@
    import * as Koa from "koa";
    import * as Router from "koa-router";

    const app = new Koa();
    const router = new Router();

    enum Stat {
    Health = 0,
    Shells = 6,
    }

    interface Stats {
    health: number;
    shells: number;
    }

    router
    .get("/stats", async (ctx, next) => {
    ctx.body = await readStats();
    })
    .post("/attack", async (ctx, next) => {
    await attack();
    ctx.body = {};
    });

    app
    .use(router.routes())
    .use(router.allowedMethods())
    .listen(1337);

    const clientState = importSymbol("cl");
    const attackDown: any = new NativeFunction(importSymbol("IN_AttackDown"), "void", []);
    const attackUp: any = new NativeFunction(importSymbol("IN_AttackUp"), "void", []);

    function readStats(): Promise<Stats> {
    return perform(() => {
    return {
    health: readStat(Stat.Health),
    shells: readStat(Stat.Shells),
    };
    });
    }

    function readStat(stat: Stat): number {
    return Memory.readInt(clientState.add(28 + stat * 4));
    }

    async function attack(): Promise<void> {
    await perform(() => {
    attackDown();
    });
    await sleep(50);
    await perform(() => {
    attackUp();
    });
    }

    function sleep(duration: number): Promise<void> {
    return new Promise(resolve => {
    setTimeout(() => { resolve(); }, duration);
    });
    }

    type PendingWork = () => any;

    const pending: PendingWork[] = [];

    function perform<T>(f: () => T): Promise<T> {
    return new Promise((resolve, reject) => {
    pending.push(() => {
    try {
    const result = f();
    resolve(result);
    } catch (e) {
    reject(e);
    }
    });
    });
    }

    Interceptor.attach(importSymbol("IN_SendKeyEvents"), {
    onEnter() {
    while (pending.length > 0) {
    const f = pending.shift();
    f();
    }
    }
    });

    function importSymbol(name: string): NativePointer {
    return Module.findExportByName("QuakeSpasm", name);
    }
    20 changes: 20 additions & 0 deletions package.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,20 @@
    {
    "name": "quake-rest-api",
    "version": "1.0.0",
    "description": "Quake REST API",
    "private": true,
    "main": "agent/index.ts",
    "scripts": {
    "prepare": "npm run build",
    "build": "frida-compile agent/index.ts -o _agent.js -x",
    "watch": "frida-compile agent/index.ts -o _agent.js -x -w"
    },
    "devDependencies": {
    "@types/koa": "^2.0.46",
    "@types/koa-router": "^7.0.31",
    "frida-compile": "^6.0.0",
    "frida-gum-types": "^2.0.0",
    "koa": "^2.5.2",
    "koa-router": "^7.4.0"
    }
    }