Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions _config.melody.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ cdn:
fontawesome: https://cdn.jsdelivr.net/npm/font-awesome@latest/css/font-awesome.min.css
# fontawesomeV5: https://use.fontawesome.com/releases/v5.3.1/css/all.css
js:
anime: https://cdn.jsdelivr.net/npm/animejs@latest/lib/anime.min.js
jquery: https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js
fancybox: https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js
anime: https://cdnjs.loli.net/ajax/libs/animejs/3.2.1/anime.min.js
jquery: https://cdnjs.loli.net/ajax/libs/jquery/3.6.0/jquery.min.js
fancybox: https://cdnjs.loli.net/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.js
velocity: https://cdn.jsdelivr.net/npm/velocity-animate@latest/velocity.min.js
velocity-ui: https://cdn.jsdelivr.net/npm/velocity-ui-pack@latest/velocity.ui.min.js

Expand Down Expand Up @@ -211,9 +211,9 @@ addThis:
# Comments System
# ---------------
disqus:
enable: false
#shortname:
#count:
enable: true
shortname: nazukis-blog
count: true

# laibili:
# enable: false
Expand Down Expand Up @@ -269,7 +269,7 @@ disqus:

# Footer Settings
# ---------------
since: 2017
since: 2015

footer_custom_text: <a href="https://icp.gov.moe/?keyword=20229233" target="_blank">萌ICP备20229233号</a>

Expand Down
19 changes: 17 additions & 2 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ timezone: ''

# URL
## Set your site url heare. For example, if you use GitHub Page, set url as 'https://username.github.io/project'
url: http://example.com
permalink: :year/:month/:day/:title/
url: https://nazuki.moe
permalink: :title/
permalink_defaults:
pretty_urls:
trailing_index: true # Set to false to remove trailing 'index.html' from permalinks
Expand Down Expand Up @@ -103,3 +103,18 @@ theme: melody
## Docs: https://hexo.io/docs/one-command-deployment
deploy:
type: ''


feed:
enable: true
type: atom
path: atom.xml
limit: 20
hub:
content:
content_limit: 140
content_limit_delim: ' '
order_by: -date
icon: icon.png
autodiscovery: true
template:
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"dependencies": {
"hexo": "^6.2.0",
"hexo-generator-archive": "^1.0.0",
"hexo-generator-category": "^1.0.0",
"hexo-generator-category": "^2.0.0",
"hexo-generator-feed": "^3.0.0",
"hexo-generator-index": "^2.0.0",
"hexo-generator-tag": "^1.0.0",
"hexo-renderer-ejs": "^2.0.0",
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

202 changes: 119 additions & 83 deletions source/_posts/netease-eapi-music-recognize-reverse-1.md
Original file line number Diff line number Diff line change
@@ -1,103 +1,139 @@
---
title: netease-eapi-music-recognize-reverse-1
date: 2022-06-27 13:46:44
title: 网易云音乐听歌识曲 API 逆向 (一)
date: 2022-05-05
tags:
---
首先是抓了一下协议, 网易云音乐的接口本身是有加密的, 但是没关系, 已经有 [NetEaseCloudMusic](https://github.com/Binaryify/NeteaseCloudMusicApi) 这样的项目逆向出了基本的通讯协议, 可以直接使用.

![surge_mitm.png](https://s2.loli.net/2022/05/05/3BGhneWfKdCQzFm.png)

<!-- more -->

网易云音乐识曲会向 `https://music.163.com/eapi/music/audio/match?_nmclfl=1` 这个接口发送请求

解密后的请求体大概长这样

```json
{
"rawdata":"eJx11H9M1HUYB\/C7+2p5\/PhHTUQGwW......",
"from":"recognize-song",
"verifyId":1,
"deviceId":"??????",
"os":"iOS",
"header":{

},
"algorithmCode":"shazam_v2",
"times":1,
"sessionId":"??????",
"duration":3.4,
"e_r":true,
"sceneParams":"{\"action\":0,\"code\":2}"
}
{
"rawdata":"eJx11H9M1HUYB/C7+2p5/PhHTUQGwW......",
"from":"recognize-song",
"verifyId":1,
"deviceId":"??????",
"os":"iOS",
"header":{
},
"algorithmCode":"shazam_v2",
"times":1,
"sessionId":"??????",
"duration":3.4,
"e_r":true,
"sceneParams":"{"action":0,"code":2}"
}
```

![response_decrypted.png](https://s2.loli.net/2022/05/05/23zLWdGwqIacXoy.png)
猜测其中 `rawdata` 就是录音的 Base64 编码, 尝试解码扔进 ffprobe, 但是失败了

猜测其中 `rawdata` 就是录音的 Base64 编码, 尝试解码扔进 ffprobe, 但是失败了
有可能 `rawdata` 是根据音频频谱生成了摘要, `algorithmCode` 字段也提示了 `shazam_v2` 这个值

接下来开始逆向 APP

下载了一份比较旧但还能用的 网易云音乐 APK
[old_version_apk.png](https://s2.loli.net/2022/05/05/uliO4SfnUpyIN1L.png)

![old_version_apk.png](https://s2.loli.net/2022/05/05/uliO4SfnUpyIN1L.png)

扔进 Jadx, 发现有比较奇特的混淆方法, 任意地方的字符串都会调用一个函数来进行解密
[Obfuscation.jpg](https://s2.loli.net/2022/05/05/wrAiL2uzyHUsPD7.jpg)

![Obfuscation.jpg](https://s2.loli.net/2022/05/05/wrAiL2uzyHUsPD7.jpg)

定位到解密函数
[decryptor.png](https://s2.loli.net/2022/05/05/d43Aw5fTby2h9ju.png)

![decryptor.png](https://s2.loli.net/2022/05/05/d43Aw5fTby2h9ju.png)

这里的解密看起来并不困难, `C0003a()` 这个类显然是 Base64 解码用的
过我们要写出一个反向的操作(用来方便逆向查找字符串)

不过我们要写出一个反向的操作(用来方便逆向查找字符串)
```java
import java.util.Base64;

class Main {
public static String key = "Encrypt";

public static void main(String[] args) {
if (args[0].equals("decrypt")) {
System.out.println(decrypt(args[1]));;
}else{
System.out.println(encrypt(args[1]));
}
}

public static String decrypt(String s) {
byte[] decode = Base64.getDecoder().decode(s);
String str2;
int length = decode.length;
int length2 = key.length();
int i = 0;
int i2 = 0;
while (true) {
int i3 = i2;
if (i >= length) {
break;
}
int i4 = i3;
if (i3 >= length2) {
i4 = 0;
}
decode[i] = (byte) (decode[i] ^ key.charAt(i4));
i++;
i2 = i4 + 1;
}
str2 = new String(decode);
return str2;
}

public static String encrypt(String str_enc) {
byte[] s = str_enc.getBytes();
int length = str_enc.length();
int length2 = key.length();
int i = 0;
int i2 = 0;
while (true) {
int i3 = i2;
if (i >= length) {
break;
}
int i4 = i3;
if (i3 >= length2) {
i4 = 0;
}
s[i] = (byte) (s[i] ^ key.charAt(i4));
i++;
i2 = i4 + 1;
}
return Base64.getEncoder().encodeToString(s);
}
}
import java.util.Base64;
class Main {
public static String key = "Encrypt";
public static void main(String[] args) {
if (args[0].equals("decrypt")) {
System.out.println(decrypt(args[1]));;
}else{
System.out.println(encrypt(args[1]));
}
}
public static String decrypt(String s) {
byte[] decode = Base64.getDecoder().decode(s);
String str2;
int length = decode.length;
int length2 = key.length();
int i = 0;
int i2 = 0;
while (true) {
int i3 = i2;
if (i >= length) {
break;
}
int i4 = i3;
if (i3 >= length2) {
i4 = 0;
}
decode[i] = (byte) (decode[i] ^ key.charAt(i4));
i++;
i2 = i4 + 1;
}
str2 = new String(decode);
return str2;
}
public static String encrypt(String str_enc) {
byte[] s = str_enc.getBytes();
int length = str_enc.length();
int length2 = key.length();
int i = 0;
int i2 = 0;
while (true) {
int i3 = i2;
if (i >= length) {
break;
}
int i4 = i3;
if (i3 >= length2) {
i4 = 0;
}
s[i] = (byte) (s[i] ^ key.charAt(i4));
i++;
i2 = i4 + 1;
}
return Base64.getEncoder().encodeToString(s);
}
}
```
根据 `rawdata` 这个关键词在 Jadx 里查找

![script.png](https://s2.loli.net/2022/05/05/HftdwSoXDxGZ4rK.png)

确实能找到这个字符串

![jadx-search.png](https://s2.loli.net/2022/05/05/21eOoprbA8XMcwf.png)

继续跟踪函数调用栈, 最终跟踪到了 `MusicDetector` 类

![classMusicDetector.png](https://s2.loli.net/2022/05/05/O2LAXsbWQKMmqfD.png)

看起来是 `getFP()` 这个 Native 函数返回了一个三维数组, 最终封装成了 `rawdata`

![native-getFP.png](https://s2.loli.net/2022/05/05/GaiMZqrzTgAUwCn.png)

![binary-reverse.jpg](https://s2.loli.net/2022/05/05/X27B6ZKoY38PAtd.jpg)

暂时跟踪不下去了, 看见汇编就头大(

敬请期待第二章(咕咕咕

![image.png](https://s2.loli.net/2022/05/05/btmIr6v8R9L2onp.png)
45 changes: 44 additions & 1 deletion source/_posts/netease-eapi-music-recognize-reverse-2.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
---
title: netease-eapi-music-recognize-reverse-2
title: 网易云音乐听歌识曲 API 逆向 (二)
date: 2022-05-12 00:33:26
tags:
---


前两天看到网易云音乐发布了一个网页上做音乐识别的 [Chrome 插件](https://juejin.cn/post/7094083239702659109)

![netease-chrome-recognize](https://s2.loli.net/2022/05/12/sdvE3LHGx19owOY.png)

<!-- more -->

于是立即下载了一份来研究

![source](https://s2.loli.net/2022/05/12/XwcgmIPYsuLpCBf.png)

Chrome 插件的请求方式和结构和客户端几乎一模一样

![request-struct](https://s2.loli.net/2022/05/12/feCIWNdhlBVJXML.jpg)

根据函数栈追踪, 可以分析出比较核心的逻辑都在 `sandbox.bundle.js` 内

看起来是用了 WebAssemble 来解析网页录音, 在点击开始录音后会开始录制当前 TAB 的音频

当录音完成后就会将音频传到 WASM 暴露的函数 `l().ExtractQueryFP(...)` 中

![parse-buffer](https://s2.loli.net/2022/05/12/Ljm8hg5Kztv9VHW.jpg)

WASM 层处理完成后就会将返回的 ArrayBuffer 封装成一个 Base64 串, 最后会将这个 Base64 串提交给 API

![request-code](https://s2.loli.net/2022/05/12/TEWq917khpOKZPd.jpg)

* * *

虽然还是不太清楚 WASM 层内部是如何处理传入的音频数据, 但是已经可以将它的代码抽出来作为一个类库了

基于插件代码制作了一份 DEMO, 可以参考 [NeteaseCloudMusic-Audio-Recognize](https://github.com/akinazuki/NeteaseCloudMusic-Audio-Recognize)

`rec.json` 内是封装成 JSON 的录音 ArrayBuffer

运行后会在 Chrome Console 打印出音频指纹的 Base64 串

[本页面已更新](/netease-eapi-music-recognize-reverse-3/)

![console](https://s2.loli.net/2022/05/12/ZuSIAYHMtlxgpsk.png)

![postman-test](https://s2.loli.net/2022/05/12/UmwNMRthrfbzQiJ.jpg)
Loading