yne / dzr

Accountless deezer.com Player (CLI & VSCode)
The Unlicense
195 stars 12 forks source link

Deezer protection 2020/04 #5

Closed yne closed 3 years ago

yne commented 4 years ago

After 12 years, Deezer finally implemented a proper tracks protection mechanism at CDN level. :tada:

Meaning:

playlist/song fetching using new API:

DZR_SID=fr12345_______________________________89

function gw(){ curl -s "https://www.deezer.com/ajax/gw-light.php?method=$1&input=3&api_version=1.0&api_token=$3" -H "cookie: sid=$2" $@;}
# 1 - User info
USR_NFO=$(gw deezer.getUserData $DZR_SID)
USR_TOK=$(jq -r .results.USER_TOKEN <<< $USR_NFO)
USR_LIC=$(jq -r .results.USER.OPTIONS.license_token <<< $USR_NFO)
API_TOK=$(jq -r .results.checkForm <<< $USR_NFO)

# 2a - Track to Tokens
TRK_IDS=137955757,960539
TRK_NFO=$(gw song.getListData $DZR_SID $API_TOK --data-binary '{"sng_ids":['"$TRK_IDS"']}')
TRK_TOK=$(jq -r [.results.data[].TRACK_TOKEN] <<< "$TRK_NFO")
TRK_MD5=$(jq -r [.results.data[].MD5_ORIGIN] <<< "$TRK_NFO")

# 2b - Playlist to Tokens
LST_ID=6324727784
LST_NFO=$(gw deezer.pagePlaylist $DZR_SID $API_TOK --data-binary '{"playlist_id":"'$LST_ID'","lang":"en","nb":2000}')
TRK_TOK=$(jq -r [.results.SONGS.data[].TRACK_TOKEN] <<< "$LST_NFO")
TRK_MD5=$(jq -r [.results.SONGS.data[].MD5_ORIGIN] <<< "$LST_NFO")

# 3 - Token to URL
TRK_SRC=$(curl -s 'https://media.deezer.com/v1/get_url' --data-binary '{"license_token":"'"$USR_LIC"'","media":[{"type":"FULL","formats":[{"cipher":"BF_CBC_STRIPE","format":"MP3_128"}]}],"track_tokens":'"$TRK_TOK"'}')
TRK_URL=$(jq -r .data[].media[].sources[1].url <<< "$TRK_SRC")
echo "$TRK_URL"
yne commented 4 years ago

I'm almost done with the new API.

I'm leaving this here, just in case the DZR_CBC changed

105579760 > cff9546b99302010612dec77700605dc > http://e-cdn-proxy-c.deezer.com/mobile/1/54EBDBF1268A9912B99F12BBDDD3DACBB1E9255A8BDBF4C3AA05645E0C5FC96FCB5A7A65072DAC6B0B3A7E30B10033C34520ED85B94ED597A131D97425D6B17BA3AC13C1119D23A3EF4B3B85EF0345F1

cff9546b99302010612dec77700605dc:105579760.enc.gz cff9546b99302010612dec77700605dc:105579760.dec.gz

milos192 commented 3 years ago

So, just to confirm. The current instructions don't work anymore then, right? I tried to follow them and I just get segmentation faults. Thanks!

yne commented 3 years ago

Deezer probably deprecated the old unsecure API

edit: they didn't dzr 5761b1cd2b80e5c8ad8a0dbecd4c7d13:16235816 | mpv - still work

I shall find the motivation to redo the work for the new API

milos192 commented 3 years ago

Is there anything that I could help with?

yne commented 3 years ago

could you give me more context for the segfault so I can reproduce it ?

my dzr work fine here with older track that dzr-db can resolve

milos192 commented 3 years ago

Of course.

The thing that I suspect might be an issue is that in the AES key there's an ASCII 26 character, and even pasting that into the Terminal is causing problems, so I'm guessing it could be a lead? I'm not sure.

yne commented 3 years ago

both DZR_AES and DZR_CBC keys are plain ascii strings

Did you found them from the web player (wee: wiki) or did you found them on the internet ?

milos192 commented 3 years ago

I extracted them from the web player via the Wiki instructions. I can post them here, if you think it's fine.

yne commented 3 years ago

Since i'm pretty sure they are wrong, you can post them here.

Also, try compare them to what others people uses as DZR_AES DZR_CBC on the internet

milos192 commented 3 years ago

Found the issue. I was looking at the wrong array. The structure has changed a bit, and in your pic, the array starts with 102, while it starts with 106 here. aes

I'll try with this one and report if it works.

milos192 commented 3 years ago

Still no luck. :/

The AES and CBC are now ok, and I'm using my SID which was freshly extracted.

segfault

I even tried the example from the README, and it was the same.

yne commented 3 years ago

Thanks,

Try a pre-resolved md5 track:

DZR_SID=none ./dzr-ubuntu-latest 5e45db8562beee18000ba19b2480a6db:997764 > some.mp3

For the record, the tail and head of the keys are

DZR_CBC=g4.............1
DZR_AES=jo.............h
milos192 commented 3 years ago

Same thing. :/

preresolved

The tail and the head match (also verified from a source on the Internet).

yne commented 3 years ago
DZR_DBG=1 DZR_SID=none ./dzr-ubuntu-latest 5e45db8562beee18000ba19b2480a6db:997764 > out

GET /mobile/1/3335663262363064303434366635326237306362643236653533613764656461A43565343564623835363262656565313830303062613139623234383061366462A430A4393937373634A430A4030303 HTTP/1.0
Host: e-cdn-proxy-5.deezer.com
Content-Length: 0

HTTP/1.0 403 Forbidden
Access-Control-Expose-Headers: x-deezer-client-ip, content-length, content-range
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Content-Type: text/html; charset=UTF-8
Date: Sat, 28 Nov 2020 15:53:10 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Last-Modified: Sat, 28 Nov 2020 15:53:10 GMT
Pragma: no-cache
Server: Apache
X-Host: blm-prxmob-63
Content-Length: 0
Connection: close
GET /mobile/1/B3261CC4224D4F833DB0D5CDF9D4BACA144325D2CDE3EAE073F4BEDB6E14501B07813B291CD286E89D1556904327913F50C25E93118FE9F43B1F77C9032E2C906DD9927B0CCDB7FA9069623ED2CC98B0 HTTP/1.0
Host: e-cdn-proxy-5.deezer.com
Content-Length: 0

HTTP/1.0 200 OK
Accept-Ranges: bytes
Access-Control-Expose-Headers: x-deezer-client-ip, content-length, content-range
Age: 39335
Cache-Control: public
Content-Type: audio/mpeg
Date: Sat, 28 Nov 2020 15:56:41 GMT
Expires: Tue, 27 Apr 2021 15:56:41 GMT
Last-Modified: Sat, 28 Nov 2020 05:01:06 GMT
Pragma: 
Server: ECAcc (sgb/C740)
X-Cache: HIT
X-Deezer-Cache: blm-prxmob-53
X-Deezer-Streamer: real
X-Host: blm-prxmob-53
Content-Length: 3418069
Connection: close

The static version use an internal crypto lib while the other rely on openSSL. So the bug seems to be the way OpenSSL is used to generate the URL

milos192 commented 3 years ago

dzrstatic

I even tried re-downloading both versions from the tag that I mentioned before, and it's the same thing. Is there any way for me to print out where the exception occurs?

yne commented 3 years ago

sure you can use strace or gdb, I hope you are familiar with them. If not, here is a quick example :

DZR_DBG=1 DZR_SID=none gdb --args ./dzr_static-ubuntu-latest 5e45db8562beee18000ba19b2480a6db:997764
# you shall see a (gdb) prompt, type "run" then press enter
(gdb) run
# some times later the segfault shall appear with a stacktrace
segfault at 0x......
# just like before, when prompted, type "where" then press enter
(gdb) where
milos192 commented 3 years ago

🤦 I didn't have Internet connectivity within my Ubuntu instance this whole time!

The pre-resolved MD5 track works now.

However, the following does not.

$ gdb --args ./dzr_static-ubuntu-latest 1022348772
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./dzr_static-ubuntu-latest...(no debugging symbols found)...done.
(gdb) run
Starting program: /mnt/c/Users/Milos/Downloads/dzr_static-ubuntu-latest 1022348772

Program received signal SIGSEGV, Segmentation fault.
0x0000000000401ee0 in ?? ()
(gdb) where
#0  0x0000000000401ee0 in ?? ()
#1  0x0000000000401a37 in ?? ()
#2  0x000000000040049f in ?? ()
#3  0x0000000000401e86 in ?? ()
#4  0x0000000000000000 in ?? ()
(gdb)
yne commented 3 years ago

Hum I did not included debug symbols while compiling, so the stacktrace is not as helpful as I expected.

Could you run out of gdb but with DZR_DBG=1 ?

I'm pretty sure it's the track MD5 fetching issue (probably because deezer API has changed)

milos192 commented 3 years ago
POST /ajax/gw-light.php?method=song.getListData&api_version=1.0&input=3&api_token=********** HTTP/1.0
Host: www.deezer.com
Content-Length: 24
Cookie: sid=fr************************

{"sng_ids":[1022348772]}HTTP/1.0 200 OK
Server: Apache
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
P3P: policyref="/w3c/p3p.xml" CP="IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA"
X-Frame-Options: SAMEORIGIN
x-deezer-client-ip: // omitted
X-Host: blm-web-05
X-UA-Compatible: IE=edge,chrome=1,requiresActiveX=true
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
Date: Sat, 28 Nov 2020 17:14:07 GMT
Content-Length: 1724
Connection: close
Set-Cookie: sid=fr********************; path=/; domain=.deezer.com; HttpOnly
x-org: FR
Set-Cookie: // omitted
Set-Cookie: // omitted

After that, this response is printed

{
  "error": [],
  "results": {
    "data": [
      {
        "SNG_ID": "1022348772",
        "ALB_ID": "160914432",
        "ALB_PICTURE": "1de89effa1e524e5ca9ed5ee99251b23",
        "ALB_TITLE": "The Raging Wrath Of The Easter Bunny Demo",
        "ARTISTS": [
          {
            "ART_ID": "2193",
            "ROLE_ID": "0",
            "ARTISTS_SONGS_ORDER": "0",
            "ART_NAME": "Mr. Bungle",
            "ARTIST_IS_DUMMY": false,
            "ART_PICTURE": "8bf7975dc16f27aa48b55f1e30e57eaa",
            "RANK": "449576",
            "LOCALES": {
              "lang_ko": { "name": "\ubbf8\uc2a4\ud130 \ubc99\uae00" }
            },
            "__TYPE__": "artist"
          }
        ],
        "ART_ID": "2193",
        "ART_NAME": "Mr. Bungle",
        "DIGITAL_RELEASE_DATE": "2020-10-30",
        "DISK_NUMBER": "1",
        "DURATION": "175",
        "EXPLICIT_LYRICS": "0",
        "EXPLICIT_TRACK_CONTENT": {
          "EXPLICIT_LYRICS_STATUS": 0,
          "EXPLICIT_COVER_STATUS": 2
        },
        "GENRE_ID": "0",
        "ISRC": "QM5WW1500836",
        "LYRICS_ID": 0,
        "PHYSICAL_RELEASE_DATE": "2020-10-30",
        "PROVIDER_ID": "30",
        "RANK_SNG": "100000",
        "SMARTRADIO": 0,
        "SNG_TITLE": "Grizzly Adams",
        "STATUS": 1,
        "TRACK_NUMBER": "1",
        "TYPE": 0,
        "UPLOAD_ID": 0,
        "USER_ID": 0,
        "VERSION": "",
        "MD5_ORIGIN": "4fd6b3089f7eb1eafdecf043b3af8c5c",
        "FILESIZE_AAC_64": "0",
        "FILESIZE_MP3_64": "0",
        "FILESIZE_MP3_128": "2814536",
        "FILESIZE_MP3_256": "0",
        "FILESIZE_MP3_320": "7036342",
        "FILESIZE_MP4_RA1": "0",
        "FILESIZE_MP4_RA2": "0",
        "FILESIZE_MP4_RA3": "0",
        "FILESIZE_FLAC": "0",
        "FILESIZE": "2814536",
        "GAIN": "-13.7",
        "MEDIA_VERSION": "2",
        "TRACK_TOKEN": "AAAAAV_ChV9fw56f8aWzoT_x8rPypkxltmJwAvI8uc7oRTOKSB_Wex0_2WfOcr0L6WbW_g6sBjwuOGnVNT57nE2TZr5G4ja4RIiPk3VEibVAnAKTWzqZIwvV4j0eLfUwTp2Yb3NN3mLym1qgeko",
        "TRACK_TOKEN_EXPIRE": 1606655647,
        "MEDIA": [
          {
            "TYPE": "preview",
            "HREF": "http://cdn-preview-4.deezer.com/stream/c-4451d2d4a9f2e4dfe4446b90d75ae04d-2.mp3"
          }
        ],
        "RIGHTS": {
          "STREAM_ADS_AVAILABLE": true,
          "STREAM_ADS": "2000-01-01",
          "STREAM_SUB_AVAILABLE": true,
          "STREAM_SUB": "2000-01-01"
        },
        "__TYPE__": "song"
      }
    ],
    "count": 1,
    "total": 1,
    "filtered_count": 0
  }
}

And right after that, the segfault occurs.

yne commented 3 years ago

And I guess the following works ?

dzr 4fd6b3089f7eb1eafdecf043b3af8c5c:1022348772
milos192 commented 3 years ago

Nope.

$ DZR_DBG=1 DZR_SID=none ./dzr_static-ubuntu-latest 4fd6b3089f7eb1eafdecf043b3af8c5c:1022348772
Segmentation fault (core dumped)
$ DZR_DBG=1 ./dzr_static-ubuntu-latest 4fd6b3089f7eb1eafdecf043b3af8c5c:1022348772
Segmentation fault (core dumped)

And just for kicks:

$ DZR_DBG=1 ./dzr-ubuntu-latest 4fd6b3089f7eb1eafdecf043b3af8c5c:1022348772
*** stack smashing detected ***: <unknown> terminated
Aborted (core dumped)

And I am connected now 😄

$ ping www.google.com
PING www.google.com (172.217.19.100) 56(84) bytes of data.
64 bytes from muc03s07-in-f100.1e100.net (172.217.19.100): icmp_seq=1 ttl=119 time=8.83 ms
64 bytes from muc03s07-in-f100.1e100.net (172.217.19.100): icmp_seq=2 ttl=119 time=8.70 ms
64 bytes from muc03s07-in-f100.1e100.net (172.217.19.100): icmp_seq=3 ttl=119 time=8.70 ms
64 bytes from muc03s07-in-f100.1e100.net (172.217.19.100): icmp_seq=4 ttl=119 time=8.67 ms
^C
--- www.google.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3002ms
rtt min/avg/max/mdev = 8.672/8.728/8.830/0.060 ms
yne commented 3 years ago

What about the combo dzr_static + DZR_SID=none + pre-MD5 ?

DZR_DBG=1 DZR_SID=none ./dzr_static-ubuntu-latest 4fd6b3089f7eb1eafdecf043b3af8c5c:1022348772
milos192 commented 3 years ago

That's the first thing I tried, and it segfaults. :(

yne commented 3 years ago

None of my friends or colleagues are using Windows and my laptop does not have enough RAM to spin a Win10+WSL virtual machine so if you feel comfortable enough with C I suggest you to build a debug version with gcc -g ... and gdb it. Or alternatively add many fprintf(stderr, "debug at %i\n", __LINE__); to find out the exact line it segfault

milos192 commented 3 years ago

I'll try. I never did C, but I'll try when I get some spare time, and I'll let you know. Thank you for all the help, it's much appreciated!

conticelso commented 3 years ago

Hello,

I don't know if I'm writing in the right place, but I read you about the encryption of the tracks. I myself started to reverse engeenering the deezer api, and I also have an encryption problem. It seems that passwords only are hashed by the application before being sent to the server. Looks like they're also encrypted with MD5, but it looks like the sid is also involved. Here is an example.

229efb0a5de006306f559118faa7418c Is the corresponding hash of Testtest123 With like sid frea9add98a4f1b05eda6dd06dc2ed0a492ed653

Do you have any idea of ​​the process used for the encryption?

yne commented 3 years ago

Hi,

The track decryption has never changed since deezer was created: it's always MD5 + trackid + DZR_CBC key

The only things that changed is how to retreive these 3 values for the decryption.

See: https://github.com/yne/dzr/wiki

yne commented 3 years ago

the old method still works. So what is the point?

Given all the security design mistakes they made so far (and they buggy react website), I won't say that deezer devs are smart people.

But if they were, they would probably kill the old/unrestricted API and only use they token+CDN one, which IMO is the best design they could come up with...

props to them for that...

I'm not bootlicking....

... (deezer please hire me).

zsugabubus commented 3 years ago

the old method still works. So what is the point?

They measure the number of pirates. But pssst!

(A more likely possibility maybe that they do not want to lose paying users who have not updated their desktop client for a while. (?))

yne commented 3 years ago

They also seems to support some smart-speaker which (I guess), are not so easy to migrate to the new API ?

yne commented 3 years ago

yes, FLAC quality require a even higher "Hi-Fi" level.

yne commented 3 years ago

I'm halfway there in my c -> shell migration.

I re-implemented the deezer bf-cbc-stripe decoder in pure shell (openssl is required for bf-cbc decryption part)

Now I just need to also implement the trackId => cdn_url to finish the job

dzr-dec

#!/bin/sh

track_id=$1
dzr_cbc_hex=$(printf "$DZR_CBC"                               | hexdump -e '16/1 "%02x"')
track_md5_l=$(printf "$track_id" | openssl md5 -r|cut -b1-16  | hexdump -e '16/1 "%02x"')
track_md5_r=$(printf "$track_id" | openssl md5 -r|cut -b17-32 | hexdump -e '16/1 "%02x"')
track_key=$(for k in $(seq 1 2 32); do
    a=$(printf $dzr_cbc_hex | cut -b $k-$(($k+1)))
    b=$(printf $track_md5_l | cut -b $k-$(($k+1)))
    c=$(printf $track_md5_r | cut -b $k-$(($k+1)))
    printf '%02x' "$((0x$a ^ 0x$b ^ 0x$c))"
done)

stripe_size=2048

while true; do
dd bs=$stripe_size count=1 status=none | openssl bf-cbc -nopad -bufsize $stripe_size -K $track_key -iv 0001020304050607 -d 1>&4
{ LC_ALL=POSIX dd bs=$stripe_size count=2 2>&3 >&4; } 3>&1 | grep -qe '^0[+]0 ' && break
done 4>&1

Usage:

# Note: DZR_CBC must be set for dzr-dec
wget -qO- http://e-cdn-proxy-c.deezer.com/mobile/1/54EBDBF1268A9912B99F12BBDDD3DACBB1E9255A8BDBF4C3AA05645E0C5FC96FCB5A7A65072DAC6B0B3A7E30B10033C34520ED85B94ED597A131D97425D6B17BA3AC13C1119D23A3EF4B3B85EF0345F1 | ./dzr-dec 105579760 | mpv -
yne commented 3 years ago

Have you considered something like PHP?

For creating a CLI player ? definitely not :)

It has builtin functions for MD5 and OpenSSL:

Just like every languages.

I've created dzr because I'm tired of all the project that rely on a 100MB electron app to wget a mp3. So my goal was to create a simple program thats "runs everywhere" (even on PSP/PSVita). Hence the static binaries that a dependency-free. But the API is changing too fast for a C project. So I'm switching to a pure shell version (windows user will have to use WSL but they are not my main target, and vice versa).

I can help with the code if you are interested.

Yep ! I'm trying to find a way to get the MD5_SUM without an account. If you have any info on that subject I'm interested :).

yne commented 3 years ago

Yeah, you can do this with deezer.ping. But only MP3_128 will be available.

Do you miss DZR_FMT=9 to get your FLAC ?

I just need to add caching + track bursting to this snippet and we'll be good

function gw(){ curl -s "https://www.deezer.com/ajax/gw-light.php?method=$1&input=3&api_version=1.0&api_token=$3" -H "cookie: sid=$2" $@;}

TRK_IDS=796309342
DZR_URL="www.deezer.com/ajax/gw-light.php?method=deezer.ping&api_version=1.0&api_token"
DZR_SID=$(curl -s "$DZR_URL" | jq -r .results.SESSION)
USR_NFO=$(gw deezer.getUserData $DZR_SID)
USR_TOK=$(jq -r .results.USER_TOKEN <<< $USR_NFO)
USR_LIC=$(jq -r .results.USER.OPTIONS.license_token <<< $USR_NFO)
API_TOK=$(jq -r .results.checkForm <<< $USR_NFO)

TRK_NFO=$(gw song.getListData $DZR_SID $API_TOK --data-binary '{"sng_ids":['"$TRK_IDS"']}')
TRK_TOK=$(jq -r .results.data[].TRACK_TOKEN <<< "$TRK_NFO")

curl 'https://media.deezer.com/v1/get_url' --data-binary '{"license_token":"'$USR_LIC'","media":[{"type":"FULL","formats":[{"cipher":"BF_CBC_STRIPE","format":"MP3_128"}]}],"track_tokens":["'$TRK_TOK'"]}'
yne commented 3 years ago

Yeah I know, but are you sad from not having FLAC ?

Anyway you can still fetch them from the old API if you have the track MD5, but they will probably remove this API soon or later

$ wget -qO- http://e-cdn-proxy-5.deezer.com/mobile/1/051A0D9CB84625B6DF14D0EAE76C3F3D209866510365835BEA5EDAC36A51C47D52CF4A7F90843C3D7BEFAD851C91B0352F8AD3284ABDB46125E85F642E6B6C708E60F3461A3748635DC8FD29A059C309 | ./dzr-dec 16235816 | mpv -
[file] Reading from stdin...
 (+) Audio --aid=1 (flac 2ch 44100Hz)
AO: [pulse] 44100Hz stereo 2ch s16
A: 00:00:12 / 00:04:34 (4%) Cache: 133s/15MB
yne commented 3 years ago

The new API is now ready and working in the last version (in pure bash + openssl). No ARL/API_KEY required, only DZR_CBC

yne commented 3 years ago

yeah, better get all your FLAC before the old "MD5" API is removed ;)

I might create another dzr-url-old wich still get URL for FLAC/MP3_320 but I wouldn't waste much time on since its going to be deprecated.