webpack打包是前端js模塊化壓縮打包常用的手段菲宴,特征明顯,比如下方的形式的代碼就是webpack分發(fā)器
// 分發(fā)器
!function(x){
function xx(n){
return ..call(**.exports, ***, ***.exports, xx)
}
}()
又或者更直觀的表現(xiàn)n["xxx"]這種速和,你可以大概知道了這是調(diào)用了webpack打包的js模塊代碼洲愤。
webpack打包后JS依賴模塊代碼的固定結(jié)構(gòu):
(this["webpackJsonpzsgk-pc"] = this["webpackJsonpzsgk-pc"] || []).push([[15], [function(e, t, n) {
"use strict";
e.exports = n(693)
}
// 參數(shù)固定為e, t, n
, function(e, t, n) {
e.exports = n(697)()
}
說個(gè)逆向webpack的通用方法:
先去找加密網(wǎng)站的加密入口蚂斤。這應(yīng)該是加密網(wǎng)站都必須要做的==> 直接根據(jù)參數(shù)名搜索參數(shù)
找到分發(fā)器的位置茬贵,或者說是加載器,n["xxx"]這種的n就是分發(fā)器姥饰,就比如下方中的exports的位置,最后執(zhí)行了d函數(shù)==>一般是runtimexxx.js中(提供環(huán)境)描焰;一般以
! function(e) {
的形式出現(xiàn)
尋找分發(fā)編號(hào)媳否、加密使用模塊(用到了哪些模塊就導(dǎo)入哪些模塊)==>一般在chunk-lib.js栅螟,以
(window.webpackJsonp = window.webpackJsonp || []).push([
的形式出現(xiàn)將函數(shù)入口的地方返回全局變量荆秦,最終返回:
var sign; var window = global;!function(){... sign = d}
, 賦值為分發(fā)器返回的d使用自定義的sign代替webpack代碼中的n進(jìn)行加密
from: https://blog.csdn.net/weixin_41586984/article/details/116268341
調(diào)試技巧
定位請(qǐng)求參數(shù)
-
打開開發(fā)者工具后力图,F(xiàn)5刷新后
Ctrl + Shift + F
搜索參數(shù)名步绸,如signdata,會(huì)顯示多個(gè)JS文件吃媒,選擇后仔細(xì)查看(點(diǎn)擊左下角{}
美觀格式化按鈕)瓤介。more: 如果文件太多,則直接通過請(qǐng)求的鏈接去找赘那,比如
user/login
Network找到新發(fā)出的xhr條目后刑桑,查看Initiator里的調(diào)用棧信息,如Login募舟;
注: 如果加密參數(shù)名稱比較簡單如s祠斧,比較難定位的話,可以借助請(qǐng)求的其他參數(shù)來查找拱礁,比如verificationCode
調(diào)試工具
- 斷點(diǎn)調(diào)試breakpoints
- XHR斷點(diǎn): XHR/fetch breakpoints
附錄-Js記錄
時(shí)間戳:
(new Date).getTime()
var a = (f1(), f2(), f3())
后琢锋,f1辕漂、f2、f3函數(shù)都會(huì)執(zhí)行吴超,而a最后的結(jié)果為f3的返回值javascript:void(0): void 是 JavaScript 中非常重要的關(guān)鍵字钉嘹,該操作符指定要計(jì)算一個(gè)表達(dá)式但是不返回值。
-
TypeError: window.btoa is not a function
btoa-atob 模塊沒有輸出一個(gè)編程接口鲸阻,它只提供命令行工具跋涣。
如果你需要轉(zhuǎn)換為Base64,你可以用Buffer來完成赘娄。
console.log(Buffer.from('Hello World!').toString('base64'));
相反的仆潮,假設(shè)你要解碼的內(nèi)容是一個(gè)base64編碼過的字符串。
console.log(Buffer.from(b64Encoded, 'base64').toString());
做題記錄
n["str"]題型:
天安財(cái)險(xiǎn)
var m = this.newEncrypt(JSON.stringify(h));
-
需要對(duì)this.privaKey的值細(xì)化下遣臼,傳入拿到固定的str
// , p = t("NFKh") , s = t("cg2h") l.prototype.newEncrypt = function(l) { var n = p.enc.Utf8.parse(this.privaKey) , t = p.enc.Utf8.parse(this.privaKey) , e = p.enc.Utf8.parse(l) , a = p.AES.encrypt(e, n, { iv: t, mode: p.mode.CBC, padding: p.pad.Pkcs7 }); return p.enc.Base64.stringify(a.ciphertext) }
財(cái)新網(wǎng)
password: this.encode(this.encrypt(this.form.password)),
-
c = a("3452")
性置、n = a.n(c)
,看到需要依賴3452后立馬Ctrl+shift+F全局搜3452揍堰,然后把整個(gè)webpack模塊扒下來
中遠(yuǎn)海運(yùn)
- n("MuMZ")中又有
r = n("XBrZ");
鹏浅,在另一個(gè)文件中,module需要放兩個(gè)
天翼云
var t = encodeURIComponent(c["c"].Des.encrypt(this.form.email, this.form.pwd)),
webpack實(shí)現(xiàn)
c = (mycode("ac6a"), mycode("b3ae"))
- 分發(fā)器和ac6a模塊在同一個(gè)文件中屏歹、而ac6a模塊依賴模塊在另一個(gè)文件內(nèi)隐砸;
- 分發(fā)器()({}),無感嘆號(hào)
- 分發(fā)器()({})大括號(hào)中自帶較多依賴模塊
自己實(shí)現(xiàn):直接扒下來encrypt加密的JS內(nèi)容
看準(zhǔn)網(wǎng)
- 分發(fā)器n("xxx")定位后跟一般的固定格式返回a蝙眶、n季希、r不同,寫的是個(gè)函數(shù)==>還是可以根據(jù)obj.Func來賦值
mycode = obj;
- JS逆向?qū)崙?zhàn)分析--看準(zhǔn)網(wǎng)webpack加解密分析——Python中使用execjs示范
企名片
u = i("x4Ab")
return e.encrypt_data && (e.data = Object(u.a)(e.encrypt_data)),
x4Ab模塊依賴aqBw幽纷,aqBw又依賴YuTi式塌、yLpj,因此依賴項(xiàng)中放"x4Ab"友浸、"aqBw"峰尝、"YuTi"、"yLpj"函數(shù)定義
-
模擬解析函數(shù)
function encrypt(data){ return data && (Object(u.a)(data)) }
n[num]題型:
大麥
- 刪除分發(fā)器多余代碼
var navigator = {}
掌上高考:
- 分發(fā)器在html文件內(nèi)
-
o = (u=a(42), a.n(u))
收恢,使用到了a.n(u)
即點(diǎn)n函數(shù) - 依賴函數(shù)的給出是以數(shù)組的形式武学,而不是字典的形式
- 模塊中依賴更多模塊==>引入整個(gè)模塊文件,但是跟"xxx"模式不同的是伦意,由于沒有用字典
{"xxx": function()}
的形式火窒,因此直接require也沒用TypeError: Cannot read property '42' of undefined
,而是將依賴模塊數(shù)組作為參數(shù)寫入到分發(fā)器依賴函數(shù)中!function(e){}([...])
即方括號(hào)中驮肉,從而才能找到42函數(shù)
酷我
t.data.reqId = n,
直接通過n(109)定位可能不那么準(zhǔn)確(雙擊后定位的函數(shù))熏矿,可以試著直接在分發(fā)器位置進(jìn)行斷點(diǎn),然后console輸出e["109"]
-
只要分發(fā)器定義部分(其他的刪了,因?yàn)橹挥昧?code>l=n(109)曲掰、c=n.n(l))+依賴模塊中定義109函數(shù)(整個(gè)function而不是t.exports)疾捍,以及觀察其中還依賴什么如n(202)、n(203)就補(bǔ)充拿什么
- n.n(l)是傳入什么就返回什么:https://www.bilibili.com/video/BV1gq4y1D781?from=search&seid=7720105602891609746&spm_id_from=333.337.0.0
// 如果不刪分發(fā)器中其他部分 l = mycode(109) c = mycode.n(l) // ==>得到l var r = c()(); console.log(r) // 由于只用到了n.n(l)栏妖,所以可以刪分發(fā)器大代碼中其他部分, 在使用時(shí)直接讓c=l l = mycode(109) c = l(); console.log(c) // 等價(jià)于 r = l(); console(r)
文章: https://blog.csdn.net/weixin_43189702/article/details/119860838
注意:
require模塊內(nèi)容可以放在逆向JS文件里一起乱豆,而不是一定得創(chuàng)建新的JS文件導(dǎo)入
先登錄然后找到加密處加斷點(diǎn),這個(gè)斷點(diǎn)會(huì)在發(fā)起登錄請(qǐng)求時(shí)才觸發(fā)吊趾;往上找分發(fā)器宛裕,加上斷點(diǎn),分發(fā)器位置的斷點(diǎn)是在頁面刷新時(shí)觸發(fā)论泛,因此要觸發(fā)這個(gè)斷點(diǎn)需要刷新頁面
找加密函數(shù)
c["c"].Des.encrypt(this.form.email, this.form.pwd)
的時(shí)候揩尸,找完整的函數(shù)如c["c"].Des.encrypt,而不是直接找cn(42), 或者n("xxx")屁奏,可以直接搜xxx岩榆,也可以在console里面輸出后找到對(duì)應(yīng)的FunctionLocation來快速定位
如果依賴模塊是字典的形式,則分發(fā)器依賴中寫字典坟瓢,如
!function(e){..}({ 32:function(){...}})
(一般情況n(32)勇边、n("ABCD")
), 如果不是則需要傳函數(shù)數(shù)組折联,如n(42)
粒褒,此處42表示的是第42個(gè)函數(shù),見掌上高考诚镰。var mycode;
后賦值的位置直接在分發(fā)器的下方即可奕坟,不用在最后面-
提示缺少
window
時(shí),定義全局變量var window = global;
清笨,(JS逆向文件月杉、依賴文件)window表示瀏覽器打開的窗口,在客戶端JavaScript中window對(duì)象是全局的對(duì)象函筋,所有 JavaScript 全局對(duì)象沙合、函數(shù)以及變量均自動(dòng)成為 window 對(duì)象的成員奠伪。但在nodejs中直接調(diào)用window是不存在的跌帐,而代替的是global,所以要用nodejs運(yùn)行時(shí)绊率,得用
var window = global;
var navigator = this
等價(jià)于var navigator = {}
谨敛,因?yàn)樵贜odeJS文件中運(yùn)行輸出this后可以發(fā)現(xiàn)this={}
,而在瀏覽器中this默認(rèn)為window(函數(shù)或類作用域內(nèi)為函數(shù)或者類實(shí)例)
心得
①所有webpack打包的的js都要先看懂打包后代碼運(yùn)行的順序滤否,找到加密處脸狸;②找到webpack對(duì)象,一般是 n(數(shù)字) 調(diào)用③確定分發(fā)器。④找依賴模塊炊甲,有時(shí)候各包的依賴關(guān)系太多泥彤,可以直接把文件爬下來引入,如果各個(gè)包的依賴關(guān)系不多卿啡,就可以只把調(diào)用到的函數(shù)找出來放到依賴中吟吝。⑤最后剩下的就是找到你要的代碼,慢慢復(fù)現(xiàn)調(diào)用加密/解密函數(shù)就好了颈娜。
做題案例學(xué)習(xí)視頻
- webpack師承:爬取webpack流程-視頻——大多都是n("Xvmd")
- 而如果n中調(diào)用的不是字典的形式剑逃,而是列表的形式,則看js逆向安全指南(3)-- webpack解包指南官辽、使用 webpack 的 js 加密參數(shù)的分析——酷我
進(jìn)階資料
- 9-爬蟲高級(jí)實(shí)戰(zhàn)【js逆向】
- JS逆向?qū)W習(xí)筆記 - 持續(xù)更新中
- webpack補(bǔ)充依賴項(xiàng)做法:
- JS逆向視頻
掌上高考解密過程
解析響應(yīng)data.text
相應(yīng)的data.text是加密的,頁面通過JS解密后渲染
-
確定加密位置
return null != l && null !== (a = l.data) && void 0 !== a && a.text && (l.data = (n = (e = { iv: u.uri, text: l.data.text, SIGN: h }).iv,
-
確定分發(fā)器位置俗批,在html內(nèi)
通過打斷
o = (u=a(42), a.n(u)) // 等價(jià)于 o = a(42)
確定依賴模塊:給
return e[a].call(c.exports, c, c.exports, r),
打斷點(diǎn)后console輸出e["42"]
查看a(42)位置:
function(e, t, n) {
e.exports = (e = n(21),
n(201),
n(825),
...
n(847),
e)
}
可以看到需要依賴多個(gè)鳖轰,因此直接把整個(gè)文件引入
- 扣解密函數(shù):注意
return
表達(dá)式后是逗號(hào)的情況:會(huì)從左到右執(zhí)行執(zhí)行,并返回最后一個(gè)扶镀。注意:JS函數(shù)并不能返回多個(gè)返回值
then((function(l) {
var e, a, t, b, n;
return null != l && null !== (a = l.data) && void 0 !== a && a.text && (l.data = (n = (e = {
iv: u.uri,
text: l.data.text,
SIGN: h
}).iv,
a = e.text,
e = e.SIGN,
e = o.a.PBKDF2(e, "secret", {
keySize: 8,
iterations: 1e3,
hasher: o.a.algo.SHA256
}).toString(),
n = o.a.PBKDF2(n, "secret", {
keySize: 4,
iterations: 1e3,
hasher: o.a.algo.SHA256
}).toString(),
a = o.a.lib.CipherParams.create({
ciphertext: o.a.enc.Hex.parse(a)
}),
n = o.a.AES.decrypt(a, o.a.enc.Hex.parse(e), {
iv: o.a.enc.Hex.parse(n)
}),
// data.text解析結(jié)果
JSON.parse(n.toString(o.a.enc.Utf8)))),
v && (t = r,
b = l,
null !== (n = window.apiConfig) && void 0 !== n && null !== (n = n.filterCacheList) && void 0 !== n && n.length ? window.apiConfig.filterCacheList.forEach((function(l) {
new RegExp(l).test(t) || d.set(t, b)
})) : d.set(t, b)),
l
}
難點(diǎn):
-
跟"xxx"模式不同的是蕴侣,由于沒有用字典
{"xxx": function()}
的形式,因此直接require也沒用TypeError: Cannot read property '42' of undefined
臭觉,而是將依賴模塊函數(shù)數(shù)組作為參數(shù)寫入到分發(fā)器依賴函數(shù)中!function(e){}([...])
即方括號(hào)中昆雀,從而才能找到42函數(shù)-
挑選push后第二個(gè)
[]
中的函數(shù)數(shù)組(this["webpackJsonpzsgk-pc"] = this["webpackJsonpzsgk-pc"] || []).push([[15], [function(e, t, n) { "use strict"; e.exports = n(693) }, ... } ]]); // 第一個(gè)]
-
理解了a.n的含義后,可以直接把
o = (u=a(42), a.n(u))
轉(zhuǎn)化為o=a(42)
獲得加密參數(shù)signsafe
大致流程跟data.text差不多蝠筑,但是
p = c()(g)
執(zhí)行時(shí)狞膘,會(huì)報(bào)錯(cuò)Md5.prototype.update = function(e) { if (!this.finalized) { var t, n = typeof e; if ("string" != n) { if ("object" != n) throw ERROR; if (null === e)
根據(jù)一步步調(diào)試之后發(fā)現(xiàn),還是
c = (u=a(291),a.n(u))
直接替換出的問題
-
Ctrl + shift + F定位參數(shù)
g = void 0, g = (t = { SIGN: h, str: f.replace(/^\/|https?:\/\/\/?/, "") }).SIGN, t = t.str, g = o.a.HmacSHA1(o.a.enc.Utf8.parse(t), g), g = o.a.enc.Base64.stringify(g).toString(), p = c()(g), u.signsafe = p,
往上找c和o.a:
o = (u = a(42),a.n(u))
,c = (u = a(291),a.n(u))
找到分發(fā)器扣出==>這次不能刪除分發(fā)器中多余的函數(shù)什乙,比如r.a挽封、r.d、r.n因?yàn)楹竺娴糜?/p>
將依賴模塊跟data.text一樣臣镣,放入分發(fā)器依賴模塊中
-
扣加密函數(shù)
網(wǎng)頁上是return后多段內(nèi)容辅愿,以及g變量不斷被修改,因此通過一步步調(diào)試確定入?yún)⒁淠常约胺纸鈘eturn抽離出真正的加密參數(shù)signsafe
h = "D23ABC@#56" var o = {} o.a = mycode(42) // ▲ c = (u = mycode(291), mycode.n(u)) /** * 對(duì)url進(jìn)行加密 */ function encrypt(f) { g = (t = { SIGN: h, str: f.replace(/^\/|https?:\/\/\/?/, "") }).SIGN, t = t.str; // console.log(t, g); g = o.a.HmacSHA1(o.a.enc.Utf8.parse(t), g); // console.log(g) g = o.a.enc.Base64.stringify(g).toString(); // console.log(g) p = c()(g); return p; } res = encrypt("https://api.eol.cn/web/api/counter?cid=1&did=263") console.log(res)
可以看到o.a和c的賦值是不一樣的点待,雖然說大多數(shù)情況x = a.n(u)等價(jià)于x=u,但難免有時(shí)會(huì)有不一樣弃舒,因此謹(jǐn)慎期間癞埠,還是還原到底最好状原。
Python調(diào)用
import execjs
def get_signsafe_by_javascript(url):
# 兩個(gè) JavaScript 腳本,兩種方法均可
with open('gk_signsafe.js', 'r', encoding='utf-8') as f:
exec_js = f.read()
signsafe = execjs.compile(exec_js).call('encrypt', url)
return signsafe
signsafe = get_encrypted_password_by_javascript("https://api.eol.cn/web/api/counter?cid=1&did=263")
print(signsafe)
RSA的加密步驟
- 獲取公鑰
- 實(shí)例化 ===> 扣出網(wǎng)站RSA實(shí)例化對(duì)象的代碼
- 設(shè)置公鑰
- 對(duì)文本進(jìn)行加密 ==> 扣出復(fù)現(xiàn)RSA加密的邏輯代碼
注: var window=global
苗踪、var navigator={}
-
// window.JSEncrypt is not a constructor 在摳出來的JS Encrypt代碼中加上 window.JSEncrpt = ze // 網(wǎng)頁中位var n = new JSEncrypt ==> JSEncrypt is not defined var n = new window.JSEncrypt; n.setPublicKey... var a = n.encrypt(t.data.hash + password); console.log(a)
網(wǎng)易云爬評(píng)論:python通過execjs來調(diào)用JS代碼颠区,代碼中用到了
CryptoJS
庫, 需要os.environ["NODE_PATH"]="F:/..../node_modules"
把庫導(dǎo)入-
JS逆向?qū)崙?zhàn)分析--某鐵網(wǎng)分析:document返回類型通铲,initiator是一條條文本(Other)瓦呼,因?yàn)槠錄]有用ajax(XHR),而是通過原生的網(wǎng)頁表單提交
loginForm.password.value = encryptByDES(loginForm.password.value), loginForm.publickey.value); loginForm.submit();
直接require導(dǎo)入CryptoJS模塊
or直接扣encryptByDES的加密函數(shù)==>出現(xiàn)
cannot read property 'createEncryptor' of undefined
-
MD5加密:
JS:
const crypto = require("CryptoJS"); crypto.MD5('待加密字符串').toString()
-
Python: https://blog.csdn.net/weixin_44799217/article/details/112486097
# 法一:創(chuàng)建md5對(duì)象 hl = hashlib.md5() # Tips # 此處必須聲明encode测暗,若寫法為hl.update(str) 報(bào)錯(cuò)為: Unicode-objects must be encoded before hashing hl.update(str.encode(encoding='utf-8')) # 法二: str_md5 = hashlib.md5(str.encode(encoding='utf-8')).hexdigest()
-
base64編碼
JS:
CryptoJS.enc.Base64.parse("待解密字符串").toString(CryptoJS.enc.Utf8)
-
Python
# 字符串 encode_str = base64.encodebytes(test_str.encode('utf8')) # b'aGVsbG8gd29ybGQh\n' print(encode_str.decode()) # 默認(rèn)以u(píng)tf8解碼央串,結(jié)果 aGVsbG8gd29ybGQh # 圖片 with open("D:\\redis.png", 'rb') as f: encode_img = base64.b64encode(f.read()) file_ext = os.path.splitext("D:\\redis.png")[1] print('data:image/{};base64,{}'.format(file_ext[1:], encode_img.decode())) f.close()
加密、摘要算法結(jié)果特征
urlencode
urlencode是一個(gè)函數(shù)碗啄,可將字符串以URL編碼质和,用于編碼處理。
URL編碼(URL encoding)稚字,也稱作百分號(hào)編碼(Percent-encoding)饲宿, 是特定上下文的統(tǒng)一資源定位符 (URL)的編碼機(jī)制。
Base64特征
最常見的用于傳輸8Bit字節(jié)碼的編碼方式之一
- 相同內(nèi)容胆描,結(jié)果是相同的
- a-zA-Z,0-9,+/共64個(gè)字符進(jìn)行編碼瘫想;每3個(gè)字節(jié)編碼成4個(gè)字節(jié),不足的在結(jié)尾有無意義的=來填補(bǔ)
- 一般情況下結(jié)尾都會(huì)有1個(gè)或者2個(gè)等號(hào)昌讲,明文長度是3的倍數(shù)時(shí)沒有=国夜;
- 內(nèi)容越長,結(jié)果越長
注:跟下面的算法區(qū)分一下短绸,base64是編碼方式车吹,并不能算加密算法。應(yīng)用場景還有傳輸圖片:data:image/jpg;base64,/9j/4QMZRXhpZgAASUk...
md5特征
消息摘要算法
- 確定唯一性:相同內(nèi)容醋闭,結(jié)果是相同的窄驹;但一般會(huì)有時(shí)間戳等參數(shù),所以導(dǎo)致了每次不同
- 不可逆性:有損的加密過程证逻,理論上無法解密(逆向推出)乐埠,除非暴力破解。安全囚企,這也是其成為校驗(yàn)是否被修改的最關(guān)鍵的性質(zhì)
- 碰撞性:原始數(shù)據(jù)與其MD5值并不是一一對(duì)應(yīng)的丈咐,有可能多個(gè)原始數(shù)據(jù)計(jì)算出來的MD5值是一樣的,這就是碰撞洞拨。
- 一般MD5值是32位扯罐,由數(shù)字“0-9”和字母“a-f”所組成的字符串负拟;字母可以是全大寫或者全小寫
- 密文一般為 16 位或者 32 位烦衣,其中 16 位是取的 32 位第 9~25 位的值;
- 長度:32個(gè)十六進(jìn)制字符組成的字符串 (128位)
RSA特征
- 相同內(nèi)容,結(jié)果也是不同的
- 明文長度需要小于密鑰長度花吟,而密文長度則等于密鑰長度秸歧。一般為1024、2048衅澈、3072键菱、4096或512(低于1024的安全不建議)
- 通過公鑰加密結(jié)果,必須私鑰解密今布。 同樣私鑰加密結(jié)果经备,公鑰可以解密
注:RSA加解密中必須考慮到的密鑰長度、明文長度和密文長度問題部默;
▲.一般會(huì)使用 JSEncrypt 庫侵蒙,會(huì)有 new 一個(gè)實(shí)例對(duì)象的操作;
SHA 系列
SHA 是比 MD5 更安全一點(diǎn)的摘要算法傅蹂,SHA 通常指 SHA 家族算法纷闺,
sha1
字母(a-f)和數(shù)字(0-9)混合
密文特征跟MD5差不多,只不過數(shù)字是40位份蝴,bit位數(shù)(160)==>4位十六進(jìn)制表示一個(gè)數(shù)
Sha256
字母(a-f)和數(shù)字(0-9)混合
對(duì)于任意長度的消息犁功,SHA256都會(huì)產(chǎn)生一個(gè)256位的哈希值,即64位十六進(jìn)制數(shù)婚夫,稱作消息摘要浸卦。
HMAC
在md5和sha1加密的基礎(chǔ)上引入了秘鑰,而秘鑰又只有傳輸雙方才知道案糙,所以基本上是破解不了的镐躲,常用于接口簽名驗(yàn)證
AES、DES侍筛、3DES萤皂、RC4、Rabbit 等
AES匣椰、DES裆熙、3DES、RC4禽笑、Rabbit 等加密算法的密文通常沒有固定的長度入录,他們通常使用crypto-js
庫來實(shí)現(xiàn)