吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 22450|回复: 383
收起左侧

[Web逆向] 某影视站加密js的还原及自动化获取真实视频地址

    [复制链接]
4028574 发表于 2022-8-5 21:35
本帖最后由 4028574 于 2022-11-24 19:39 编辑

主页 :aHR0cHM6Ly93d3cuNXlpbmcuZnVuLw==
地址 : aHR0cHM6Ly93d3cuNWR5Ni52aXAvdm9kcGxheS85MTA5Ny02LTEuaHRtbA==

一.分析执行流程

1.干掉debugger反调试

  • 首先打开f12后 进入上面的地址,会出现如下的情况

  • 这里我采用最简单的方案干掉他,点击这个忽略断点,就不会进入此debug,但是会有另一个问题 ,就是cpu使用率是变高,但是因为我们只分析执行流程,找到关键的js,所以这里就无所谓了

2.分析谁发起了index.m3u8的调用

  • 我这边因为分析过多次,发现每次再请求.m3u8之前,都会有一次webscoket的请求,那么断定websocket一定是非常重要的

    其实在这里我们已经拿到了m3u8的视频地址,但是只拿到m3u8的地址不是咱们的目的。

  • 当我们点击上图右边蓝色链接进入这个main.js的这个js文件中发现,他是加密的如下图

    其实也可以侧面的反应出,我们的推断是没错的。  如果不是非常重要的东西,他基本不可能加密

3.不正常的代码

  • 当我们再网页上格式化之后,会发现很多不太正常的代码,如下图

    我们发现这个图的很多代码的格式他是不正确的
    例如803行的if语句,如下图
    • 当我们把光标放到左变括号的时候,他右边的括号高亮的居然也是一个左括号,同时他的最后边没有任何括号。
    • 我们知道再程序开发中,括号都是成对出现的,不可能出现这种所谓的左括号 的右边还是左括号的情况,但是他这里得代码缺没有任何得报错,说明这个代码本身是没有任何问题得。
    • 那么产生这个问题的原因其实很简单,我们知道再ascii中有一个特殊的字符,这个字符的功能就是会将在他之前的各种字符串进行反转,然后加密就使用了这个字符来扰乱我们的分析。

二.还原代码

我们将main.js的代码复制下来,然后开始进行还原,我是复制了两份一份是没有格式化的代码,另一分是格式化过的代码。

1.环境问题

  • 需要node环境
  • 安装babel

2.大致分析一下js代码

  • 我们可以看到整个_0x3a74这个函数一共有1041个之多,说明这个函数就是解密字符串的重要函数

3.抽取解密函数

  • 我们将这个_0x3a71的函数包括他之前的所有代码全部复制出来保存到名叫decrypt.js的文件中。

  • 然后再decrypt.js文件末尾增加两个导出,以供我们代码调用使用

4.第一次还原

4.1 还原代码如下
const parser = require("@babel/parser");
const template = require("@babel/template").default;
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;

const path = require("path");
const fs = require("fs");

const {decryptStr,decryptStrFunName } = require("E:\\desktop\\2\\decrypt.js");
let filepath = "e:/desktop/main.js";

fs.readFile(filepath,{"encoding":"utf-8"},function(err,data) {
        const ast = parser.parse(data);
        step(ast);

        let {code} = generator(ast);
        fs.writeFile("e:/desktop/main1.js",code,err => {
                if(err) throw err;
                console.log("保存成功");

        });
});

function step(ast) {
        traverse(ast,{
                CallExpression: ObjToStr,
        })
}

function ObjToStr(path) {
   let node = path.node;
   try{
           if(node.callee.name == decryptStrFunName && node.arguments.length == 2) 
           {

                   let val = decryptStr(node.arguments[0].value,node.arguments[1].value);
                   path.replaceWith(t.stringLiteral(val));
           }
   }catch(e) {
           console.log(e);
   }

}
4.2 开始运行
  1. 当我们运行的时候发现他报了一个错误,如下图

  2. 那么我们开始调试,我们再decrypt.js的71行下断

    我们发现这里他会进入一个叫setcookie的函数里

  3. 这里我采用了一个简单的方案,就是我从网页分析这个执行流程,发现他应该走得下面得getcookie,而且我发现他得这个函数是个循环,如下图。那么我们已经知道他应该进入getCookie那么我们就不浪费时间了。

  4. 我们可以看到进入getCookie函数的条件是_0x163f4这个函数返回为true,那么我们就直接让他返回true,当我们再次运行的时候发现他已经可以进入getCookie了,此时这个暗桩被我门解决了

  5. 当我们再次运行的时候,发现程序又报错了

  6. 我们先再我门的还原代码处调用函数处下断,如下图

  7. 当我们单步跟踪到此处的时候,如下图,发现他会进入一个函数

  8. 当我们再进入这个函数的时候,就发现了有意思的东西出现了

    1. 他会按照图上标注的顺序再以此向下执行
    2. 当执行到第4个函数的时候,迟迟没有执行到return
    3. 分析代码可知,他做的事情是
      • push一个数到数组,
      • 取数组的长度赋值给_0x492e66
      • for循环的退出条件是_0x5da125大于_0x492e66
      • _0x5da125又永远不会_0x492e66,就等于做了一个死循环,那永远也不会退出。
    4. 所以这里的代码也是一个暗装,没有任何用处。
  9. 我们直接将调用代码注释

4.3 真正的开始运行
  • 我们可以看到运行前后的代码比对

    可以看出来左边的还原之后的效果还是不错的,已经比右边的清晰了不少了,那么我们接下来就要还原 'Tuhel': _0x7818f1["pSzXT"], 'YUAtI': _0x7818f1["cVvhi"]  这种代码了

5. 第二次还原

5.1 我们对MemberExpression类型进行遍历


可以看到我们需要操作的这个地方正好就是MemberExpression的type

5.2 代码
  • 一些重复的代码,我就不复制上来了,可以参照前面的,比如引入包啥的

let filepath = "e:/desktop/2/main1.js";

fs.readFile(filepath,{"encoding":"utf-8"},function(err,data) {
        const ast = parser.parse(data);
        step(ast);

        let {code} = generator(ast);
        fs.writeFile("e:/desktop/2/main2.js",code,err => {

                if(err) throw err;
                console.log("保存成功");

        });

});

function step(ast) {
        traverse(ast,{
                 MemberExpression:HandleMemberExpression,
        })
}

        function HandleMemberExpression(path) {
                        if(path.get("object").isIdentifier() && t.isLiteral(path.node.property))
        {
                let objname = path.node.object.name;
                let propertyValue = path.node.property.value;
                let bindPath = path.scope.getBinding(objname);
                if(bindPath === undefined) return;

                try{
                        if(!t.isObjectExpression( bindPath.path.node.init))
                        return;
                }
                catch(e)
                {
                        console.log(e.message);
                        return ;
                }

                let propertys = bindPath.path.node.init.properties;
                if(propertys.length === 0)
                        return;

                try{
                        for (let i = 0;i<propertys.length;i++)
                        {
                                console.log(propertys[i].key.value);
                                if(propertys[i].key.value === propertyValue)
                                {
                                        if(!t.isStringLiteral( propertys[i].value))
                                                return;

                                        path.replaceWith(propertys[i].value);
                                        break;
                                }
                        }
                }catch (e){
                        console.log(e.message);
                        console.log(e.stack);
                }
        }
}
5.3 第二次还原后的结果

我们可以看到右边的结果比左边又更清晰了不少,而且很多函数调用的参数上已经是明文字符串了

6. 第三次还原

  • 此次还原的主要是是针对 ”_0x7818f1["RniiM"](_0x509efb, _0x3a807c)“ 这类函数进行还原
6.1 对CallExpression进行遍历

6.2 代码

let filepath = "e:/desktop/2/main1.js";
fs.readFile(filepath,{"encoding":"utf-8"},function(err,data) {
        const ast = parser.parse(data);
        step(ast);

        let {code} = generator(ast);
        fs.writeFile("e:/desktop/2/main2.js",code,err => {

                if(err) throw err;
                console.log("保存成功");
        });
});

function step(ast) {
        traverse(ast,{
                 CallExpression: HandleCallExpression,
        })
}

function  HandleCallExpression(path) {
    if(path.get("callee").isMemberExpression())
    {
        let objname = path.node.callee.object.name;
        if(!t.isStringLiteral(path.node.callee.property))
            return;

        let propertyValue = path.node.callee.property.value;
        let bindPath = path.scope.getBinding(objname);
        if(bindPath === undefined) return;

        if(!(t.isVariableDeclarator(bindPath.path.node) && t.isObjectExpression(bindPath.path.node.init)))
            return;

        let propertys = bindPath.path.node.init.properties;
        if(propertys.length === 0)
            return;

        let args = path.node.arguments;
        try {
            for (let i = 0; i < propertys.length; i++){
                if(propertys[i].key.value === propertyValue)
                {
                    if(!t.isFunctionExpression(propertys[i].value))
                        return;

                    console.log(propertys[i].key.value);
                    let retStmt = propertys[i].value.body.body[0];
                    if(t.isCallExpression(retStmt.argument) && t.isIdentifier(retStmt.argument.callee) )
                    {
                         path.replaceWith(t.CallExpression(args[0],args.slice(1)));                        
                         break;
                    }else if (t.isBinaryExpression(retStmt.argument) && args.length === 2)
                    {
                        path.replaceWith(t.BinaryExpression(retStmt.argument.operator,args[0],args[1]));
                        break;
                    }
                }
            }
        }catch (e)
        {
            console.log(e.message);
            console.log(e.stack);
        }
    }
}
6.3运行后的先后对比图
  • 此时我们可以看到 右边的还原后的代码已经非常清晰了,而且_0x59045e变量也已经变灰,说明没有任何引用关系了,这个时候你可以选择手动删除它,或者使用ast分析引用自动删除
6.4 总结
  • 至此我们的代码还原就结束了,目前代码执行流程非常清晰,下面我们就是分析函数的执行逻辑了

三.分析代码

1. 分析websocket的数据

  • 可以发现不管是发出去的数据,还是接收的数据 都是看不懂的

2. 分析websocket相关函数

  • 我们从下图可知,websocket响应数据的时候需要设定一个回调函数onmessage

  • 我们在还原完成的代码中搜索一下,如下图

    我们可以看到,这里面有些重要的东西,比如decryptPackData,说明websocket接收到的数据,需要到这里解密

  • 搜索decryptPackData,如下图

    我们发现,不仅解密函数在这里,加密函数也在这里

3.解密接收数据

  • 我们在上面的解密中可以看到,没有啥特殊的操作,直接就是拿到数据扔进来解密
  • 我们需要注意的一点是decryptPackData中有一个hex.parse 这个是将字符串数据转成hex数组  例如”05B1“ ==> [0x05,0xb1]
3.1 python解密代码
from Crypto.Cipher import AES

KEY= b"55ca5c48a943afdc"
IV = b"d11424dcecfe16c0"

def AesDecrypt(data,key,iv):
    cipher = AES.new(key,AES.MODE_CBC,iv)
    text_decrypted = cipher.decrypt(data)
    unpad = lambda s: s[0:-s[-1]]
    text_decrypted = unpad(text_decrypted)
    return text_decrypted.decode("utf-8")

def main():
    data = "05B1BEB5AF7E36DA88C6F05F66DE36B538D89E87EF822C966F1EBDDE39A4821C3EB3252E8CBFB7CC0959277CDEAF66E1967EFA09CC0C3CDDDBFC10ACBE5A0916E69C889648B4B0E948A294686803F72AB0082C24C0C70A43FA41DCDEF7C9B8E5593A450203F7A9824D04AEAFF85BF0888D28EA33BE8138B6BDF58FE1D0FBA21851C1880AA5A735D9E3E4947088C3BCFEA59AD9FB577E34080A6088BB749FE217637F07C86044B1B52565AA4E21C9DFF88B3294513DCF6B829EDC699555105BD3C6102B462A999638BA77280A0765C6A2"
    hexdata = bytes.fromhex(data)
    result = AesDecrypt(hexdata,KEY,IV)
    print(result)

if __name__ == '__main__':
    main()

解密后的数据如下图

可以看到此时我们已经拿到了m3u8的地址

4 解密发送的数据

  • 我们在上面的加密上可以看到,他最终是给加密的数据转成字符串同时变成大写

  • 从这里可以看到 他将字符串又转成了二进制的数组,正好跟我们前面看到的那个二进制数据一致

    其实这里的解密操作就跟前面的接收数据的解密操作完全一致

4.1 python解密代码
from Crypto.Cipher import AES

KEY=b"55ca5c48a943afdc"
IV = b"d11424dcecfe16c0"

def AesDecrypt(data,key,iv):
    cipher = AES.new(key,AES.MODE_CBC,iv)
    text_decrypted = cipher.decrypt(data)
    unpad = lambda s: s[0:-s[-1]]
    text_decrypted = unpad(text_decrypted)
    return text_decrypted.decode("utf-8")

def main():
    data = "B41585343636DAF917D1FE95FDB299994966407EED69F70C9D63E68CE41CEEFBF158890F7FC3ABB31E71606B02C429599EB6552EA662086F99627682BA5B079297C072E4A63427CCAA26B4C3E94A04B2E5693F721F49F15FF12D269BB0D7343F20D8C7AE314B64AC5A9186D3B14A95D9F7675ED41C772A506AB6EF7F379ED21741CBBD55DBF408A940B751EB723CDE6AD795C18503A902FCE32A9D6880A15E196ECBAAE12B96BCB3C04383ED80791BD22CC0FD77744D00DEC1A9FB9B0DB09BCAE416069B399683199D61F9EF72EFF3022CB4320AF8C7BCAF9DC60796257449051F7713E2B1DDF078B35A28D32C5EF913"
    hexdata = bytes.fromhex(data)
    result = AesDecrypt(hexdata,KEY,IV)
    print(result)

if __name__ == '__main__':
    main()

解密后的数据如下图

{"type":"getUrl","url":"HGbFux0Gub4gJ9LonhdVbo000orckSwQNMo000oiyo000oNH8PjeqHuSNsrkYvqFwpEd5YDZpUh352CUmhznDWlodcmfeA0mJz0qkbEAkychz2TBRLCGG1gO0O0O","sign":"7a4b804dc54d93acc5992f6f48221f9338cf896b00693a8c00a0e4d720ddb4fe"}
到此处我们来分析一下参数

4.2 分析发送websocket发送数据的参数
  1. 搜索"getUrl" ,可以看到如下图

    此时我们知道type是固定的

  2. 查找url的来源,这里采用一种简单的方法, 我们直接在网页f12这里 ctrl+shift+f 搜索HGbFux0Gub4gJ9LonhdVbo000orckSwQNMo000oiyo000oNH8PjeqHuSNsrkYvqFwpEd5YDZpUh352CUmhznDWlodcmfeA0mJz0qkbEAkychz2TBRLCGG1gO0O0O在这里插入图片描述
    此时我们搜到到他其实就是在请求地址的主页上就有

  3. 查找sign的来源,如下图

    我们发现他其实就是用url的值进行了sha,你可以看到_0x1ad6f6正好就是url的值 同时也传进了createSign这个函数内

  4. 验证sign

from hashlib import sha256
import hmac

def HmacHash256(data):
    rawdata = data.encode("utf-8")
    key = b"55ca5c4d11424dcecfe16c08a943afdc"
    return hmac.new(key, rawdata, digestmod=sha256).digest().hex()

def main():
    data = "HGbFux0Gub4gJ9LonhdVbo000orckSwQNMo000oiyo000oNH8PjeqHuSNsrkYvqFwpEd5YDZpUh352CUmhznDWlodcmfeA0mJz0qkbEAkychz2TBRLCGG1gO0O0O"
    result = HmacHash256(data)
    print(result)

if __name__ == '__main__':
    main()

我们发现加密后的数据 正好跟前面解密开的数据完全一致

至此发送数据的所有流程已经分析完成

四. 编写自动获取m3u8地址的代码

  1. 代码打包在附件

  2. 代码测试截图

  3. 下载测试

此网站得解析路线已经变更,二进制解析工具已经失效,本人得m3u8下载器已经可以直接下载此站视频,如果需要下载此站视频得可以进入本人空间去下载m3u8下载器进行下载

5dy5Extractor.7z

1.65 KB, 下载次数: 50, 下载积分: 吾爱币 -1 CB

python源码

免费评分

参与人数 120威望 +2 吾爱币 +218 热心值 +102 收起 理由
xfjyz2023 + 1 我很赞同!
nonull + 1 用心讨论,共获提升!
erthbgf008 + 1 + 1 我很赞同!
letting + 1 + 1 谢谢@Thanks!
wfht2004 + 1 + 1 热心回复!
aaaa7618 + 1 + 1 我很赞同!
bulusLi + 1 我很赞同!
li1357986420 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
yangzg + 1 + 1 我很赞同!
chengdragon + 1 谢谢@Thanks!
katkat + 1 + 1 我很赞同!
LIweiwei + 1 + 1 我很赞同!
thefiend1986 + 1 用心讨论,共获提升!
Michael_Yang + 1 + 1 用心讨论,共获提升!
aq4716411 + 1 + 1 很少有大佬这么分享啦,多谢~
盛情不旧 + 1 + 1 谢谢@Thanks!
dincia + 1 谢谢@Thanks!
allspark + 1 谢谢@Thanks!
lcl124252 + 1 + 1 我很赞同!
xjlws555 + 1 我很赞同!
wangseasmile + 1 + 1 我很赞同!
LionJee + 1 我很赞同!
神马还在浮云里 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
杀猪用牛刀 + 1 + 1 哪个无限循环的暗装,我之前被恶心过一次。原来是这样啊,感谢分享!
海水很咸 + 1 + 1 我很赞同!
shelley + 1 + 1 谢谢@Thanks!
incoming + 1 + 1 我很赞同!
限度gg + 1 + 1 鼓励转贴优秀软件安全工具和文档!
maiwens + 1 + 1 谢谢@Thanks!
tzwsoho + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
三爷 + 1 + 1 很棒
lost159 + 1 + 1 谢谢@Thanks!
Bluesky10 + 1 + 1 热心回复!
lcy782 + 1 + 1 我很赞同!
CEXO3574 + 1 我很赞同!
qwxgg + 1 + 1 我很赞同!
billsmiless + 2 谢谢@Thanks!
LiChan + 1 我很赞同!
skym + 1 + 1 我很赞同!
Dom-Lewis + 1 + 1 用心讨论,共获提升!
pdcba + 1 + 1 谢谢@Thanks!
q110 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
1983 + 1 用心讨论,共获提升!
guiyidegui + 1 + 1 佩服兰州大大,囧rz
dpc520 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
ligxi + 1 + 1 用心讨论,共获提升!
motoWW + 1 + 1 谢谢@Thanks!
lingyun011 + 1 + 1 热心回复!
抱歉、 + 1 用心讨论,共获提升!
ayanami丶 + 1 + 1 用心讨论,共获提升!
lyslxx + 1 + 1 我很赞同!
hbiui + 1 谢谢@Thanks!
faith.eve + 1 谢谢@Thanks!
guiwuzhe + 1 + 1 谢谢@Thanks!
执手相看泪眼 + 1 + 1 我很赞同!
Indra + 1 谢谢@Thanks!
哒劳德 + 1 + 1 我很赞同!
af8889 + 1 + 1 虽然看不懂,但是感觉大神真的好牛啊!
森罗千引 + 1 + 1 大佬
Naxietania + 1 + 1 我很赞同!
sjaixf1714 + 1 + 1 虽然看不懂,但是感觉大神好牛啊!
Tjut + 1 + 1 我很赞同!
gomg007 + 1 + 1 太强了!
jalor + 1 + 1 谢谢@Thanks!
HappyCrazy + 1 我很赞同!
yzw1130 + 1 我很赞同!
虞珂啊 + 1 + 1 用心讨论,共获提升!
Aries15123 + 2 + 1 我很赞同!
beibeibei + 1 + 1 我很赞同!
lechenging + 1 + 1 我很赞同!
schedule + 1 热心回复!
abc156980 + 1 谢谢@Thanks!
WLW1234567 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
shuaiqi + 1 + 1 谢谢@Thanks!
djmingge + 2 + 1 我很赞同!
zhoushengzhi + 1 + 1 用心讨论,共获提升!
wapjyyy123 + 1 谢谢@Thanks!
nyant + 1 + 1 谢谢@Thanks!
4630269wu + 1 热心回复!
wangshisuifeng1 + 1 + 1 我很赞同!
笙若 + 1 + 1 谢谢@Thanks!
zhuzijie + 1 我很赞同!
XIAODINGTONGXUE + 1 我很赞同!
tao2017 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
JuanMao1337 + 1 + 1 用心讨论,共获提升!
独行风云 + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
yjjcm321 + 1 + 1 谢谢@Thanks!
tanc + 1 + 1 用心讨论,共获提升!
time2s + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
5omggx + 1 + 1 用心讨论,共获提升!
yjn866y + 1 + 1 热心回复!
执念i_ + 1 + 1 热心回复!
wocuole + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
逆向学习 + 1 + 1 谢谢@Thanks!
a2108888 + 1 + 1 谢谢@Thanks!
外酥内嫩 + 1 + 1 用心讨论,共获提升!
zudeng + 1 热心回复!
Lxinghun + 1 + 1 谢谢@Thanks!
固相膜 + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

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

caidem 发表于 2022-8-6 10:35
哪个影视网站啊?
gomg007 发表于 2022-8-9 10:15
我平时也用上面的加密,网站上宣称“不可逆”,但在大佬手中都是小菜一碟呀。太强了!
swsjjdcs 发表于 2022-8-9 08:33
谢谢分享、大佬就是牛、小白就是需要告诉一下怎么使用就行了、别的看多了头脑发胀
 楼主| 4028574 发表于 2022-8-9 22:33
motoWW 发表于 2022-8-9 22:31
File "F:\m3u8%utils.py", line 2, in
    from crypto.Cipher import AES
ModuleNotFoundError: No m ...

pip install pycryptodome
 楼主| 4028574 发表于 2022-8-9 17:38
af8889 发表于 2022-8-9 11:46
虽然看不懂,但是感觉大神真的好牛啊!请问这个附件下载下来怎么用呢?

python代码 安装python  然后
python main.py
shift1110 发表于 2022-8-5 21:50
收藏一下,好好研究
2snfks29fais 发表于 2022-8-5 21:54
不错,写的很好
cxq297532493 发表于 2022-8-5 21:57
感谢大佬分享知识
tangyidianxia 发表于 2022-8-5 22:09
感谢大佬分享知识
卡尔999 发表于 2022-8-5 22:12

++

%BB%B9%D4%AD%B2%C5%CA%C7%B9%D8%BC%FC
liguang0527 发表于 2022-8-5 22:21
虽然看不懂,还是谢谢楼主好心分享。
wenpc89757 发表于 2022-8-5 22:39
你们千多级,咋弄的十几万攻击的
Garfiel 发表于 2022-8-5 22:41
学到了学到了
Barbara 发表于 2022-8-5 22:44
收藏学习!希望有一日自己能独立分析
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-11-15 10:44

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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