一.分析负载参数
我们来到登录的界面,然后输入账号密码,再观察这个登录的网络请求。
能很清楚看到,我们输出的账号密码(123456,123456)被加密成了两串密文进行传送,如果我们要实现模拟登录的话,必须要能够复现它的加密逻辑。
二.堆栈分析
我们利用启动器来进行堆栈分析,在最后发送网络请求的地方打上一个断点。
然后重新登录,来到右边的堆栈进行分析。
在u的这个堆栈里面,我们发现了很可能是开始加密的地方,我们尝试在这里打上断点,重新登录。
于是,我们可以确定了,这里就是对账号密码进行加密的地方了。另外如果有经验的话,我们从这个文件里面就能够看出,这里的登录是使用的webpack。我们再单步跟进去看看,我们就能得知,加密方法应当使用的是RSA加密。
RSA的一个重要关键字就是setpublickey,我们以此进行全局搜索,看看能不能找到加密的公钥。
非常简单,很显然MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDvekdPMHN3AYhm/vktJT+YJr7\ncI5DcsNKqdsx5DZX0gDuWFuIjzdwButrIYPNmRJ1G8ybDIF7oDW2eEpm5sMbL9zs\n9ExXCdvqrn51qELbqj0XxtMTIpaCHFSI50PfPpTFV9Xt/hmyVwokoOXFlAEgCn+Q\nCgGs52bFoYMtyi+xEQIDAQAB就是加密的公钥了。
根据上面的分析,我们有以下几种方法来实现加密方法,第一中是利用现成的库来实现RSA加密,第二种是通过抠webpack的方式实现调用加密函数,得到加密结果,第三种是利用RCP的方式来调用加密函数得到加密结果。
三.套库实现加密
这种方式最为简单,这里就不再赘述,需要注意的是密钥的格式。
import base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import requests
account='你的账号'
password='你的密码'
key='-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDvekdPMHN3AYhm/vktJT+YJr7\ncI5DcsNKqdsx5DZX0gDuWFuIjzdwButrIYPNmRJ1G8ybDIF7oDW2eEpm5sMbL9zs\n9ExXCdvqrn51qELbqj0XxtMTIpaCHFSI50PfPpTFV9Xt/hmyVwokoOXFlAEgCn+Q\nCgGs52bFoYMtyi+xEQIDAQAB\n-----END PUBLIC KEY-----'
def RSA_jiami(data,key):
publicKey = RSA.importKey(key)
cipher = PKCS1_v1_5.new(publicKey)
output_data = base64.b64encode(cipher.encrypt(data.encode('utf-8'))).decode('utf-8')
return output_data
if __name__=="__main__":
url='https://passport-api.miyoushe.com/account/ma-cn-passport/web/loginByPassword'
headers = {
"Accept": "application/json, text/plain, */*",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Accept-Language": "zh-CN,zh;q=0.9,en-GB;q=0.8,en-US;q=0.7,en;q=0.6",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Length": "372",
"Content-Type": "application/json",
"Cookie": "_MHYUUID=9c9aca85-7087-4af2-8542-94860eff21b2; DEVICEFP_SEED_ID=9b75d0123731369d; DEVICEFP_SEED_TIME=1714528737065; DEVICEFP=38d7f9f285deb; _ga=GA1.1.1351865754.1714528738; aliyungf_tc=7aced00f801d605acaa574cdc7906fb0737d5662bf1899633fe3c8306d67d9c7; _ga_KS4J8TXSHQ=GS1.1.1717463999.8.1.1717464259.0.0.0; MIHOYO_LOGIN_PLATFORM_LIFECYCLE_ID=43278ee300",
"Host": "passport-api.miyoushe.com",
"Origin": "https://user.miyoushe.com",
"Pragma": "no-cache",
"Referer": "https://user.miyoushe.com/",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
"sec-ch-ua": "\"Google Chrome\";v=\"123\", \"Not:A-Brand\";v=\"8\", \"Chromium\";v=\"123\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"x-rpc-app_id": "bll8iq97cem8",
"x-rpc-client_type": "4",
"x-rpc-device_fp": "38d7f9f285deb",
"x-rpc-device_id": "9c9aca85-7087-4af2-8542-94860eff21b2",
"x-rpc-device_model": "Chrome%20123.0.0.0",
"x-rpc-device_name": "Chrome",
"x-rpc-device_os": "Windows%2010%2064-bit",
"x-rpc-game_biz": "bbs_cn",
"x-rpc-lifecycle_id": "43278ee300",
"x-rpc-mi_referrer": "https://user.miyoushe.com/login-platform/index.html?app_id=bll8iq97cem8&theme=&token_type=4&game_biz=bbs_cn&message_origin=https%253A%252F%252Fwww.miyoushe.com&succ_back_type=message%253Alogin-platform%253Alogin-success&fail_back_type=message%253Alogin-platform%253Alogin-fail&ux_mode=popup&iframe_level=1#/login/password",
"x-rpc-sdk_version": "2.27.0",
"x-rpc-source": "v2.webLogin"
}
response=requests.post(url=url,headers=headers,json={
'account':RSA_jiami(account,key),
'password':RSA_jiami(password,key)
})
print(response.text)
四.抠webpack的方式
我们回到加密方法的地方分析一下。
可以看出,想要调用加密方法encrypt的话,就需要构造出i这个对象,那么这个i哪里来的呢?我们不断往上面找,最终可以找到生成i的地方。
在这里打上断点,我们刷新页面。
进入到这个n函数定义的地方看看。
很显然这是一个标准的加载器。我们把整个该文件扣下来,并利用全局变量的方式导出这个加载器。
然后我们利用原JS里面的调用加密函数的方式尝试调用输出:
可以看到缺少了一些环境,我们尝试手动补充。再次运行。
这次报的错误是抠webpack里面非常常见的一个错误,也就是缺少一些模块,但是如果你一个一个去补充这些模块的话,将会非常费时,于是我们干脆将所有模块全部扣下来,放在一起。再次运行。
加密生成成功,并且没有很深度的检测环境,利用这个生成结果,可以验证我们的生成是正确的。
五.RCP法
我们找到调用加密函数的地方,进行文件替换,然后在这里插入一段websocket代码,
var ws;
// 检查ws是否存在且连接处于打开状态
if(ws && ws.readyState === WebSocket.OPEN) {
console.log("已存在一个打开的连接");
return;
}
ws = new WebSocket("ws://127.0.0.1:5678");
ws.onmessage = function (evt) {
console.log("成功连接到服务器");
ws.send(i.Z.encrypt(evt));
};
然后我们在本地利用python编写一段简单的websocket服务端代码
import asyncio
import websockets
import aioconsole
async def receive_massage(websocket):
while True:
send_text = await aioconsole.ainput("请输入要加密的字符串: ") # 改用 aioconsole
if send_text.strip() == "exit":
print("Exit, goodbye!")
await websocket.send(send_text)
await websocket.close()
break
else:
await websocket.send(send_text)
response_text = await websocket.recv()
print("返回的结果是:", response_text)
start_server = websockets.serve(receive_massage, '127.0.0.1', 5678) # 自定义端口
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
当然,这些代码都可以改进,这里写的不是很好,不过能用就行。
随后我们先运行本地代码,然后需要打开浏览器执行一遍登录。然后回到本地,输入待加密的字符串,这样就能成功获得加密结果了。
RCP是一种比较万能的方法,一般别的方法行不通的话,可以尝试这个方法。