Don't want to miss out on Elixir news? Subscribe to ElixirPulse!

How I jailbroke the language learning app that sucked

During Lockdown™ 2020 I was one of the unlucky essential workers making my way to the office daily.

My commute was about an hour in total (30 minutes there, 30 minutes back). My playlists were getting stale, and the true crime podcast craze just didn’t resonate with me. Listening to stories about guys murdering their wives just wasn’t the way I wanted to start my day.

I wanted to put my hour of time towards something a little more productive. Like many people during the pandemic I decided to try picking up a second language. I settled on German because I had taken smatterings of Spanish courses over the years and it just didn’t really interest me to take any more. German had always intrigued me and I figured an hour a day’s practice, consistently, could probably go a pretty long ways!

Anyway, when you google “how to learn language X”, the standard advice tends to be along the lines of “Download Duolingo, make a bunch of flashcards, and read a bunch in the target language.”

And while I could do all of these things while driving to work, other drivers might take issue with it. I needed another way.

Enter Michel Thomas German. It’s an audio-only course where rote memorization and note-taking is actually discouraged. Instead, you learn by pausing the audio and translating the spoken phrases into the target language (German, in my case).

It was perfect: I didn’t need to stare at my phone, I didn’t have to memorize a bunch of flashcards, and I could listen for around an hour a day. I was set.

What does this have to do with tech?

In order to use the course, I had to download the Michel Thomas app. That’s no big deal. What is a big deal is that the app sucks.

Every time Michel Thomas says a phrase (in English) there is a pause of about two or three seconds. This is where you, the listener, are supposed to pause the audio track, think for a second, and attempt to guess the phrase in the target language. Then, you un-pause and Michel Thomas says the correct phrase often followed by a quick explanation.

The issue with the app was that every time I paused the audio there was a 2 or 3 second delay before the audio actually stopped playing. Every phrase was spoiled before I had a chance to even think, so I wasn’t learning. I was just listening.

I wanted to know if this was an issue with the app or the bluetooth connection from my phone to my car stereo. I played a song from the stock Music app and discovered that it did not have this limitation - it paused almost instantly.

If I could figure out some way to get the audio files from the Michel Thomas app into the stock Music app, my commute would be saved!

How do I free the audio files?

My first thought was to jailbreak my phone and pull the files from the app’s .IPA. I’d jailbroken iPhones in the past and there were actually some jailbreak options available!

However, I didn’t particularly want to jailbreak my phone mainly because jailbreaking is a gigantic pain. I needed another way.

What I noticed is that the app would let me remove and re-download the course content whenever I wanted with the “Remove from this device” button at the bottom of the screen:

remove from device button

If I wanted to download the course again, I just had to hit the download button at the top to get all of the tracks back:

download all tracks in the app

And then I had a thought: What if I could intercept the download request and redirect the files?

Time to hack myself

mitmproxy bills itself as the “swiss-army knife for debugging” and it certainly lives up to that claim. The tool allows you to analyze, intercept, inspect, modify, and replay web traffic.

It also offers a Python API to write your own add-ons. Notably, the homepage states the API makes it possible to “modify messages, redirect traffic, visualize messages, or implement custom commands.” (emphasis mine)

So I hatched a plan:

  1. Proxy all of my phone traffic through my computer
  2. Capture the download requests in-flight
  3. Redirect the audio files to my computer before forwarding them to the phone

How to proxy

mitmproxy cannot be run on iOS so I needed some way to send my phone’s traffic through my computer.

This is actually fairly simple: Just enable HTTP(S) proxies on your computer:

mac network settings

Then set your phone’s wifi to use a proxy with your computer’s local ip address:

phone network settings

And while mitmproxy is running you can see the network requests that your phone makes!

mitmproxy flows view

In order to get HTTPS/SSL support, you need to install a certificate, a network profile, and then explicitly trust the certificate. Once that’s done you can see all of your phone’s network traffic on your computer with mitmproxy! (Unless an app uses cert pinning - which is out of scope for this article)

App Recon

Now that I had visibility into what my phone was doing, I wanted to see how and from where it was downloading the audio tracks. So I kicked off a download from the app and let mitmproxy do its thing:

german audio files download flows

The request log shows us that the process is essentially two steps:

Step one: Request a signed url from S3 (/api/GetSignedURL)

The first request hits the Papertrell API (Papertrell appears to be like Shopify for digital storefronts) to get a signed url from S3. This request returns a JSON object with a signed_url key that has the link to the actual audio file.

You can see the JSON object in the response body:

signed url detail view

Step two: Download the audio from the signed URL

The next request is just a GET to the signed url from the previous step which returns the actual mp3 file that we’re looking for:

The actual audio file download request detail view

Nice and easy (to hack)!

Armed with this info, the goal became clear: Redirect the contents of the GET request to my computer!

Redirecting requests with mitmproxy

I briefly considered just copying the signed urls from the terminal output and passing it to wget. However, there are 155 audio files, and what’s the point of having computers if we don’t make them do the dirty work for us?

Like I mentioned previously, mitmproxy has a python API for scripting exactly these sorts of workflows. So after digging around the documentation a little bit I came up with about 25 lines of python to write the request contents to disk:

import urllib
from mitmproxy import ctx

class WriteMp3ToDisk:
    def response(self, flow):
        # Grab the mp3 files
        if flow.response.headers["content-type"] == "application/mp3":
            ctx.log.info("Downloading Track")
            ctx.log.info("Flow request path:")
            ctx.log.info(flow.request.path)
            ctx.log.info("track number string:")
            ctx.log.info(getTrackName(flow.request.path))
            # Write contents to disk
            f = open(getTrackName(flow.request.path), "wb")
            f.write(flow.response.content)

def getTrackName(path):
    filename = urllib.parse.unquote(path[path.rindex('/'):])
    # includes slash, so return first char onward
    return filename[1:]

addons = [
    WriteMp3ToDisk()
]

The script is simple: Look for requests with a content-type header of application/mp3 and write the content of that request’s response to disk - with a little function to parse the track names.

The moment of truth

I erased the audio tracks from the app, fired up mitmproxy with my add-on enabled, and hit download. The app began downloading the tracks.

I checked on the directory I was redirecting the files to and:

$ ls -1 german/
01.01 Intermediate German.mp3
01.02 Intermediate German.mp3
01.03 Intermediate German.mp3
01.04 Intermediate German.mp3
01.05 Intermediate German.mp3
01.06 Intermediate German.mp3
01.07 Intermediate German.mp3
...

Jackpot.

With the audio liberated from its digital prison, I could use the stock Music app during my commute and learn a new language with a sense of smug satisfaction.

In Conclusion

Getting the audio into the regular Music app ended up being much less difficult than I expected. And, in the end, I found that it was pretty difficult to focus on driving, actively listening to the course, pausing the audio at the correct spots, and doing all of it at the same time. So, I listened to about half a dozen tracks before I shelved the idea and haven’t touched it since.

Danke fürs Lesen.