@warlo

MITM'ing native app and replicating API calls

warlo
warlo

So today I figured I should tinker a bit with an app that I sometimes use. The app has a paywall to get notifications of specific information, but is free to use ad-hoc. I know the app is built by a couple of people as a side-project, not backed by a company – so the API's were most likely glued together without too much SSL pinning sophistication.

Started whipping up mitmweb after brew install mitmweb. Set up the proxy on my iPhone by clicking the small (i) in the WiFi settings and installed the certificate from mitm.it. Then all network calls by opening websites or apps are logged. I found the relevant app by domain with all the info I needed, and there I also found an api key. Success! (I also tried this approach on a payment app to reverse engineer their requests, but that threw errors at me – refusing me opening the app. Though actually happy about that! 😅)

I noticed that the app sends along the previous timestamp the api was queried as a query param, probably to know when to trigger push notifications or something like that. Though if I am to use this API unofficially, might be smart to replicate that behavior, don't want to raise suspicion in the logs. Something like this should do:

let timestamp = new Date().toISOString();
const fetchAPI = async () => {
    const url = `https://<url>?timestamp=${timestamp}`;
    const res = await fetch(url, ...);
    timestamp = new Date().toISOString();
}

mitmweb has a cool copy as curl command, which grabs all headers and stuff like that. So I whipped up a simple node app and replicated the curl query using fetch(url, { headers: { ...<all headers> }}).

So now we got some data, I figured I could add some cron logic. Figured I might as well do this simple and hacky, and do it inside the node process. So I found this library node-cron which was pretty nice. It was as simple as:
cron.schedule("<cron schedule>", () => { runMethod() }).
Being in the hacky fun corner, I wanted to emulate a real person not run the api call exactly at the minute / hour. That ended up with a funky cron schedule: cron.schedule("0/8 8-18 * * *", () => {}).
Meaning it runs every 8th minute between 8:00 and 18:00. That said I don't need the data that often, so to introduce some randomness I added a simple check making it actually call the API 1/20 of the times it runs. Ultimately this becomes ~4 runs on average each day. Roughly 7.5 runs an hour for 10 hours => 75. Then 75 * 1/20 = 3.75.

Never being sure of my calculations I like to whip up a shell and test. So node became the rescue.

[...new Array(75)].map(
  _ => Math.random() < 1/20
).filter(Boolean)

=> [true, true, true, true], fluctuating between 1x true and 6x true on a couple of test runs. Good enough 😂

So ultimately:

cron.schedule("0/8 8-18 * * *", () => {
  const shouldRun = Math.random() < 1 / 20;
  console.log("Running funky schedule", Date.now(), shouldRun);
  if (shouldRun) {
    fetchPrices();
  }
});

Now I have an unofficial node client and as far as I got. Might as well keep something for another time 🤥. If I ever do that.