From 142b79d3973c8ac35765b536c573907ace00f069 Mon Sep 17 00:00:00 2001 From: Zaephor Date: Wed, 12 Oct 2016 14:19:01 -0700 Subject: [PATCH] Added HTTP MITM Proxy and pac renderer --- .gitignore | 2 + README.md | 12 +++++ cfg.js.example | 9 ++++ package.json | 5 +- src/http-proxy.js | 125 ++++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 2 + src/process.js | 1 + src/request.js | 3 ++ src/setup.js | 4 +- 9 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 src/http-proxy.js diff --git a/.gitignore b/.gitignore index 3763473..f16233d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ logs/ cfg.js .save npm-debug.* +.idea/ +ssl/ diff --git a/README.md b/README.md index f6569aa..7a03029 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,18 @@ MYSQL_PASSWORD: "", The required database tables get generated automatically. +## Proxy Setup +Newer versions of Pokemon Go application have negated the custom endpoint support of [rastapasta](https://github.com/rastapasta)'s [Pokemon Go Xposed](https://github.com/rastapasta/pokemon-go-xposed/releases) app. But the certificate pinning bypass still appears functional. + +Based on a rough dissection of [rastapasta](https://github.com/rastapasta)'s [pokemon-go-mitm](https://github.com/rastapasta/pokemon-go-mitm) for reference, an MITM proxy server, PAC generator and cert downloader have all been added. + +Multiple client connection/configuration options are available: + + * (Android) Settings->Wi-Fi->[Connect]->Advanced Options->Proxy->Proxy Auto-Config `http://:/proxy/pac` + * (Android) Settings->Wi-Fi->[Connect]->Advanced Options->Proxy->Manual: Proxy hostname ``, Proxy port: `` (May require CA cert) + +In-case the client device needs the CA cert: `http://:/proxy/ca.(pem|crt|der)` + ## Server setup You need at minimum [Node.js](https://nodejs.org/en/) version 6.x. diff --git a/cfg.js.example b/cfg.js.example index 9022b4e..46e3248 100644 --- a/cfg.js.example +++ b/cfg.js.example @@ -31,6 +31,15 @@ export default { DEFAULT_CONSOLE_COLOR: 32, TRANSFER_ACCOUNTS: false, + // Proxy Server settings + PROXY: { + port: 3001, + forceSNI: process.env.PROXY_SNI || true, + sslCaDir: process.cwd() + '/ssl', + debug: (typeof process.env.PROXY_DEBUG === "boolean") ? process.env.PROXY_DEBUG : false, + silent: (typeof process.env.PROXY_SILENT === "boolean") ? process.env.PROXY_SILENT : true, + }, + // Server debug options DEBUG_DUMP_PATH: "logs/", DEBUG_DUMP_TRAFFIC: true, diff --git a/package.json b/package.json index b878320..babc692 100644 --- a/package.json +++ b/package.json @@ -25,14 +25,15 @@ "babel-preset-stage-0": "^6.5.0", "directory-tree": "^1.1.1", "fs-extra": "^0.30.0", + "http-mitm-proxy": "git+https://github.com/joeferner/node-http-mitm-proxy.git", "jwt-decode": "^2.1.0", "mysql": "^2.11.1", - "nodegit": "^0.16.0", "node-pogo-protos": "^1.4.0", + "nodegit": "^0.16.0", "pcrypt": "git+https://github.com/laverdet/pcrypt.git", "pogo-asset-downloader": "^0.3.1", - "pokerare": "^0.1.1", "pokemongo-protobuf": "^1.11.0", + "pokerare": "^0.1.1", "prompt": "^1.0.0", "s2-geometry": "^1.2.9", "url": "^0.11.0" diff --git a/src/http-proxy.js b/src/http-proxy.js new file mode 100644 index 0000000..368f7be --- /dev/null +++ b/src/http-proxy.js @@ -0,0 +1,125 @@ +import fs from "fs"; +import Proxy from "http-mitm-proxy"; +import DNS from "dns"; + +import print from "./print"; +import CFG from "../cfg"; + +/** + * @return {Proxy} + */ +export function createServerProxy() { + let dnsEntries = this.dns = { + domains: {}, + ips: {} + }; + + /* Quick DNS lookups */ + ['pgorelease.nianticlabs.com'].map((domain)=> { + DNS.resolve4(domain, (err, result)=> { + dnsEntries.domains[domain] = dnsEntries.ips[result[0]] = {domain: domain, ip: result[0]}; + }); + }); + + let proxy = new Proxy(); + proxy.use(Proxy.gunzip); + proxy.use(Proxy.wildcard); + + /* Proxy error handler */ + proxy.onError((ctx, err, errorKind) => { + // ctx may be null + if (errorKind !== "PROXY_TO_SERVER_REQUEST_ERROR") { + var url = (ctx && ctx.clientToProxyRequest) ? ctx.clientToProxyRequest.url : ''; + print(errorKind + ' on ' + url + ':' + err, 31); + } + }); + + /* IP<->Domain Translation */ + proxy.onConnect((req, socket, head, callback) => { + var ip; + if (!req.url.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:443/)) { + return callback(); + } + ip = req.url.split(/:/)[0]; + if (dnsEntries.ips[ip]) { + req.url = dnsEntries.ips[ip].domain + ':443'; + } + return callback(); + }); + + /* Request handler */ + proxy.onRequest((reqCtx, reqCB) => { + if (dnsEntries.domains[reqCtx.clientToProxyRequest.headers.host] && this.world.isFull()) { + print(`Server is full! Refused ${reqCtx.clientToProxyRequest.headers.host}`, 31); + return void 0; + } + var chunks = []; + reqCtx.onRequestData((ctx, chunk, callback)=> { + if (dnsEntries.domains[ctx.clientToProxyRequest.headers.host]) { + chunks.push(chunk); + } else { + callback(); + } + }); + reqCtx.onRequestEnd((ctx, callback)=> { + if (dnsEntries.domains[ctx.clientToProxyRequest.headers.host]) { + let buffer = Buffer.concat(chunks); + ctx.clientToProxyRequest.body = buffer; + this.routeRequest(ctx.clientToProxyRequest, ctx.proxyToClientResponse); + } else { + callback(); + } + }); + return reqCB(); //? Can't recall if i was supposed to disable this + }); + + /* Start server */ + proxy.listen(CFG.PROXY); + return (proxy); +} + +export function shutdownProxy() { + this.proxy.close(() => { + print("Closed proxy server!", 33); + this.closeConnection(() => { + print("Closed database connection!", 33); + print("Server shutdown!", 31); + setTimeout(() => process.exit(1), 2e3); + }); + }); +} + +export function processProxyFiles(req, res, route) { + let item = route[2]; + switch (item) { + case "pac": + print(`Sent Proxy PAC file to client`, 36); + let localIPv4 = this.getLocalIPv4(); + let response = ['function FindProxyForURL(url,host){']; + response.push("\tif(shExpMatch(host,\"(" + Object.keys(this.dns.domains).join('|') + ")\")){"); + // response.push("\t\treturn \"PROXY " + localIPv4 + ":" + CFG.PROXY.port + "\";"); + response.push("\t\treturn \"PROXY " + (CFG.LOCAL_IP || localIPv4) + ":" + CFG.PROXY.port + "\";"); + response.push("\t} else {"); + response.push("\t\treturn \"DIRECT\";"); + response.push("\t}"); + response.push("}\n"); + res.end(response.join("\n")); + break; + case "ca.crt": + case "ca.der": + case "ca.pem": + fs.readFile(CFG.PROXY.sslCaDir + "/certs/ca.pem", (error, data) => { + if (error) { + print(`Error file resolving CA file:` + error, 31); + return void 0; + } + print(`Sent Proxy Certificate file to client`, 36); + res.end(data); + }); + break; + default: + res.end(""); + break; + } + +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index dc458ed..85db00f 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,7 @@ import * as _api from "./api"; import * as _commands from "./commands"; import * as _dump from "./dump"; import * as _http from "./http"; +import * as _proxy from "./http-proxy"; import * as _setup from "./setup"; import * as _cycle from "./cycle"; import * as _request from "./request"; @@ -196,6 +197,7 @@ inherit(GameServer, _api); inherit(GameServer, _commands); inherit(GameServer, _dump); inherit(GameServer, _http); +inherit(GameServer, _proxy); inherit(GameServer, _setup); inherit(GameServer, _cycle); inherit(GameServer, _request); diff --git a/src/process.js b/src/process.js index 5b3dadc..84653f5 100644 --- a/src/process.js +++ b/src/process.js @@ -16,6 +16,7 @@ export function processCommand(cmd, data) { // Exit the server case "/exit": this.shutdown(); + this.shutdownProxy(); break; case "/kick": this.world.kickPlayer(data[0]); diff --git a/src/request.js b/src/request.js index 83bc7fd..a6aed9a 100644 --- a/src/request.js +++ b/src/request.js @@ -30,6 +30,9 @@ export function routeRequest(req, res) { case "model": this.processModelRequest(req, res, route); break; + case "proxy": + this.processProxyFiles(req,res,route); + break; case "api": if (!CFG.API_ENABLE) { print(`API is disabled! Denied API access for ${host}!`, 31); diff --git a/src/setup.js b/src/setup.js index 5bdde55..9fb9c0c 100644 --- a/src/setup.js +++ b/src/setup.js @@ -33,9 +33,11 @@ export function setup() { return void 0; } this.socket = this.createHTTPServer(); + this.proxy = this.createServerProxy(); setTimeout(this::this.cycle, 1); let localIPv4 = this.getLocalIPv4(); - print(`Server listening at ${localIPv4}:${CFG.PORT}`, 33); + print(`Server listening at ${CFG.LOCAL_IP || localIPv4}:${CFG.PORT}`, 33); + print(`Proxy Server listening at ${CFG.LOCAL_IP || localIPv4}:${CFG.PROXY.port}`, 33); resolve(); });