【JS逆向】新浪微博登錄RSA+其他 | 每日JS

已遷移平臺:segmentfault,搜索 erma0
換平臺了,簡書發(fā)什么都鎖定,廣告一堆,趁早倒閉吧。

第六天

JavaScript逆向練習6

0x01 目標網(wǎng)址

http://weibo.com

0x02 定位JS

1. 隨便輸入賬號密碼驗證碼,如果是不存在的賬號,會直接提示密碼錯誤,如果是是存在的賬號,會提示輸入驗證碼,,這里假設(shè)是帶驗證碼的,點擊登錄后,查看提交的參數(shù),可以看到,在登錄包里有許多看不懂的加參數(shù),一個個來分析。

1.png

2. 分析下參數(shù)

參數(shù) 來源
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 固定

其中上次返回是在登陸之前的一次請求中返回的內(nèi)容

2.png

其他的JS計算也不全是加密,有的只是單純計算的值,下面一個個找。

3. Ctrl+Shift+F調(diào)出搜索面板,本著越生僻越好搜的原則,先搜索prelt,看到只有一個js結(jié)果。

點進第一個結(jié)果,點花括號格式化一下,Ctrl+Fprelt,只有兩個結(jié)果,而且第一個returntype: "TEXT"明顯不是要找的,所以第二個上面下斷點。

3.png

同時觀察斷點處,發(fā)現(xiàn)f是在上面由makeRequest定義的,所以這里先搜一下看看makeRequest的定義在哪。

4.png

直接找到了加密處,就是makeRequest,可以看到還有其他參數(shù)也是在這里生成的,包括固定的一些。在782行這里也下個斷點。

4. 重新輸入賬號密碼驗證碼,點擊登錄,JS被斷了下來。

5.png

直接先把su給扣下來:

e.su = sinaSSOEncoder.base64.encode(urlencode(a));

可以看到參數(shù)a是賬號,跟進urlencode發(fā)現(xiàn)只是一個簡單的encodeURIComponent

urlencode = function(a) {
   return encodeURIComponent(a)
}

再找sinaSSOEncoder,直接Ctrl+F搜索,找到定義處:

var sinaSSOEncoder = sinaSSOEncoder || {};

這個語句意思是如果sinaSSOEncoder不為空(即轉(zhuǎn)為bool類型不為false),則把sinaSSOEncoder賦值給前面的變量(也是sinaSSOEncoder),如果sinaSSOEncoder為空,則把后面的{}賦值給前面的sinaSSOEncoder,所以這里首先生成了空對象。

所以再找定義處,往下翻一下,看到有兩個函數(shù)調(diào)用了.call(sinaSSOEncoder);,這就是加密對象的定義了,兩個函數(shù)全部拿走。

繼續(xù)往下運行,發(fā)現(xiàn)對servertime進行了賦值,那就搜一下后面的.servertime =(前面不帶對象名,但帶上.,后面帶上空格和等于號),可以快速定位。

6.png

這里看到,其實就是一個a循環(huán)加2的意思,在這里下個斷點,斷下來之后可以從右側(cè)Call Stack里看到參數(shù)a的來源,找到調(diào)用處發(fā)現(xiàn)就是上一次請求返回的servertime,也就是說,參數(shù)servertime就是上次返回的servertime循環(huán)加2,所以這里可以直接在外部語言實現(xiàn)。

下一個參數(shù)是sp,在上面的加密處已經(jīng)看到了sp,大致看到是一個RSA,而且RSA對象的定義已經(jīng)被扣下來過了,這里沒啥要做的了,只是加密的參數(shù)要注意看下,一個是上次返回的nonce,一個是計算完的servertime,還有一個是明文密碼,再加上其他字符組合成了加密參數(shù)。

7.png

最后一個參數(shù)是prelt,這個參數(shù)位置在一開始第一次下斷點的那里:

f.prelt = preloginTime;

直接搜preloginTime,可以定位到269行:

8.png

這段意思是prelt就是上一次請求的回調(diào)函數(shù)中new Date減去上一次請求的起始new Date再減去請求返回的exectime,也就是等于上一次請求的本地總耗時減去云端耗時。

5. 參數(shù)找完了,到這里已經(jīng)可以開始改寫了。

參數(shù)列表如下:

參數(shù) 來源
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 以上次返回的為基數(shù),每次+2(從上次請求到提交登錄的時間差除以2取整,一般為4~10)
nonce NLUYC0 上次返回
pwencode rsa2 固定
rsakv 1330428213 上次返回
sp 2395de422727c06ce JS計算 RSA加密
sr 1536*864 固定 屏幕寬*高
encoding UTF-8 固定
prelt 82 可隨機,約等于上一次請求的本地耗時減去云端耗時,上一次請求的回調(diào)函數(shù)中new Date-上一次請求的起始new Date-請求返回的exectime
url https://weibo.co 固定
returntype META 固定

0x03 改寫JS

1. 先把調(diào)用函數(shù)拿過來。

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. 除了參數(shù)sp,其他全部可以用Python直接實現(xiàn),具體代碼見下文。

PS. base64也可以用JS內(nèi)的方法實現(xiàn),寫個調(diào)用函數(shù)就行了。

function b64(username) {
    a = sinaSSOEncoder.base64.encode(encodeURIComponent(username));
    return a
}

0x04 代碼

# -*- encoding: utf-8 -*-
'''
@File    :   0x05-weibo.com.py
@Time    :   2019/12/12 18:17:26
@Author  :   獨孤孤獨嘟咕嚕犢子
@Version :   1.0
@Link    :   http://m.itdecent.cn/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

# 初始化參數(shù)
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)'

# 登錄參數(shù)
username = '15555556666'
password = 'ermao6@qq.com'

# 加載js
with open('js/0x05-weibo.com.js') as f:  # 坑0x01 相對路徑前面不帶/,帶/不報錯但讀不出數(shù)據(jù)
    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庫實現(xiàn)
    # username_b64 = str(base64.b64encode(username.encode('utf-8')), encoding='utf-8')

    # 通過js內(nèi)函數(shù)實現(xiàn)
    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調(diào)用js代碼里的函數(shù)
    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:  # 登錄成功
        # 取下一個要跳轉(zhuǎn)才能實現(xiàn)登錄的URL
        success = re.findall(r'url='(.*?)'', result)
        if success:
            res = s.get(success[0]).text
            # 取下一個要跳轉(zhuǎn)才能實現(xiàn)登錄的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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容