BetaMao

安全牛-加密视频提取

字数统计: 1.3k阅读时长: 6 min
2018/08/06 Share

早上起早了没事打开jeb说来分析一下安全牛的app,结果又是一天的时间·····最后一次,再手贱是狗!

  1. 本篇只是技术分享,请勿用于盗版侵权(老大人那么好,盗版良心会痛啊)。
  2. 复习要紧,没什么时间了,下面主要贴代码,长话短说!
  3. 我没有vip买的课也都过期了,不要问我要资源,有也不给(ε=ε=ε=┏(゜ロ゜;)┛)
  4. 以下代码不够晚上,自动化程度不高,有兴趣可以改,反正希望保存的视频自己珍藏就好了,不要传播。

分析

1.下载的视频被保存在/sdcard/edusoho目录下,下级目录根据用户id与课程,网校名分类。
2.下载的视频为加密分段存储,每段几百kb:

3.此目录下只会保留视频文件,m3u8的列表信息和key保存在数据库/data/data/com.edusoho.kuozhi/databases/edusoho里:

4.密钥没有加密,分段的视频文件被加了两次密:

才开始看到密钥文件刚好16字节还很开心,但是无法解密视频,接着看密钥数据格式有点奇怪,莫非两个连在一起用?结果猜错了,就只好乖乖逆算法,才开始看得脑壳疼,发现它是本地搭建一台web服务器,返回用户请求的数据,很明显就是m3u8列表文件(在数据库的data_m3u8表中)那种样子的请求:

那么猜测要么服务器返回没有任何问题,再由客户端解密,要么服务端返回解密后的数据,于是就用tcpdump -i lo抓包:

经对比发现服务端返回的m3u8list是无变化的,但是视频片段却和本地保存的很不一样啦,有点小激动,使用AES/CBC/PKCS5Padding方式成功解密数据!于是关键就在搭建的服务端处了,经过分析发现它的运算方式也很简单,这里就不多说了,下面直接上代码!

代码

为了避免无意义的粘贴复制,放上三个关键类的代码,稍微按照以上所述再补充一下就可以写出全部代码了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//主类用于解密
package edusohu;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.json.JSONArray;
import org.json.JSONObject;

public class Test {
private static final String DBPATH = "C:\\Users\\HAHA\\Desktop\\edusoho";
private static Pattern M3U8_EXT_X_KEY_PAT = Pattern.compile("#EXT-X-KEY:METHOD=AES-128,URI=\"([^,\"]+)\",IV=(\\w+)",
0x20);
private static String PWD = "C:\\Users\\HAHA\\Desktop\\20519\\";

public static void main(String[] args) throws Exception {
SqliteHelper sqlLite = new SqliteHelper(DBPATH);
JSONArray videolists = sqlLite.getAllDataFromData_M3u8();
for (Object json : videolists) {
dec((JSONObject) json);
}
}

private static void dec(JSONObject json) throws Exception {
String host = json.getString("host");
String playlist = json.getString("playlist");
StringBuilder sb = new StringBuilder();
String[] lines = playlist.split("\n");
Map<String, String> hasmap = new HashMap<>();
//处理m3u8列表文件
for (String line : lines) {
if (line.startsWith("#EXT-X-KEY")) {
Matcher matcher = M3U8_EXT_X_KEY_PAT.matcher(line);
if (matcher.find()) {
String g1 = matcher.group(1);
String keyName = g1.replace("http://localhost:8800/ext_x_key/", "");
sb.append(line.replace(g1, keyName) + "\n");
if (hasmap.get(keyName) == null) {
SqliteHelper sqliteHelper = new SqliteHelper(DBPATH);
String keyValue = sqliteHelper.getKeyValueFromData_Cache(keyName);
if (keyValue == null) {
return;
}
hasmap.put(keyName, keyValue);
Utils.saveFile(keyValue.getBytes(), PWD + "/" + keyName);
}
} else {
sb.append(line + "\n");
}
} else if (line.startsWith("http")) {
Pattern pattern = Pattern
.compile("http://localhost:8800/\\d+/([0-9a-f]{32})\\?.*fileGlobalId=[0-9a-f]{32}");
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
String g1 = matcher.group(1);
DecryptData.decrypt(PWD + g1, PWD + "dir/" + g1, host); //解密数据
sb.append(g1 + "\n");
}
} else {
sb.append(line + "\n");
}
}
Utils.saveFile(sb.toString().getBytes(), "1.m3u8");
String cmd = String.format("powershell H:\\Tools\\cmdtool\\ffmpeg.exe -allowed_extensions ALL -i %s.m3u8 -c copy -bsf:a aac_adtstoasc %s.mp4", "1","1");
System.out.println(cmd);
System.out.println(Utils.execCmd(cmd, new File(PWD + "dir/")));
}
}

解密的封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package edusohu;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class DecryptData {
public static void decrypt(String inputPath, String outputPath, String host) throws IOException {
FileInputStream fis = new FileInputStream(inputPath);
FileOutputStream os = new FileOutputStream(outputPath);
decrypt(fis, os, host);
}

public static void decrypt(FileInputStream inputfile, FileOutputStream outfile, String host) throws IOException {
DigestInputStream dis = new DigestInputStream(inputfile, host);
int bufLen = 0x1000;
try {
byte[] buf = new byte[bufLen];
while (true) {
int hasRead = dis.read(buf);
if (hasRead == -1) {
break;
}
outfile.write(buf, 0, hasRead);
}
outfile.flush();
} finally {
dis.close();
}
}
}

解密类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package edusohu;

import java.io.IOException;
import java.io.InputStream;

public class DigestInputStream extends InputStream {
private int mCurrentDigestIndex;
private byte[] mDigestKey;
private InputStream mTargetInputStream;

public DigestInputStream(InputStream arg2, String host) {
super();
this.initDigestKey(host, true);
this.mTargetInputStream = arg2;
}

public DigestInputStream(InputStream arg1, String arg2, boolean arg3) {
super();
this.initDigestKey(arg2, arg3);
this.mTargetInputStream = arg1;
}

public void close() throws IOException {
super.close();
this.mTargetInputStream.close();
}

private void initDigestKey(String arg3, boolean arg4) {
String v0 = arg3;
if((arg4) && !arg3.isEmpty()) {
v0 = DigestUtils.md5(arg3);
}

this.mCurrentDigestIndex = 0;
this.mDigestKey = v0.getBytes();
}

private void processorByteArray(int len, byte[] buf) {
if(len > 0 && this.mDigestKey.length != 0) {
int digestKeyLen_1 = this.mDigestKey.length - 1;
for(int i = 0;i < len;i++) {
int tmp = buf[i];
int v3 = this.mCurrentDigestIndex > digestKeyLen_1 ? 0 : this.mCurrentDigestIndex;
this.mCurrentDigestIndex = v3;
byte[] digestKey = this.mDigestKey;
int v4 = this.mCurrentDigestIndex;
this.mCurrentDigestIndex = v4 + 1;
buf[i] = ((byte)(digestKey[v4] ^ tmp));
}
}
}

public int read(byte[] buf) throws IOException {
int hasRead = this.mTargetInputStream.read(buf);
this.processorByteArray(hasRead, buf);
return hasRead;
}

public int read() throws IOException {
byte[] v0 = new byte[]{((byte)this.mTargetInputStream.read())};
this.processorByteArray(1, v0);
return v0[0];
}

public int read(byte[] arg3, int arg4, int arg5) throws IOException {
int v0 = this.mTargetInputStream.read(arg3, arg4, arg5);
this.processorByteArray(v0, arg3);
return v0;
}
}

效果如下:

总结

阔知学堂官网写的很厉害,事实上还是没有i春秋的加密做的好,另外我猜同理网页上的高清视频可以用类似的方式提取,不过他开源读读源码就清楚了。。。。

CATALOG
  1. 1.
  2. 2. 分析
  3. 3. 代码
  4. 4. 总结