主页 :aHR0cHM6Ly93d3cuNXlpbmcuZnVuLw==
地址 : aHR0cHM6Ly93d3cuNWR5Ni52aXAvdm9kcGxheS85MTA5Ny02LTEuaHRtbA==
一.分析执行流程
1.干掉debugger反调试
2.分析谁发起了index.m3u8的调用
我这边因为分析过多次,发现每次再请求.m3u8之前,都会有一次webscoket的请求,那么断定websocket一定是非常重要的
其实在这里我们已经拿到了m3u8的视频地址,但是只拿到m3u8的地址不是咱们的目的。当我们点击上图右边蓝色链接进入这个main.js的这个js文件中发现,他是加密的如下图
其实也可以侧面的反应出,我们的推断是没错的。 如果不是非常重要的东西,他基本不可能加密
3.不正常的代码
- 当我们再网页上格式化之后,会发现很多不太正常的代码,如下图
我们发现这个图的很多代码的格式他是不正确的
例如803行的if语句,如下图- 当我们把光标放到左变括号的时候,他右边的括号高亮的居然也是一个左括号,同时他的最后边没有任何括号。
- 我们知道再程序开发中,括号都是成对出现的,不可能出现这种所谓的左括号 的右边还是左括号的情况,但是他这里得代码缺没有任何得报错,说明这个代码本身是没有任何问题得。
- 那么产生这个问题的原因其实很简单,我们知道再ascii中有一个特殊的字符,这个字符的功能就是会将在他之前的各种字符串进行反转,然后加密就使用了这个字符来扰乱我们的分析。
二.还原代码
我们将main.js的代码复制下来,然后开始进行还原,我是复制了两份一份是没有格式化的代码,另一分是格式化过的代码。
1.环境问题
- 需要node环境
- 安装babel
2.大致分析一下js代码
3.抽取解密函数
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 开始运行
这里我采用了一个简单的方案,就是我从网页分析这个执行流程,发现他应该走得下面得getcookie,而且我发现他得这个函数是个循环,如下图。那么我们已经知道他应该进入getCookie那么我们就不浪费时间了。
我们可以看到进入getCookie函数的条件是_0x163f4这个函数返回为true,那么我们就直接让他返回true,当我们再次运行的时候发现他已经可以进入getCookie了,此时这个暗桩被我门解决了
- 他会按照图上标注的顺序再以此向下执行
- 当执行到第4个函数的时候,迟迟没有执行到return
- 分析代码可知,他做的事情是
- push一个数到数组,
- 取数组的长度赋值给_0x492e66
- for循环的退出条件是_0x5da125大于_0x492e66
- _0x5da125又永远不会_0x492e66,就等于做了一个死循环,那永远也不会退出。
- 所以这里的代码也是一个暗装,没有任何用处。
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); } }}