Skip to content
Llorenç Romà

How Not to Enforce Device-Limited Content

A travel app sold device-limited content packages protected by PDF passwords and GPS track encryption. On a rooted Android device, both the password and the tracks were sitting in cleartext in a SQLite database.

Security Research 5 min read

TL;DR: A travel content app enforced a two-device download limit on paid PDF guides and GPS tracks. On a rooted Android device, both assets were trivially recoverable from the app’s local SQLite database: the PDF password was stored in cleartext, and the GPS tracks were stored as plain GeoJSON. The vendor was notified but did not respond.

Some time ago I purchased a travel guide through a mobile app. The product consisted of a PDF guide and a set of GPS tracks for a specific region.

The business model was simple: pay for the content, download it, and use it through the app. To discourage sharing, the app enforced a maximum of two devices per purchase. Once a package had been downloaded twice, further downloads were blocked.

The idea makes sense. If you’re selling digital content, you probably don’t want customers forwarding it around indefinitely. What surprised me was how little stood between the downloaded package and the underlying files.

To be clear, this isn’t a story about compromising a backend system or accessing other users’ data. This is a story about client-side content protection and how difficult it is to make offline DRM work when the user controls the device.

Testing was performed on a rooted Google Pixel running stock Android with Magisk.

The toolset was minimal:

  • ADB — to access the app’s private data directory
  • DB Browser for SQLite — to inspect local databases
  • Frida — for runtime instrumentation
  • Burp Suite — to inspect network traffic

The app implemented certificate pinning, which Frida was used to bypass. The network traffic revealed nothing particularly interesting beyond standard authentication and content delivery flows.

The more interesting findings were entirely local.

The guide itself was delivered as a standard password-protected PDF. Opening the file outside the app prompted for a password, which the application presumably supplied automatically when rendering the document.

After pulling the app’s data directory, I did what most curious people would do: opened the SQLite database to see what was inside.

One table contained metadata about downloaded content packages. Among the fields was the PDF password itself, stored as plaintext.

content_packages
├── id
├── package_name
├── pdf_path
├── pdf_password ← cleartext
├── download_count
└── ...

Supplying that password to any standard PDF viewer opened the document immediately.

At that point, the PDF was just a normal PDF. The two-device limit no longer mattered because both the encrypted file and the key required to open it were already present on the device.

The GPS tracks lived in the same SQLite database.

Each track was stored as a GeoJSON document containing coordinates, waypoints, and metadata.

gps_tracks
├── id
├── package_id
├── track_name
├── geojson ← plaintext GeoJSON
└── ...

Exporting the rows and saving them as .geojson files made them immediately usable in GIS tools, mapping software, and GPS devices.

Unlike the PDF, there wasn’t even an encryption layer to remove. The data was already sitting in a directly usable format.

Again, the download restriction became largely irrelevant once the content had been delivered.

The underlying issue is a misplaced trust boundary.

The content protection model assumes the device is effectively a black box. On a typical unrooted phone, that’s not an unreasonable assumption. On a rooted device, it falls apart quickly.

To be fair to the developers, storing the PDF password locally is not necessarily careless design. The app is built around offline use. If someone is using the guide in a remote area with no connectivity, the application still needs to be able to open the document and display the tracks.

That creates a fundamental challenge: the content and the mechanism required to unlock it must both exist on the device.

There is no perfect solution to that problem.

A sufficiently motivated user who controls the device can eventually extract whatever the application needs to render. Root access, dynamic instrumentation, and enough patience are usually enough.

The goal therefore isn’t to make extraction impossible. The goal is to make it expensive enough that casual users don’t bother.

A few improvements could have raised the bar considerably:

  • Encrypt the database. Solutions such as SQLCipher would not prevent extraction, but they would force an attacker to recover the key rather than simply opening the database in a GUI tool.

  • Obfuscate the application. The app appeared to ship with minimal obfuscation, making it straightforward to locate relevant code paths through static analysis. Obfuscation is not security, but it does increase effort.

  • Derive secrets at runtime. Rather than storing a PDF password directly, the application could derive it from a combination of Keystore-backed secrets, installation-specific values, and server-provided material. A determined attacker could still recover it during execution, but the database alone would no longer be sufficient.

  • Encrypt content consistently. The GPS tracks were stored entirely in plaintext. Encrypting them using the same mechanism as the PDF would at least provide a consistent protection model.

None of these measures would stop a determined adversary. They simply move the attack from “open a database” to “reverse engineer an application.”

For many commercial content products, that’s often good enough.

The server-side download limit itself appeared reasonable. The problem was that once the content reached the device, the protection effectively ended there.

I would classify this primarily as a weakness in content protection rather than a traditional security vulnerability.

I was only able to access content that I had legitimately purchased. No backend systems were compromised, no authentication controls were bypassed, and no other users’ data was exposed.

The practical impact is that a purchaser with control of their device can extract and redistribute the downloaded assets, undermining the intended device-limit model.

The findings were reported through the vendor’s official contact channel.

I did not attempt to bypass payment mechanisms, access content I had not purchased, or redistribute the extracted material. Testing was limited to content associated with my own account.

As of publication, I have not received a response or confirmation that the issues have been addressed.