types组件types能用于节点类型的判断和节点的生成,但是主要还是用来节点的生成,因为节点类型的判断可以不用该组件,也能实现类型判断//其他类型也是一样type.isIdentifier(path.node)//相当于path.node.type==='Identifier'type.isIdentifier(path.node,{'name':'x'})//相当于path.node.type==='Identifier'&&path.node.name==='x'节点生成生成实例代码constfs=require('fs')consttype=require("@babel/types")constgenerator=require("@babel/generator").default//AST转换为代码letobjPro1=type.objectProperty(key=type.identifier(name="name"),//第一个属性value=type.stringLiteral(value="haha"))//第一个属性值letbojPro2=type.objectProperty(key=type.identifier(name="add"),value=type.functionExpression(//函数节点id=null,params=[type.identifier(name="a"),type.identifier(name="b")],//参数列表body=type.blockStatement(//函数体body=[type.returnStatement(//返回值argument=type.binaryExpression(//二项式operator="+",left=type.binaryExpression(operator="+",left=type.identifier(name="a"),right=type.identifier(name="b")),right=type.numericLiteral(value=1000)))])))letbojPro3=type.objectProperty(key=type.identifier(name="mul"),//第三个属性value=type.functionExpression(//第三个属性值id=null,params=[type.identifier(name="a"),type.identifier(name="b")],body=type.blockStatement(body=[type.returnStatement(argument=type.binaryExpression(operator="+",left=type.binaryExpression(operator="*",left=type.identifier(name="a"),right=type.identifier(name="b")),right=type.numericLiteral(value=1000)))])))letobj=type.objectExpression([objPro1,bojPro2,bojPro3])//变量初始化letletDec=type.variableDeclarator(type.identifier(name="obj"),init=obj)//变量名letlocalAst=type.variableDeclaration("let",[letDec])//声明变量//ast转化为代码letcode=generator(localAst).codefs.writeFile("./output/code.js",code,(err=>{}))上面使用到stringLiteral、numericLiteraltypes还提供了其他字面量,按照语法树来,都是差不多的declarefunctionstringLiteral(value:string):StringLiteral;declarefunctionnumericLiteral(value:number):NumericLiteral;declarefunctionnullLiteral():NullLiteral;declarefunctionbooleanLiteral(value:boolean):BooleanLiteral;declarefunctionregExpLiteral(pattern:string,flags?:string):RegExpLiteral;当界面量太多,太复杂的时候,Bable也提供了一个简便的方式valueToNode()//可以看出valueToNode可以很方便的生成其他类型exportfunctionvalueToNode(value:undefined):IdentifierexportfunctionvalueToNode(value:boolean):BooleanLiteralexportfunctionvalueToNode(value:null):NullLiteralexportfunctionvalueToNode(value:string):StringLiteralexportfunctionvalueToNode(value:number):NumericLiteral|BinaryExpression|UnaryExpressionexportfunctionvalueToNode(value:RegExp):RegExpLiteralexportfunctionvalueToNode(value:ReadonlyArray<undefined|boolean|null|string|number|RegExp|object>):ArrayExpressionexportfunctionvalueToNode(value:object):ObjectExpressionexportfunctionvalueToNode(value:undefined|boolean|null|string|number|RegExp|object):Expressionconsole.log(generator(type.valueToNode(123)).code)console.log(generator(type.valueToNode('mimi')).code)console.log(generator(type.valueToNode(undefined)).code)console.log(generator(type.valueToNode(null)).code)/*123"mimi"undefinednull*/
parse&generator组件parse作用是代码转化为AST结构解析网站:https://astexplorer.net/constparser=require("@babel/parser");//需要导入letast=parser.parse(js_code)//代码转化为ast结构,与网站上一样letast1=parser.parse(js_code,{sourceType:"module",//如果有importexport关键字,需要使用该参数})console.log(JSON.stringify(ast,null,2))generator作用是AST结构转化为代码letcode=generator(ast).codeletcode1=generator(ast,{retainLines:false,//默认false,是否输出与源代码相同的行号comments:false,//默认true,是否保留注释compact:true//默认false,是否压缩代码}).codetraverse&visitor组件traverse用来遍历AST结构的节点,简单点说就是把所有节点都运行一遍traverse使用的深度优先策略//导入consttraverse=require("@babel/traverse").default;//遍历节点constgenerator=require("@babel/generator").default//AST转换为代码//相关操作letvisitor={}visitor.FunctionExpression=function(path){console.log("mmmmm")}traverse(ast,visitor)/*源码有两个函数节点所以输出两次mmmmmmmm*/visitor三种写法按照自己的喜好选择,最常用的visitor2letvisitor1={FunctionExpression:function(path){console.log('...')}}letvisitor2={FunctionExpression(path){console.log('...')}}letvisitor3={FunctionExpression:{enter(path){console.log('进入->函数节点')},exit(path){console.log('退出<-函数节点')}}}traverse(ast,visitor3)visitor组合写法使用|组合不同的类型letvisitor1={'FunctionExpression|BinaryExpression':function(path){console.log('...')}}letvisitor2={'FunctionExpression|BinaryExpression'(path){console.log('...')}}letvisitor3={'FunctionExpression|BinaryExpression':{enter(path){console.log('进入->节点')},exit(path){console.log('退出<-节点')}}}traverse(ast,visitor3)使用多个函数处理节点,会按照函数顺序执行functionfun1(path){console.log('11')}functionfun2(path){console.log('22')}letvisitor={FunctionExpression:{enter:[fun1,fun2]}}traverse(ast,visitor)traverse并非必须从头遍历//修改函数第一个参数为x,并修改函数内所有用了该参数的地方constupdateParamNameVisitor={Identifier(path){if(path.node.name===this.paramName){path.node.name="x"}}}constvisitor={FunctionExpression(path){//遍历函数节点constparamName=path.node.params[0].name//内部循环path.traverse(updateParamNameVisitor,{paramName//向子循环传递参数})}}traverse(ast,visitor)
依赖安装npminstall--save-dev@babel/core@babel/cli@babel/preset-envnpminstall--save@babel/polyfill结构操作思想:读取代码转换为AST->对节点进行增删改查操作->保存结果constfs=require('fs');constparser=require("@babel/parser");//代码解析为ASTconsttraverse=require("@babel/traverse").default;//遍历节点consttype=require("@babel/types")//节点类型判断constgenerator=require("@babel/generator").default//AST转换为代码constjs_code=fs.readFileSync("./input",{encoding:"utf-8"})letast=parser.parse(js_code)//代码转化为ast//相关操作//ast转化为代码letcode=generator(ast).code//保存fs.writeFile("./output",code,(err=>{}))案例代码letobj={name:'haha',add:function(a,b){returna+b+1000},mul:function(a,b){returna*b+1000}}
jsvmp特点jsvmp有一个最大的特点,就是有一长串字符串._$jsvmprt(...),算法代码的具体细节都在这个一节长字符串里,所有在算法的扣取上就很不友好,本案例采取补环境的方式实现。(glb="undefined"==typeofwindow?global:window)._$jsvmprt(.....)逻辑分析定位加密入口添加XHR断点,然后向下滑动,断点短住搜索_signature,在关键位置下断点,然后在刷新页面断点位置r变量,来自于S(n,e)函数计算,该S函数就是入口函数分析加密得值逻辑进入S函数,分析这一段逻辑,其实调用的就是//o是{'url':'xxxx'}window.byted_acrawler.sign(o)进入window.byted_acrawler.sign函数,来到acrawler.js文件,也就是jsvmp的Js文件补环境把acrawler.js文件全部复制下来,代码起始有一句(varglb;glb="undefined"==typeofwindow?global:window),这是检查当前是什么环境的,是node还是浏览器有两种处理办法使用jsdom补充window使用window=global当然还有其他更好的方式,这里采用第二次这样就和浏览器一样了加上一个输出,尝试使用node,浏览器执行,查看两者的区别浏览器正常得值,node报错,说明node缺少一些条件在浏览器添加条件断点,查看referrer是什么//referrer来自于document,继续添加环境document={referrer:''}node继续执行,发现没有sign,说明node还被检查着经过分析,发现这些代码,exports、module,这是node才存在的东西,直接改成undefined当然也有更好的方式直接过滤这些特征//nodetrue浏览器false"undefined"!=typeofexports//nodetrue浏览器false"undefined"!=typeofmodule继续执行,缺少href,就根据浏览器的插装输出补充就好了然后是缺少‘protocol’location={href:'',protocol:'https:'}然后是缺少userAgentnavigator={userAgent:'Mozilla/5.0(WindowsNT10.0;WOW64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/86.0.4240.198Safari/537.36'}再次执行,出现结果因为校验的不严格,所以这个短Cookie也是可以过的,长Cookie需要添加一些Cookie才行长Cookie需要加上其他的Cookie,这个cookie位置需要靠后,不然会被清空如果是自己写的环境框架,就不需要担心cookie的位置为了稳定,请求头部还需要带一个随机Cookie完整结构
—-思路来自志远大佬什么是TLS说道ja3指纹,必然是要知道TLS的安全传输层协议(TLS)用于在两个通信应用程序之间提供保密性和数据完整性。—-来自百度百科什么是ja3官方链接:https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967大概意思就是:JA3和JA3S作为一种方法来识别客户端和服务器之间的TLS协议,可以帮助对特定客户端与其服务器之间的某些指纹信息,来识别恶意通信。ja3测试使用网站:https://ja3er.com/jsonrequests、httpx、aiohttp的结果,这里就不做其他IP、UA等测试了,结果都是以知的一样查看TLS/SSL指纹信息过滤条件tls.handshake.version==0x00303使用的软件:鲨鱼魔改OpenSSL并编译把编译好的libcrypto-1_1-x64.dll、libssl-1_1-x64.dll使用curl和python测试curl测试Python测试
说明本文仅供学习交流,严禁用于非法用途,文章如有不当可联系博主删除网址:aHR0cHM6Ly9tLmN0eXVuLmNuL3dhcC9tYWluL2F1dGgvbG9naW4/cmVkaXJlY3Q9JTJGbXk=加密参数:password,comParam_seqCode,comParam_signature开始断点追栈找password加密处这里有个小技巧,先尝试看函数名,成的话就可以不用一层一层追了第一个login打断点,就发现了加密处//分析发现//加密函数是Object(l["c"])(a.value,Object(l["f"])(Object(l["g"])(s.value)))//其中Object(l["c"])为加密函数,它有两个参数第一个参数是密码a.value第二个参数是对邮箱进行了处理Object(l["f"])(Object(l["g"])(s.value))主要对邮箱的处理函数是Object(l["f"]),作用是保证第二个参数是'24位'不够'补0'多了'截取'分析加密逻辑复制整个js文件,发现是webpak打包的js,那就简单了//整个加密的核心是这行p.a.TripleDES.encrypt(e,d,l);//找到p变量是怎么来的,d=t("80e3"),l=t.n(d),s=t("3452")//s来自t("3452"),p=t.n(s)//p变量来自s,f=t("c466"),h=null,m=function(e){returnfunction(n){returnObject.prototype.toString.call(n)==="[object".concat(e,"]")}扒代码发现这是webpack打包过的,我们构造一个简单的加载器varfff;!function(e){varn={}functionf(t){if(n[t])returnn[t].exports;console.log('-->',t);varr=n[t]={i:t,l:!1,exports:{}};returne[t].call(r.exports,r,r.exports,f),r.l=!0,r.exports}fff=f;}({//函数})填入3452对象运行代码执行fff('3452'),发现报错,这是缺少代码在网站Js代码的加载器打断点,找到缺少的函数,然后复制粘贴代码补充完整,得到加密对象改写加密逻辑然后测试运行继续找comParam_seqCode、comParam_signature看着主要的逻辑//h.getTimestampOffset()的逻辑f是h.getTimestampOffset=function(){returnlocalStorage.getItem("timestampOffset")||f}//其他关键的点`u['k']`、`o()`向上翻,找到u['k']、o()定义点扒取代码,跟上面一样,这里的u['k']可以简单一点直接复制函数就行测试结果一样
难点头部顺序反爬、某些框架内部会对头部处理造成获取不到CookieTip:文末可以下载例子猿人学该题官网:http://match.yuanrenxue.com/match/3确定逆向目标请求加密一般在参数头部(Cookie,token,还有其他的自定义的),这里就只有Cookie了开始逆向既然第二个请求,带有Cookie,那么就需要确定该Cookie是生成,还是服务器返回,这里还有第一个请求,所以大概率是服务器返回分析请求第一个请求状态码是202(表示服务器端已经收到请求消息,但是尚未进行处理),还有Set-Cookie,那就可以确定是服务器返回的了获取不到Cookie的原因这里获取不到Cookie,有两个原因一、反爬(对请求头部的顺序有要求,最好是使用原始顺序)二、requests框架的原因(框架会对设置的头部进行优化修改)获取Cookie规避了上面两个问题后,就可以获取到Cookie请求数据importrequestsdefapp(page:int)->dict:session=requests.Session()session.headers={'Host':'match.yuanrenxue.com','Connection':'keep-alive','Pragma':'no-cache','Cache-Control':'no-cache','User-Agent':'Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/91.0.4472.101Safari/537.36','DNT':'1','Accept':'*/*','Origin':'http://match.yuanrenxue.com','Referer':'http://match.yuanrenxue.com/match/3','Accept-Encoding':'gzip,deflate','Accept-Language':'zh-CN,zh;q=0.9',}session.post(url="http://match.yuanrenxue.com/jssm",)print(session.cookies)s=session.get(url="http://match.yuanrenxue.com/api/match/3",headers={"User-Agent":"yuanrenxue.project",},params={"page":page})returns.json()if__name__=='__main__':for_inrange(1,6):print(app(_))结果正确Tip:直接访问数据接口,会返回一段Js其实这段代码一点用都没有。完整代码:https://pan.bigdataboy.cn/s/JZh7
难点加密在Cookie,Cookie有时间限制,暗桩反调,混淆,调试阻塞Tip:文末可以下载例子猿人学该题官网:http://match.yuanrenxue.com/match/2确定逆向目标开始逆向加密值在Cookie有两种情况,该加密Cookie是服务器返回或者是本地生成分析请求反ob混淆这段代码经过了ob混淆,格式化后是这样的一键ob反混淆(猿人学提供有)网址:http://tool.yuanrenxue.com/decode_obfuscator分析加密处反混淆后,直接看见了document["cookie"],还有location["reload"]();这个不就就是:设置Cookie然后再次请求嘛调试加密值发现M()函数为空,V(Y)函数疑似加密结果导出加密值这个加密值在函数内部,想要使用就用全局变量接收一下暗桩一这段代码,其中!a6["test"](a2)执行起来很慢,并且一直返回true,所以直接写死,就会发现执行起来很快了,一些调试工具也不会阻塞了vara2=B(this,function(){vara5=function(){vara6=a5["constructor"]("return/\"+this+\"/")()["compile"]("^([^]+(+[^]+)+)+[^]}");return!a6["test"](a2);//true};returna5();});暗桩二如果说暗桩一是技巧,那暗桩二才是真正的暗桩,可以看这段代码改造了console.log只要我们一使用就会陷入,虽然这个暗桩对Js执行没有影响,但是调试就很容易陷入console=newObject()console.log=function(s){while(1){for(i=0;i<1100000;i++){history.pushState(0,0,i)}}}console.toString='[objectObject]'console.log.toString='?toString(){[nativecode]}'请求计算结果稍微修改Js,计算结果importexecjsimportrequestsdefget_c():returnexecjs.compile(open('./main.js','r',encoding="utf-8").read()).call('f')defapp(page:int)->dict:rep=requests.get(url="http://match.yuanrenxue.com/api/match/2",headers={"Cookie":f"sessionid=你的sessionid;m={get_c()};","User-Agent":"yuanrenxue.project",},params={"page":page})returnrep.json()if__name__=='__main__':sum=0for_inrange(1,6):data=app(_)forvalueindata.get('data'):sum=sum+value.get("value")print(sum)结果正确完整代码:https://pan.bigdataboy.cn/s/EYSW
难点源码压缩,混淆,反调,时间检测Tip:文末可以下载例子猿人学官网:http://match.yuanrenxue.com/确定逆向目标这个m值开始逆向过反调跟值此处是加密的url在发送出去最后出现的地方,我们可以通过函数调用的栈,进行跟值,找到大体加密的地方本地调试最终我们跟到了此处,发现它在<script>标签里,我们无法格式化,就无法断点调试我采用把整个网页扒下来,修改其中的<href>为绝对路径,采用fiddler本地替换网页响应,实现把在本地把<script>压缩的代码格式化,在手动加断点寻找window.f值逆向需要有怀疑精神,oo0O0()这个函数返回空,不会这么简单吧,进入它定位加密位置经过分析最终确定在这一行改造加密函数到这里,差不多就分析完成,就改造一下加密函数就完成了functionf(){varmv=Date.parse(newDate())+100000000;varwindow={};varm=eval("把eval里的全部复制过来window.f=hex_md5('bigdataboy')".replace(/bigdataboy/,mv))//这里有个小坑必须要用替换,因为拼接的md5值是不对的returnm+'丨'+mv/1000}请求计算结果importexecjsimportrequestsdefget_m():returnexecjs.compile(open('./main.js','r',encoding="utf-8").read()).call('f')defapp(page:int)->dict:rep=requests.get(url="http://match.yuanrenxue.com/api/match/1",headers={"Cookie":"自己的sessionid",'User-Agent':'yuanrenxue.project',},params={"page":page,"m":get_m(),})returnrep.json()if__name__=='__main__':sum=0count=0for_inrange(1,6):data=app(_)count=count+len(data.get("data"))forvalueindata.get("data"):sum=sum+value.get("value")print(sum/count)结果成功完整代码:https://pan.bigdataboy.cn/s/Nnsy
难点&目标网站:https://spa6.scrape.center/逆向参数:Token有时间限制,混淆,加密查找加密出位置通过技术分析,我们很容易找到加密生成的位置,主要观察_0x51c425['a']这个函数又很容易找到_0x51c425=_0x32d76d('7d92'),所以只要扣出_0x32d76d('7d92')就算解决问题扣代码再通过观察,发现网站是webpack打包的网站,那么就简单很多了,首先找到这样一段代码,然后改造成如下:varfff;!(function(_0x776dc){var_0x54509b={}function_0x478fb7(_0x2ad438){if(_0x54509b[_0x2ad438])return_0x54509b[_0x2ad438]['exports'];var_0xcb17fa=_0x54509b[_0x2ad438]={'i':_0x2ad438,'l':!0x1,'exports':{}};return_0x776dc[_0x2ad438]['call'](_0xcb17fa['exports'],_0xcb17fa,_0xcb17fa['exports'],_0x478fb7),_0xcb17fa['l']=!0x0,_0xcb17fa['exports'];}fff=_0x478fb7;}({模块代码的位置,完整下面网盘下载})仿写加密逻辑functiongetToken(){var_0x51c425=fff('7d92')returnObject(_0x51c425['a'])("/api/movie")();}最终效果完成特别说明:50积分只是0.5元,为防止恶意下载,也可以联系博主要积分兑换码即可完整代码:https://pan.bigdataboy.cn/s/4dcp
特别声明本文仅供学习交流,相关敏感数据已做脱敏处理分析目标通过抓包我们发现有两处加密值,sign、bv寻找加密位置搜索关键值,锁定这段代码为加密函数代码改写通过分析发现,sign、bv都是通过md5加密的所以我们就只需要进行一点点改写就好了//CryptoJS.MD5下面网盘可以下载functionMD5_Encrypt(word){returnCryptoJS.MD5(word).toString();}navigator={appVersion:"5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/91.0.4472.77Safari/537.36"}//e为翻译的文本functionget_sign_bv(e){vart=MD5_Encrypt(navigator.appVersion),r=""+(newDate).getTime(),i=r+parseInt(10*Math.random(),10);return{bv:t,sign:MD5_Encrypt("fanyideskweb"+e+i+"Tbh5E8=q6U3EXe+&L[4c@")}}相关资料下载特别说明:10积分只是0.1元,为防止恶意下载,也可以联系博主要积分兑换码即可https://pan.bigdataboy.cn/s/L0FV