
JavaScript逆向练习6
0x01 目标网址
http://weibo.com
0x02 定位JS
1. 随便输入账号密码验证码,如果是不存在的账号,会直接提示密码错误,如果是是存在的账号,会提示输入验证码,,这里假设是带验证码的,点击登录后,查看提交的参数,可以看到,在登录包里有许多看不懂的加参数,一个个来分析。

2. 分析下参数
参数 |
值 |
来源 |
entry |
weibo |
固定 |
gateway |
1 |
固定 |
from |
固定 |
savestate |
7 |
固定 |
qrcode_flag |
false |
固定 |
useticket |
1 |
固定 |
pagerefer |
固定 |
pcid |
yf-826260aed11d* |
上次返回 |
door |
1111 |
验证码 |
vsnf |
1 |
固定 |
su |
MT*****NjY= |
JS计算 |
service |
miniblog |
固定 |
servertime |
1576135789 |
JS计算 |
nonce |
NLUYC0 |
上次返回 |
pwencode |
rsa2 |
固定 |
rsakv |
1330428213 |
上次返回 |
sp |
2395de422727c06* |
JS计算 |
sr |
1536*864 |
固定 |
encoding |
UTF-8 |
固定 |
prelt |
82 |
JS计算 |
url |
https://weibo.com/* |
固定 |
returntype |
META |
固定 |
其中上次返回
是在登陆之前的一次请求中返回的内容

其他的JS计算
也不全是加密,有的只是单纯计算的值,下面一个个找。
3. Ctrl+Shift+F调出搜索面板,本着越生僻越好搜的原则,先搜索prelt
,看到只有一个js结果。
点进第一个结果,点花括号格式化一下,Ctrl+F
搜prelt
,只有两个结果,而且第一个returntype: "TEXT"
明显不是要找的,所以第二个上面下断点。

同时观察断点处,发现f
是在上面由makeRequest
定义的,所以这里先搜一下看看makeRequest
的定义在哪。

直接找到了加密处,就是makeRequest
,可以看到还有其他参数也是在这里生成的,包括固定的一些。在782行这里也下个断点。
4. 重新输入账号密码验证码,点击登录,JS被断了下来。

直接先把su
给扣下来:
e.su = sinaSSOEncoder.base64.encode(urlencode(a));
可以看到参数a
是账号,跟进urlencode
发现只是一个简单的encodeURIComponent
urlencode = function(a) {
return encodeURIComponent(a)
}
再找sinaSSOEncoder
,直接Ctrl+F搜索,找到定义处:
var sinaSSOEncoder = sinaSSOEncoder || {};
这个语句意思是如果sinaSSOEncoder
不为空(即转为bool类型不为false),则把sinaSSOEncoder
赋值给前面的变量(也是sinaSSOEncoder
),如果sinaSSOEncoder
为空,则把后面的{}
赋值给前面的sinaSSOEncoder
,所以这里首先生成了空对象。
所以再找定义处,往下翻一下,看到有两个函数调用了.call(sinaSSOEncoder);
,这就是加密对象的定义了,两个函数全部拿走。
继续往下运行,发现对servertime
进行了赋值,那就搜一下后面的.servertime =
(前面不带对象名,但带上.
,后面带上空格和等于号),可以快速定位。

这里看到,其实就是一个a
循环加2的意思,在这里下个断点,断下来之后可以从右侧Call Stack里看到参数a
的来源,找到调用处发现就是上一次请求返回的servertime
,也就是说,参数servertime
就是上次返回的servertime
循环加2,所以这里可以直接在外部语言实现。
下一个参数是sp
,在上面的加密处已经看到了sp,大致看到是一个RSA,而且RSA对象的定义已经被扣下来过了,这里没啥要做的了,只是加密的参数要注意看下,一个是上次返回的nonce
,一个是计算完的servertime
,还有一个是明文密码,再加上其他字符组合成了加密参数。

最后一个参数是prelt
,这个参数位置在一开始第一次下断点的那里:
f.prelt = preloginTime;
直接搜preloginTime
,可以定位到269行:

这段意思是prelt
就是上一次请求的回调函数中new Date减去上一次请求的起始new Date再减去请求返回的exectime,也就是等于上一次请求的本地总耗时减去云端耗时。
5. 参数找完了,到这里已经可以开始改写了。
参数列表如下:
参数 |
值 |
来源 |
entry |
weibo |
固定 |
gateway |
1 |
固定 |
from |
固定 |
savestate |
7 |
固定 |
qrcode_flag |
false |
固定 |
useticket |
1 |
固定 |
pagerefer |
固定 |
pcid |
yf-826260aed11d |
上次返回 |
door |
1111 |
验证码 |
vsnf |
1 |
固定 |
su |
MTU1NTU1NTY2NjY= |
base64+URL编码 手机号 |
service |
miniblog |
固定 |
servertime |
1576135789 |
以上次返回的为基数,每次+2(从上次请求到提交登录的时间差除以2取整,一般为4~10) |
nonce |
NLUYC0 |
上次返回 |
pwencode |
rsa2 |
固定 |
rsakv |
1330428213 |
上次返回 |
sp |
2395de422727c06ce |
JS计算 RSA加密 |
sr |
1536*864 |
固定 屏幕宽*高 |
encoding |
UTF-8 |
固定 |
prelt |
82 |
可随机,约等于上一次请求的本地耗时减去云端耗时,上一次请求的回调函数中new Date-上一次请求的起始new Date-请求返回的exectime |
url |
https://weibo.co |
固定 |
returntype |
META |
固定 |
0x03 改写JS
1. 先把调用函数拿过来。
function test(pass, servertime, nonce) {
rsaPubkey = "EB2A38568661887FA180BDDB5CABD5F21C7BFD59C090CB2D245A87AC253062882729293E5506350508E7F9AA3BB77F4333231490F915F6D63C55FE2F08A49B353F444AD3993CACC02DB784ABBB8E42A9B1BBFFFB38BE18D78E87A0E41B9B8F73A928EE0CCEE1F6739884B9777E4FE9E88A1BBE495927AC4A799B3181D6442443"
var f = new sinaSSOEncoder.RSAKey;
f.setPublic(rsaPubkey, "10001");
b = f.encrypt([servertime, nonce].join("\t") + "\n" + pass)
return b
}
2. 除了参数sp
,其他全部可以用Python直接实现,具体代码见下文。
PS. base64也可以用JS内的方法实现,写个调用函数就行了。
function b64(username) {
a = sinaSSOEncoder.base64.encode(encodeURIComponent(username));
return a
}
0x04 代码
# -*- encoding: utf-8 -*-
'''
@File : 0x05-weibo.com.py
home.php?mod=space&uid=116177 : 2019/12/12 18:17:26
@Author : 独孤孤独嘟咕噜犊子
home.php?mod=space&uid=59980 : 1.0
home.php?mod=space&uid=95579 : https://www.jianshu.com/u/6a4c6ef97be7
@Desc : 微博登录RSA+其他
'''
# start
import execjs
import requests
import time
import json
import re
import random
import base64
from urllib.parse import quote, unquote
# 初始化参数
HEADERS = {
'User-Agent':
'Mozilla/5.0 (Linux; Android 8.0; DUK-AL20 Build/HUAWEIDUK-AL20; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 MQQBrowser/6.2 TBS/044353 Mobile Safari/537.36 MicroMessenger/6.7.3.1360(0x26070333) NetType/WIFI Language/zh_CN Process/tools'
}
s = requests.Session()
s.headers.update(HEADERS)
loginURL = 'https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.19)'
# 登录参数
username = '15555556666'
password = 'ermao6@qq.com'
# 加载js
with open('js/0x05-weibo.com.js') as f: # 坑0x01 相对路径前面不带/,带/不报错但读不出数据
jscode = f.read()
ctx = execjs.compile(jscode) # execjs载入js代码
def get_key(username_b64):
url = 'https://login.sina.com.cn/sso/prelogin.php'
params = {
'entry': 'weibo',
'callback': 'sinaSSOController.preloginCallBack',
'su': quote(username_b64, encoding='utf-8'),
'rsakt': 'mod',
'checkpin': '1',
'client': 'ssologin.js(v1.4.19)',
'_': int(time.time() * 1000)
}
res = s.get(url, params=params).text
start = res.index('{')
res = res[start:-1]
json_res = json.loads(res)
# print(json_res)
return json_res
def get_verify(p):
url = 'https://login.sina.com.cn/cgi/pin.php?s=0&p=' + p
image = s.get(url).content
ocr = 'http://127.0.0.1:678' # 本地验证码识别接口
code = s.post(ocr, data=image).text
with open('js/image/' + code + '.png', 'wb') as f:
f.write(image)
return code
def login(username, password):
# 通过base64库实现
# username_b64 = str(base64.b64encode(username.encode('utf-8')), encoding='utf-8')
# 通过js内函数实现
username_b64 = ctx.call('b64', username)
key = get_key(username_b64)
imageCode = get_verify(key['pcid'])
servertime = key['servertime'] + random.randint(2, 6) * 2
# 通过call调用js代码里的函数
enPass = ctx.call('test', password, servertime, key['nonce'])
data = {
'entry': 'weibo',
'gateway': '1',
'from': '',
'savestate': '7',
'qrcode_flag': 'false',
'useticket': '1',
'pagerefer': '',
'pcid': key['pcid'],
'door': imageCode,
'vsnf': '1',
'su': username_b64,
'service': 'miniblog',
'servertime': servertime,
'nonce': key['nonce'],
'pwencode': 'rsa2',
'rsakv': key['rsakv'],
'sp': enPass,
'sr': '1536*864',
'encoding': 'UTF-8',
'prelt': random.randint(21, 45) * 2,
'url':
'https://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack',
'returntype': 'META'
}
result = s.post(loginURL, data=data).text
if '&sign=' in result: # 登录成功
# 取下一个要跳转才能实现登录的URL
success = re.findall(r'url='(.*?)'', result)
if success:
res = s.get(success[0]).text
# 取下一个要跳转才能实现登录的URL
next_url = re.findall(r"location.replace\('(.*?)'\)", res)[0]
s.get(next_url)
userid_url = 'https://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack&sudaref=weibo.com'
# 登录成功,取userID
res = s.get(userid_url).text
start = res.index('{')
end = res.index(')')
res = res[start:end]
json_res = json.loads(res)
uid = json_res['userinfo']['uniqueid']
print('登录成功 => UID:' + uid)
else: # 登录失败
reason = re.findall(r'&reason=(.*?)'', result)
if reason:
print(unquote(reason[0], encoding='gbk'))
# print(result)
return result
if __name__ == "__main__":
login(username, password)