javascript
【JS 逆向百例】webpack 改写实战,G 某游戏 RSA 加密
關(guān)注微信公眾號(hào):K哥爬蟲,QQ交流群:808574309,持續(xù)分享爬蟲進(jìn)階、JS/安卓逆向等技術(shù)干貨!
聲明
本文章中所有內(nèi)容僅供學(xué)習(xí)交流,抓包內(nèi)容、敏感網(wǎng)址、數(shù)據(jù)接口均已做脫敏處理,嚴(yán)禁用于商業(yè)用途和非法用途,否則由此產(chǎn)生的一切后果均與作者無(wú)關(guān),若有侵權(quán),請(qǐng)聯(lián)系我立即刪除!
逆向目標(biāo)
- 目標(biāo):G某游戲登錄
- 主頁(yè):aHR0cHM6Ly93d3cuZ205OS5jb20v
- 接口:aHR0cHM6Ly9wYXNzcG9ydC5nbTk5LmNvbS9sb2dpbi9sb2dpbjM=
- 逆向參數(shù):
Query String Parameters:password: kRtqfg41ogc8btwGlEw6nWLg8cHcCW6R8JaeM......
逆向過(guò)程
抓包分析
來(lái)到首頁(yè),隨便輸入一個(gè)賬號(hào)密碼,點(diǎn)擊登陸,抓包定位到登錄接口為 aHR0cHM6Ly9wYXNzcG9ydC5nbTk5LmNvbS9sb2dpbi9sb2dpbjM=,GET 請(qǐng)求,Query String Parameters 里,密碼 password 被加密處理了。
加密入口
直接搜索關(guān)鍵字 password 會(huì)發(fā)現(xiàn)結(jié)果太多不好定位,使用 XHR 斷點(diǎn)比較容易定位到加密入口,有關(guān) XHR 斷點(diǎn)調(diào)試可以查看 K 哥往期的教程:【JS 逆向百例】XHR 斷點(diǎn)調(diào)試,Steam 登錄逆向,如下圖所示,在 home.min.js 里可以看到關(guān)鍵語(yǔ)句 a.encode(t.password, s),t.password 是明文密碼,s 是時(shí)間戳。
跟進(jìn) a.encode() 函數(shù),此函數(shù)仍然在 home.min.js 里,觀察這部分代碼,可以發(fā)現(xiàn)使用了 JSEncrypt,并且有 setPublicKey 設(shè)置公鑰方法,由此可以看出應(yīng)該是 RSA 加密,具體步驟是將明文密碼和時(shí)間戳組合成用 | 組合,經(jīng)過(guò) RSA 加密后再進(jìn)行 URL 編碼得到最終結(jié)果,如下圖所示:
RSA 加密找到了公鑰,其實(shí)就可以直接使用 Python 的 Cryptodome 模塊來(lái)實(shí)現(xiàn)加密過(guò)程了,代碼如下所示:
import time import base64 from urllib import parse from Cryptodome.PublicKey import RSA from Cryptodome.Cipher import PKCS1_v1_5password = "12345678" timestamp = str(int(time.time() * 1000)) encrypted_object = timestamp + "|" + password public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDq04c6My441Gj0UFKgrqUhAUg+kQZeUeWSPlAU9fr4HBPDldAeqzx1UR92KJHuQh/zs1HOamE2dgX9z/2oXcJaqoRIA/FXysx+z2YlJkSk8XQLcQ8EBOkp//MZrixam7lCYpNOjadQBb2Ot0U/Ky+jF2p+Ie8gSZ7/u+Wnr5grywIDAQAB" rsa_key = RSA.import_key(base64.b64decode(public_key)) # 導(dǎo)入讀取到的公鑰 cipher = PKCS1_v1_5.new(rsa_key) # 生成對(duì)象 encrypted_password = base64.b64encode(cipher.encrypt(encrypted_object.encode(encoding="utf-8"))) encrypted_password = parse.quote(encrypted_password) print(encrypted_password)即便是不使用 Python,我們同樣可以自己引用 JSEncrypt 模塊來(lái)實(shí)現(xiàn)這個(gè)加密過(guò)程(該模塊使用方法可參考 JSEncrypt GitHub),如下所示:
/* 引用 jsencrypt 加密模塊,如果在 PyCharm 里直接使用 require 引用最新版 jsencrypt, 運(yùn)行可能會(huì)提示 jsencrypt.js 里 window 未定義,直接在該文件定義 var window = this; 即可, 也可以使用和網(wǎng)站用的一樣的 2.3.1 版本:https://npmcdn.com/jsencrypt@2.3.1/bin/jsencrypt.js 也可以將 jsencrypt.js 直接粘貼到此腳本中使用,如果提示未定義,直接在該腳本中定義即可。 */JSEncrypt = require("jsencrypt")function getEncryptedPassword(t, e) {var jsEncrypt = new JSEncrypt();jsEncrypt.setPublicKey('MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDq04c6My441Gj0UFKgrqUhAUg+kQZeUeWSPlAU9fr4HBPDldAeqzx1UR92KJHuQh/zs1HOamE2dgX9z/2oXcJaqoRIA/FXysx+z2YlJkSk8XQLcQ8EBOkp//MZrixam7lCYpNOjadQBb2Ot0U/Ky+jF2p+Ie8gSZ7/u+Wnr5grywIDAQAB');var i = e ? e + "|" + t : t;return encodeURIComponent(jsEncrypt.encrypt(i)); }var password = "12345678"; var timestamp = (new Date).getTime(); console.log(getEncryptedPassword(password, timestamp));webpack 改寫
本文的標(biāo)題是 webpack 改寫實(shí)戰(zhàn),所以很顯然本文的目的是為了練習(xí) JavaScript 模塊化編程 webpack 代碼的改寫,現(xiàn)在大多數(shù)站點(diǎn)都使用了這種寫法,然而并不是所有站點(diǎn)都像本文遇到的站點(diǎn)一樣,可以很容易使用其他方法來(lái)實(shí)現(xiàn)的,往往大多數(shù)站點(diǎn)需要你自己扒下他的源碼來(lái)還原加密過(guò)程,有關(guān) JavaScript 模塊化編程,即 webpack,在 K 哥往期的文章中有過(guò)詳細(xì)的介紹:爬蟲逆向基礎(chǔ),理解 JavaScript 模塊化編程 webpack
一個(gè)標(biāo)準(zhǔn)的 webpack 整體是一個(gè) IIFE 立即調(diào)用函數(shù)表達(dá)式,其中有一個(gè)模塊加載器,也就是調(diào)用模塊的函數(shù),該函數(shù)中一般具有 function.call() 或者 function.apply() 方法,IIFE 傳遞的參數(shù)是一個(gè)列表或者字典,里面是一些需要調(diào)用的模塊,寫法類似于:
!function (allModule) {function useModule(whichModule) {allModule[whichModule].call(null, "hello world!");} }([function module0(param) {console.log("module0: " + param)},function module1(param) {console.log("module1: " + param)},function module2(param) {console.log("module2: " + param)}, ]);觀察這次站點(diǎn)的加密代碼,會(huì)發(fā)現(xiàn)所有加密方法都在 home.min.js 里面,在此文件開(kāi)頭可以看到整個(gè)是一個(gè) IIFE 立即調(diào)用函數(shù)表達(dá)式,function e 里面有關(guān)鍵方法 .call(),由此可以判斷該函數(shù)為模塊加載器,后面?zhèn)鬟f的參數(shù)是一個(gè)字典,里面是一個(gè)個(gè)的對(duì)象方法,也就是需要調(diào)用的模塊函數(shù),這就是一個(gè)典型的 webpack 寫法,如下圖所示:
接下來(lái)我們通過(guò) 4 步完成對(duì) webpack 代碼的改寫,將原始代碼扒下來(lái)實(shí)現(xiàn)加密的過(guò)程。
1、找到 IIFE
IIFE 立即調(diào)用函數(shù)表達(dá)式,也稱為立即執(zhí)行函數(shù),自執(zhí)行函數(shù),將源碼中的 IIFE 框架摳出來(lái),后續(xù)將有用的代碼再往里面放:
!function (t) {}({})2、找到模塊加載器
前面我們已經(jīng)講過(guò),帶有 function.call() 或者 function.apply() 方法的就是模塊加載器,也就是調(diào)用模塊的方法,在本例中,function e 就是模塊加載器,將其摳下來(lái)即可,其他多余的代碼可以直接刪除,注意里面用到了 i,所以定義 i 的語(yǔ)句也要摳下來(lái):
!function (t) {function e(s) {if (i[s])return i[s].exports;var n = i[s] = {exports: {},id: s,loaded: !1};return t[s].call(n.exports, n, n.exports, e),n.loaded = !0,n.exports}var i = {}; }({})3、找到調(diào)用的模塊
重新來(lái)到加密的地方,第一個(gè)模塊是 3,n 里面的 encode 方法最終返回的就是加密后的結(jié)果,如下圖所示:
第二個(gè)模塊是 4,可以看到模塊 3 里面的 this.jsencrypt.encrypt(i) 方法實(shí)際上是調(diào)用的第 3340 行的方法,該方法在模塊 4 里面,這里定位在模塊 4 的方法,可以在瀏覽器開(kāi)發(fā)者工具 source 頁(yè)面,將鼠標(biāo)光標(biāo)放到該函數(shù)前面,一直往上滑動(dòng),直到模塊開(kāi)頭,也可以使用 VS Code 等編輯器,將整個(gè) home.min.js 代碼粘貼過(guò)去,然后選擇折疊所有代碼,再搜索這個(gè)函數(shù),即可快速定位在哪個(gè)模塊。
確定使用了 3 和 4 模塊后,將這兩個(gè)模塊的所有代碼扣下來(lái)即可,大致代碼架構(gòu)如下(模塊 4 具體的代碼太長(zhǎng),已刪除):
!function (t) {function e(s) {if (i[s])return i[s].exports;var n = i[s] = {exports: {},id: s,loaded: !1};return t[s].call(n.exports, n, n.exports, e),n.loaded = !0,n.exports}var i = {}; }({4: function (t, e, i) {},3: function (t, e, i) {var s;s = function (t, e, s) {function n() {"undefined" != typeof r && (this.jsencrypt = new r.JSEncrypt,this.jsencrypt.setPublicKey("-----BEGIN PUBLIC KEY-----略-----END PUBLIC KEY-----"))}var r = i(4);n.prototype.encode = function (t, e) {var i = e ? e + "|" + t : t;return encodeURIComponent(this.jsencrypt.encrypt(i))},s.exports = n}.call(e, i, e, t),!(void 0 !== s && (t.exports = s))}} )這里需要我們理解一個(gè)地方,那就是模塊 3 的代碼里有一行 var r = i(4);,這里的 i 是 3: function (t, e, i) {},傳遞過(guò)來(lái)的 i,而模塊 3 又是由模塊加載器調(diào)用的,即 .call(n.exports, n, n.exports, e) 里面的某個(gè)參數(shù)就是 i,前面在講解基礎(chǔ)的時(shí)候已經(jīng)說(shuō)過(guò),.call 的第一個(gè)參數(shù)指定的是函數(shù)體內(nèi) this 對(duì)象的指向,并不代表真正參數(shù),所以第一個(gè) n.exports 并不是參數(shù),從第二個(gè)參數(shù)即 n 開(kāi)始算,那么 i 其實(shí)就是 .call(n.exports, n, n.exports, e) 里面的 e,所以 var r = i(4); 實(shí)際上就是模塊加載器 function e 調(diào)用了模塊 4,由于這里模塊 4 是個(gè)對(duì)象,所以這里最好寫成 var r = i("4");,這里是數(shù)字,所以可以成功運(yùn)行,如果模塊 4 名字變成 func4 或者其他名字,那么調(diào)用時(shí)就必須要加引號(hào)了。
4、導(dǎo)出加密函數(shù)
目前關(guān)鍵的加密代碼已經(jīng)剝離完畢了,最后一步就是需要把加密函數(shù)導(dǎo)出來(lái)供我們調(diào)用了,首先定義一個(gè)全局變量,如 eFunc,然后在模塊加載器后面使用語(yǔ)句 eFunc = e,把模塊加載器導(dǎo)出來(lái):
var eFunc;!function (t) {function e(s) {if (i[s])return i[s].exports;var n = i[s] = {exports: {},id: s,loaded: !1};return t[s].call(n.exports, n, n.exports, e),n.loaded = !0,n.exports}var i = {};eFunc = e }({4: function (t, e, i) {},3: function (t, e, i) {}} )然后定義一個(gè)函數(shù),傳入明文密碼,返回加密后的密碼:
function getEncryptedPassword(password) {var timestamp = (new Date).getTime();var encryptFunc = eFunc("3");var encrypt = new encryptFunc;return encrypt.encode(password, timestamp) }其中 timestamp 為時(shí)間戳,因?yàn)槲覀冏罱K需要調(diào)用的是模塊 3 里面的 n.prototype.encode 這個(gè)方法,所以首先調(diào)用模塊 3,返回的是模塊 3 里面的 n 函數(shù)(可以在瀏覽器運(yùn)行代碼,一步一步查看結(jié)果),然后將其 new 出來(lái),調(diào)用 n 的 encode 方法,返回加密后的結(jié)果。
自此,webpack 的加密代碼就剝離完畢了,最后調(diào)試會(huì)發(fā)現(xiàn) navigator 和 window 未定義,定義一下即可:
var navigator = {}; var window = global;這里擴(kuò)展一下,在瀏覽器里面 window 其實(shí)就是 global,在 nodejs 里沒(méi)有 window,但是有個(gè) global,與瀏覽器的 window 對(duì)象類型相似,是全局可訪問(wèn)的對(duì)象,因此在 nodejs 環(huán)境中可以將 window 定義為 global,如果定義為空,可能會(huì)引起其他錯(cuò)誤。
完整代碼
GitHub 關(guān)注 K 哥爬蟲,持續(xù)分享爬蟲相關(guān)代碼!歡迎 star !https://github.com/kgepachong/
**以下只演示部分關(guān)鍵代碼,不能直接運(yùn)行!**完整代碼倉(cāng)庫(kù)地址:https://github.com/kgepachong/crawler/
JavaScript 加密關(guān)鍵代碼架構(gòu)
方法一:webpack 改寫源碼實(shí)現(xiàn) RSA 加密:
var navigator = {}; var window = global; var eFunc;!function (t) {function e(s) {if (i[s])return i[s].exports;var n = i[s] = {exports: {},id: s,loaded: !1};return t[s].call(n.exports, n, n.exports, e),n.loaded = !0,n.exports}var i = {};eFunc = e; }({4: function (t, e, i) {},3: function (t, e, i) {}} )function getEncryptedPassword(password) {var timestamp = (new Date).getTime();var encryptFunc = eFunc("3");var encrypt = new encryptFunc;return encrypt.encode(password, timestamp) }// 測(cè)試樣例 // console.log(getEncryptedPassword("12345678"))方法二:直接使用 JSEncrypt 模塊實(shí)現(xiàn) RSA 加密:
/* 引用 jsencrypt 加密模塊,此腳適合在 nodejs 環(huán)境下運(yùn)行。 1、使用 require 語(yǔ)句引用,前提是使用 npm 安裝過(guò); 2、將 jsencrypt.js 直接粘貼到此腳本中使用,同時(shí)要將結(jié)尾 exports.JSEncrypt = JSEncrypt; 改為 je = JSEncrypt 導(dǎo)出方法。 PS:需要定義 var navigator = {}; var window = global;,否則提示未定義。 */// ========================= 1、require 方式引用 ========================= // var je = require("jsencrypt")// =================== 2、直接將 jsencrypt.js 復(fù)制過(guò)來(lái) =================== /*! JSEncrypt v2.3.1 | https://npmcdn.com/jsencrypt@2.3.1/LICENSE.txt */ var navigator = {}; var window = global;// 這里是 jsencrypt.js 代碼function getEncryptedPassword(t) {var jsEncrypt = new je();jsEncrypt.setPublicKey('MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDq04c6My441Gj0UFKgrqUhAUg+kQZeUeWSPlAU9fr4HBPDldAeqzx1UR92KJHuQh/zs1HOamE2dgX9z/2oXcJaqoRIA/FXysx+z2YlJkSk8XQLcQ8EBOkp//MZrixam7lCYpNOjadQBb2Ot0U/Ky+jF2p+Ie8gSZ7/u+Wnr5grywIDAQAB');var e = (new Date).getTime();var i = e ? e + "|" + t : t;return encodeURIComponent(jsEncrypt.encrypt(i)); }// 測(cè)試樣例 // console.log(getEncryptedPassword("12345678"));Python 登錄關(guān)鍵代碼
#!/usr/bin/env python3 # -*- coding: utf-8 -*-import re import json import time import random import base64 from urllib import parseimport execjs import requests from PIL import Image from Cryptodome.PublicKey import RSA from Cryptodome.Cipher import PKCS1_v1_5login_url = '脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler' verify_image_url = '脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler' check_code_url = '脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler'headers = {'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_jquery():jsonp = ''for _ in range(21):jsonp += str(random.randint(0, 9))jquery = 'jQuery' + jsonp + '_'return jquerydef get_dict_from_jquery(text):result = re.findall(r'\((.*?)\)', text)[0]return json.loads(result)def get_encrypted_password_by_javascript(password):# 兩個(gè) JavaScript 腳本,兩種方法均可with open('gm99_encrypt.js', 'r', encoding='utf-8') as f:# with open('gm99_encrypt_2.js', 'r', encoding='utf-8') as f:exec_js = f.read()encrypted_password = execjs.compile(exec_js).call('getEncryptedPassword', password)return encrypted_passworddef get_encrypted_password_by_python(password):timestamp = str(int(time.time() * 1000))encrypted_object = timestamp + "|" + passwordpublic_key = "脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler"rsa_key = RSA.import_key(base64.b64decode(public_key)) # 導(dǎo)入讀取到的公鑰cipher = PKCS1_v1_5.new(rsa_key) # 生成對(duì)象encrypted_password = base64.b64encode(cipher.encrypt(encrypted_object.encode(encoding="utf-8")))encrypted_password = parse.quote(encrypted_password)return encrypted_passworddef get_verify_code():response = session.get(url=verify_image_url, headers=headers)with open('code.png', 'wb') as f:f.write(response.content)image = Image.open('code.png')image.show()code = input('請(qǐng)輸入圖片驗(yàn)證碼: ')return codedef check_code(code):timestamp = str(int(time.time() * 1000))params = {'callback': get_jquery() + timestamp,'ckcode': code,'_': timestamp,}response = session.get(url=check_code_url, params=params, headers=headers)result = get_dict_from_jquery(response.text)if result['result'] == 1:passelse:raise Exception('驗(yàn)證碼輸入錯(cuò)誤!')def login(username, encrypted_password, code):timestamp = str(int(time.time() * 1000))params = {'callback': get_jquery() + timestamp,'encrypt': 1,'uname': username,'password': encrypted_password,'remember': 'checked','ckcode': code,'_': timestamp}response = session.get(url=login_url, params=params, headers=headers)result = get_dict_from_jquery(response.text)print(result)def main():# 測(cè)試賬號(hào):15434947408,密碼:iXqC@aJt8fi@VwVusername = input('請(qǐng)輸入登錄賬號(hào): ')password = input('請(qǐng)輸入登錄密碼: ')# 獲取加密后的密碼,使用 Python 或者 JavaScript 實(shí)現(xiàn)均可encrypted_password = get_encrypted_password_by_javascript(password)# encrypted_password = get_encrypted_password_by_python(password)# 獲取驗(yàn)證碼code = get_verify_code()# 校驗(yàn)驗(yàn)證碼check_code(code)# 登錄login(username, encrypted_password, code)if __name__ == '__main__':main()總結(jié)
以上是生活随笔為你收集整理的【JS 逆向百例】webpack 改写实战,G 某游戏 RSA 加密的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 东盟成我国最大贸易伙伴!东盟十国中,与我
- 下一篇: 浦发随借金提前还款手续费多少_浦发随借金