MITM'ing native app and replicating API calls
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.