天天看點

【JS 逆向百例】層層嵌套!某加速商城 RSA 加密

【JS 逆向百例】層層嵌套!某加速商城 RSA 加密

聲明

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

逆向目标

  • 目标:某加速商城登入接口
  • 首頁:aHR0cDovL3d3dy4xNXl1bm1hbGwuY29tL3BjL2xvZ2luL2luZGV4
  • 接口:aHR0cDovL3d3dy4xNXl1bm1hbGwuY29tL3BjL2xvZ2luL2NoZWNr
  • 逆向參數:

    Cookie:

    PHPSESSID=g9jp7sfpukg99v03gj69nr9r56           
    Form Data:
    u[password]: 7aad70a547b016a07f2e20bee7b4936111e650aa5c419fafdfb28322......
    _csrfToken: 4bea63330c5ccdd37588321d027f4c40129687b0           

逆向過程

抓包分析

在首頁點選登陸,來到登入頁面,随便輸入一個賬号密碼登陸,抓包定位到登入接口為 aHR0cDovL3d3dy4xNXl1bm1hbGwuY29tL3BjL2xvZ2luL2NoZWNr ,POST 請求,Form Data 裡,密碼

u[password]

被加密處理了,此外還有一個

_csrfToken

也是需要我們解決的,cookie 裡面有一個

PHPSESSID

,經過測試,如果不帶此參數,最終的請求也是失敗的。

【JS 逆向百例】層層嵌套!某加速商城 RSA 加密

參數逆向

首先看看

_csrfToken

,先嘗試直接搜尋一下它的值,可以發現其實在首頁的源碼裡面就有,直接比對拿過來即可:

【JS 逆向百例】層層嵌套!某加速商城 RSA 加密

再看一下 cookie 裡面的

PHPSESSID

,首先想到的,可能是第一次通路頁面,Response Headers 傳回的 Set-Cookie 得到的,檢視第一次請求,确實是的,如果沒有的話,需要清除緩存再通路(開發者工具 ——> Application ——> Storage ——> Clear site data)。

【JS 逆向百例】層層嵌套!某加速商城 RSA 加密

最後一個密碼參數

u[password]

,肯定是通過 JS 加密得到的,直接 Ctrl+Shift+F 全局搜尋,可以直接在 index 首頁找到 RSA 加密的地方,埋下斷點進行調試,最後的 res 正是加密後的密碼:

【JS 逆向百例】層層嵌套!某加速商城 RSA 加密

我們将這段關鍵代碼進行改寫,封裝成一個函數:

function getEncryptedPassword(password) {
    var public_key = "00bdf3db924714b9c4ddd144910071c282e235ac51371037cf89fa08f28b9105b6326338ed211280154c645bf81bae4184c2b52e2b02b0953e7aa8b25a8e212a0b";
    var public_length = "10001";
    var rsa = new RSAKey();
    rsa.setPublic(public_key, public_length);
    return rsa.encrypt(password);
}           

這裡主要用到的三個函數

RSAKey()

setPublic()

encrypt()

,在開發者工具中,滑鼠放到函數上,可以看到這裡都是調用的 rsa.js 裡面的方法,我們直接将整個檔案剝離下來進行本地調試:

【JS 逆向百例】層層嵌套!某加速商城 RSA 加密

本地調試會發現提示

BigInteger

未定義,滑鼠移到這個函數上面,可以發現是調用了 jsbn.js 裡面的方法,同樣的,直接将整個 jsbn.js 檔案剝離下來進行本地調試。

這裡其實在 rsa.js 檔案的第一行有一句注釋:

// Depends on jsbn.js and rng.js

,我們可以猜測 rsa.js 是可能依賴 jsbn.js 和 rng.js 這兩個檔案的。

【JS 逆向百例】層層嵌套!某加速商城 RSA 加密
【JS 逆向百例】層層嵌套!某加速商城 RSA 加密

有了 jsbn.js 的代碼,再次進行調試,會發現又提示

navigator

SecureRandom

未定義,

navigator

我們已經非常熟悉了,是浏覽器的相關資訊,一般情況下直接定義為空即可(

navigator = {};

);将滑鼠移到

SecureRandom

函數上面,可以發現是調用了 rng.js 裡面的方法,同樣的,直接将整個 rng.js 檔案剝離下來進行本地調試。這裡就證明了前面我們的猜想,rsa.js 确實是依賴 jsbn.js 和 rng.js 的。

我們注意到,這裡在 rng.js 檔案的第一行,同樣有一句注釋:

// Random number generator - requires a PRNG backend, e.g. prng4.js

,表明 rng.js 是随機數生成器,需要 PRNG 後端,例如 prng4.js,在密碼學中,PRNG 全稱是 pseudorandom number generator,即僞随機數生成器,是指通過特定算法生成一系列的數字,使得這一系列的數字看起來是随機的,但是實際是确定的,是以叫僞随機數,感興趣的朋友可以深入研究一下,在這裡我們知道 rng.js 可能還依賴于 prng4.js,需要進一步調試才清楚。

【JS 逆向百例】層層嵌套!某加速商城 RSA 加密
【JS 逆向百例】層層嵌套!某加速商城 RSA 加密

rsa.js、jsbn.js、rng.js 都齊全了,再次本地調試,會發現 rng.js 裡面的

rng_psize

未定義,滑鼠放上去看到

rng_psize

就是一個定值 256,在右邊的 Global 全局變量裡也可以看到值為 256,嘗試搜尋一下

rng_psize

,可以發現在 prng4.js 裡面有定義

var rng_psize = 256;

,果然和注釋說得一樣,rng.js 是依賴 prng4.js 的,但是這裡似乎直接定義一下

rng_psize

就行了。

【JS 逆向百例】層層嵌套!某加速商城 RSA 加密

直接在本地代碼定義一下

var rng_psize = 256;

,再次進行調試,此時又會提示 rng.js 裡缺少

prng_newstate()

對象,再次回到開發者工具,可以看到

prng_newstate()

是 prng4.js 裡面的方法,果然 rng.js 和 prng4.js 的關系并不簡單,同樣的,我們也直接将整個 prng4.js 檔案剝離下來進行本地調試。

【JS 逆向百例】層層嵌套!某加速商城 RSA 加密
【JS 逆向百例】層層嵌套!某加速商城 RSA 加密

再次調試,運作無誤,可以成功拿到加密後的密碼了:

【JS 逆向百例】層層嵌套!某加速商城 RSA 加密

邏輯總結

  1. 加密入口可以在 index 首頁找到,用到了 rsa.js 裡面的三個加密函數

    RSAKey()

    setPublic()

    encrypt()

  2. rsa.js 裡的

    BigInteger()

    函數依賴 jsbn.js,

    SecureRandom()

    函數依賴 rng.js;
  3. rng.js 裡的變量

    rng_psize

    在 prng4.js 中定義,

    prng_newstate()

    函數也依賴 prng4.js;

要将 rsa.js、jsbn.js、rng.js、prng4.js 這四個 JS 加密檔案完整的剝離下來才能還原整個加密過程。

完整代碼

GitHub 關注 K 哥爬蟲:

https://github.com/kuaidaili

,持續分享爬蟲相關代碼!歡迎 star !

以下隻示範部分關鍵代碼,完整代碼倉庫位址:

https://github.com/kuaidaili/crawler/

參數 JS 加密關鍵代碼

navigator = {};

// ================== prng4.js begin ================== //

function Arcfour() {}

function ARC4init(key) {}

function ARC4next() {}

// 此處省略 N 個函數

var rng_psize = 256;

// ================== prng4.js end ================== //

// ================== rng.js begin ================== //

var rng_state;
var rng_pool;
var rng_pptr;

function rng_seed_int(x) {}

function rng_seed_time() {}

// 此處省略 N 個函數

function SecureRandom() {}

SecureRandom.prototype.nextBytes = rng_get_bytes;

// ================== rng.js end ================== //

// ================== jsbn.js begin ================== //

var dbits;

var canary = 0xdeadbeefcafe;
var j_lm = ((canary & 0xffffff) == 0xefcafe);

function BigInteger(a, b, c) {}

function nbi() {}

// 此處省略 N 個函數

// protected
BigInteger.prototype.copyTo = bnpCopyTo;
BigInteger.prototype.fromInt = bnpFromInt;
BigInteger.prototype.fromString = bnpFromString;
BigInteger.prototype.clamp = bnpClamp;
BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
BigInteger.prototype.drShiftTo = bnpDRShiftTo;
BigInteger.prototype.lShiftTo = bnpLShiftTo;
BigInteger.prototype.rShiftTo = bnpRShiftTo;
BigInteger.prototype.subTo = bnpSubTo;
BigInteger.prototype.multiplyTo = bnpMultiplyTo;
BigInteger.prototype.squareTo = bnpSquareTo;
BigInteger.prototype.divRemTo = bnpDivRemTo;
BigInteger.prototype.invDigit = bnpInvDigit;
BigInteger.prototype.isEven = bnpIsEven;
BigInteger.prototype.exp = bnpExp;

// public
BigInteger.prototype.toString = bnToString;
BigInteger.prototype.negate = bnNegate;
BigInteger.prototype.abs = bnAbs;
BigInteger.prototype.compareTo = bnCompareTo;
BigInteger.prototype.bitLength = bnBitLength;
BigInteger.prototype.mod = bnMod;
BigInteger.prototype.modPowInt = bnModPowInt;

// "constants"
BigInteger.ZERO = nbv(0);
BigInteger.ONE = nbv(1);

// ================== jsbn.js end ================== //

// ================== rsa.js begin ================== //

function parseBigInt(str, r) {}

function linebrk(s, n) {}

function byte2Hex(b) {}

function pkcs1pad2(s, n) {}

function RSAKey() {}

function RSASetPublic(N, E) {}

function RSADoPublic(x) {}

function RSAEncrypt(text) {}

// protected
RSAKey.prototype.doPublic = RSADoPublic;

// public
RSAKey.prototype.setPublic = RSASetPublic;
RSAKey.prototype.encrypt = RSAEncrypt;
//RSAKey.prototype.encrypt_b64 = RSAEncryptB64;

// ================== rsa.js end ================== //

function getEncryptedPassword(password) {
    var public_key = "00bdf3db924714b9c4ddd144910071c282e235ac51371037cf89fa08f28b9105b6326338ed211280154c645bf81bae4184c2b52e2b02b0953e7aa8b25a8e212a0b";
    var public_length = "10001";
    var rsa = new RSAKey();
    rsa.setPublic(public_key, public_length);
    return rsa.encrypt(password);
}

// 測試樣例
console.log(getEncryptedPassword("123456"))           

Python 登入關鍵代碼

#!/usr/bin/env python3
# -*- coding: utf-8 -*-


import execjs
import requests

from lxml import etree
from PIL import Image


index_url = '脫敏處理,完整代碼關注 GitHub:https://github.com/kuaidaili/crawler/'
login_url = '脫敏處理,完整代碼關注 GitHub:https://github.com/kuaidaili/crawler/'
code_url = '脫敏處理,完整代碼關注 GitHub:https://github.com/kuaidaili/crawler/'

headers = {
    'Host': '脫敏處理,完整代碼關注 GitHub:https://github.com/kuaidaili/crawler/',
    'Referer': '脫敏處理,完整代碼關注 GitHub:https://github.com/kuaidaili/crawler/',
    'Origin': '脫敏處理,完整代碼關注 GitHub:https://github.com/kuaidaili/crawler/',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'
}
session = requests.session()


def get_encrypted_password(password):
    with open('encrypt.js', 'r', encoding='utf-8') as f:
        yunmall_js = f.read()
    encrypted_password = execjs.compile(yunmall_js).call('getEncryptedPassword', password)
    return encrypted_password


def get_csrf_token_cookie():
    response = session.get(url=index_url, headers=headers)
    tree = etree.HTML(response.text)
    csrf_token = tree.xpath("//input[@name='_csrfToken']/@value")[0]
    cookies = response.cookies.get_dict()
    # print(csrf_token, cookies)
    return csrf_token, cookies


def get_very_code(cookies):
    response = session.get(url=code_url, cookies=cookies, headers=headers)
    with open('code.png', 'wb') as f:
        f.write(response.content)
    image = Image.open('code.png')
    image.show()
    very_code = input('請輸入驗證碼: ')
    return very_code


def login(csrf_token, very_code, cookies, username, encrypted_password):
    data = {
        'u[loginType]': 'name',
        'u[phone]': username,
        'u[password]': encrypted_password,
        'u[veryCode]': very_code,
        'u[token]': '',
        '_csrfToken': csrf_token
    }
    header_info = {
        'X-Requested-With': 'XMLHttpRequest',
    }
    headers.update(header_info)
    response = session.post(url=login_url, data=data, cookies=cookies, headers=headers)
    response.encoding = 'utf-8-sig'
    response_code = response.text
    # print(response_code)
    status_code = {
        '31': '恭喜,登陸成功。',
        '32': '登陸失敗。',
        '33': '使用者名或密碼錯誤。',
        '35': '該使用者已被管理者鎖定。',
        '311': '該賬号已綁定裝置,請在綁定的裝置登陸。',
        '20001': '驗證碼填寫錯誤!'
    }
    try:
        print(status_code[response_code])
    except KeyError:
        print('請求逾時!')


def main():
    username = input('請輸入登入賬号: ')
    password = input('請輸入登入密碼: ')
    if len(password) > 32:
        raise Exception('請輸入正确的密碼!')
    encrypted_password = get_encrypted_password(password)
    csrf_token, cookies = get_csrf_token_cookie()
    very_code = get_very_code(cookies)
    login(csrf_token, very_code, cookies, username, encrypted_password)


if __name__ == '__main__':
    main()