IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    优酷真实视频地址解析破解思路,含破解方法(更新至2016-2-28)

    陈华发表于 2016-09-19 14:09:48
    love 0

    前些天一位高人发的贴子被删了,好不容易找回来,放在这里慢慢学习

    以下为转载内容:

    优酷视频的算法在2015年11月24日起至今连续更改了好几个版本,之前发的这篇临时解决方案得到很多响应,非常感谢!现在对这篇文章重新修改,全面规整完整的破解思路(含破解方法)!

    对了,这篇文章只是针对m3u8格式的视频。

    一.准备工作

    所谓工欲善其事必先利其器,做好破解的准备工作会令你事半功倍。

    1.首先准备一个Http抓包工具,PC上推荐Fiddler或者Postman,iOS上推荐Surge

    2.手备一台iOS测试设备(因为在Safari里优酷视频是确定使用m3u8进行播放的)

    二.抓包过程

    以Fiddler为例子,先配置Fiddler的设置和iOS设备,让Fiddler可以抓取iOS设备的请求数据,这一步如果不会请先自行摸索。

    打开Safari,输入优酷网址,进入一个视频查看Fiddler窗口,需要关注的一些重要信息:

    • 请求v.youku.com/v_show/id_{vid}.html时,返回的Cookie信息ykss
    • 请求play.youku.com/play/get.json?vid={vid}&ct=12时,请求头部的Cookie信息ykss和__ysuid,以及返回的security节点
    • 最终拼出m3u8地址pl.youku.com/playlist/m3u8?vid=....,请求后得到m3u8的具体视频内容

    前两步都好说,可第三部的m3u8这么一个复杂的串,格式如http://pl.youku.com/playlist/m3u8?vid=XMTQzNzIwODQ3Ng==&type=flv&ts=1452839810&keyframe=0&ep=ciaRGEGOX8YB5SPYjD8bNC6xJnIGXJZ3kn7P%2F5gbR8RQKevBzjPcqJ21TPs%3D&sid=045283981007412c2cb59&token=0524&ctype=12&ev=1&oip=2093868719,是怎么得到的?回头看一下抓到的一堆js代码,你会发现精髓就在这里——http://player.youku.com/embed/unifull/unifull_.js,这里面就是全部优酷真实地址的解析算法,源码非常长,如果你有兴趣,可以对文件格式化一下后慢慢阅读。下一节就具体来讲讲这里边的算法是怎样的。

    三.真实地址算法解析

    在上一节各个步骤标注的重要信息中,get.json这个接口返回的securiy节点就对算法起着很重要的作用(其实从名字就能看出它的特别意义嘛~)

    1. 获取sid, token和ep

    security节点有两个值,一个是encryp_string,一个是ip。

    在http://player.youku.com/embed/unifull/unifull_.js里,随处可以找到YK.m3u8src这个function,其中传入两个参数,一个是视频的vid,另一个是视频片段的格式(如”mp4“)。

    总结一下这个方法做的事情(这个算法需要计算3个值:sid, token和ep):

    1). 对encrypt_string进行Decode64,即对其进行base64解码,得到一个byte数组decoded_ep

    2).生成一个秘钥key_a(其值其实是固定的),利用这个秘钥与decoded_ep作Rc4算法加密,得到一个字符串temp

    3).temp的结果由“xxxx_xxxx”组成,切割‘_’,前者为sid,后者为token

    4).生成一个秘钥key_b(其值也是固定的),利用这个秘钥与字符串{sid}_{vid}_{token}的ASCII码字节数组whole记性Rc4算法加密,得到一个字符串temp2

    5).对temp2进行base64转换,然后再做url encode,得到ep

    至此sid, token和ep就得到了!

    关于Rc4加解密,可以参考https://zh.wikipedia.org/wiki/RC4

    上代码:

    ?
    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
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    /// <Summary>
      2 /// 计算sid, token和ep
      3 /// </Summary>
      4 public static void GetParameters(string vid, string encryptString, ref string sid, ref string token, ref string ep)
      5 {
      6             string keyA= "becaf9be";
      7             byte[] decodeEp= Decode64(encryptString); // 对encryptString作base64解码
      8             string temp = Rc4(keyA, decodeEp, false); // 用秘钥keyA之作Rc4加密,不做base64编码
      9             string[] part = temp.Split('_');
     10             sid = part[0];
     11             token = part[1];
     12
     13             string keyB= "bf7e5f01";
     14             byte[] whole= Encoding.ASCII.GetBytes(string.Format("{0}_{1}_{2}", sid, vid, token)); // 组合字符串的ASCII字节数组
     15             ep= WebUtility.UrlEncode(Rc4(keyB, whole, true)); // 用秘钥keyB与之作Rc4加密,且结果进行base64编码,之后再做url encode
     16 }
     17
     18 private static byte[] Decode64(string a)
     19 {
     20             if (string.IsNullOrEmpty(a))
     21             {
     22                 return null;
     23             }
     24             int f;
     25             int g;
     26             string h;
     27             List<byte> l = new List<byte>();
     28             int[] i =
     29             {
     30                 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
     31                 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
     32                 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
     33                 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29,
     34                 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1,
     35                 -1, -1
     36             };
     37             for (g = a.Length, f = 0, h = ""; g > f;)
     38             {
     39                 int b;
     40                 do
     41                 {
     42                     b = i[255 & a[f++]];
     43                 } while (g > f && -1 == b);
     44
     45                 if (-1 == b) break;
     46
     47                 int c;
     48                 do
     49                 {
     50                     c = i[255 & a[f++]];
     51                 } while (g > f && -1 == c);
     52
     53                 if (-1 == c) break;
     54
     55                 byte[] bytes0 = { (byte)(b << 2 | (48 & c) >> 4) };
     56                 h += Encoding.ASCII.GetString(bytes0);
     57                 l.Add(bytes0[0]);
     58                 int d;
     59                 do
     60                 {
     61                     d = 255 & a[f++];
     62                     if (61 == d) return l.ToArray();
     63                     d = i[d];
     64                 } while (g > f && -1 == d);
     65
     66                 if (-1 == d) break;
     67                 byte[] bytes1 = { (byte)((15 & c) << 4 | (60 & d) >> 2) };
     68                 h += Encoding.ASCII.GetString(bytes1);
     69                 l.Add(bytes1[0]);
     70                 int e;
     71                 do
     72                 {
     73                     e = 255 & a[f++];
     74                     if (61 == e) return l.ToArray();
     75                     e = i[e];
     76                 } while (g > f && -1 == e);
     77
     78                 if (-1 == e) break;
     79                 byte[] bytes2 = { (byte)((3 & d) << 6 | e) };
     80                 h += Encoding.ASCII.GetString(bytes2);
     81                 l.Add(bytes2[0]);
     82             }
     83             return l.ToArray();
     84 }
     85
     86 private static string Rc4(string a, byte[] c, bool isToBase64)
     87 {
     88             // rc4加密算法
     89             int f = 0, h = 0, q;
     90             int[] b = new int[256];
     91             for (int i = 0; i < 256; i++)
     92             {
     93                 b[i] = i;
     94             }
     95             while (h < 256)
     96             {
     97                 f = (f + b[h] + a[h % a.Length]) % 256;
     98                 int temp = b[h];
     99                 b[h] = b[f];
    100                 b[f] = temp;
    101                 h++;
    102             }
    103
    104             f = 0; h = 0; q = 0;
    105             string result = "";
    106             List<byte> bytesR = new List<byte>();
    107             while (q < c.Length)
    108             {
    109                 h = (h + 1) % 256;
    110                 f = (f + b[h]) % 256;
    111                 int temp = b[h];
    112                 b[h] = b[f];
    113                 b[f] = temp;
    114                 byte[] bytes = { (byte)(c[q] ^ b[(b[h] + b[f]) % 256]) };
    115                 bytesR.Add(bytes[0]);
    116                 result += Encoding.ASCII.GetString(bytes);
    117                 q++;
    118             }
    119
    120             if (isToBase64)
    121             {
    122                 var byteR = bytesR.ToArray();
    123                 result = Convert.ToBase64String(byteR);
    124                 //result = Encode64(result);
    125             }
    126
    127             return result;
    128 }

     

    2. 固定秘钥key_a和key_b的生成方法

    如果对秘钥key_a和秘钥key_b怎么得到有兴趣,请继续细度下面的内容,不感兴趣可以跳过这一段。

    在http://player.youku.com/embed/unifull/unifull_.js里,秘钥由方法translate(a, b)生成,其中a为一个组合字符串,b是一个固定的目标数组。

    translate方法的作用是利用字符的ASCII码进行变形,对于组合字符串中的每个字符,如果是小写字母a-z,则取其ASCII码,否则将其ASCII码+26;接下来如果能在目标数组里找到这个数码,如果能找到,且码的值 > 25,则取码-26,否则取码+97对应的字符;最后组合成新的位数相等的字符串,这就是秘钥。上代码:

     

    ?
    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
    private static string Translate(string a, int[] b)
     2 {
     3             List<string> c = new List<string>();
     4             foreach (char t in a)
     5             {
     6                 int e;
     7                 e = t >= 'a' && t <= 'z' ? t - 'a' : t - '0' + 26;
     8                 for (int j = 0; j < 36; j++)
     9                 {
    10                     if (b[j] == e)
    11                     {
    12                         e = j;
    13                         break;
    14                     }
    15                 }
    16                 if (e > 25)
    17                 {
    18                     c.Add((e - 26).ToString());
    19                 }
    20                 else
    21                 {
    22                     var bytes = new[] { (byte)(e + 97) };
    23                     c.Add(Encoding.ASCII.GetString(bytes));
    24                 }
    25             }
    26             string result = c.Aggregate(string.Empty, (current, cc) => current + cc.ToString());
    27             return result;
    28 }

     

    两个秘钥的原始字符串分别为b4eto0b4和boa4poz1

    目标数组:[19, 1, 4, 7, 30, 14, 28, 8, 24, 17, 6, 35, 34, 16, 9, 10, 13, 22, 32, 29, 31, 21, 18, 3, 2, 23, 25, 27, 11, 20, 5, 15, 12, 0, 33, 26]

    (不要问我怎么知道,看js源码自己组一下,都是固定的)

    3.拼接m3u8地址

    接下来就是按照m3u8指定的格式将数值填入即可,格式如下:

    http://pl.youku.com/playlist/m3u8?vid={vid}&type={type}&ts={ts}&keyframe=1&ep={ep}&sid={sid}&token={token}&ctype=12&ev=1&oip={oip}

    在这个串中还有其他一些变量需要填写,其中

    type指m3u8的内容中视频片段的格式,可取值标清(flv, 3pgphd)、高清(mp4, flvhd)、超清(hd2)、1080P(hd3)

    ts指当前时间的Unix时间戳(精确到秒)

    oip指security节点中的ip

    组合完成后就去请求m3u8地址吧,会得到一系列的视频片段。

     

    4.注意事项

    算法逻辑清楚了,还需要有写注意点,一旦忽略就会导致最终即使拼出m3u8地址,也请求不到视频内容。

    回顾一下第二节的3个步骤,每个步骤都有注意点:

    第一个步骤,请求v.youku.com/v_show/id_{vid}.html ,返回值会带有名字为ykss的Cookie,这个很重要,不能丢

    第二个步骤,请求get.json时,第一步的Cookie:ykss也要发送,并且还要增加一个名为__ysuid的Cookie,__ysuid=getPvid(6),算法请看以往的更新记录;还有一点,Referer要填视频的url地址,绝对不能丢,否则即使get.json能正常返回数据,也能拿到security节点,但是拼出来的m3u8无论你怎么请求都无法得到视频内容,切记Referer不要丢!

    第三个步骤,请求m3u8地址时,就目前看已经不需要再传名为r的Cookie,但是以优酷的尿性,保不准以后还会再加上。

     

    如果有什么疑问,欢迎留言。(以下内容是以往的一些变更记录,已作废)

    ————————————————————————————————————————————————————————————————

    11-30更新:

    今日又发现了一个问题,这里补充一下。get.json这个接口返回的时候,Response带有Cookie,如果你是自己提供web api,这个Cookie记得也要返回去给应用,否则应用即使拼出m3u8的地址,也无法获取到完整的视频内容。

     

    ————————————————————————————————————————————————————————————————

    12-8更新:

    感谢@kkia 发现,

    请求http://play.youku.com/play/get.json?vid={vid}&ct=12这个api的时候,http请求需要带上Referer:{url},url为需要获取的视频的页面链接,不然会出现非主站请求的错误。

    另外,cookie校验机制又启动了。

    所以,如果自己封装web api,建议每次请求,都把m3u8文件的内容下载下来,自己生成并存储临时的m3u8文件,再把这个文件的链接返回给客户端,再定时去清理这些临时文件。

     

    ————————————————————————————————————————————————————————————————

    12-29更新:

    感谢@kkia 发现,name=r的Cookie获取途径再次发生了变化,步骤更新如下:

    1.通过完整的视频地址url(http://v.youku.com/v_show/id_{vid}.html)中拿到一堆Cookie,只提取其中的ykss=132b825604a2f7516fd91fc0; path=/; domain=.youku.com; 这条Cookie

    2.请求get.json时带上以上名为ykss的Cookie,且加上Referer=视频url,请求结果中就会出现名为r的Cookie了

    3.最后请求m3u8地址的时候带上名为r的Cookie,Referer=视频url,就能得到结果了。

     

    ————————————————————————————————————————————————————————————————

    12-30更新:

    优酷日常作死,又更新了Cookie算法,看来这是持久战,大家做好心理准备,相信这么折腾大家都明白名为r的Cookie的重要性了吧。

    目前可以这样获取名为r的Cookie:

    直接请求get.json这个API,头部需要有如下格式的信息:

    Cookie: __ysuid={16-length-string};

    Referer: http://v.youku.com/v_show/id_{vid}.html

    其中__ysuid的算法如下(感谢@kkia)

     

     

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public string getPvid(int len)
     2 {
     3     string[] randchar = new string[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
     4 "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
     5 "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
     6     };
     7     var i = 0;
     8     var r = "";
     9     TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
    10     Int64 seconds = Convert.ToInt64(ts.TotalMilliseconds);
    11 
    12     for (i = 0; i < len; i++)
    13     {
    14         var index = System.Convert.ToInt32(new Random().Next() * Math.Pow(10, 6) % randchar.Length);
    15         r += randchar[index];
    16     }
    17     return seconds + r;
    18 }

     

    由13位长的UNIX时间戳拼接3位长的随机字符串组成__ysuid。

    事实上,只要在请求m3u8链接的时候Cookie里含有r(不用管value是啥,至少现阶段一直是这样的),就能请求成功。

     

    附上WPF的demo

    自由转载,转载请注明: 转载自WEB开发笔记 www.chhua.com

    本文链接地址: 优酷真实视频地址解析破解思路,含破解方法(更新至2016-2-28) http://www.chhua.com/web-note5339

    随机笔记

    • 搜房网信息不可靠,有炒作房价嫌疑!
    • 移动互联时代,你还在等什么?
    • php+mysql无限级分类(非递归)
    • 我的FLEX学习瓶颈
    • PHP判断搜索引擎蜘蛛并自动记忆到文件



沪ICP备19023445号-2号
友情链接