Reverse engineering streamov

Tento článok je určený len na výskumné a vzdelávacie účely.

Nie je to tak dávno, čo DMCA stiahlo stránky ako Openload, Streamango, Verystream. Niektoré stremovacie služby zostali, pozrieme sa na to aké zabezpečovacie techniky využívajú, aby zamedzili sťahovaniu obsahu z ich stránok.

Dnes sa pozrieme na 2 tieto služby, prvou je mixdrop.co a druhou netu.tv.

Mixdrop.co

Táto služba sa nesnaží nijak extra skrývať svoj obsah, postup tejto služby je jednoduchý:

  1. Stránka, ktorá má zobraziť prehrávať si vloží iframe kód na svoju stránku
<iframe id="iframe" src="https://mixdrop.co/e/xzmzp/" scrolling="no" frameborder="0" width="850" height="370" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>
  1. Následne sa načíta obsah iframe
  2. Získanie adresy videa.

Stránka po načítaní má vo svojom script tagu, takúto funkciu. Na prvý pohľad môže táto funkcia vyzerať zložito, no je obalená v eval (naprosto zbytočný), teda vieme povedať, že sa niekto snažil zakryť jej obsah cez obfuscator. Naštastie stačí zbehnúť tento script cez stránku ako

MDCore.ref = "xzmzp";
eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?
String.fromCharCode(c+29):c.toString(36))};
if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}
k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){
if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p
}
('0.J="//s-3.5.6/l/2.k";0.j="//s-3.5.6/v/2.7?s=i-h&e=d";0.f="2.7";0.c="s-3";0.b="";0.a="1";0.9="n%g%o%p.I.H%G%F%E.D.C%B%A%z%y%x%w-u-t%r%4%q%4%m%8";',
46,
46,
'MDCore||003b0e6a154979b7c1d1b33b11d7e3c3|delivery7|3D|mxdcontent|net|mp4|3D137583|referrer|chromeInject|remotesub|vserver|1577144412||vfile|3A|CVp4calqaNlD9Qw|HPDjzc|vsrc1|jpg|thumbs|26i|http|2F|....|26tit|26vip||2019|witcher||3Dthe|26url|3D5|26version|3Dxzmzp0IKPEciTWQ9L6FVJjB2o|3Fid|php|co|2Fmixdrop|....|2Fserial|...|...|poster'.split('|'),
0,
{}))

// Nech žije https://beautifier.io/
MDCore.ref = "xzmzp";
MDCore.poster = "//s-delivery7.mxdcontent.net/thumbs/003b0e6a154979b7c1d1b33b11d7e3c3.jpg";
MDCore.vsrc1 = "//s-delivery7.mxdcontent.net/v/003b0e6a154979b7c1d1b33b11d7e3c3.mp4?s=HPDjzc-CVp4calqaNlD9Qw&e=1577144412";
MDCore.vfile = "003b0e6a154979b7c1d1b33b11d7e3c3.mp4";
MDCore.vserver = "s-delivery7";
MDCore.remotesub = "";
MDCore.chromeInject = "1";
MDCore.referrer = "";
  1. Prehratie videa

Následne si ich prehrávať vezme vsrc1 adresu a načíta mp4 video pre prehránie. Otázne je prečo by sa to snažil niekto takto komplikovať keď je to tak jednoduché získať?

Zistenie ako sa dostať k obsahu trvalo pár minút, vzhľadom na to že tento obsah je už na stránke po jej načítaní, skúsil som jednoduho vyhľadať Media požiadavky v Chrome Network tab, kde som uvidel nádherne sa načítavajúce video formátu mp4 z adresy mxdcontent.net, jediné čo ostávalo bolo vyľadať funkciu zodpovednú za vytvorenie tejto adresy, nič zložité Ctrl + F.

Netu.tv

Táto stránka je najzaujímavejšia, tak sa poďme na to pozrieť.

Prvý pokus: Otvoriť developer tools a vyhľadať mediálny obsah

Well, tudy cesta nevede:

Niečo, zatiaľ nevieme čo, rozpozná, že Developer tools sú otvorené a následne odstráni obsah stránky a napíše: Dont open Developer Tools.. To celé funguje na základe inštrukcie debugger, ktorá ak je detekovaná dokáže zastaviť spúštanie zvyšných scriptov. Ale nik mi predsa nebude hovoriť čo nemôžem otvoriť no nie? Tu som ešte skúšal triky ako otvorí remote developer tools, no bez štastia.

Druhý pokus a pritvrďujeme

Ako prvú som na detekciu network aktivity vyskúšal WebSniffer:

Zatiaľ vieme o nasledovnej aktivite:

Tak poďme vytiahnuť väčší kaliber, ReproNow, dokáže ukázať čo sa deje a spojiť to aj s časovaním videa.

Video sa však začalo prehrávať až po týchto 2 požiadavkach, teda je jasné, že majú čo dočinenia s prehrávaním videa, otázne je ako.

Pozrime sa na ich raw požiadavky:

GET https://hqq.tv/player/get_md5.php?ver=3&secure=0&adb=0%2F&v=WUQ4a0VFcW12M3ljTzlBVGJLcFRGUT09&token=&gt=&embed_from=0 HTTP/1.1
Accept:*/*, */*
DNT:1
X-Requested-With:XMLHttpRequest
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36


GET https://5nr6of.vkcache.com/secip/0/wQUcI79opRPRbvIMFdHyBA/MTg4LjExMi42NC43Mg==/1577134800/hls-vod-s11/flv/api/files/videos/2019/12/20/15768439403i0ul HTTP/1.1
Accept:*/*
Origin:https://hqq.tv
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
DNT:1

Najviac nás zaujíma, ale ich obsah, tak zistime čo vracajú ak ich zavoláme cez cURL.

➜ curl -XGET -H 'Accept:*/*, */*' -H 'DNT:1' -H 'X-Requested-With:XMLHttpRequest' -H 'User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36' 'https://hqq.tv/player/get_md5.php?ver=3&secure=0&adb=0%2F&v=WUQ4a0VFcW12M3ljTzlBVGJLcFRGUT09&token=&gt=&embed_from=0' --compressed
{"wrong_recaptcha":"1"} # Nevyzera to povzbudivo, ale recaptcha?
➜ curl -XGET -H 'Accept:*/*' -H 'Origin:https://hqq.tv' -H 'User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36' -H 'DNT:1' 'https://5nr6of.vkcache.com/secip/0/wQUcI79opRPRbvIMFdHyBA/MTg4LjExMi42NC43Mg==/1577134800/hls-vod-s11/flv/api/files/videos/2019/12/20/15768439403i0ul' --compressed
#EXTM3U
#EXT-X-TARGETDURATION:20
#EXT-X-ALLOW-CACHE:YES
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:1
#EXTINF:20.000,
15768439403i0ul.mp666/Frag-1-v1-a1
#EXTINF:20.000,
15768439403i0ul.mp666/Frag-2-v1-a1
#EXTINF:20.000,
15768439403i0ul.mp666/Frag-3-v1-a1

BEEEM! We hit jackpot!

yess

A čo sme to vlastne dostali? Jedná sa o hls playlist, ktorý funguje tak, že video je rozdelené do nieľokých oddelených fragmentov po rovnakých časových úsekoch. Takže video by sme mali, no ako to celé funguje?

V predchádzajúcej požiadavke sme zistili, že nám zlyhala reCaptcha, takže predpokladám, že potrebujeme overiť požiadavku na reCaptchu a následne adresa vráti daný odkaz na hls playlist. Skúsme získať zdrojové kódy stránky.

Analýza kódu, trvala trošku dlhšie, nechcem zbytočne naťahovať, tak sa pozrime na tie najzaujímavejšie veci:

<script>
    var iss = '';

    function h() {
        check();
        tcheck();
    };
</script>

Takže kontrola? Ale aká? Poďme sa pozrieť na tieto funkcie:

function check() {
    // ...
    eval(function (p, a, c, k, e, r) {
        e = String;
        if (!''.replace(/^/, String)) {
            while (c--) r[c] = k[c] || c;
            k = [function (e) {
                return r[e]
            }];
            e = function () {
                return '\\w+'
            };
            c = 1
        };
        while (c--)
            if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]);
        return p
    }('0;', 2, 1, 'debugger'.split('|'), 0, {}))
    var after = new Date().getTime();
    if ((after - before > minimalUserResponseInMiliseconds) || (devtools)) {
        dest();
    } else {
        before = null;
        after = null;
        delete before;
        delete after;
    }
    timeout1 = window.rtimeOut(function () {
        check();
    }, 200);
}

Aaahaa! Tu máme náš debugger, presne táto časť rieši detekciu devtools, a čo tcheck?

function tcheck() {
    try {
        ! function t(e) {
            1 === ("" + e / e).length && 0 != e % 20 || function () {}.constructor("debugger")(), t(++e)
        }(0)
    } catch (e) {
        timeout2 = window.rtimeOut(function () {
            tcheck();
        }, 1000);
    }
}

Same story, opať detekcia debuggera. No kde je naše video? V kóde môžeme nájsť funkciu player_init, ktorá obsahuje volanie na get_md5.php, pred touto časťou nájdeme overenie, či daný klient je dôveryhodný, následne nasleduje zaujímavá časť:

  var link_m3u8 = "/player/get_md5.php?ver=3&secure=0&adb=" + encodeURIComponent(adb) + "&v=" +
      encodeURIComponent("WUQ4a0VFcW12M3ljTzlBVGJLcFRGUT09") + "&token=" + encodeURIComponent(token) +
      "&gt=&embed_from=0";
  // ... o pár riadkov nižšie
  var link_m3u8 = 'https:' + un(data.obf_link) + ext;

Aby sme dostali výsledný link potrebujeme premennú adb, token, ext a funkciu un. Z predchádzajúcej požiadavky vidíme, že adb má hodnotu 0, tak asi nebude nijako dôležitá, dúfajme. Token, pravdepodobne ide o odpoveď reCaptcha, pozrime sa teda na jeho získanie:

grecaptcha.ready(function () {
    grecaptcha.execute('6Ldf5F0UAAAAALErn6bLEcv7JldhivPzb93Oy5t9', {
      action: 'watch_video'
    })
    .then(function (token) {
      console.log('token: ' + token);
      go_next(token, event);
    });
});

Takže po spustení tejto funkcie dostávame token, well not that hard, následne púšta funkciu go_next, ktorá rieši získanie adresy. Ostáva nám funkcia un, tá o sebe veľa nehovorí. Pozrime sa na jej definíciu:

var _0xf70b = ["\x2E", "\x69\x6E\x64\x65\x78\x4F\x66", "\x73\x75\x62\x73\x74\x72", "", "\x6C\x65\x6E\x67\x74\x68", "\x25\x75\x30", "\x73\x6C\x69\x63\x65"];

function un(_0xf43cx2) {
    if (_0xf43cx2[_0xf70b[1]](_0xf70b[0]) == -1) {
        _0xf43cx2 = _0xf43cx2[_0xf70b[2]](1);
        s2 = _0xf70b[3];
        console.log(s2)
        for (i = 0; i < _0xf43cx2[_0xf70b[4]]; i += 3) {
            s2 += _0xf70b[5] + _0xf43cx2[_0xf70b[6]](i, i + 3)
        };
        _0xf43cx2 = unescape(s2)
    };
    return _0xf43cx2
};

What the hell bro?! Čo, ale tie stringy predtým?

var _0xf70b = ["\x2E", "\x69\x6E\x64\x65\x78\x4F\x66", "\x73\x75\x62\x73\x74\x72", "", "\x6C\x65\x6E\x67\x74\x68", "\x25\x75\x30", "\x73\x6C\x69\x63\x65"];
(7) [".", "indexOf", "substr", "", "length", "%u0", "slice"]

Ide o názvy funkcii na spracovanie stringov, takže keď si preložíme túto funkcie do ľudskej reči:

function un(string) {
    if (string.indexOf('.') == -1) {
        string = string.substr(1);
        s2 = "";
        for (i = 0; i < string.length; i += 3) {
            s2 += "%u0" + string.slice(i, i + 3)
            console.log(s2)
        };
        string = unescape(s2)
    };
    return string
};

Tak skúsme si to teraz spojiť celé dokopy. Získame token cez grecaptcha, potom vytvoríme adresu pre požiadavku na /player/get_md5.php? výsledok, bude pravdepodobne JSON, z ktorého prečítame obf_link a predáme funkcii un().

Tak si to vyskúšajme!

{
  "pending":"0",
  "need_captcha":"0",
  "ip":"x.x.x.x",
  "hash":"b44d07f8b68844cdd2ffe66a9a64ee8e",
  "blocked":"0","obf_link":"#02f02f06a03406507203403302e07606b06306106306806502e06306f06d02f07306506306907002f03002f04604306d06204604a03104504904606507606e03604306403304d06403104805102f04d05406703404c06a04507804d06903403204e04303403304d06703d03d02f03103503703703103503603403003002f06806c07302d07606f06402d07303103002f06606c07602f06107006902f06606906c06507302f07606906406506f07302f03203003103902f03103202f03203002f03103503703603803503403703803606e06a06d06f06b"
}

Po predaní do funkcie un, získavame: //j4er43.vkcache.com/secip/0/FCmbFJ1EIFevn6Cd3Md1HQ/MTg4LjExMi42NC43Mg==/1577156400/hls-vod-s10/flv/api/files/videos/2019/12/20/1576854786njmok

Záver

Prečo a načo to robia? Well, popravde netuším, pretože neexistuje zaručený spôsob ako by mohli tieto služby chrániť svoj obsah. Nepredpokladám, žeby chceli zaviesť síce deravú, ale funkčnú DRM ochranu, dovtedy to ostáva len ako zábava Geekov. Každopádne bola to skvelá skúsenosť a zábava. Tak nabudúce!