最近收到粉丝私信,想多学习下js逆向相关的知识和案例,应大多数粉丝要求:今日份js逆向练习,36氪登录模拟
目标地址:https://36kr.com/
初步探测
-
点击登录按钮,切换到账户密码登录
-
随便输入一个手机号码和6位数以上的密码,点击登录
可以看到XHR请求已经发出来了
byMobilePassword
-
查看
byMobilePassword
相关的request header和payload request header中并未发现特殊加密的参数很明显payload中mobileNo和password是经过加密的
牛刀小试
-
先来第一枪,根据关键字搜索
mobileNo
only一个,点击文件进去试一下 -
格式化下代码
-
打上断点,点击登录按钮
-
断点成功断住,从chrome控制台可以看到
o.a.get(t, "mobileNo")
就是我们的明文手机号码,Object(i.b)()
就是加密明文的方法,因此我们的重点任务就是破解Object(i.b)()
这个方法
重点突破
-
鼠标光标放到
Object(i.b)()
上,点击弹出来的浮窗进入文件 -
文件定位到这段代码
简单分析下这段代码:
ar r = n(14)
, o = n(4)
,
i = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeiLxP4ZavN8qhI+x+whAiFpGWpY9y1AHSQC86qEMBVnmqC8vdZAfxxuQWeQaeMWG07lXhXegTjZ5wn9pHnjg15wbjRGSTfwuZxSFW6sS3GYlrg40ckqAagzIjkE+5OLPsdjVYQyhLfKxj/79oOfjl/lV3rQnk/SSczHW0PEyUbQIDAQAB"
, a = function () {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : "";
if (!Object(o.isNodeEnv)()) {
var t = n(769)
, r = new t.JSEncrypt;
r.setPublicKey(i);
var a = r.encrypt(e);
return a
}
}
-
当我们看到
r = new t.JSEncrypt;
这行代码的时候,有经验的一看就应该明白这可能是标准的RSA加密;r.setPublicKey(i);
是设置公钥,这里的i的值在上面也很清晰的写死了
i = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeiLxP4ZavN8qhI+x+whAiFpGWpY9y1AHSQC86qEMBVnmqC8vdZAfxxuQWeQaeMWG07lXhXegTjZ5wn9pHnjg15wbjRGSTfwuZxSFW6sS3GYlrg40ckqAagzIjkE+5OLPsdjVYQyhLfKxj/79oOfjl/lV3rQnk/SSczHW0PEyUbQIDAQAB"
三种方式来破解登录
JSEncrypt库
这种方式最直接,也最简单,不过需要对相关加密库比较熟悉 在node.js环境中使用jsEncrypt库 首先, Node.js 环境中使用 JSEncrypt 需要先安装该库,可以通过 npm 安装: npm install jsencrypt
补一个window环境就可以直接使用了
代码如下:
window = global
function get_encrypt(message) {
const JSEncrypt = require('jsencrypt');
const encrypt = new JSEncrypt();
p_k = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeiLxP4ZavN8qhI+x+whAiFpGWpY9y1AHSQC86qEMBVnmqC8vdZAfxxuQWeQaeMWG07lXhXegTjZ5wn9pHnjg15wbjRGSTfwuZxSFW6sS3GYlrg40ckqAagzIjkE+5OLPsdjVYQyhLfKxj/79oOfjl/lV3rQnk/SSczHW0PEyUbQIDAQAB'
encrypt.setPublicKey(p_k)
// 使用公钥加密数据
return encrypt.encrypt(message);
}
console.log(get_encrypt('1'));
通过python调用,看到这个结果,就已经说明我们模拟的加密已经通过了36kr服务器的验证,只是输入的账户不对,换成你们正确的账户就行
硬扣
如果不知道是RSA加密,也没有关系,我们用其他的方法,那就硬扣呗;
-
这几行代码n(**)差不多可以知道是webpack加载器n来的,在几个n处打上断点,刷新页面
-
断点成功进入,点击浮窗进入代码文件
成功定位到加载器
-
把runtime.****.js文件内容都拷贝下来
补环境
-
代码拷贝下来后,直接运行js文件
提示
ReferenceError: window is not defined
-
补一个
window = global
再次运行,代码ok -
格式化代码,缩近代码,可以看到是一个[]空数组
加密函数的文件看到这里的代码都是在[]数组中,
-
这里是webpack的数组方式,因此把这些function都拷贝到[]数组中
ReferenceError: navigator is not defined
-
导出加载器函数,加载器函数是
n
,_ps = n
后面就可以使用_ps
全局对象来调用对应的函数了 -
因为加载器中是数组对象,搜索下
r = new t.JSEncrypt
我们所需要的方法是在哪个functuion数组跟python中一样,也是从0开始的,所以我们需要的是第7个
_ps(7)
打印出来的结果就是三个方法
{ a: [Getter], b: [Getter], c: [Getter] }
b方法返回的就是a对象,而a对象就是我们所需要的加密方法
7. 因此_ps(7).b('123345')
就可以得到加密结果 构造一个函数返回加密结果给python程序调用
function get_encrypt(message) {
return _ps(7).b(message)
}
-
接下来就是我们通过python程序来进行登录,看到这个结果,就已经说明我们模拟的加密已经通过了36kr服务器的验证,只是输入的账户不对,换成你们正确的账户就行;
附上相关的python代码,js代码因此太长了,就不附上了,有需要学习的朋友可以私信我。
import requests
import json
import time
import execjs
headers = {
"authority": "gateway.36kr.com",
"accept": "*/*",
"accept-language": "zh-CN,zh;q=0.9",
"cache-control": "no-cache",
"content-type": "application/json",
"origin": "https://www.36kr.com",
"pragma": "no-cache",
"referer": "https://www.36kr.com/",
"sec-ch-ua": "\"Chromium\";v=\"110\", \"Not A(Brand\";v=\"24\", \"Google Chrome\";v=\"110\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"
}
url = "https://gateway.36kr.com/api/mus/login/byMobilePassword"
round(time.time() * 1000)
mobileNo = '18814378681'
password = '123456'
exjs = execjs.compile(open('36ke.js', encoding='utf-8').read())
mobileNo = exjs.call('get_encrypt', mobileNo)
password = exjs.call('get_encrypt', password)
data = {
"krtoken": "",
"partner_id": "web",
"timestamp": round(time.time() * 1000),
"param": {
"countryCode": "86",
"mobileNo": mobileNo,
"password": password
}
}
data = json.dumps(data, separators=(',', ':'))
response = requests.post(url, headers=headers, data=data)
print(response.text)
Websocket方式
-
导出加密函数
-
启动我们的sekiro工具
可以看到启动日志是正常的,我这里是sekiro3,默认启动的netty端口是5612
-
在浏览器控制台注入sekiro_web_client.js和我们自己的脚本文件
function guid() {
function S4() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
}
var client = new SekiroClient("ws://127.0.0.1:5612/business/register?group=ws-group&clientId=" + guid());
client.registerAction("sss", function (request, resolve, reject) {
const {param1} = request.params;
resolve(sss(param1));
})
控制台提示已经和服务端建立了websocket连接
-
接下来我们就可以通过python发送我们的请求获取加密的手机号和密码了
然后可以通过加密后的数据请求登录接口了。。 这种方式的缺点就是刷新浏览器的时候注入的脚本就消失了,需要重新注入,当然我们也可以通过Tampermonkey注入,不过这个小小的登录用这些,感觉就有点大炮轰蚊子了
总结:
js逆向根据实际情况,可以有多种方式破解方式,可以按照自己熟悉的来,前提要了解各种破解套路,才能找到对应最合适的。
更多资源信息可关注公众号:python君