Skip to content

Commit

Permalink
Add dzr-db
Browse files Browse the repository at this point in the history
  • Loading branch information
yne committed Oct 28, 2019
1 parent 3b24b62 commit 75546ad
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 88 deletions.
27 changes: 22 additions & 5 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,29 @@ on: [push]

jobs:
build:

runs-on: ubuntu-latest

runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v1
- name: build
run: gcc dzr.c -lssl -lcrypto -o dzr
- name: package
run: tar -cz dzr dzr-db > dzr.tar.gz
- name: publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUNNER_CONTEXT: ${{ toJson(runner) }}
run: |
gcc dzr.c -lssl -lcrypto
ls -al
echo "$RUNNER_CONTEXT"
RELEASE_TAG="$(date +%y%m%d)"
curl -svL -XPOST -d '{"tag_name": "'$RELEASE_TAG'"}' \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H 'Content-Type: application/json' \
https://api.github.com/repos/${GITHUB_REPOSITORY}/releases
RELEASE_ID=$(curl -svL https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/$RELEASE_TAG|jq .id)
curl -svL -XPOST -T "dzr.tar.gz" \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type:application/octet-stream" \
https://uploads.github.com/repos/${GITHUB_REPOSITORY}/releases/$RELEASE_ID/assets?name=dzr.x86_64-linux.tar.gz
102 changes: 25 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,101 +3,49 @@
# Usage

```
dzr [TRACKS...]
dzr [TRACKID...]
```

# Environment variables
⚠️ For legal reasons, `dzr` does contain any encryption/decryption keys.

- `DZR_SID` = your_sid (See bellow to get it)
- `DZR_AES` = `jo..............` (See bellow to get it)
- `DZR_CBC` = `g4el............` (See bellow to get it)
- `DZR_FMT` = `0 (can be: 128Kb:`0`, 320Kb:`3`, AAC96:`8`, FLAC:`9`)
However they can easily be found by
[reversing](https://github.com/yne/dzr/wiki) the HTML5 player.
Then given to `dzr` via [environment variables](#environment-variables).

# Exemples

```sh
dzr 600629 # basic usage
dzr deezer.com/en/track/526893582 # url is fine too
dzr 572537042 572537052 # multiple tracks is fine
dzr 600629 > my.mp3 # manual namming
dzr 600629 | mpv - # streaming (--cache-secs=30)
curl api... | jq ... | shuf | xargs dzr | mpv - # see API Example
dzr 997764 # basic usage
dzr 997764 | mpv - # piped streaming
dzr 997764 > my.mp3 # redirect namming
dzr 997764 997763 # multiple tracks
```

# API Examples

```sh
curl api.deezer.com/artist/27/top?limit=500| jq .data[].id
curl api.deezer.com/playlist/3631662942 | jq .tracks.data[].id
curl api.deezer.com/artist/27/top?limit=500| jq .data[].id
curl api.deezer.com/playlist/3631662942 | jq .tracks.data[].id
curl api... | jq ... | shuf | xargs dzr | mpv -
```

# `DZR_SID`, `DZR_AES` and `DZR_CBC` keys

If you analysed how the HTML5 player works,
you may have noticed that all it played tracks files
are available in the network tab of your browser debugger.

- The track URL is generated using the `DZR_AES` key and the track `MD5_SUM`
- The track `MD5_SUM` is given only if your request have a `DZR_SID`
- The track is encrypted using the `DZR_CBC` key

In order to get those keys we must extract them from the player sources.
You can look at the sources of the player webworker using the "Sources" tab:

![](https://imgur.com/pwS370Q.png)

Note that the first line of this worker code is a symbol table,
and a few lines later we got the symbol resolver function,
which is called each time the script need to call a names function.

In the following sections we will use this translation function a lot.

# DZR_SID

Your Session ID is very easy to find. Just look at the network request panel
of your debugger and you shall see a Set-Cookie line with `sid=yoursidnumber`

![](https://imgur.com/DEQHaB6.png)

With this ID, `dzr` can now get the full track MD5, otherwise it could only get 30 seconds.
# dzr-db

## `DZR_AES` aka the 'Jo' Key for URL Generation
`dzr` must be authenticated (via `DZR_SID`) in order to get full tracks URLs.
However, if the track is old enough, `dzr-db` can get the URL from it ID.
To do that, it request an anonymous external DataBase.

Since the track URL was not part of any JSON we can assume that it was generated by the JS.
By looking at the symbol table, we see that we have an 'encrypt' function.
Let's have a conditional breakpoint on the resolver when it resolve 'encrypt'.
After pressing play on the HTML5 player, out "encrypt" breakpoint is triggered.
Now let's take a look at the function scope values we have the following params:
This allows you to use `dzr` account-less,
but probably wont work for recent tracks that haven't been added in the DB.

- cdn
- format
- id
- md5

That's all the ingredients required to generate the CDN URL.
A closer look to the AES cyphering scope will give us the 16 Bytes AES key.

![](https://imgur.com/I1mdGGj.png)

Congratulation, you just found the AES key, aka the "jo" key, just convert it in ASCII.

```
[106,111,54,...].map(e=>String.fromCharCode(e)).join('')
```sh
dzr-db 997764 | xargs dzr | mpv -
```

## `DZR_CBC` aka the 'Gael' Key for Track deciphering

First, add a logpoint on the translator and play a track for 30 seconds.
This will give you an overview of the function in charge of the deciphering.
You can then, traceback those name (for example `decipher`)
to they function and have a look at they closure.

![](https://imgur.com/ptCZxdo.png)

The keys are unziped into 2 UInt8Array, in little endian.
Zip them back to get the Blowfish/CBC key, aka the "Gaël" key:
# Environment variables

```js
[103,52,101,...].map(e=>String.fromCharCode(e)).join('')
```
- `DZR_SID` = your_sid (optional) [HOWTO](https://github.com/yne/dzr/wiki)
- `DZR_AES` = `jo..............` [HOWTO](https://github.com/yne/dzr/wiki)
- `DZR_CBC` = `g4el............` [HOWTO](https://github.com/yne/dzr/wiki)
- `DZR_FMT` = `0` (can be: 128Kb:`0`, 320Kb:`3`, AAC96:`8`, FLAC:`9`)

42 changes: 42 additions & 0 deletions dzr-db
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/sh

SERVER=https://gitlab.com/fckdzr/dzr/raw/master
DBS="0-199999-0.txt
200000-365473-1.bin
365474-506000-0.txt
507533-18240991-1.bin
18241001-60681991-10.bin
60682000-79095802-1.bin
79095803-145709108-2.bin
354510101-409999999-10.bin
510000001-570274000-10.bin"

resolve(){
id=$1
https://www.deezer.com/en/track/129312192
for f in $DBS; do
START=$(echo $f | cut -f1 -d-)
END=$(echo $f | cut -f2 -d-)
STEP=$(echo $f | cut -f3 -d-| cut -f1 -d.)
TYPE=$(echo $f | cut -f2 -d.)
if [ $id -ge $START -a $id -le $END ] ; then
DB=$f
break
fi
done

[ -z "$DB" ] && echo "$id not found" 1>&2;

if [ "$TYPE" = bin ] ; then
IDX=$((($id-$START)/$STEP))
MD5=$(curl -sH Range:bytes=$((IDX*16))-$((IDX*16+15)) $SERVER/$DB | od -An -txC | tr -d ' ')
elif [ "$TYPE" = txt ] ; then
MD5=$(curl -s $SERVER/$DB | grep "^$id " | cut -f2 -d' ')
fi
printf "%s:%i" $MD5 $id
}

for id in $@ ; do
resolve $id
done

22 changes: 16 additions & 6 deletions dzr.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#define expect(cond, fmt...) if(!(cond))return fprintf(stderr,"expect(" #cond ") failed \n" fmt),-1;
#define HOST_WWW "www.deezer.com"
#define HOST_API "api.deezer.com"
#define HOST_CDN "e-cdn-proxy-0.deezer.com"
#define GW(method) "/ajax/gw-light.php?method=" method "&api_version=1.0&input=3&api_token="
#define PKCS5(total, current) (int)((total) - strlen(current))
Expand Down Expand Up @@ -131,9 +132,12 @@ char *getTokenFromSID(char *sid, char token[32 + 1]) {
page, sizeof(page), NULL, NULL);
return memcpy(token, find(page, "checkForm\":\""), 32);
}

//fre84ab3c3a885631cdec4f9b874c0e67692b53e
int getTrackInfo(char *sid, char *tkn, char *trackid, char md5[32 + 1], char artist[64 + 1], char title[64 + 1]) {
char page[1024 * 32];
if (*md5) {
return memcpy(title, md5, 64),0;
}
fetch("POST", GW("song.getListData"), tkn, HOST_WWW,
(char *[]) {"Cookie: sid=", sid, "\r\n", 0},
(char *[]) {"{\"sng_ids\":[", trackid, "]}", 0},
Expand All @@ -146,7 +150,7 @@ int getTrackInfo(char *sid, char *tkn, char *trackid, char md5[32 + 1], char art

char *getTrackUrl(char *md5, int version, char *trackid, char *format, char url[160 + 1], AES_KEY*aes_key) {
char line[50] = {};
snprintf(line, sizeof(line), "%s\xA4%s\xA4%s\xA4%i", md5, format, trackid, version);
snprintf(line, sizeof(line), "%.32s\xA4%s\xA4%s\xA4%i", md5, format, trackid, version);
char *line_md5 = toHex(md5(line), 16, (char[32 + 48 + 1]) {}, "%02x");
sprintf(line_md5 + 32, "\xA4%s\xA4%c%c%c", line, PKCS5(46, line), PKCS5(46, line), PKCS5(46, line));
unsigned char pth[80];
Expand Down Expand Up @@ -197,7 +201,7 @@ int main(int argc, char *argv[]) {
argv++, argc--;
char *sid = getenv("DZR_SID")?:getenv("sid")?:getenv("SID");
char *bf = getenv("DZR_CBC"), *aes=getenv("DZR_AES"), *fmt = getenv("DZR_FMT") ?: "0";
if (argc < 1 || !sid || !bf || !aes)
if (argc < 1 || !bf || !aes)
return fprintf(stderr, USAGE, sid, aes, bf, fmt);
WSAStartup(MAKEWORD(2, 2), &(WSADATA) {});
SSL_library_init();
Expand All @@ -209,17 +213,23 @@ int main(int argc, char *argv[]) {
//if (dbg)fprintf(stderr, "token=%s\n", token);

for (char *track = *argv; argc; --argc, track = *++argv) {
for (; *track && !isdigit(*track); track++);//shift arg up to it the first digit
char md5[32 + 1] = {}, artist[64] = {}, title[64] = {};
if (!strcmp(track,"http")) { // track URL : shift arg up to it the first digit
for (; *track && !isdigit(*track); track++);
} else if (strlen(track) >= 32 && track[32]==':') { // "MD5:*:id" format
memcpy(md5,track,sizeof(md5)-1);
track += sizeof(md5);
}
int version = getTrackInfo(sid, token, track, md5, artist, title);
if(!*md5){continue;}/* private/blocked track */
int artist_len = (int) (strchr(artist, '"') - artist), title_len = (int) (strchr(title, '"') - title);
int artist_len = *artist ? (int) (strchr(artist, '"') - artist) : 0;
int title_len = *title ? (int) (strchr(title, '"') - title) : 0;
//if(dbg)fprintf(stderr, "version=%i md5=%s : %.*s - %.*s\n", version, md5, artist_len, artist, title_len, title);
char *url = getTrackUrl(md5, version, track, fmt, (char[160 + 1]) {}, &aes_key);
FILE *fd = stdout;
if (isatty(STDOUT_FILENO)) { // TTY == no STDOUT piping => output to a pre-named file
char mp3name[1024], *ext = fmt[0] == '9' ? "flac" : "mp3";
sprintf(mp3name, "%.*s - %.*s.%s", artist_len, artist, title_len, title, ext);
sprintf(mp3name, "%.*s%s%.*s.%s", artist_len, artist, (*artist && *title) ? " - " : "", title_len, title, ext);
fd = fopen(mp3name, "wb+");
}
fetchTrack(getTrackKey(track, bf, (BF_KEY[1]) {}), url, md5[0], fd);
Expand Down

0 comments on commit 75546ad

Please sign in to comment.