Strictly speaking, this article can be attributed to reversing only with a stretch.
You all know a service like zaycev.net. I’m not mistaken, assuming that everyone at least once downloaded music from him, either through a web interface or through a mobile application.
At the request of the representatives of the service, I replaced the constant in the publication, if you wish, you can request them.
If you are still interested, welcome under cat.
One day, one of my good friends asked me to figure out how their official Android client works. After downloading the client, I began to study and uploaded the test subject to Jadx (Dex to Java decompiler). All links at the end of the article.
The first thing that catches your eye is the presence of obfuscation:
Well, never mind, we will break through, not for the first time. A quick inspection showed that the functionality we need is concentrated in the package:
package free.zaycev.net.api;
public synchronized String b() { String str; if (Looper.myLooper() == Looper.getMainLooper()) { throw new IllegalThreadStateException("Method must run in not main thread!"); } else if (ae.b(ZaycevApp.ay())) { String str2 = ""; str2 = ""; str2 = ""; try { str = (String) new JSONObject(ga("https://api.zaycev.net/external/hello", false)).get("token"); if (ZaycevApp.W().equals("4pda")) { str2 = str + "kmskoNdkYHDnl3ol3"; aa(); } else { str2 = str + "kmskoNdkYHDnl3ol3"; } ha("ZAuth", "token - " + str2); str2 = a(str2); str = new JSONObject(ga(String.format("https://api.zaycev.net/external/auth?code=%s&hash=%s", new Object[]{str, str2}), false)).getString("token"); if (!ae.b((CharSequence) str)) { ZaycevApp.ae(str); } } catch (Exception e) { } str = ""; } else { str = ZaycevApp.ay(); } return str; } private String a(String str) { try { MessageDigest instance = MessageDigest.getInstance("MD5"); instance.update(str.getBytes()); byte[] digest = instance.digest(); StringBuffer stringBuffer = new StringBuffer(); for (byte b : digest) { String toHexString = Integer.toHexString(b & 255); while (toHexString.length() < 2) { toHexString = "0" + toHexString; } stringBuffer.append(toHexString); } return stringBuffer.toString(); } catch (Exception e) { ha((Object) this, e); return ""; } }
As is clear from the code, the order of requests to the service is as follows:
Hello, receiving Hello token:
https://api.zaycev.net/external/hello
To which the server responds with a json object:
{ "token":"I-fte8MSfXjw8bYFQkcq629iB6uLb5thZSoj3rGvlCPG4ZJzpgbFPylrtLDpw7L_qQ2EBeuBIMvA7BUWkwilS8IWUg3CWGwj8SCmdIU5I8M" }
" { "token":"I-fte8MSfXjw8bYFQkcq629iB6uLb5thZSoj3rGvlCPG4ZJzpgbFPylrtLDpw7L_qQ2EBeuBIMvA7BUWkwilS8IWUg3CWGwj8SCmdIU5I8M" }
Calculation hash:
hash = md5 (helloToken + "kmskoNdkYHDnl3ol3")
Looking ahead, I will say that the constant wired into the program (kmskoNdkYHDnl3ol3) changes from version to version, at the moment I have met 3 different constants:
android: "63kQw2LlpV3jv", "kmskoNdkYHDnl3ol3"
ios: "d7DVdaELf"
Authentication, getting Access Token:
https://api.zaycev.net/external/auth?code=%s&hash=%s
To which the server responds with a json object:
{ "token":"wnfQgLZoLErwL6g_axTTTUkCcobXGLMRZS75Zozr3oC05kWNfd07Bngjpg2VRY2GgXYPaCPqSGarqki6YU278ZO6XJP4RLdNqZMqHFwv-25iH8M_R6rSna2CmnP5OuwgTuUundxiTWqI2Am5rHA2gbU8kbB9Ya0gRJ1mHhq_MpksW3R49Fm4VBDd6vYnNUWykibWmxzxvhRBhJ2dmiKJkw" }
" { "token":"wnfQgLZoLErwL6g_axTTTUkCcobXGLMRZS75Zozr3oC05kWNfd07Bngjpg2VRY2GgXYPaCPqSGarqki6YU278ZO6XJP4RLdNqZMqHFwv-25iH8M_R6rSna2CmnP5OuwgTuUundxiTWqI2Am5rHA2gbU8kbB9Ya0gRJ1mHhq_MpksW3R49Fm4VBDd6vYnNUWykibWmxzxvhRBhJ2dmiKJkw" }
We check the performance:
curl -X "GET" "https://api.zaycev.net/external/track/1310964?access_token=wnfQgLZoLErwL6g_axTTTUkCcobXGLMRZS75Zozr3oC05kWNfd07Bngjpg2VRY2GgXYPaCPqSGarqki6YU278ZO6XJP4RLdNqZMqHFwv-25iH8M_R6rSna2CmnP5OuwgTuUundxiTWqI2Am5rHA2gbU8kbB9Ya0gRJ1mHhq_MpksW3R49Fm4VBDd6vYnNUWykibWmxzxvhRBhJ2dmiKJkw"
JSON-Response:
{ "track": { "name": "Sharp Dressed Man", "bitrate": 128, "duration": 258, "size": 4.08, "created": 1333340577000, "userId": 2750888, "userName": "zver19", "artistId": 272997, "artistName": "ZZTop", "lyrics": {}, "lyricAuthor": [], "musicAuthor": [], "rightPossessors": [ { "url": "http://zaycev.net/legal/reriby", "name": "nETB", "pictureUrl": "http://cdnimg.zaycev.net/rp/logo/29/2954-35447.png" } ], "artistImageUrlSquare100": "http://cdnimg.zaycev.net/artist/2729/272997-52076.jpg", "artistImageUrlSquare250": "http://cdnimg.zaycev.net/artist/2729/272997-86370.jpg", "artistImageUrlTop917": null }, "rating": 0.0, "rbtUrl": "" }
Auth token - temporary, valid approximately 24 hours after which you need to request again.
After completing these simple steps, we got the Auth token, which we need to fulfill the requests to the service server. Time to start searching for queries that are used by the program.
A text search on " https://api.zaycev.net " issued a list of all requests.
List of API requests:
" https://api.zaycev.net/external/hello "
" https://api.zaycev.net/external/auth?code=%s&hash=%s "
" https://api.zaycev.net/external/search?query=%s&page=%s&type=%s&sort=%s&style=%s&access_token=%s "
" https://api.zaycev.net/external/autocomplete?access_token=%s&code%s "
" https://api.zaycev.net/external/top?page=%s&access_token=%s "
" https://api.zaycev.net/external/musicset/list?page=%s&access_token=%s "
" https://api.zaycev.net/external/musicset/detail?id=%s&access_token=%s "
" https://api.zaycev.net/external/genre?genre=%s&page=%s&access_token=%s "
“ https://api.zaycev.net/external/artist/%d?access_token=%s ”
" https://api.zaycev.net/external/track/%d?access_token=%s "
" https://api.zaycev.net/external/options?access_token=%s "
" https://api.zaycev.net/external/track/%d/download/?access_token=%s&encoded_identifier=%s "
" https://api.zaycev.net/external/track/%s/play?access_token=%s&encoded_identifier=%s "
" https://api.zaycev.net/external/bugs?access_token=%s "
" https://api.zaycev.net/external/feedback?email=%s&clientInfo=%s&text=%s&access_token=%s "
Here we come to the final stage of our research, now we have to transfer this knowledge to the code. We will use, as indicated, the Go language as indicated in the title of the article. I will not give you all the code, you can find it at the link at the end of the article.
const ( apiURL string = "https://api.zaycev.net/external" helloURL string = apiURL + "/hello" authURL string = apiURL + "/auth?" topURL string = apiURL + "/top?" artistURL string = apiURL + "/artist/%d?" musicSetListURL string = apiURL + "/musicset/list?" musicSetDetileURL string = apiURL + "/musicset/detail?" genreURL string = apiURL + "/genre?" trackURL string = apiURL + "/track/%d?" autoCompleteURL string = apiURL + "/autocomplete?" searchURL string = apiURL + "/search?" optionsURL string = apiURL + "/options?" playURL string = apiURL + "/track/%d/play?" downloadURL string = apiURL + "/track/%d/download/?" )
To implement, select one of the requests, for example, the TOP request of the tracks, and describe the JSON object:
type ZTop struct { Page int `json:"page"` PagesCount int `json:"pagesCount"` Tracks []struct { Active bool `json:"active"` ArtistID int `json:"artistId"` ArtistImageURLSquare100 string `json:"artistImageUrlSquare100"` ArtistImageURLSquare250 string `json:"artistImageUrlSquare250"` ArtistImageURLTop917 string `json:"artistImageUrlTop917"` ArtistName string `json:"artistName"` Bitrate int `json:"bitrate"` Block bool `json:"block"` Count int `json:"count"` Date int64 `json:"date"` Duration string `json:"duration"` HasRingBackTone bool `json:"hasRingBackTone"` ID int `json:"id"` LastStamp int `json:"lastStamp"` Phantom bool `json:"phantom"` Size float64 `json:"size"` Track string `json:"track"` UserID int `json:"userId"` } `json:"tracks"` }
Api specific errors:
type ClientError struct { msg string } func (self ClientError) Error() string { return self.msg }
Create a client:
type ZClient struct { client *http.Client helloToken string accessToken string staticKey string }
func NewZClient(httpClient *http.Client, token, sKey string) *ZClient { if httpClient == nil { httpClient = http.DefaultClient } return &ZClient{client: httpClient, accessToken: token, staticKey: sKey} }
Feature request Top list:
func (zc *ZClient) Top(page int) (r *ZTop, err error) { r = &ZTop{} if err = zc.checkAccessToken(); err != nil { return r, err } values := url.Values{} values.Add("page", strconv.Itoa(page)) values.Add("access_token", zc.accessToken) if err := zc.fetchApiJson(topURL, values, r); err != nil { return r, err } return r, err }
The function that performs http requests:
func (zc *ZClient) makeApiGetRequest(fullUrl string, values url.Values) (resp *http.Response, err error) { req, err := http.NewRequest("GET", fullUrl+values.Encode(), nil) if err != nil { return resp, err } resp, err = zc.client.Do(req) if err != nil { return resp, err } if resp.StatusCode != 200 { var msg string = fmt.Sprintf("Unexpected status code: %d", resp.StatusCode) resp.Write(os.Stdout) return resp, ClientError{msg: msg} } return resp, nil }
Function for the json decode:
func (zc *ZClient) fetchApiJson(actionUrl string, values url.Values, result interface{}) (err error) { var resp *http.Response resp, err = zc.makeApiGetRequest(actionUrl, values) if err != nil { return err } defer resp.Body.Close() dec := json.NewDecoder(resp.Body) if err = dec.Decode(result); err != nil { return err } return err }
func (zc *ZClient) Auth() (err error) { if err = zc.checkStaticKey(); err != nil { return err } return zc.hello() } func (zc *ZClient) hello() (err error) { if err = zc.checkStaticKey(); err != nil { return err } t := &ZToken{} if err := zc.fetchApiJson(helloURL, url.Values{}, t); err != nil { return err } zc.helloToken = t.Token return zc.auth() } func (zc *ZClient) auth() (err error) { if err = zc.checkHelloToken(); err != nil { return err } r := &ZToken{} hash := MD5Hash(zc.helloToken + zc.staticKey) values := url.Values{} values.Add("code", zc.helloToken) values.Add("hash", hash) if err := zc.fetchApiJson(authURL, values, r); err != nil { return err } zc.accessToken = r.Token return err }
Md5 counting function:
func MD5Hash(text string) string { hasher := md5.New() hasher.Write([]byte(text)) return hex.EncodeToString(hasher.Sum(nil)) }
The source is available at the links below.
PS: The code is very far from perfect. If you have thoughts on his correction and improvement - I will be glad to your requests.
References:
Jadx: https://github.com/skylot/jadx
github: https://github.com/pixfid/go-zaycevnet
zaycev.net_4.9.3_10.apk: http://bit.ly/1MZW7UA
Source: https://habr.com/ru/post/301838/
All Articles