一、声明:
本文仅供参考学习,请勿用于其他途径,违者后果自负!
二、前言:
目标网站:喜马拉雅
目标接口: 如下
'aHR0cHM6Ly9wYXNzcG9ydC54aW1hbGF5YS5jb20vcGFnZS93ZWIvbG9naW4/ZnJvbVVyaT1odHRwcyUzQSUyRiUyRnN0dWRpby54aW1hbGF5YS5jb20lMkZ1cGxvYWQ='
目的:逆向加密参数,实现比如:批量账号登录自动化相关操作
三、查看加密参数:
根据接口地址,进行账号密码登录(当前为测试,账号不为任何人手机号),F12打开开发者工具,通过简单滑块验证后,查看ajax包,发现下图红箭头这个包为我们想要的登陆包
点击进入查看发现请求标头中无加密参数,再看载荷,发现请求载荷中多数参数都已加密,如:我想会不会部分加密值是固定的?于是继续发包对比,发现两次相同账号不同密码请求对应载荷的加密参数除了fromUri都不相同,说明这些参数可能都需解密。
四、入口定位:
当我看到这些参数时,我想到的就是搜索关键字,如:account,发现匹配到了13个对应位置
根据上述加密参数的格式,可以在account后加个:发现现在匹配到3个了,我们要加的断点就更少了,当然这种不一定在所有案例中都受用,有的可能加密位置就不在这3个中,那就老实在那13个里面依次打断点验证。但当前这样搜索确实很舒服,直接找到加密位置了。
于是我们加上断点,点击页面的登录按钮,通过滑块,发现在断点处断住了,说明至少目前大框架下的加密位置找对了。
接下来,我们对断点处的参数,在控制台打印查看发现n,i,确实为我们开始输入的账号密码。
然后我们再在箭头处在加一个断点,并让断点断到那里,查看我们要找的加密参数:account,nonce,password,signature是否都有。
发现我们要解密的参数都在这里。
五、参数解密:
走到这里我有两个思路:
a:硬扣js
b:webpack+补环境
这里我推荐b方案可以学到很多,a方案我简要说明,有兴趣的师傅可以自己去试试。这里我先简单说说a方案:鼠标分别悬停在对应四个箭头处,点击进入对应加密函数,把代码扣到本地,可以通过查看报错的方式看还需要扣的代码,直至最后可以得到加密结果。
我先只拿第一个 箭头指向的a.getEncryptPwd举例,首先要理解(0,a.getEncryptPwd)(n),(0,a.getEncryptPwd)这是个括号运算符,最后结果就是a.getEncryptPwd(n),n就是我们输入的账号,类型为字符串型。往上控制台有
如下图,鼠标悬浮点击进入函数:
其实进入后我们发现上面四个要的四个参数的加密方法都写在一起了,
但我们现在关心的是:下面这段代码,其实对各种加密方法熟悉的师傅,看到e.publicKey跟 e.securityKey其实应该猜出来这是什么加密 ,他俩其实是RSA非对称加密的公私钥,当然如果没看出来我们可以继续往下看:
m = e.getEncryptPwd = function(t) {
var e = new a.default;
return e.setPublicKey(h),
e.encrypt(t)
看这段代码,其实本质加密是不是在最后一行e.encrypt(t),我们可以在这一行加上断点,跟前面一样鼠标悬浮,进入。
发现跟剥苞谷一样一层还有一层,他是一个try catch的异常处理,实际加密的位置还是在最后一层this.getKey().encrypt(t),所以跟上述一样,下一断点,鼠标悬浮,点击进入。
这就找到最后一层加密的位置了,如果前面没有发现是RSA加密的师傅,现在应该明白了吧
当然,找到这里是为了搞清楚a.getEncryptPwd()的加密方法是啥,并非是让你从最后一步扣js,crypto-js库里面的就有这些加密方法,拿到公私钥,就可以加密,python同理,这里就不过多说明写法。
六、webpack+补环境解密:
这个方法为我分享的重点:如果你不想像上述一样一步步找加密位置,就可以采取下述方法,但你需要对webpack跟补环境有一定了解,这里推荐去b站找相关视频了解一下或者相关文档。
所以我们回到第一步,如下:我们发现其实我们找到对象a,是不是它的属性getEncryptPwd我们就可以直接调用,然后传递参数得到加密结果,而非上述一样找到getEncryptPwd具体加密位置。所以我们明确了,我们现在要找a这对象在哪里赋值的,所以我们可以根据根据缩进往上找。
往上找跟e.accoutLogin在一个缩进下,最终找到了a=r(20)
一样嘛,我们要弄清楚r对象是啥,接下来就是这一行加断点,刷新页面(注意这里放开断点点登录不会在这里断住,需要刷新页面重新加载模块),点进去查看r里面具体是啥。进去看到是一个html页面跟我们经常看到的纯js代码还不一样,但是不影响,但是看到return e[r].call(t.exports, t, t.exports, i)这一行,熟悉的师傅是不是知道这不就是webpack的调度器嘛
还不清楚的师傅,把整个函数收起来!function(){}([]);这个格式就是经典的webpack,[ ]里面放的就是需要调度的函数,以列表的形式存放,通过下标索引的方式调用,这里其实也可以解释了,为啥a =r(20)括号里面是20,其实就是对应下标,但这种webpack是属于多文件webpack,即:[ ]里面没有放对应函数,而是放在其它js文件中。
接下来,我们是不是就可以把<srcipt></srcipt>里面的函数copy到本地进行调试,为了方便查看可以创建先创建如下四个js文件:
env.js:放环境配置
loader.js:加载器即调度器
mod.js:需要的模块
test.js:测试脚本
单独执行loader.js发现缺少window,但在node中global为全局变量,所以在环境env.js代码中加入window =global;
再在test.js中引用其它三个js模块,执行发现没有问题
接着我们的思路是能从外部引用这个调度器,通过这个调度器调用下标为20对应的函数从而得到a,通过a调用getEncryptPwd函数,传值加密。所以在loader中如下位置加入对应代码,一个是打印调用模块的信息,方便我们看到我们具体调用了哪些模块,第二个就是刚提到的通过window.loader从外部调用i函数
然后我们进行外部调用测试,发现可行,可以从外部成功调用。
调用下标为20的模块时,发现报错缺少下标为20这个模块
这时我们思路是不是就需要去浏览器里找下标为20的模块在哪个js文件中,然后我们把整个js文件复制粘贴到本地的mod.js内作为webpack,!function(){}([]);数组内的模块。但我们想列表调度时20只是下标,他不跟字典一样一个键对应一个值,可以通过搜索键名找到对应值。所以,这时我们就要回到浏览器,思考既然是调度器调度模块,不管过程调度了多少,是不是最终还是会调度getEncryptPwd这个函数,因为它参数的加密要走这个函数呀,所以,我们大胆想象a.getEncryptPwd里面的内容就是一整个加载器所需的,写在另外js文件中的模块。
点进去把函数整合缩进发现确实就是我们需要的所有的模块
在([])在这个列表下放了很多个模块即函数,不同下标对应不同的函数,即通过下标调用对应函数。所以,现在把他copy到本地mod里面。其实这里还进行了迷惑,将webpack改成了__award__,但其实你要明白其实这里就是window.webpack,只是开发人员玩的障眼法,不过不影响我们判断这里就是我们要找到模块。
接着,我们再测试,发现是不是刚刚那个问题就变了,出现了新的问题。
所有我们去浏览器用关键字搜索__award_library__发现有这么一个函数,同样我们copy到本地,因为这也是环境配置问题,所以我们新建一个env2.js存放改配置。
同时去掉var,让它变成全局变量,同时再test.js中用require引用,执行发现走通了,调用了很多模块。
眼力好的师傅会发现r::::20上面还有好多模块,但按说我只调用了20下标对应这一个模块,其实上面的都是开发人员在写webpack时为了适配浏览器,同时协调各方代码,初试环境检测所用的模块,这些模块对我们来说没啥意义,而20下面调用的模块就是解密函数中引用到的需要的模块。说了这么多的意义在哪呢?就是你可以将20以上出现的下标如:73,72,71等在mod中对应的函数删掉,这么做就是为了防止你的pycharm或者vscode加载上万行代码时卡顿。
回过来我们继续看报错发现,它提醒我们document未定义,从这开始就需要我们开始补环境了,也是这个方法最重要的一步。
了解补环境的师傅,到这里我们发现它给我们的报错信息提示的很明显就是document对象下的createElement方法里面的canvas属性里面的toDataURL方法返回值找不到。但我想说的是,我所说的是基于我能看到报错信息做为前提,但如果他给代码做了混淆了呢?这时是不是我们就无法知道哪些对象下的哪些属性或方法空缺了,这里我提供几个思路:1.在环境env.js内添加代理,这里就不展示我的代理大家可以自己去搜或者问ai 补环境代理如何书写;2.将报错的那一行代码去浏览器搜,然后下断点,控制台查看各个参数信息,先弄清楚那个对象,随后属性、方法,配合上你的代理,基本就可以解决很大一部分问题;3.当然也可以去找一些解混淆的代码的网站,看密文对应的明文信息。
环境中遇到不清楚的属性与方法,自己去网上搜,都能找到。话不多说,开始补环境。
运行报错,发现上面有好多环境缺少,当然我们可以选择从上往下依次补,但是我推荐从后往前,哪步报错补哪步,这样不容易乱与漏补,继续补环境。
报新的错误,说明上一步环境没问题,继续补新的环境,发现是没userAgent,所以toLowerCase()调用出现问题
发现location对象找不到,这里为了补的逼真可以去浏览器,控制台输入location,复制object对应的属性值到本地。
又是新错误,如下,在createElement里面继续补充div属性
又跳新错误,说明上次环境没问题,继续补环境,发现还是div里面缺少setAttribute方法
这里style跟setAttribute我没有跟浏览器保持一致补具体的值,因为我不补具体的值,他也不出错就没必要保持一致,但如果继续在这个地方报错,就代表在这里对属性或方法具体值做了校验,如果你不补完整,就还是会继续卡在这里。
留心的师傅可能发现报错上面出现了许多内容,其实这就是上完代理后出现的内容,在此之前我们可以不用上代理,但到这一步我们就需要给前面创建的对象都上一遍代理,目的是搞清楚这个e是哪个对象,这里我们可以看到是document对象下的addEventListener方法没补全。当然我们也可以在浏览器找到相应位置,下断点,刷新页面,悬浮在e,发现其实就是document对象跟刚才加代理的方法一样,两种方法师傅舒服着来。
多的不说继续补环境
新的错误,r.clientWidth未定义,因为这里我们看代理的信息发现没有关于clientWidth的内容,所以就跟刚提到的,我们得去浏览器搜(这里复制错误内容时要把中间的空格删掉,不然会找不到对应内容)
根据图片我们发现r =e.documentElement,而e就是刚才提到的document,这样我们是不是就清楚了是:document.documentElement.clientWidth未定义,好继续补环境。
上述内容中的1536是浏览器对应的属性值,又出新的错误,接上r我们知道是document.documentElement,错误就是r下对应属性值未找到,继续补环境
又有新的错误,继续补环境,根据代理提示的发现是window下addEventListener方法未找到,继续补环境。
发现虽然仍然有报错,但我们需要的对象已经拿到了,不需要继续补环境了。
最后我们在test.js中调用加密函数,发现已经可以得到加密值,而其它的几个参数的加密值也同理根据window.loader(20).对应参数的加密函数(上图)(传入的参数)这样的格式,完成加密。
当你做的时候,你会发现nonce这个参数虽然变化,但服务器不检验这个参数。
最后,有的师傅可能遇到,下述问题:
这是因为后续还有环境为补完全,但那些环境不影响你解密,所以在最后加上process.exit(0)
同时去掉补环境时添加的提示信息就得到上上图片里面纯净的加密信息。
最最后,如果师傅们还想结合python做自动登录,这里提示一下可能遇到的问题,如果你用execjs从js中引用模块,会因为process.exit(0)的存在导致在execjs.compile编译完到那一步后,直接退出当前进程。举个例子:如下图,我调用signature.js中的getSignature函数,传入params参数后运行报错,报错原因为:前面提到的编译完成后当前进程退出找不到getSignature函数。
encrypt_sign =execjs.compile(open("signature.js",encoding='utf-8').read()).call("getSignature",params)
这里提供的解决方案是用python库里的模块subprocess,以命令的方式执行调用,如下。
test.js里面内容更改为:
七、最终展示:
因为我频繁登录测试,可能服务器发现我不是人为操作,可能把我ip短暂封了,导致msg显示为试试验证码登录吧,如果师傅们走到这一步,只用输入正确的账号密码,msg就会为succ,并返回登录凭证token,后续绕过滑块完成登录就不说了,很简单,师傅们可以自己去试试。
如果你不想注册,随意输入账号密码,又想验证你逆向的对不对,下述为服务器的响应是对你逆向的认可,只是账号密码不对所以得不到对应token值。
当然会有人说你这也没有拿到token,未必你做出来了,确实,但如果你没有完成对你输入的账号密码的加密或不完整或不正确,你得到服务器的响应为这样:
八、总结:
内容较长,但里面有我对遇到的每个问题的思考,希望对自学逆向的师傅有帮助,感谢大家观看。