吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 40206|回复: 122
上一主题 下一主题
收起左侧

[Web逆向] XXX视频cKey9.1的生成分析和实现

    [复制链接]
跳转到指定楼层
楼主
ZSAIm 发表于 2019-5-5 17:09 回帖奖励
本帖最后由 Kido 于 2019-6-6 18:13 编辑

腾讯视频cKey9.1的生成分析和实现

说明:

这文章并不是对算法的分析,而仅仅是为了实现cKey9.1(因为有Nodejs了,所以就不需要担心JS脚本的运行)。

而其中的算法就是取自于腾讯视频的9.1加密版本的cKey,所以本文章仅仅是对腾讯视频cKey生成算法的提取。

项目实现

前提

建议

在阅读本文章之前先阅读我的上一篇文章 爱奇艺视频H5解析分析过程 ,因为这一文章将基于默认已知上一篇的一些使用操作方法的前提下进行。
这文章只对cKey生成的实现和分析,其他所需提交的参数不会在这里分析说明。

预备知识

以下所需要的知识不需要精通,只需要了解和知道基本的语法即可。

  • JavaScript
  • WebAssembly

正文

关于腾讯视频cKey

  • 腾讯视频的cKey是解析视频直接地址的核心关键,只有通过该算法的加密才能够请求到要解析的数据。
  • 目前我所知的腾讯算法有8.1和9.1两个版本,8.1版本是为了兼容不支持WebAssembly的那一部分用户。所以实际上这两者在PC网页端里面所做的工作基本一致。因为8.1版本的cKey基本上和爱奇艺那一部分算法的分析过程差不多所以我就不在这里分析了(腾讯视频cKey的那一部分解析可能要繁琐一些)。
  • 所以这文章将对9.1版本进行分析实现。

关于工具

  • 这一文章将不使用Fiddler来抓包,而是全程使用chromium核的开发者工具进行分析。

  • 一说到chromium,可能大家第一反应就是使用谷歌浏览器。但是很遗憾,在这里不使用谷歌浏览器,因为谷歌浏览器所用到的cKey是8.1版本的。(我这里的浏览器版本是74.0.3729.131, 以后的版本可能会使用9.1)

  • 除了谷歌浏览器外还有很多使用chromium核的浏览器,360浏览器(未知),搜狗浏览器(ckey8.1),,我这里将使用百分浏览器(ckey9.1),并且在腾讯视频解析里面他使用到的是cKey9.1版本。这正是我们所要分析的对象。

  • 下面将以https://v.qq.com/x/cover/bzfkv5se8qaqel2/j002024w2wg.html 为分析例子。

初次分析

  • F12打开开发者工具。
  • 打开链接:https://v.qq.com/x/cover/bzfkv5se8qaqel2/j002024w2wg.html
  • 切换Network查看抓包。
  • 搜索proxyhttp,找到两项 https://vd.l.qq.com/proxyhttp 。(这里搜proyhttp是因为前面省略了从视频到解析链接的寻找过程,如果不知道怎么做,就先看上文的前提建议)
  • 既然知道了请求视频的链接是proxyhttp,那么在proxyhttp发送前中断如何?

    • 转到Sources页面,在XHR/fetch Breakpoints+进行添加条件断点 proxyhttp,意思就是在包含proxyhttp字串的请求链接时进行中断。
    • [图1.1]
    • 按F5刷新,等待中断发生。
    • [图1.2]
    • 之后看到右边的调用栈信息Call Stack,可以看到调用函数的右边表明了被调用函数所在的JS链接。
    • [图1.3]
    • 为什么要看这些呢,因为对于一个具有庞大的JS脚本链接的视频网站来说,找准加密所在的JS算法所在的链接是第一步。首先要知道的是,在POSThttps://vd.l.qq.com/proxyhttp之前肯定先需要先收集所要发送的data,所以必然这将调用到获取data的函数,而获取部分必然会与加密部分有联系,所以可以通过这样的方式来找到加密部分。
    • (事实上你可以直接在Network页面搜索proxyhttp来定位到目标链接(注意这不是一定的),但是由于在爱奇艺分析过程中使用了这一方法,我在这里用一下别的方法来解决。)
    • 由[图1.3]可以知道的是tvx.core.js是用来对发送请求的。所以大概可以估计这文件就是对请求函数的集合,既然已经到了发送的地步了,那么data肯定是已经获取完成了。
    • 第二个JS文件pecker.js,点击他,然后往下滚看到Scope项,看到e,f两项就是要发送的请求的所有数据,展开发现data中cKey已经存在,所以这里Call Stack往上走(往上一层调用走)。
    • [图1.4] [图1.5] [图1.6]
    • e.requestPostCgi位于htmlframe.......(关于Call Stack看图1.3),粗看函数名似乎就是提交data的获取。将其作为重点深找一下。
    • 进入e.requestPostCgi后往下滚看到Scope,下图,本地变量c就是要提交的data,图1.7的中间红框部分就是本地变量c的获取,发现vinfoparam是由62455行生成的数据。f.param(b.vinfoparam),发现该函数传入了参数b.vinfoparam,鼠标停在该参数出现了数据cKey。所以可以断定重点在于b.vinfoparam,而不是函数f.param
    • [图1.7]
    • 发现b.vinfoparam中的变量b是调用e.requestPostCgi时传入的参数(位于62446
    • 既然这样,看【图1.3】Call Stack,往上一层调用栈走,进入调用栈c
    • [图1.8]
    • 传入的是
      {
      vinfoparam: g,
      adparam: e,
      domain: v,
      method: w
      }
    • 我们关注的对象是vinfoparam: g,往前找g的生成代码。看【图1.8】的62742进入函数f.getInfoConfig。却没有发现cKey的踪迹,既然我们无法直接知道,不如放个断点走一走。
    • [图1.9] [图1.10] [图1.11]
    • 看上图1.11,我们进入了getInfoConfig的调试中。
    • [图1.12] [图1.13]
    • 一直往下走【看图1.12、图1.13】都发现cKey还没获取,一直到了e(h)。【图1.14】【图1.15】
    • [图1.14] [图1.15]
    • a.cKey = b || ""这就是cKey生成的地方。就是变量b,也就是
    f ? (a.encryptVer = "9.1",
            b = f(a.platform, a.appVer, a.vids || a.vid, "", a.guid, a.tm)) : (a.encryptVer = "8.1",
            b = i(a.vids || a.vid, a.tm, a.appVer, a.guid, a.platform)),
            a.cKey = b || ""
    • 从这里可以看到8.1版本和9.1版本的控制是由f()参数控制的。但这不是我们的重点,既然我们分析的是9.1版本,那么进入函数f()
重点分析
function i(a, b, c, d, e) {
      // 注意下面的函数k(a, b)是函数f(a)里面的k函数,为了方便起见直接在合起来写了
      function k(a, b) {
          if (0 === b || !a)
              return "";
          for (var c, d = 0, e = 0; ; ) {
              if (g(a + e < db),
              c = Ga[a + e >> 0],
              d |= c,
              0 == c && !b)
                  break;
              if (e++,
              b && e == b)
                  break
          }
          b || (b = e);
          var f = "";
          if (d < 128) {
              for (var h, i = 1024; b > 0; )
                  h = String.fromCharCode.apply(String, Ga.subarray(a, a + Math.min(b, i))),
                  f = f ? f + h : h,
                  a += i,
                  b -= i;
              return f
          }
          return m(a)
      }

      function f(a) {
          return "string" === b ? k(a) : "boolean" === b ? Boolean(a) : a
      }
      var i = h(a)
        , j = []
        , l = 0;
      if (g("array" !== b, 'Return type should not be "array".'),
      d)
          for (var m = 0; m < d.length; m++) {
              var n = $a[c[m]];
              n ? (0 === l && (l = Ub()),
              j[m] = n(d[m])) : j[m] = d[m]
          }
      var o = i.apply(null, j);
      return o = f(o),
      0 !== l && Tb(l),
      o
  }
  • 由上面找到的【图1.15】开始。
  • 断点继续往下走,进入【图2.1】【图2.2】
    • [图2.1] [图2.2] [图2.3]
    • 返回的是变量o,那么我们重点关注他,走到o64084行,进去,【图2.3】看到ua._getckey,可以知道看来是找对地方了。
ua._getckey = function() {
    return g(ib, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"),
    g(!jb, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"),
    ua.asm._getckey.apply(null, arguments)
}
  • 进去ua.asm._getckey.apply(null, arguments),??????????什么鬼【图2.4】
  • [图2.4] [图2.5]
    • 这函数名怎么是个数字???而且发现也进不去,而且提示的是native code,这说明了这不是JS的原生代码,可能是其他语言实现的方法。
    • 事实上这是WebAssembly,这是一种JS的一种可以理解成是交叉编程的一种方式,目的是为了提高JS运行效率,这是由C或者其他编程语言生成的代码,生成*.wasm然后交给WebAssembly加载处理运行。
    • 可以通过【图2.5】看到加载的wasm文件,而其中的函数名29就是对应wasm-0005098e-29,你点进去查看就看反汇编到具体的指令。
    • 好了,基本说明了这一种JS的技术,如果要了解更多就百度谷歌把。
  • 那么重要的是要找到这被加载的wasm文件。
  • 一个最简单的方法就是直接在Sources页面搜索wasm就能找到加载的wasm文件。
  • [图2.6]
  • 对于找wasm也可以使用其他方法实现,但是既然是请求GET到的,当然能抓包到了,所以这里就偷懒不通过代码分析了。(不然篇幅会很长)
    • 要知道的是,我们虽然得到了wasm文件,但是任何交叉编程类的东西,都需要有接口,而这些接口或者必须提供的,所以我们还需要找到wasm接口部分,但这里先放一边,待会再进行。
  • 通过【图2.4】可以看到的是传了参数arguments,虽然我们得到了wasm,但是我们还是需要知道参数arguments才能实现算法。
  • arguments就是前面【图2.3】传递的参数j,我们要得到j
  • 看【图2.2】进入函数Ub()n(),而n()是由var n = $a[c[m]];提供的。所以我们F5刷新下页面在【图2.2】重新断点。为的就是单步执行,找所需。
  • [图2.7]
  • 由【图2.7】出单步走,你会发现有两种n,一种是undefined
stringToC: function(a) {
    var b = 0;
    if (null !== a && void 0 !== a && 0 !== a) {
        var c = (a.length << 2) + 1;
        b = Sb(c),
        o(a, b, c)
    }
    return b
}
  • 往下一直找能找到Sb()o()n(),其中包括了循环中的Ub还有f()函数中的k()然后你能整理出来
Ub = function() {
   return g(ib, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"),
   g(!jb, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"),
   ua.asm.stackSave.apply(null, arguments)
}

Sb = function() {
     return g(ib, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"),
     g(!jb, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"),
     ua.asm.stackAlloc.apply(null, arguments)
 }

function o(a, b, c) {
  return g("number" == typeof c, "stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),
  n(a, Ga, b, c)
}

function n(a, b, c, d) {
    if (!(d > 0))
        return 0;
    for (var e = c, f = c + d - 1, g = 0; g < a.length; ++g) {
        var h = a.charCodeAt(g);
        if (h >= 55296 && h <= 57343) {
            var i = a.charCodeAt(++g);
            h = 65536 + ((1023 & h) << 10) | 1023 & i
        }
        if (h <= 127) {
            if (c >= f)
                break;
            b[c++] = h
        } else if (h <= 2047) {
            if (c + 1 >= f)
                break;
            b[c++] = 192 | h >> 6,
                b[c++] = 128 | 63 & h
        } else if (h <= 65535) {
            if (c + 2 >= f)
                break;
            b[c++] = 224 | h >> 12,
                b[c++] = 128 | h >> 6 & 63,
                b[c++] = 128 | 63 & h
        } else if (h <= 2097151) {
            if (c + 3 >= f)
                break;
            b[c++] = 240 | h >> 18,
                b[c++] = 128 | h >> 12 & 63,
                b[c++] = 128 | h >> 6 & 63,
                b[c++] = 128 | 63 & h
        } else if (h <= 67108863) {
            if (c + 4 >= f)
                break;
            b[c++] = 248 | h >> 24,
                b[c++] = 128 | h >> 18 & 63,
                b[c++] = 128 | h >> 12 & 63,
                b[c++] = 128 | h >> 6 & 63,
                b[c++] = 128 | 63 & h
        } else {
            if (c + 5 >= f)
                break;
            b[c++] = 252 | h >> 30,
                b[c++] = 128 | h >> 24 & 63,
                b[c++] = 128 | h >> 18 & 63,
                b[c++] = 128 | h >> 12 & 63,
                b[c++] = 128 | h >> 6 & 63,
                b[c++] = 128 | 63 & h
        }
    }
    return b[c] = 0,
    c - e
}
  • 大家应该发现了上面的函数o(a, b, c)调用了方法n(a, Ga, b, c),其中a, b,c 我们都知道,但是Ga是什么东西?
  • 既然在Locan变量无法找到,那么网上一级找。看下图2.8
  • [图2.8] [图2.9]
  • 发现上一级有Ga,所以,我们找到他了,看【图2.9】
  • 既然知道了要找Ga的缘由,那么把所有对于给Ga赋值的东西联系起来。
  • 这将是个漫长的过程。

// 只要知道ArrayBuffer的都知道这将导致下面的 Fa,Ha, Ja, Ga, Ia, Ka, La, Ma绑在了Ea下
// 也就是说Ea是数据,而Fa,Ha, Ja, Ga, Ia, Ka, La, Ma就是描述这个数据的方式,所有的改变只是对Ea操作。所以对其中一个改变都会改变我们的目标Ga
function w() {
    Fa = new Int8Array(Ea),
        Ha = new Int16Array(Ea),
        Ja = new Int32Array(Ea),
        Ga = new Uint8Array(Ea),
        Ia = new Uint16Array(Ea),
        Ka = new Uint32Array(Ea),
        La = new Float32Array(Ea),
        Ma = new Float64Array(Ea);
}

function d(a) {
    var b = Oa;
    return Oa = Oa + a + 15 & -16,
        b
}
function e(a, b) {
    b || (b = Da);
    var c = a = Math.ceil(a / b) * b;
    return c
}

var Da = 16;

var Ea, Fa, Ga, Ha, Ia, Ja, Ka, La, Ma, Na, Oa, Pa, Qa, Ra, Sa, Ta, Ua, Va = {
    "f64-rem": function(a, b) {
        return a % b
    },
    "debugger": function() {}
}, Wa = (new Array(0), 1024) ;

Na = Oa = Qa = Ra = Sa = Ta = Ua = 0,
    Pa = !1;
var cb = 5242880 , db = 16777216, ab = 65536;

var wasmMemory = new WebAssembly.Memory({
    initial: db / ab,
    maximum: db / ab
});
Ea = wasmMemory.buffer;

w();
Ja[0] = 1668509029;
Ha[1] = 25459;

var eb = []
    , fb = []
    , gb = []
    , hb = []
    , ib = !1
    , jb = !1;

Na = Wa,
    Oa = Na + 6928,
    fb.push();

Oa += 16;

Ua = d(4),
Qa = Ra = e(Oa),
Sa = Qa + cb,
Ta = e(Sa),
Ja[Ua >> 2] = Ta,
Pa = !0;
  • 以上解决了Ga的初始化。
  • 目前为止解决了循环这一部分了。
for (var m = 0; m < d.length; m++) {
    var n = $a[c[m]];
    n ? (0 === l && (l = Ub()),
    j[m] = n(d[m])) : j[m] = d[m]
}
  • 那么下一部分就是
var o = i.apply(null, j);
              return o = f(o),
              0 !== l && Tb(l),
              o
  • 前面我们已经说了i.apply(null, j);,他的代码位于wasm中。
  • 所以目前我们需要的是正确加载wasm,只要完成这一步,所以函数都可以串起来实现cKey了。
  • 我们先看下如下代码
var ub = ua.asm(ua.asmGlobalArg, ua.asmLibraryArg, Ea)

var Cb = ub._getckey;
ub._getckey = function() {
    return g(ib, "you need to wait for the runtime to be ready (e.g. wait for main() to be called)"),
    g(!jb, "the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)"),
    Cb.apply(null, arguments)
}
  • 也就是说,我们先知道ub也就是ua.asm(ua.asmGlobalArg, ua.asmLibraryArg, Ea)
  • 调试进去,找到以下代码。
ua.asm = function(a, b, c) {
    if (!b.table) {
        var d = ua.wasmTableSize;
        void 0 === d && (d = 1024);
        var f = ua.wasmMaxTableSize;
        "object" == typeof WebAssembly && "function" == typeof WebAssembly.Table ? void 0 !== f ? b.table = new WebAssembly.Table({
            initial: d,
            maximum: f,
            element: "anyfunc"
        }) : b.table = new WebAssembly.Table({
            initial: d,
            element: "anyfunc"
        }) : b.table = new Array(d),
        ua.wasmTable = b.table
    }
    b.memoryBase || (b.memoryBase = ua.STATIC_BASE),
    b.tableBase || (b.tableBase = 0);
    var h;
    return h = e(a, b, c),
    g(h, "no binaryen method succeeded. consider enabling more options, like interpreting, if you want that: http://kripken.github.io/emscripten-site/docs/compiling/WebAssembly.html#binaryen-methods"),
    h
}
  • 这里面就是对wasm的加载了。而这一切加载的前提是知道参数a,b,c,所以再回到ua.asm(ua.asmGlobalArg, ua.asmLibraryArg, Ea)
  • 也就是ua.asmGlobalArg, ua.asmLibraryArg, Ea,而其中Ea我们已经在前面说过了,跟Ga有关系。
  • 很容易找到
ua.wasmTableSize = 99,
        ua.wasmMaxTableSize = 99,
        ua.asmGlobalArg = {},
ua.asmLibraryArg = {
    abort: sa,
    assert: g,
    enlargeMemory: B,
    getTotalMemory: C,
    abortOnCannotGrowMemory: A,
    abortStackOverflow: z,
    nullFunc_ii: ca,
    nullFunc_iiii: da,
    nullFunc_v: ea,
    nullFunc_vi: fa,
    nullFunc_viiii: ga,
    nullFunc_viiiii: ha,
    nullFunc_viiiiii: ia,
    invoke_ii: ja,
    invoke_iiii: ka,
    invoke_v: la,
    invoke_vi: ma,
    invoke_viiii: na,
    invoke_viiiii: oa,
    invoke_viiiiii: pa,
    __ZSt18uncaught_exceptionv: Q,
    ___cxa_find_matching_catch: S,
    ___gxx_personality_v0: T,
    ___lock: U,
    ___resumeException: R,
    ___setErrNo: ba,
    ___syscall140: V,
    ___syscall146: X,
    ___syscall54: Y,
    ___syscall6: Z,
    ___unlock: $,
    _abort: _,
    _emscripten_memcpy_big: aa,
    _get_unicode_str: P,
    flush_NO_FILESYSTEM: W,
    DYNAMICTOP_PTR: Ua,
    tempDoublePtr: rb,
    STACKTOP: Ra,
    STACK_MAX: Sa
};

var ub = ua.asm(ua.asmGlobalArg, ua.asmLibraryArg, Ea)
  • 可以看到wasm的加载连接了很多接口,但是我在这里只说其中比较重要的方法P,也就是_get_unicode_str: P,中的P,对应如下
function P() {
    function a(a) {
        return a ? a.length > 48 ? a.substr(0, 48) : a : ""
    }
    function b() {
        var b = document.URL
          , c = window.navigator.userAgent.toLowerCase()
          , d = "";
        document.referrer.length > 0 && (d = document.referrer);
        try {
            0 == d.length && opener.location.href.length > 0 && (d = opener.location.href)
        } catch (e) {}
        var f = window.navigator.appCodeName
          , g = window.navigator.appName
          , h = window.navigator.platform;
        return b = a(b),
        d = a(d),
        c = a(c),
        b + "|" + c + "|" + d + "|" + f + "|" + g + "|" + h
    }
    var c = b()
      , d = p(c) + 1
      , e = Pb(d);
    return o(c, e, d + 1),
    e
}
  • 为什么这个重要呢?当你把刚开始重点分析后面的那一个函数单步走一遍你就会发现了,当在执行_getckey()的时候,他会call 20,也就是wasm文件中的编号20的函数,但是你仔细看【图2.5】会发现缺少缺少了20号函数,这是因为他会上面在链接接口的时候链接了函数P(),而函数P()就是20号函数。
  • 而除此之外其他的函数对我们来说用处不大,所以你大可以使用空函数来链接。
  • 所以我如下处理了接口链接和wasm环境的配置
var fun_ = function(){};

wasm_env = {
    abort: fun_,
    assert: fun_,
    enlargeMemory: fun_,
    getTotalMemory: C,
    abortOnCannotGrowMemory: fun_,
    abortStackOverflow: fun_,
    nullFunc_ii: fun_,
    nullFunc_iiii: fun_,
    nullFunc_v: fun_,
    nullFunc_vi: fun_,
    nullFunc_viiii: fun_,
    nullFunc_viiiii: fun_,
    nullFunc_viiiiii: fun_,
    invoke_ii: fun_,
    invoke_iiii: fun_,
    invoke_v: fun_,
    invoke_vi: fun_,
    invoke_viiii: fun_,
    invoke_viiiii: fun_,
    invoke_viiiiii: fun_,
    __ZSt18uncaught_exceptionv: fun_,
    ___cxa_find_matching_catch: fun_,
    ___gxx_personality_v0: fun_,
    ___lock: fun_,
    ___resumeException: fun_,
    ___setErrNo: fun_,
    ___syscall140: fun_,
    ___syscall146: fun_,
    ___syscall54: fun_,
    ___syscall6: fun_,
    ___unlock: fun_,
    _abort: fun_,
    _emscripten_memcpy_big: fun_,
    _get_unicode_str: P,              // function 20( ) => P( )
    flush_NO_FILESYSTEM: fun_,
    DYNAMICTOP_PTR: 7968,               //Ua
    tempDoublePtr: 7952,                //rb
    STACKTOP: 7984,                     //Ra
    STACK_MAX: 5250864,                 //Sa

    memoryBase: 1024,
    tableBase: 0,
    memory: wasmMemory,
    table: new WebAssembly.Table({
        initial: 99,
        maximum: 99,
        element: "anyfunc"
    })
};

var importObject = {
    'env': wasm_env,
    'asm2wasm': {
        "f64-rem": function(a, b) {
            return a % b
        },
        "debugger": function() {}
    },
    'global': {
        NaN: NaN,
        Infinity: 1 / 0
    },
    "global.Math": Math,
    // "parent": {};

};
  • 到目前为止,已经完成了对接口的链接,也就是可以进行加载wasm了,再然后就可以对cKey进行测试了。
  • 注意前面花了很大篇幅来再次回顾了对变量或函数的定位方法,所以后面很大一部分我会省略这一步骤,只是直接一步带过,直说个结果。

结束分析

因为本来这篇幅就很长,所以完整的ckey代码不会在这里列出来。如果要看JS实现代码可以进入 https://github.com/ZSAIm/iqiyi-parser/blob/master/js/tencent.js

最后

  • Github项目链接: 点这
  • 本文章仅用于技术交流。

免费评分

参与人数 69吾爱币 +64 热心值 +60 收起 理由
lyyq + 1 + 1 楼主辛苦了,让人很有启发,很希望再介绍一下flowid、rfid等参数的获取教程.
安尼大大 + 1 + 1 我很赞同!
tianbukongbai + 1 + 1 谢谢@Thanks!
z157394168 + 1 + 1 大佬太厉害了,膜拜膜拜
晚辈小生 + 1 + 1 谢谢@Thanks!
hhpx + 1 + 1 鼓励转贴优秀软件安全工具和文档!
zkhx + 1 热心回复!
Jerry_bean + 1 + 1 用心讨论,共获提升!
王宝宝啊 + 1 6
ttd_2001 + 1 + 1 用心讨论,共获提升!
squall007 + 1 + 1 用心讨论,共获提升!
13137 + 1 + 1 用心讨论,共获提升!
伍陆柒 + 1 + 1 热心回复!
fanweifang + 1 谢谢@Thanks!
一念苍穹变 + 1 + 1 我很赞同!
cloudlose + 1 这东西我是小白,完全看不懂,但是是真心顶你,辛苦了!
gtxing + 1 + 1 用心讨论,共获提升!
love88715 + 1 + 1 这东西我是小白,完全看不懂,但是是真心顶你,辛苦了!
poisonbcat + 1 谢谢@Thanks!
17605010607 + 1 用心讨论,共获提升!
挥汗如雨 + 2 + 1 谢谢@Thanks!
hack现实 + 1 用心讨论,共获提升!
LvMou + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
f4cku + 1 + 1 热心回复!
梦回凉亭的她 + 1 鼓励转贴优秀软件安全工具和文档!
tzxinqing + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
oneju + 1 + 1 谢谢@Thanks!
daimiaopeng + 1 + 1 谢谢@Thanks!
zhuzhu0921 + 1 + 1 我很赞同!
nzx123 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
cxp521 + 1 + 1 谢谢@Thanks!
baby5202 + 1 用心讨论,共获提升!
kaysen888 + 1 谢谢@Thanks!
qsws3344 + 1 + 1 谢谢@Thanks!
yxid + 1 + 1 用心讨论,共获提升!
beawes0me + 1 用心讨论,共获提升!
mn7522 + 1 + 1 谢谢@Thanks!
冷沐晨 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
hujie110290 + 1 + 1 谢谢@Thanks!
yixi + 1 + 1 谢谢@Thanks!
cloudwater + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
yrstf + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
卡拉肖克倩 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
笙若 + 1 + 1 谢谢@Thanks!
听雨长风 + 1 + 1 用心讨论,共获提升!
归路碧迢迢 + 1 + 1 用心讨论,共获提升!
qwekk + 1 + 1 热心回复!
tianshu44 + 1 + 1 用心讨论,共获提升!
独行风云 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
电脑清洁圆 + 1 + 1 看不懂也要顶你,这么多图解,辛苦了。我很少评分的哦。
贵族天王星 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
momosys + 1 + 1 谢谢@Thanks!
echovaio + 1 + 1 看着就牛x
bfxhsc + 1 用心讨论,共获提升!
wq811120823 + 1 虽然看不懂,但这么多代码和字看着就牛逼啊
cxsize + 1 + 1 谢谢@Thanks!
hxw0204 + 1 + 1 谢谢@Thanks!
爱拍阴天 + 1 + 1 我很赞同!
ggshihai + 1 + 1 热心回复!
某些人 + 1 + 1 谢谢@Thanks!
zgy150010318 + 1 + 1 热心回复!
sunnylds7 + 1 + 1 热心回复!
gunxsword + 1 + 1 看不懂,默默的点个赞!
jxf269 + 1 用心讨论,共获提升!
c00kie + 1 谢谢@Thanks!
Qrince + 1 优秀
Thending + 1 + 1 谢谢@Thanks!
逍遥一仙 + 3 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
叶泉 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

头像被屏蔽
推荐
叶泉 发表于 2019-5-5 17:17
本帖最后由 叶泉 于 2019-5-5 18:14 编辑

很不错的一个技术帖子。。。我当时找的时候发现存在一些问题,有些视频可以用这个算法,有些就会报错,然后时间紧就没继续研究下去了。原来还有另一个算法。。。啧啧。。孤陋寡闻了。。。找这些东西真的很烦人。。。
https://www.52pojie.cn/forum.php ... 859308&pid=23765802
推荐
Awesome丶ABC 发表于 2019-5-5 17:48
大佬大佬,虽然看不懂,还是要给你回复下。。。
4#
o369516550 发表于 2019-5-5 17:14
5#
叼烟炒菜 发表于 2019-5-5 17:16
最后出来了吗
6#
字母 发表于 2019-5-5 17:28
牛逼了大佬 这玩意看起来很费时间的
7#
GarenMorbid 发表于 2019-5-5 17:40
膜拜大佬
8#
cdygr 发表于 2019-5-5 17:45
这就是牛人,专业人员能看得懂的,我就只能看到字多,支持了。
9#
keweiye 发表于 2019-5-5 17:49
老板你写的好长啊,文笔可以的
10#
帅帅的颖宝宝 发表于 2019-5-5 17:49
cookie怎么导入视频下载器?
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 16:26

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表