JS解混淆

来源:https://www.cnblogs.com/jicey/p/18130816
-Advertisement-
Play Games

JS解混淆 最近在整理之前和一些同伴的分享資料,發現時間已經過了好久,特此整理一些有價值的分享記錄。 JS混淆 學習js混淆可以逆向分析混淆和加密過程,實戰可用於爬蟲和滲透信息獲取 本文檔用於初步介紹js混淆的基礎概念以及如何解混淆、調試,便於幹掉反爬蟲和滲透信息收集思路拓展 概念解釋 混淆/加密 ...


JS解混淆

最近在整理之前和一些同伴的分享資料,發現時間已經過了好久,特此整理一些有價值的分享記錄。

JS混淆

學習js混淆可以逆向分析混淆和加密過程,實戰可用於爬蟲和滲透信息獲取

本文檔用於初步介紹js混淆的基礎概念以及如何解混淆、調試,便於幹掉反爬蟲和滲透信息收集思路拓展

概念解釋

混淆/加密

降低代碼可讀性加強安全性,防止被人任意查看,在一定程度保護資源

理想的混淆或加密應該具備如下特點

1、沒有確定的破解模式;

2、很難編製自動破解程式(只能手工破解);

3、破解過程繁瑣、耗時;

4、“混淆|加密”後的代碼,比原始代碼長度增加少;

代碼里諸如此類就是經過了混淆的結果,可以通過console+斷點打出來看看值

image-20220519164011809

image-20220519164047706

js混淆和eval加密

前端雖然開源, 但是由於前端代碼量很多,也有一些特殊的保護代碼的方法

其中Eval、js混淆是常用的方式,但是在大的互聯網產品上用得很少,因為前端加密(RSA、AES、MD5等)是為了保證數據傳輸中的安全性,而非要讓人難以模仿數據傳輸請求

而前端中的js混淆、eval對於專業的人來說形同虛設,所以也沒必要做混淆和eval,並且對於代碼維護是及其不利的

eval加密

js中的eval()方法就是一個js語言的執行器

它能把其中的參數按照JavaScript語法進行解析並執行

簡單來說就是把原本的js代碼變成了eval的參數,變成參數後代碼就成了字元串,其中的一些字元就會被按照特定格式“編碼”

是最早JS出現的混淆加密,據說第一天就被破解,修改一下代碼,alert一下就可以破解了

#eval加密

源代碼:
var showmsg="粘貼要加密/解密的javascript代碼到這裡";
if(1==1){
  alert(showmsg);
}

加密後的樣子:
eval(function(p,a,c,k,e,d){e=function(c)
{return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?
String.fromCharCode(c+29):c.toString(36))};
if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return 
d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new 
RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('5 4="粘貼要加密/解密的3代碼到這裡";2(0==0){  
1(4);}',62,6,'1|alert|if|javascript|showmsg|var'.split('|'),0,{}))

eval()語句還有一個重要用途:在反調試中可以使用該語句來進行一些函數值賦空從而跳出debugger的函數

JS混淆

把其中的變數、方法位置順序打亂,但是又用一些無關的變數或者方法來保證執行順序

常見手段

1、去除縮進、空行、換行、註釋

2、變數名替換(縮短/改亂)

3、通過自定義變數名引用JS關鍵字

4、添加大段空白,增加代碼前後間隔,干擾閱讀

5、混眼法(通過利用[]和["、']及變數定義語句來添加與代碼功能無關的字元/增添與代碼功能無關的運算語句)

6、對源代碼進行加密,同時附上解密的代碼(運行時先解密,然後通過document.write()或eval()或innerHTML把代碼釋放出來執行)

其他混淆類型

hash類型

壓縮類型

常用工具

混淆

這裡是從使用工具加密信息方出發,具體工具的使用可以自行學習。

  • webassembly
  • esprima

針對JavaScript

  • JavaScript Obfuscator

​ 具體使用參考:7.8k Star!一個強大的 JS 代碼混淆工具 - 掘金 (juejin.cn)

  • terser
  • uglify-js
  • uglify-es
  • Google Closure Compiler
  • YUI Compressor

針對CSS

  • PostCSS

  • clean-css

  • CSSO

  • YUI Compressor

針對HTML

  • html-minifier

混淆示例

此處使用JavaScript Obfuscator Tool,由JavaScript Obfuscator作者搭建的一個線上混淆網站,直接輸入需要混淆的代碼輸出混淆結果即可

以下麵的一個簡單hello world為例

##源代碼
function hi() {
  console.log("Hello World!");
}
hi();

經過混淆之後

##混淆後的代碼
(function(_0x1522cf,_0x263348){var _0x2bf84c=_0x42bb,_0x47bae4=_0x1522cf();while(!![]){try{var _0x301f10=parseInt(_0x2bf84c(0x11b))/0x1*(-parseInt(_0x2bf84c(0x10f))/0x2)+-parseInt(_0x2bf84c(0x114))/0x3+parseInt(_0x2bf84c(0x112))/0x4*(-parseInt(_0x2bf84c(0x117))/0x5)+-parseInt(_0x2bf84c(0x110))/0x6+parseInt(_0x2bf84c(0x115))/0x7*(parseInt(_0x2bf84c(0x118))/0x8)+parseInt(_0x2bf84c(0x119))/0x9*(parseInt(_0x2bf84c(0x116))/0xa)+parseInt(_0x2bf84c(0x11a))/0xb*(parseInt(_0x2bf84c(0x113))/0xc);if(_0x301f10===_0x263348)break;else _0x47bae4['push'](_0x47bae4['shift']());}catch(_0x2af3c3){_0x47bae4['push'](_0x47bae4['shift']());}}}(_0x22dc,0x1e93e));function hi(){var _0xfdbe99=_0x42bb;console[_0xfdbe99(0x111)]('Hello\x20World!');}hi();function _0x42bb(_0x4a56bb,_0x17e1ee){var _0x22dca2=_0x22dc();return _0x42bb=function(_0x42bb1c,_0x597cba){_0x42bb1c=_0x42bb1c-0x10f;var _0x2ad529=_0x22dca2[_0x42bb1c];return _0x2ad529;},_0x42bb(_0x4a56bb,_0x17e1ee);}function _0x22dc(){var _0x1ca681=['937926xGdCzf','log','344SUuAGG','1124988WMYeGw','111081MLZhWo','35SqOFWp','670aFpiLz','12820fkuEha','108152xzQqbd','15975Prsnjz','44YZHRMa','1oaFebR','44836HvkwgV'];_0x22dc=function(){return _0x1ca681;};return _0x22dc();}

##為了展示直觀,經過代碼美化處理結果如下
(function(_0x1522cf, _0x263348) {
	var _0x2bf84c = _0x42bb,
		_0x47bae4 = _0x1522cf();
	while (!![]) {
		try {
			var _0x301f10 = parseInt(_0x2bf84c(0x11b)) / 0x1 * (-parseInt(_0x2bf84c(0x10f)) / 0x2) + -parseInt(_0x2bf84c(0x114)) / 0x3 + parseInt(_0x2bf84c(0x112)) / 0x4 * (-parseInt(_0x2bf84c(0x117)) / 0x5) + -parseInt(_0x2bf84c(0x110)) / 0x6 + parseInt(_0x2bf84c(0x115)) / 0x7 * (parseInt(_0x2bf84c(0x118)) / 0x8) + parseInt(_0x2bf84c(0x119)) / 0x9 * (parseInt(_0x2bf84c(0x116)) / 0xa) + parseInt(_0x2bf84c(0x11a)) / 0xb * (parseInt(_0x2bf84c(0x113)) / 0xc);
			if (_0x301f10 === _0x263348) break;
			else _0x47bae4['push'](_0x47bae4['shift']());
		} catch (_0x2af3c3) {
			_0x47bae4['push'](_0x47bae4['shift']());
		}
	}
}(_0x22dc, 0x1e93e));

function hi() {
	var _0xfdbe99 = _0x42bb;
	console[_0xfdbe99(0x111)]('Hello\x20World!');
}
hi();

function _0x42bb(_0x4a56bb, _0x17e1ee) {
	var _0x22dca2 = _0x22dc();
	return _0x42bb = function(_0x42bb1c, _0x597cba) {
		_0x42bb1c = _0x42bb1c - 0x10f;
		var _0x2ad529 = _0x22dca2[_0x42bb1c];
		return _0x2ad529;
	}, _0x42bb(_0x4a56bb, _0x17e1ee);
}

function _0x22dc() {
	var _0x1ca681 = ['937926xGdCzf', 'log', '344SUuAGG', '1124988WMYeGw', '111081MLZhWo', '35SqOFWp', '670aFpiLz', '12820fkuEha', '108152xzQqbd', '15975Prsnjz', '44YZHRMa', '1oaFebR', '44836HvkwgV'];
	_0x22dc = function() {
		return _0x1ca681;
	};
	return _0x22dc();
}

可以發現代碼混淆有幾個比較固定的特征,一些變數的命名會賦隨機值,而後通過一個數組去進行存儲。同時使用一個while-try-catch的結構。

再看一下實際環境中經過混淆的代碼

// 此處也是經過格式美化,源代碼只有一行
eval(function(p, a, c, k, e, d) {
	e = function(c) {
		return (c < a ? "" : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36))
	};
	if (!''.replace(/^/, String)) {
		while (c--) d[e(c)] = k[c] || e(c);
		k = [function(e) {
			return d[e]
		}];
		e = function() {
			return '\\w+'
		};
		c = 1;
	};
	while (c--)
		if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]);
	return p;
}('4 3(1){2 0=5 8();7 0.6(1)}', 9, 9, 'b|tksl|var|dswejwehxt|function|new|decode|return|Base64'.split('|'), 0, {}));

// respond
eval(function(p, a, c, k, e, d) {
	e = function(c) {
		return (c < a ? "" : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36))
	};
	if (!''.replace(/^/, String)) {
		while (c--) d[e(c)] = k[c] || e(c);
		k = [function(e) {
			return d[e]
		}];
		e = function() {
			return '\\w+'
		};
		c = 1;
	};
	while (c--)
		if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]);
	return p;
}('(c(w){"2O 2E";8 7={};w.7=7;7.21=c(){};8 V=[],1T=(c(){8 16=1E;2t{16=1l w.2s()}2u(e){16=1l w.2w("2v.2o")}l c(){l 16}})(),14=c(1t,22){8 p=1T();5(!p){l}p.2n("2p",1t,1f);p.2r=c(){5(p.1Q!==4||p.2a!==2q&&p.2a!==2D){l}22(p.2C)};5(p.1Q===4){l}p.2G(1b)},1J=c(25){l 25.U(7.f.2g,\'\').I(7.f.1R)};7.14=14;7.2F=V;7.2y=1J;7.f={b:/@b[^\\{]+\\{([^\\{\\}]*\\{[^\\}\\{]*\\})+/17,1j:/@(?:\\-(?:o|2x|2z)\\-)?1j[^\\{]+\\{(?:[^\\{\\}]*\\{[^\\}\\{]*\\})+[^\\}]*\\}/17,2f:/\\/\\*[^*]*\\*+([^/][^*]*\\*+)*\\//17,20:/(1t\\()[\'"]?([^\\/\\)\'"][^:\\)\'"]+)[\'"]?(\\))/g,1U:/@b *([^\\{]+)\\{([\\S\\s]+?)$/,Y:/(Y\\s+)?([a-2e-Z]+)\\s?/,12:/\\(\\s*v\\-19\\s*:\\s*(\\s*[0-9\\.]+)(1o|E)\\s*\\)/,15:/\\(\\s*u\\-19\\s*:\\s*(\\s*[0-9\\.]+)(1o|E)\\s*\\)/,2g:/\\(\\s*m(1h|2B)\\-(2A|19)\\s*:\\s*(\\s*[0-9\\.]+)(1o|E)\\s*\\)/17,1R:/\\([^\\)]*\\)/g};7.2b=w.1s&&w.1s("Y 1M")!==1b&&w.1s("Y 1M").2k;5(7.2b){l}8 h=w.2j,t=h.2m,X=[],F=[],B=[],1i={},1w=30,G=h.1H("G")[0]||t,1z=h.1H("1z")[0],W=G.1H("2l"),1a,1p,1c,T=c(){8 Q,H=h.1k(\'H\'),d=h.d,29=t.q.J,1n=d&&d.q.J,1d=1E;H.q.26="2i:2H;34-33:1Z;19:1Z";5(!d){d=1d=h.1k("d");d.q.36="35"}t.q.J="1S%";d.q.J="1S%";d.27(H);5(1d){t.23(d,t.2Z)}Q=H.2Y;5(1d){t.1r(d)}K{d.1r(H)}t.q.J=29;5(1n){d.q.J=1n}Q=1c=10(Q);l Q},18=c(1X){8 1C="32",1m=t[1C],1u=h.31==="3c"&&1m||h.d[1C]||1m,C={},28=W[W.y-1],1v=(1l 3a()).37();5(1X&&1a&&1v-1a<1w){w.38(1p);1p=w.2d(18,1w);l}K{1a=1v}N(8 i 1h X){5(X.1q(i)){8 z=X[i],v=z.12,u=z.15,1y=v===1b,1x=u===1b,E="E";5(!!v){v=10(v)*(v.1D(E)>-1?(1c||T()):1)}5(!!u){u=10(u)*(u.1D(E)>-1?(1c||T()):1)}5(!z.1Y||(!1y||!1x)&&(1y||1u>=v)&&(1x||1u<=u)){5(!C[z.b]){C[z.b]=[]}C[z.b].M(F[z.F])}}}N(8 j 1h B){5(B.1q(j)){5(B[j]&&B[j].2N===G){G.1r(B[j])}}}B.y=0;N(8 k 1h C){5(C.1q(k)){8 A=h.1k("q"),L=C[k].2P("\\n");A.2J="2I/L";A.b=k;G.23(A,28.2K);5(A.R){A.R.26=L}K{A.27(h.2V(L))}B.M(A)}}},1B=c(P,6,b){8 11=P.U(7.f.2f,\'\').U(7.f.1j,\'\').I(7.f.b),1e=11&&11.y||0;6=6.1O(0,6.2U("/"));8 1N=c(L){l L.U(7.f.20,"$1"+6+"$2$3")},1L=!1e&&b;5(6.y){6+="/"}5(1L){1e=1}N(8 i=0;i<1e;i++){8 1g,D,13,1K;5(1L){1g=b;F.M(1N(P))}K{1g=11[i].I(7.f.1U)&&r.$1;F.M(r.$2&&1N(r.$2))}13=1g.1A(",");1K=13.y;N(8 j=0;j<1K;j++){D=13[j];5(1J(D)){2W}X.M({b:D.1A("(")[0].I(7.f.Y)&&r.$2||"1M",F:F.y-1,1Y:D.1D("(")>-1,12:D.I(7.f.12)&&10(r.$1)+(r.$2||""),15:D.I(7.f.15)&&10(r.$1)+(r.$2||"")})}}18()},1I=c(){5(V.y){8 O=V.2R();14(O.6,c(P){1B(P,O.6,O.b);1i[O.6]=1f;w.2d(c(){1I()},0)})}},1G=c(){N(8 i=0;i<W.y;i++){8 x=W[i],6=x.6,b=x.b,2h=x.24&&x.24.2T()==="2S";5(!!6&&2h&&!1i[6]){5(x.R&&x.R.2c){1B(x.R.2c,6,b);1i[6]=1f}K{5((!/^([a-2e-Z:]*\\/\\/)/.2Q(6)&&!1z)||6.U(r.$1,"").1A("/")[0]===w.1P.2X){5(6.1O(0,2)==="//"){6=w.1P.2L+6}V.M({6:6,b:b})}}}}1I()};1G();7.21=1G;7.T=T;c 1F(){18(1f)}5(w.1V){w.1V("2M",1F,1E)}K 5(w.1W){w.1W("39",1F)}})(3b);', 62, 199, '|||||if|href|respond|var|||media|function|body||regex||doc||||return||||req|style|RegExp||docElem|max|min||sheet|length|thisstyle|ss|appendedEls|styleBlocks|thisq|em|rules|head|div|match|fontSize|else|css|push|for|thisRequest|styles|ret|styleSheet||getEmValue|replace|requestQueue|links|mediastyles|only||parseFloat|qs|minw|eachq|ajax|maxw|xmlhttpmethod|gi|applyMedia|width|lastCall|null|eminpx|fakeUsed|ql|true|fullq|in|parsedSheets|keyframes|createElement|new|docElemProp|originalBodyFontSize|px|resizeDefer|hasOwnProperty|removeChild|matchMedia|url|currWidth|now|resizeThrottle|maxnull|minnull|base|split|translate|name|indexOf|false|callMedia|ripCSS|getElementsByTagName|makeRequests|isUnsupportedMediaQuery|eql|useMedia|all|repUrls|substring|location|readyState|other|100|xmlHttp|findStyles|addEventListener|attachEvent|fromResize|hasquery|1em|urls|update|callback|insertBefore|rel|query|cssText|appendChild|lastLink|originalHTMLFontSize|status|mediaQueriesSupported|rawCssText|setTimeout|zA|comments|minmaxwh|isCSS|position|document|matches|link|documentElement|open|XMLHTTP|GET|200|onreadystatechange|XMLHttpRequest|try|catch|Microsoft|ActiveXObject|moz|unsupportedmq|webkit|height|ax|responseText|304|strict|queue|send|absolute|text|type|nextSibling|protocol|resize|parentNode|use|join|test|shift|stylesheet|toLowerCase|lastIndexOf|createTextNode|continue|host|offsetWidth|firstChild||compatMode|clientWidth|size|font|none|background|getTime|clearTimeout|onresize|Date|this|CSS1Compat'.split('|'), 0, {}));

可以從裡面發現一些規律,例如一大段字元的split替換、eval(function)的聲明。

或者類似如下的大段以單個字母進行的隨機命名

!function() {
    var t = document
      , e = 0;
    (window.isLogin || "object" == typeof OP_CONFIG && OP_CONFIG.userInfo && OP_CONFIG.userInfo.uid) && (e = 1);
    var d = 1646064e6
      , o = 16487424e5
      , i = 16461396e5
      , a = 16471872e5
      , c = 0
      , n = null
      , u = 1e4
      , r = "//www.imooc.com"
      , s = "//www.imooc.com/static/moco/v1.0/images/redrain2"
      , l = "20220301";
    location.href.indexOf("guoyuchen") > -1 && (r = "//www-xiongwenhui.imooc.com",
    s = "/static/moco/v1.0/images/redrain2");
    var f = [s + "/ready.png?t=" + l, s + "/go.png?t=" + l, s + "/close-btn.png?t=" + l, s + "/redpacket.png?t=" + l, s + "/boom.png?t=" + l, s + "/result-bg1.png?t=" + l, s + "/use-btn.png?t=" + l, s + "/result-bg2.png?t=" + l, s + "/more-btn.png?t=" + l, s + "/more-btn2.png?t=" + l, s + "/result-bg3.png?t=" + l, s + "/halfAd1.jpeg?t=" + l, s + "/halfAd2.jpeg?t=" + l, s + "/coupon-bg2.png?t=" + l, s + "/coupon-btn2.gif?t=" + l, "//www.imooc.com/static/moco/v1.0/images/march2022/big-ad2.png?t=" + l, "//www.imooc.com/static/moco/v1.0/images/march2022/big-ad2-btn.png?t=" + l]
      , m = {
        modal: '<div class="redRain-modal" id="redRainModal"></div>',
        coupon: '<div class="coupon-wrap center">                    <div class="coupon-btn js-startCoupon"></div>                    <div class="close-btn js-closeCoupon couponCloseBtn618"></div>                </div>',
        halfScreenAd: '<div class="half-wrap center">                            <div class="close-adv imv2-close js-closeHalfScreenAd"></div>                            <a href="//www.imooc.com/act/march2022?utm_source=imooc&utm_campaign=half" target="_blank"><img src="$img" /></a>                        </div>',
        gameStart: '<div id="march2022" class="red-rain">                        <a class="activity-center" data-type="2" target="_blank" style="background-image: url(//www.imooc.com/static/moco/v1.0/images/march2022/big-activity2.png?t=3)">                            <span class="close imv2-add_circle_o js-close-activity"></span>                            <button class="activity-center-btn js-start-game" style="background-image: url(//www.imooc.com/static/moco/v1.0/images/march2022/big-activity2-btn.gif?t=3)"></button>                        </a>                    </div>',
        loading: '<div class="loading center"></div>',
        readyGo: '<div class="readyGo center">                    <img src="' + s + "/ready.png?t=" + l + '" alt="" class="ready">                    <img src="' + s + "/go.png?t=" + l + '" alt="" class="go hide">                </div>',
        gameMain: '<div class="gameMain-wrap">                    <div class="rain-wrap">                        <div class="rain-box js-rain-box"></div>                    </div>                    <div class="line"></div>                    <div class="rainInfo-wrap">                        <div class="clickNum">Combo X <span class="js-rain-clickNum">0</span></div>                        <div class="interval">剩餘時間 <span class="js-rain-restTime">15</span>s</div>                    </div>                </div>',
        result1: '<div class="result-wrap1 center">                    <div class="redpacket-price offset">¥$redpacketPrice</div>                    <div class="to-use-btn js-rainToActive offset"></div>                    <p class="tip offset">紅包將在 <span class="js-redpacket-lefttime">3天</span> 後失效哦</p>                    <p class="tip offset">下單自動結算,可與優惠券疊加使用</p>                    <div class="close-btn js-closeRedRain"></div>                </div>',
        result2: '<div class="result-wrap2 center">                    <div class="time">$nextTime</div>                    <div class="to-use-btn js-rainToActive"></div>                    <div class="close-btn js-closeRedRain"></div>                </div>',
        result3: '<div class="result-wrap3 center">                    <div class="to-use-btn js-rainToActive"></div>                    <div class="close-btn js-closeRedRain"></div>                </div>',
        rightFloat: '<div id="rightFloat20201111" class="js-rainToActive">                        <div class="redpacket">                            <div class="redpacketContent">                                $content                            </div>                        </div>                        <div class="bottomTitle"></div>                    </div>',
        rightFloat2: '<div id="rightFloat20201111" class="js-rainToActive double11"></div>'
    }

反混淆

反混淆的工具是依據混淆原理生成代碼,實際需要不斷觀察分析及調整,比較考驗人的耐性

需要具備將問題劃分為N個子問題的能力

同時還需要具備js的基礎知識,以及還原後如何重構代碼(什麼工具打包的webpack)

繼續深入的話還需要瞭解JS語法解釋器、AST抽象語法樹、編程語言實現模式

  • jspacker -> 針對eval
  • unjsa -> 針對JSA
  • crack.js -> 針對javascript-obfuscator
  • jsnice -> 針對UnuglifyJS

本文檔針對js解混淆初步入手,如何調試和如何定位進行說明

調試

在網頁的調試過程中,需要藉助一些工具去“投巧”(_)

  • Fiddler/Reres:替換髮包和請求內容

image-20220526144505490

  • WT-JS_DEBUG:可以直接調試或美化js代碼,同時附帶多種解密

    image-20220526144522616

調試

alert調試

聯網剛剛起步的時代,網頁前端還主要以內容展示為主,瀏覽器腳本還只能為頁面提供非常簡單的輔助功能

那個時候,網頁主要運行在以IE6為主的瀏覽器中,JS的調試功能還非常弱,只能通過內置於Window對象中的alert方法來調試

另一方面,alert的調試信息,必須在程式邏輯中添加類似"alert(xxxxx)"這樣的語句,才能正常工作,並且alert會阻礙頁面的繼續渲染

這就意味著開發人員調試完成後,必須手動清除這些調試代碼

console調試

新一代的瀏覽器Firefox、Chrome,包括IE,都相繼推出了JS調試控制台,支持使用類似"console.log(xxxx)"的形式,在控制台列印調試信息,而不直接影響頁面顯示

image-20220519155900045

如果在使用console對象之前先進性存在性驗證,其實不刪除也不會對業務邏輯造成破壞

為了代碼整潔,在調試完成後,還是應儘可能刪除這些與業務邏輯無關的調試代碼

Chrome開發團隊為Chrome瀏覽器拓展了更豐富的功能,具體操作可以使用Chrome瀏覽器

斷點調試

JS斷點調試,即是在瀏覽器開發者工具中為JS代碼添加斷點,讓JS執行到某一特定位置停住,方便開發者對該處代碼段的分析與邏輯處理。為了能夠觀察到斷點調試的效果

給一段代碼添加斷點的流程是"F12(Ctrl + Shift + I)打開開發工具"——"點擊Sources菜單"——"左側樹中找到相應文件"——"點擊行號列"即完成在當前行添加/刪除斷點操作

當斷點添加完畢後,刷新頁面JS執行到斷點位置停住,在Sources界面會看到當前作用域中所有變數和值,只需對每個值進行驗證

此處選中第五行,再次刷新頁面即將執行到此處

image-20220519160502399

刷新之後的效果

image-20220519160715970

可以發現右側有這樣一行工具欄

image-20220519160739806

工具欄從左到右各圖標的功能分別如下:

Pause/Resume script execution:F8 暫停/恢復腳本執行(程式執行到下一斷點停止)

Step over next function call: F10 執行到下一步的函數調用(跳到下一行)

Step into next function call: F11 進入當前函數

Step out of current function:Shift+F11 跳出當前執行函數

Step: F9 同F11,將跨國非同步函數進入下一行

Deactive/Active all breakpoints:Ctrl+F8 關閉/開啟所有斷點(不會取消)

Pause on exceptions:異常情況自動斷點設置

Debugger斷點

在開發中偶爾會遇到非同步載入html片段(包含內嵌JS代碼)的情況,而這部分JS代碼在Sources樹種無法找到

因此無法直接在開發工具中直接添加斷點,那麼如果想給非同步載入的腳本添加斷點,此時"debugger;"就發揮作用了

通過在代碼中添加"debugger;"語句,當代碼執行到該語句的時候就會自動斷點

接下去的操作就跟在Sources面板添加斷點調試幾乎一模一樣,唯一的區別在於調試完後需要刪除該語句

DOM斷點調試

在DOM元素上添加斷點,進而達到調試的目的

代碼展開

如果頁面源代碼顯示單行,可以點擊左下角的大括弧展開,更為直觀的瀏覽代碼

image-20220519163056019

結果如下圖

image-20220519163118540

搜索關鍵字

在頁面內通過ctrl+F,可以出現搜索框

image-20220519163158080

反調試

一些網站會通過監控網頁視窗的長寬高以此監視是否開啟調試模式以此來進行反調試,對此需要將devtool獨立出來

image-20220523161109942

具體請點擊右上角的三個點,選擇第一行的左邊第一個按鍵,即可將調試視窗獨立

image-20220523161100341

實驗

普通解混淆

以某東的登錄為例子

image-20220523175721059

隨便輸入數據找到post

image-20220523175637616

看一下payload這裡可以發現密碼nloginpwd是經過加密的,還有一個pubKey和sa_token。那麼需要解決的加密方式就是這三個

image-20220523175711706

全局搜索nloginpwd,判斷一下可能的位置,定斷點刷新一下,定過來了

image-20220523175817583

可以看到這裡有個data,console打出來看看能和我們抓到的Post匹配上,可以發現這裡的pubKey和sa_token是寫死的

image-20220523175841152

可以看到這裡對於nloginpwd有個getEntryPwd函數,應該是對此進行了加密,跟進去看一下。這裡可以列印一下getEntryPwd的賦值,可以發現是我們輸入的原密碼

image-20220523180022647

image-20220523180142991

根據名字看一下,這裡賦值pubKey,同時進行一個JSEncrypt的操作,跟進去看一下

可以發現這段特別長,直接將整段copy出來嘗試運行

image-20220523180419152

可以發現這個代碼是經過加密的,首行直接說明瞭。這種情況建議copy下來通過直接WT-JS_DEBUG嘗試運行

image-20220523180622563

代碼copy過來發現有一些變數沒有賦值,這裡在首行直接賦空值,保證代碼順利運行就行

image-20220526103318093

賦值之後重新運行可以看到載入成功

image-20220526103345375

回溯源碼理一下整個加密流程,將其串起來結合copy的加密演算法寫個解密過程。發現和抓到的密碼是不一樣的,而且每次運行結果都不一樣,懷疑該加密跟時間有關

image-20220526104621935

在某東的登錄再用同樣的密碼登錄幾次嘗試,發現每次加密後的密碼也不一樣

image-20220526104654874

image-20220526104705667

反調試解混淆

一些網站可能通過監視屏幕的寬高比,判斷是否開啟開發者工具而禁止調試,或者直接禁用F12。

這種情況以PM2.5實時查詢|PM2.5歷史數據查詢|PM2.5全國城市排名|PM2.5霧霾地圖|中國空氣質量線上監測分析平臺|真氣網 (aqistudy.cn)為例舉例說明如何反調試跳出debugger()函數

image-20220526110805867

嘗試F12開啟調試視窗,直接被彈窗禁止了。這樣就需要手動通過更多工具-開發者人員工具調出調試台。同時將其分離成獨立視窗

image-20220526110818945

可以看見因為開始調試直接進入debugger反調試

image-20220526110955108

可以發現這個網頁因為反調試網路啥信息都沒有了(Φ皿Φ),這個時候可以通過視窗旁邊的調用堆棧看看這個debugger是從哪裡彈出來的

image-20220526111759296

發現是一個txsdefwsw和c,進去看一下

image-20220526111950633

追溯c的源碼過去看看沒有發現什麼有用的信息,感覺程式棧不完整沒有捕獲到相關函數。重新刷新頁面一下,發現新內容

image-20220526131827671

(_)發現了反調試的代碼,發現首頁的源碼。這裡還包含一些其他反調試的檢測。那麼就是在這裡觸發的debug。首頁當檢測到非法調試之後,觸發txsdefwsw()函數,全局搜索一下這個函數。

image-20220526131851285

發現這裡是通過eval()函數去執行一個function,(eval函數可以執行表達式,具體深入可以自行google)根據註釋可以發現這裡有一個debug的檢測,那麼看一下這兩個eval里的function是什麼。

image-20220526132427283

控制台通過var列印一下這兩個function的返回值,可以發現兩個eval分別執行endebug和txsdefwsw兩個函數。

image-20220526133402582

image-20220526143514050

image-20220526143557326

思路就是通過替換這個eval函數的執行函數,讓他執行一個空值的函數從而跳過這裡的debug函數。可以用工具reres去替換這個js鏈接為本地經過改寫的js文件。這樣網頁執行時,調用同名的空函數則不會觸發debug。

image-20220526143611477

image-20220526143626478

可以發現替換後源碼的js變成了這樣

image-20220527140449510

再次刷新還是debug,(╯▔皿▔)╯跟過去發現這裡還有一層反調試

image-20220527142212473

console列印一下,發現這裡再次調用了首頁的檢測邏輯

image-20220527142357030

但這裡經過多次刷新後發現,此處的eval()表達式執行的函數名是隨機變換的。因此前步涉及到的直接替換函數在這裡就不起作用了。這個時候就需要跟進函數,發現這個隨即名稱的函數是針對隨機輸入的固定base64加密,最後輸出debug函數

那麼此處只要單步調試,打斷點定在程式執行加密前,將輸入賦空值,這樣輸出必定為空,則可以繞過debug

其他

從慕課網的源碼里也發現了一些信息,諸如內網ip或者一些網站設置的一些弱密碼。(此類信息一般很好分析,大多源碼里會被加上註釋╮(╯▽╰)╭)

image-20220526144753813

同時JS混淆還包括多種加密,加密方法需要視具體源碼所定,其加密可能是傳統加密亦或者編寫者進行過一些調整。這些都需要調試者去嘗試判斷

具體分析依舊回歸源碼,諸如實驗一的某東JSencode加密

防護

以IPS特征庫登錄界面登錄邏輯為例

image-20220527113411921

首先看一下一個成功登錄的流量

image-20220602091141109

再看一下失敗登錄的流量,可以發現沒有調試的情況下流量不會向相關js文件發送請求

image-20220527164925675

走正常的登錄流程,可以發現,只是向登錄介面發送了一個post請求

image-20220602091237436

分析payload發現發送Username和Password,本例沒有加密(常規會對此加密),為作說明同樣全局搜索Password,尋找可疑處打斷點

image-20220527115855569

當給源碼打上斷點後,再次刷新登錄,可以發現向斷點處介面jquery.min.map也發送了請求。根據源碼註釋也也可以發現,我們打斷點的位置正好對應了請求的url

image-20220527113714521

image-20220602091334186

防護

針對此類調試情況,可以根據流量是否向項目結構內js網頁發送請求判斷是否正在js調試。此類防護IPS規則不好寫受用戶自定義的命名限制,建議自定義添加規則,針對隱私/重要的js的url地址提取content進行JS調試的防護

結合上文提到的反調試,即跳出eval賦值表達式函數。替換網頁js源碼。

防護

針對反調試函數賦空可以總結eval(function xxx{})此類格式,發現替換js頁面請求里會沒有這個url

嘗試替換ace-extra.min.js

替換頁面,沒有該url

image-20220602091421892

無替換,出現

image-20220602091458230

嘗試通過瀏覽器的覆蓋功能,可以出現修改的請求,但流量內依舊沒有改寫後的字元

image-20220527145645472

image-20220527145655393

因此,對於替換類的解混淆,無法提取規則。需要依據上文提到的特殊url訪問去進行js解混淆的防護

但在調試時,response會返回經過混淆的代碼。可以根據混淆代碼的一些固定格式去提取特征,進行防護

總結

JS混淆一般被用於反爬蟲和信息保護,不過只要善用工具和足夠的耐心,結合一些工具就可以從源碼里收穫很多的有用信息

對於攻擊者進行js調試時,網站的代碼需要考慮從如何反調試的角度去思考

作為js解混淆的使用者出發,需要瞭解和積累解混淆的經驗,才能應對更多的反爬蟲,收集到更多的爬蟲信息


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Oracle數據類型 簡要說明 字元類型 char和varchar2,可表達任何字元串 數字類型 number(m,n),可表達任何數字,m是數字的精度,n是小數點後的位數,如果n為0則表示是一個整數。 日期類型 date,存放日期和時間,包括年(yyyy)、月(mm)、日(dd)、小時(hh24) ...
  • 目錄一、關閉MySQL服務1、win+R打開運行,輸入services.msc回車2、服務里找到MySQL並停止二、卸載MySQL軟體1、打開控制模板--卸載程式--卸載MySQL相關的所有組件三、刪除MySQL在物理硬碟上的所有文件1、刪除MySQL的安裝目錄(預設在C盤下的Program Fil ...
  • 原創力作flutter3+getX+window_manager仿Mac桌面系統平臺Flutter-MacOS。 flutter3_macui基於最新跨端技術flutter3.19+dart3.3+window_manager+system_tray構建的一款桌面端仿MacOS風格os系統項目。支持 ...
  • 一、路由導航 路由導航是指在應用程式中通過路徑導航定位到特定頁面的過程。路由導航的實現通常採用路由器(router)來進行管理,路由器根據路徑的不同值將用戶請求導向到不同的頁面。 在HarmonyOS中路由導航主要有:頁面跳轉、頁面返回和頁面返回前增加一個詢問框 1.編程路由 1.1 頁 ...
  • 一、Menu組件 Menu組件是一種常見的用戶界面(UI)控制項,用於在移動應用程式中顯示可選項列表。它通常由一系列鏈接或按鈕組成,以幫助用戶導航和選擇所需的操作。Menu組件可以在水平或垂直方向上呈現,可以是下拉或彈出式的,並可以在需要時動態更新其內容。通常,Menu組件可以在導航欄、側邊欄或上 ...
  • 描述 我們在使用 node 的 npm 下載依賴的時候,往往下載速度很慢,那是因為 npm 預設的是 npm 處於國外的官方鏡像源。所以需要切換到國內的鏡像源來加速依賴下載。所以本文推薦一款簡單好用 npm 鏡像源管理器,可以方便開發者管理自己的鏡像源。 推薦:npm-registry-nrs ...
  • vscode插件liveserver增加對thymeleaf模板的簡單支持 背景 vscode+liveserver開發時,多個頁面引用的公用靜態資源在每個頁面都寫一個遍比較麻煩,想讓liveserver支持簡單的thymeleaf語法,只要能把公用資源抽出來單獨放到一個文件中聲明即可。 網上找了一 ...
  • Scriptable 腳本合集 iOS桌面組件神器(Scriptable)原創腳本,精美作品收集、分享! 如果喜歡,歡迎點個 ⭐️ Star ⭐️ 給予小支持,感謝您的使用!喜歡這個項目?有好的腳本?請考慮留言來幫助完善它! 如果您使用過程中發現有問題或可以改進的流程,請提出 Issue 或 Pul ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...