<menu id="ycqsw"></menu><nav id="ycqsw"><code id="ycqsw"></code></nav>
<dd id="ycqsw"><menu id="ycqsw"></menu></dd>
  • <nav id="ycqsw"></nav>
    <menu id="ycqsw"><strong id="ycqsw"></strong></menu>
    <xmp id="ycqsw"><nav id="ycqsw"></nav>
  • 重新加載js文件(頁面重新加載JS方法)


    聲明

    本文章中所有內容僅供學習交流,抓包內容、敏感網址、數據接口均已做脫敏處理,嚴禁用于商業用途和非法用途,否則由此產生的一切后果均與作者無關,若有侵權,請聯系我立即刪除!

    逆向目標

    本次的逆向目標是WB的登錄,雖然登錄的加密參數沒有太多,但是登錄的流程稍微復雜一點,經歷了很多次中轉,細分下來大約要經過九次處理才能成功登錄。

    在登錄過程中遇到的加密參數只有一個,即密碼加密,加密后的密碼在獲取 token 的時候會用到,獲取 token 是一個 POST 請求,其 Form Data 里的 sp 值就是加密后的密碼,類似于:
    e23c5d62dbf9f8364005f331e487873c70d7ab0e8dd2057c3e66d1ae5d2837ef1dcf86……

    登錄流程

    首先來理清一下登錄流程,每一步特殊的參數進都行了說明,沒有提及的參數表示是定值,直接復制即可。

    大致流程如下:

    1. 預登陸
    2. 獲取加密密碼
    3. 獲取 token
    4. 獲取加密后的賬號
    5. 發送驗證碼
    6. 校驗驗證碼
    7. 訪問 redirect url
    8. 訪問 crossdomain2 url
    9. 通過 passport url 登錄

    1.預登陸

    「JS 逆向百例」復雜的登錄過程,最新WB逆向

    預登陸為 GET 請求,Query String Parameters 中主要包含兩個比較重要的參數:su:用戶名經過 base64 編碼得到,_: 13 位時間戳,返回的數據包含一個 JSON,可用正則提取出來,JSON 里面包含 retcode,servertime,pcid,nonce,pubkey,rsakv, exectime 七個參數值,其中大多數值都是后面的請求當中要用到的,部分值是加密密碼要用到的,返回數據數示例:

    xxxxSSOController.preloginCallBack({
        "retcode": 0,
        "servertime": 1627461942,
        "pcid": "gz-1cd535198c0efe850b96944c7945e8fd514b",
        "nonce": "GWBOCL",
        "pubkey": "EB2A38568661887FA180BDDB5CABD5F21C7BFD59C090CB2D245......",
        "rsakv": 1330428213,
        "exectime": 16
    })
    

    2.獲取加密后的密碼

    密碼的加密使用的是 RSA 加密,可以通過 Python 或者 JS 來獲取加密后的密碼,JS 加密的逆向在后面拿出來單獨分析。

    3.獲取 token

    「JS 逆向百例」復雜的登錄過程,最新WB逆向

    這個 token 值在后面的獲取加密手機號、發送驗證碼、校驗驗證碼等步驟中都會用到,獲取 token 值為 POST 請求,Query String Parameters 的值是固定的:client: ssologin.js(v1.4.19),Form Data 的值相對來說比較多,但是除了加密的密碼以外,其他參數其實都是可以在第1步預登陸返回的數據里找到,主要的參數如下:

    • su:用戶名經過 base64 加密得到
    • servertime:通過第1步預登陸返回的 JSON 里面獲取
    • nonce:通過第1步預登陸返回的 JSON 里面獲取
    • rsakv:通過第1步預登陸返回的 JSON 里面獲取
    • sp:加密后的密碼
    • prelt:隨機值

    返回數據為 HTML 源碼,可以從里面提取 token 值,類似于:
    2NGFhARzFAFAIp_QwX70Npj8gw4lgj7RbCnByb3RlY3Rpb24.,如果返回的 token 不是這種,則說明賬號或者密碼錯誤。

    4.獲取加密后的賬號

    「JS 逆向百例」復雜的登錄過程,最新WB逆向

    前面我們遇到的 su 是用戶名經過 base64 加密得到,這里它對用戶名進行了進一步的加密處理,加密后的用戶名在發送驗證碼和校驗驗證碼的時候會用到,GET 請求,Query String Parameters 的參數也比較簡單,token 就是第3步獲取的 token 值,callback_url 是網站的主頁,返回數據是 HTML 源碼,可以使用 xpath 語法://input[@name=’encrypt_mobile’]/@value 來提取加密后的賬號,其值類似于:f2de0b5e333a,這里需要注意的是,即便是同一個賬號,每次加密的結果也是不一樣的。

    5.發送驗證碼

    「JS 逆向百例」復雜的登錄過程,最新WB逆向

    發送驗證碼是一個 POST 請求,其參數也比較簡單,Query String Parameters 里的 token 是第3步獲取的 token,Form Data 里的 encrypt_mobile 是第4步獲取的加密后的賬號,返回的數據是驗證碼發送的狀態,例如:{‘retcode’: 20000000, ‘msg’: ‘succ’, ‘data’: []}。

    6.校驗驗證碼

    「JS 逆向百例」復雜的登錄過程,最新WB逆向

    校驗驗證碼是一個 POST 請求,其參數也非常簡單,Query String Parameters 里的 token 是第3步獲取的 token,Form Data 里的 encrypt_mobile 是第4步獲取的加密后的賬號,code 是第5步收到的驗證碼,返回數據是一個 JSON,retcode 和 msg 代表校驗的狀態,redirect url 是校驗步驟完成后接著要訪問的頁面,在下一步中要用到,返回的數據示例:

    {
      "retcode": 20000000,
      "msg": "succ",
      "data": {
        "redirect_url": "https://login.xxxx.com.cn/sso/login.php?entry=xxxxx&returntype=META&crossdomain=1&cdult=3&alt=ALT-NTcxNjMyMTA2OA==-1630292617-yf-78B1DDE6833847576B0DC4B77A6C77C4-1&savestate=30&url=https://xxxxx.com"
      }
    }
    

    7.訪問 redirect url

    「JS 逆向百例」復雜的登錄過程,最新WB逆向

    這一步的請求接口其實就是第6步返回的 redirect url,GET 請求,類似于:
    https://login.xxxx.com.cn/sso/login.php?entry=xxxxx&returntype=META……

    返回的數據是 HTML 源碼,我們要從中提取 crossdomain2 的 URL,提取的結果類似于:
    https://login.xxxx.com.cn/crossdomain2.php?action=login&entry=xxxxx……,同樣的,這個 URL 也是接下來需要訪問的頁面。

    8.訪問 crossdomain2 url

    「JS 逆向百例」復雜的登錄過程,最新WB逆向

    這一步的請求接口就是第7步提取的 crossdomain2 url,GET 請求,類似于:
    https://login.xxxx.com.cn/crossdomain2.php?action=login&entry=xxxxx……

    返回的數據同樣是 HTML 源碼,我們要從中提取真正的登錄的 URL,提取的結果類似于:
    https://passport.xxxxx.com/wbsso/login?ssosavestate=1661828618&url=https……,最后一步只需要訪問這個真正的登錄 URL 就能實現登錄操作了。

    9.通過 passport url 登錄

    「JS 逆向百例」復雜的登錄過程,最新WB逆向

    這是最后一步,也是真正的登錄操作,GET 請求,請求接口就是第8步提取的 passport url,類似于:
    https://passport.xxxxx.com/wbsso/login?ssosavestate=1661828618&url=https……

    返回的數據包含了登錄結果、用戶 ID 和用戶名,類似于:

    ({"result":true,"userinfo":{"uniqueid":"5712321368","displayname":"tomb"}});
    

    自此,WB的完整登錄流程已完成,可以直接拿登錄成功后的 cookies 進行其他操作了。

    加密密碼逆向

    在登錄流程中,第2步是獲取加密后的密碼,在登錄的第3步獲取 token 里,請求的 Query String Parameters 包含了一個加密參數 sp,這個就是加密后的密碼,接下來我們對密碼的加密進行逆向分析。

    直接全局搜索 sp 關鍵字,發現有很多值,這里我們又用到了前面講過的技巧,嘗試搜索 sp=、sp: 或者 var sp 等來縮小范圍,在本案例中,我們嘗試搜索 sp=,可以看到在 index.js 里面只有一個值,埋下斷點進行調試,可以看到 sp 其實就是 b 的值:

    PS:搜索時要注意,不能在登錄成功后的頁面進行搜索,此時資源已刷新,重新加載了,加密的 JS 文件已經沒有了,需要在登錄界面輸入錯誤的賬號密碼來抓包、搜索、斷點。

    「JS 逆向百例」復雜的登錄過程,最新WB逆向

    繼續往上追蹤這個 b 的值,關鍵代碼有個 if-else 語句,分別埋下斷點,經過調試可以看到 b 的值在 if 下面生成:

    「JS 逆向百例」復雜的登錄過程,最新WB逆向

    分析一下兩行關鍵代碼:

    f.setPublic(me.rsaPubkey, "10001");
    b = f.encrypt([me.servertime, me.nonce].join("t") + "n" + b)

    me.rsaPubkey、me.servertime、me.nonce 都是第1步預登陸返回的數據。

    把鼠標移到 f.setPublic 和 f.encrypt,可以看到分別是 br 和 bt 函數:

    「JS 逆向百例」復雜的登錄過程,最新WB逆向
    「JS 逆向百例」復雜的登錄過程,最新WB逆向

    分別跟進這兩個函數,可以看到都在一個匿名函數下面:

    「JS 逆向百例」復雜的登錄過程,最新WB逆向

    直接將整個匿名函數復制下來,去掉最外面的匿名函數,進行本地調試,調試過程中會提示 navigator 未定義,查看復制的源碼,里面用到了 navigator.appName 和 navigator.appVersion,直接定義即可,或者置空都行。

    navigator = {
        appName: "Netscape",
        appVersion: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
    }

    繼續調試會發現在 var c = this.doPublic(b); 提示對象不支持此屬性或方法,搜索 doPublic 發現有一句 bq.prototype.doPublic = bs;,這里直接將其改為 doPublic = bs; 即可。

    分析整個 RSA 加密邏輯,其實也可以通過 Python 來實現,代碼示例(pubkey 需要補全):

    import rsa
    import binascii
    
    
    pre_parameter = {
            "retcode": 0,
            "servertime": 1627461942,
            "pcid": "gz-1cd535198c0efe850b96944c7945e8fd514b",
            "nonce": "GWBOCL",
            "pubkey": "EB2A38568661887FA180BDDB5CABD5F21C7BFD59C090CB2D245......",
            "rsakv": 1330428213,
            "exectime": 16
    }
    
    password = '12345678'
    
    public_key = rsa.PublicKey(int(pre_parameter['pubkey'], 16), int('10001', 16))
    text = '%st%sn%s' % (pre_parameter['servertime'], pre_parameter['nonce'], password)
    encrypted_str = rsa.encrypt(text.encode(), public_key)
    encrypted_password = binascii.b2a_hex(encrypted_str).decode()
    
    print(encrypted_password)

    完整代碼

    GitHub 關注 K 哥爬蟲,持續分享爬蟲相關代碼!歡迎 star !
    https://github.com/kgepachong/

    **以下只演示部分關鍵代碼,不能直接運行!**完整代碼倉庫地址:
    https://github.com/kgepachong/crawler/

    關鍵 JS 加密代碼架構

    navigator = {
        appName: "Netscape",
        appVersion: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
    }
    
    function bt(a) {}
    
    function bs(a) {}
    
    function br(a, b) {}
    
    // 此處省略 N 個函數
    
    bl.prototype.nextBytes = bk;
    doPublic = bs;
    bq.prototype.setPublic = br;
    bq.prototype.encrypt = bt;
    this.RSAKey = bq
    
    
    function getEncryptedPassword(me, b) {
        br(me.pubkey, "10001");
        b = bt([me.servertime, me.nonce].join("t") + "n" + b);
        return b
    }
    
    // 測試樣例
    // var me = {
    //     "retcode": 0,
    //     "servertime": 1627283238,
    //     "pcid": "gz-a9243276722ed6d4671f21310e2665c92ba4",
    //     "nonce": "N0Y3SZ",
    //     "pubkey": "EB2A38568661887FA180BDDB5CABD5F21C7BFD59C090CB2D245A87AC253062882729293E5506350508E7F9AA3BB77F4333231490F915F6D63C55FE2F08A49B353F444AD3993CACC02DB784ABBB8E42A9B1BBFFFB38BE18D78E87A0E41B9B8F73A928EE0CCEE1F6739884B9777E4FE9E88A1BBE495927AC4A799B3181D6442443",
    //     "rsakv": "1330428213",
    //     "exectime": 13
    // }
    // var b = '12312312312'  // 密碼
    // console.log(getEncryptedPassword(me, b))

    Python 登錄關鍵代碼

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    
    import re
    import json
    import time
    import base64
    import binascii
    
    import rsa
    import execjs
    import requests
    from lxml import etree
    
    
    # 判斷某些請求是否成功的標志
    response_success_str = 'succ'
    
    pre_login_url = '脫敏處理,完整代碼關注 GitHub:https://github.com/kgepachong/crawler'
    get_token_url = '脫敏處理,完整代碼關注 GitHub:https://github.com/kgepachong/crawler'
    protection_url = '脫敏處理,完整代碼關注 GitHub:https://github.com/kgepachong/crawler'
    send_code_url = '脫敏處理,完整代碼關注 GitHub:https://github.com/kgepachong/crawler'
    confirm_url = '脫敏處理,完整代碼關注 GitHub:https://github.com/kgepachong/crawler'
    
    headers = {
        'Host': '脫敏處理,完整代碼關注 GitHub:https://github.com/kgepachong/crawler',
        'Referer': '脫敏處理,完整代碼關注 GitHub:https://github.com/kgepachong/crawler',
        'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
    }
    session = requests.session()
    
    
    def get_pre_parameter(username: str) -> dict:
        su = base64.b64encode(username.encode())
        time_now = str(int(time.time() * 1000))
        params = {
            'entry': '脫敏處理,完整代碼關注 GitHub:https://github.com/kgepachong/crawler',
            'callback': '脫敏處理,完整代碼關注 GitHub:https://github.com/kgepachong/crawler',
            'su': su,
            'rsakt': 'mod',
            'checkpin': 1,
            'client': 'ssologin.js(v1.4.19)',
            '_': time_now,
        }
        response = session.get(url=pre_login_url, params=params, headers=headers).text
        parameter_dict = json.loads(re.findall(r'((.*))', response)[0])
        # print('1.【pre parameter】: %s' % parameter_dict)
        return parameter_dict
    
    
    def get_encrypted_password(pre_parameter: dict, password: str) -> str:
        # 通過 JS 獲取加密后的密碼
        # with open('encrypt.js', 'r', encoding='utf-8') as f:
        #     js = f.read()
        # encrypted_password = execjs.compile(js).call('getEncryptedPassword', pre_parameter, password)
        # # print('2.【encrypted password】: %s' % encrypted_password)
        # return encrypted_password
    
        # 通過 Python 的 rsa 模塊和 binascii 模塊獲取加密后的密碼
        public_key = rsa.PublicKey(int(pre_parameter['pubkey'], 16), int('10001', 16))
        text = '%st%sn%s' % (pre_parameter['servertime'], pre_parameter['nonce'], password)
        encrypted_str = rsa.encrypt(text.encode(), public_key)
        encrypted_password = binascii.b2a_hex(encrypted_str).decode()
        # print('2.【encrypted password】: %s' % encrypted_password)
        return encrypted_password
    
    
    def get_token(encrypted_password: str, pre_parameter: dict, username: str) -> str:
        su = base64.b64encode(username.encode())
        data = {
            'entry': '脫敏處理,完整代碼關注 GitHub:https://github.com/kgepachong/crawler',
            'gateway': 1,
            'from': '',
            'savestate': 7,
            'qrcode_flag': False,
            'useticket': 1,
            'pagerefer': '',
            'vsnf': 1,
            'su': su,
            'service': 'miniblog',
            'servertime': pre_parameter['servertime'],
            'nonce': pre_parameter['nonce'],
            'pwencode': 'rsa2',
            'rsakv': pre_parameter['rsakv'],
            'sp': encrypted_password,
            'sr': '1920*1080',
            'encoding': 'UTF-8',
            'prelt': 38,
            'url': '脫敏處理,完整代碼關注 GitHub:https://github.com/kgepachong/crawler',
            'returntype': 'META'
        }
        response = session.post(url=get_token_url, headers=headers, data=data)
        # response.encoding = 'gbk'
        ajax_login_url = re.findall(r'replace("(.*)")', response.text)[0]
        token = ajax_login_url.split('token%3D')[-1]
        if 'weibo' not in token:
            # print('3.【token】: %s' % token)
            return token
        else:
            raise Exception('登錄失敗! 用戶名或者密碼錯誤!')
    
    
    def get_encrypted_mobile(token: str) -> str:
        params = {
            'token': token,
            'callback_url': '脫敏處理,完整代碼關注 GitHub:https://github.com/kgepachong/crawler'
        }
        response = session.get(url=protection_url, params=params, headers=headers)
        tree = etree.HTML(response.text)
        encrypted_mobile = tree.xpath("http://input[@name='encrypt_mobile']/@value")[0]
        # print('4.【encrypted mobile】: %s' % encrypted_mobile)
        return encrypted_mobile
    
    
    def send_code(token: str, encrypt_mobile: str) -> str:
        params = {'token': token}
        data = {'encrypt_mobile': encrypt_mobile}
        response = session.post(url=send_code_url, params=params, data=data, headers=headers).json()
        if response['msg'] == response_success_str:
            code = input('請輸入驗證碼: ')
            # print('5.【code】: %s' % code)
            return code
        else:
            # print('5.【failed to send verification code】: %s' % response)
            raise Exception('驗證碼發送失敗: %s' % response)
    
    
    def confirm_code(encrypted_mobile: str, code: str, token: str) -> str:
        params = {'token': token}
        data = {
            'encrypt_mobile': encrypted_mobile,
            'code': code
        }
        response = session.post(url=confirm_url, params=params, data=data, headers=headers).json()
        if response['msg'] == response_success_str:
            redirect_url = response['data']['redirect_url']
            # print('6.【redirect url】: %s' % redirect_url)
            return redirect_url
        else:
            # print('6.【驗證碼校驗失敗】: %s' % response)
            raise Exception('驗證碼校驗失敗: %s' % response)
    
    
    def get_cross_domain2_url(redirect_url: str) -> str:
        response = session.get(url=redirect_url, headers=headers).text
        cross_domain2_url = re.findall(r'replace("(.*)")', response)[0]
        # print('7.【cross domain2 url】: %s' % cross_domain2_url)
        return cross_domain2_url
    
    
    def get_passport_url(cross_domain2_url: str) -> str:
        response = session.get(url=cross_domain2_url, headers=headers).text
        passport_url_str = re.findall(r'setCrossDomainUrlList((.*))', response)[0]
        passport_url = json.loads(passport_url_str)['arrURL'][0]
        # print('8.【passport url】: %s' % passport_url)
        return passport_url
    
    
    def login(passport_url: str) -> None:
        response = session.get(url=passport_url, headers=headers).text
        login_result = json.loads(response.replace('(', '').replace(');', ''))
        if login_result['result']:
            user_unique_id = login_result['userinfo']['uniqueid']
            user_display_name = login_result['userinfo']['displayname']
            print('登錄成功!用戶 ID:%s,用戶名:%s' % (user_unique_id, user_display_name))
        else:
            raise Exception('登錄失?。?s' % login_result)
    
    
    def main():
        username = input('請輸入登錄賬號: ')
        password = input('請輸入登錄密碼: ')
    
        # 1.預登陸,獲取一個字典參數,包含后面要用的 servertime、nonce、pubkey、rsakv
        pre_parameter = get_pre_parameter(username)
    
        # 2.通過 JS 或者 Python 獲取加密后的密碼
        encrypted_password = get_encrypted_password(pre_parameter, password)
    
        # 3.獲取 token
        token = get_token(encrypted_password, pre_parameter, username)
    
        # 4.通過 protection url 獲取加密后的手機號
        encrypted_mobile = get_encrypted_mobile(token)
    
        # 5.發送手機驗證碼
        code = send_code(token, encrypted_mobile)
    
        # 6.校驗驗證碼,校驗成功則返回一個重定向的 URL
        redirect_url = confirm_code(encrypted_mobile, code, token)
    
        # 7.訪問重定向的 URL,提取 crossdomain2 URL
        cross_domain2_url = get_cross_domain2_url(redirect_url)
    
        # 8.訪問 crossdomain2 URL,提取 passport URL
        passport_url = get_passport_url(cross_domain2_url)
    
        # 9.訪問 passport URL 進行登錄操作
        login(passport_url)
    
    
    if __name__ == '__main__':
        main()

    版權聲明:本文內容由互聯網用戶自發貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如發現本站有涉嫌抄襲侵權/違法違規的內容, 請發送郵件至 舉報,一經查實,本站將立刻刪除。

    發表評論

    登錄后才能評論
    国产精品区一区二区免费