<commit_before>---
layout: post
title: "Removing Edge Magazine DRM"
---
# Removing Edge Magazine DRM

[Edge Magazine](https://www.myfavouritemagazines.co.uk/gaming/edge-magazine-subscription/) is one of the oldest
gaming magazines in the world. It is famous for the quality of its reviews, all of them available online up until
a few years ago, when their website was closed. The magazine continued its life in print and digital subscription
format only. I really liked the content they produced, so I decided to purchase a one year subscription for the
digital version.

Every time I buy some digital content online, the first thing I do is creating backup of it and making
sure that I can use it without the official client application (if it exists). Most of the
technical literature I buy comes in PDF format without DRM (InformIT, O'Reilly, No Starch Press). Amazon
Kindle books come with DRM that is easy to remove, and the same thing goes for Audible books. For the
Dark Horse Comics I wrote a little [tool](https://github.com/Metalnem/dark-horse-downloader) that converts
their proprietary format into CBZ, which is a format that every comic book reader supports. Digital
version of the Edge Magazine came with its own app, so there was no way to backup the magazines.
Application didn't have any interactivity, so I assumed that it was just a viewer for PDF, EPUB or some
other book format. If that was true, there must have been some way to extract the content from it.
First step towards that goal was to reverse the application's API.

## Reversing the API

I've written about reversing private APIs in the post
[Reversing Runtastic API]({{ site.baseurl }}{% post_url 2016-10-20-reversing-runtastic-api %}), so I won't get
into the details again. I'll just give you a tour of the API, with the annotated requests and responses.
API server is located at <https://api.futr.efs.foliocloud.net>. All requests are POST requests,
with the URL encoded parameters. Responses are JSON formatted.

The exchange starts with client sending the request to the **/createAnonymousUser/** endpoint.
Request contains hard-coded *appKey* and *secretKey* parameters.

```
appKey: RymlyxWkRBKjDKsG3TpLAQ
platform: iphone-retina
secretKey: b9dd34da8c269e44879ea1be2a0f9f7c
```

Server responds with the random ID that the client must include in all following requests.

```json
{  
  "data": {  
    "uid": "05681280014840428595874b26b8ab4f9991495580"
  }
}
```

Client sends its email address and password in the **/login/** request.

```
api_params: {"identifier":"<EMAIL>","password":"<PASSWORD>"}
uid: 05681280014840428595874b26b8ab4f9991495580
```

Server responds with the download ticket number. That's unusual response for a login request.

```json
{  
  "data": {  
    "download_ticket_no": "00649540014842485615877d5f10fdbf6313623950"
  }
}
```

This is the moment where things get interesting. When the login request is completed and
the client receives a download ticket, login process is only halfway done. Next step
is to call the **/getDownloadUrl/** endpoint with the download ticket.

```
ticket: 00649540014842485615877d5f10fdbf6313623950
uid: 05681280014840428595874b26b8ab4f9991495580
```

Server will respond with *status* field initially set to -1. After that, Client sends the getDownloadUrl
repeatedly, until the server responds with *status* set to 1. I never established<commit_msg>Fix case<commit_after>---
layout: post
title: "Removing Edge Magazine DRM"
---
# Removing Edge Magazine DRM

[Edge Magazine](https://www.myfavouritemagazines.co.uk/gaming/edge-magazine-subscription/) is one of the oldest
gaming magazines in the world. It is famous for the quality of its reviews, all of them available online up until
a few years ago, when their website was closed. The magazine continued its life in print and digital subscription
format only. I really liked the content they produced, so I decided to purchase a one year subscription for the
digital version.

Every time I buy some digital content online, the first thing I do is creating backup of it and making
sure that I can use it without the official client application (if it exists). Most of the
technical literature I buy comes in PDF format without DRM (InformIT, O'Reilly, No Starch Press). Amazon
Kindle books come with DRM that is easy to remove, and the same thing goes for Audible books. For the
Dark Horse Comics I wrote a little [tool](https://github.com/Metalnem/dark-horse-downloader) that converts
their proprietary format into CBZ, which is a format that every comic book reader supports. Digital
version of the Edge Magazine came with its own app, so there was no way to backup the magazines.
Application didn't have any interactivity, so I assumed that it was just a viewer for PDF, EPUB or some
other book format. If that was true, there must have been some way to extract the content from it.
First step towards that goal was to reverse the application's API.

## Reversing the API

I've written about reversing private APIs in the post
[Reversing Runtastic API]({{ site.baseurl }}{% post_url 2016-10-20-reversing-runtastic-api %}), so I won't get
into the details again. I'll just give you a tour of the API, with the annotated requests and responses.
API server is located at <https://api.futr.efs.foliocloud.net>. All requests are POST requests,
with the URL encoded parameters. Responses are JSON formatted.

The exchange starts with client sending the request to the **/createAnonymousUser/** endpoint.
Request contains hard-coded *appKey* and *secretKey* parameters.

```
appKey: RymlyxWkRBKjDKsG3TpLAQ
platform: iphone-retina
secretKey: b9dd34da8c269e44879ea1be2a0f9f7c
```

Server responds with the random ID that the client must include in all following requests.

```json
{  
  "data": {  
    "uid": "05681280014840428595874b2<PASSWORD>991495580"
  }
}
```

Client sends its email address and password in the **/login/** request.

```
api_params: {"identifier":"<EMAIL>","password":"<PASSWORD>"}
uid: 0<PASSWORD>014840428<PASSWORD>91495580
```

Server responds with the download ticket number. That's unusual response for a login request.

```json
{  
  "data": {  
    "download_ticket_no": "00649540014842485615877d5f10fdbf6313623950"
  }
}
```

This is the moment where things get interesting. When the login request is completed and
the client receives a download ticket, login process is only halfway done. Next step
is to call the **/getDownloadUrl/** endpoint with the download ticket.

```
ticket: 00649540014842485615877d5f10fdbf6313623950
uid: 05681280014840428595874b26b8ab4f9991495580
```

Server will respond with *status* field initially set to -1. After that, client sends the getDownloadUrl
repeatedly, until the server responds with *status* set to 1. I never established
{'fraction_non_alphanumeric': 0.06977771235094951, 'fraction_numerical': 0.058736935080229646, 'mean_word_length': 4.757627118644068, 'pattern_counts': {'":': 12, '<': 12, '<?xml version=': 0, '>': 12, 'https://': 6, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '864292', 'n_tokens_mistral': 2214, 'n_tokens_neox': 1897, 'n_words': 916}