- A+
抓了半年多的裁判文书突然停了,查看log发现HTTP响应码全是202,分析页面发现原网站在cookie上做了加密。经分析正确的请求流程如下
1. 请求首页,返回两个set-cookie响应头,cookie名称分别为FSSBBIl1UgzbN7N80S
, FSSBBIl1UgzbN7N80T
,响应码为202。响应体中有三段不明代码
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> </head> <!-- 1. meta['content']意义不明 --> <body><meta name="renderer" content="webkit"><meta id="9DhefwqGPrzGxEp9hPaoag" content="{qqqqqr0.YUQeBgClraT6.dA28Ej3I1UKSbXlJ8NIUKxHc2UmGAqqkknLqqqqhvefgTey0JSvqkxJqkqLWqqqqk130r0HMvpQpmzDUlrUpmQ83oYJA2wDVVYY1mrWY0f3s2ptVlNp1aqqqqt1073741856VIJRJg9UB18SE49UPp8fH8h5lq2Y8psLYtXTCqAg2iGacfA3Tt9GOFqTs8GTGDCQwHGz5oKqQHAe4oO7h8mEBKcWqqqqhTrB4MbBgeoxqKGKR2zkG8iNtX0bwqfYF_uGc80qqqqr0qqDe5191aqqqqhb6rmecbj1Zrr4q{WYozbAKrlMPE9mOVpEiZygKFg3hxX_u.SidW64uobWi227DF_MXET5u6NJI74gv.axiEcePMvHIL_qqqqYDUvHttHp6whZmAp7KOQtW}{tPgKp0pIU0AtpYJcsagEUlJps2JlmANpsGAEYVpUpYTioaNA1TxHIAGCFAr7xGAWwAl8k67pUumcIlQ.D6xnhSSgVfzZDmg4DfzoD6a5KcrDWfEURnTmlm9dRn2PWS7TAOwGmVRXKcx9JULM3rRrHpLfrkJrlK3DlPwMrl9cF0gtD07cUaLUV20v8YShrYWSMSRZD90l8OSvxbzc8saEAG|qqU7aWVE0A9yYlv7CMX9T4THtVhmE4UX0Fjfh_vIdsiNUgCd3JJSPa6hZsZfcgVuLJzRXgkkhAWrC4uoKF82V.vbXKITtyVsSpIJvy9.SY5zYeuUlWzJG_lkrJwYoaucRQQg6y96Ch5rsnbU71HJ.nkkD85xjOoscMWRbGCiHUXy74mDJRW00e0.UV8f7ZDsBQIzOfuDJYjeBdCbXMhlCyCjW8hzMPlbWR_w.LKKFwywal7445"><!--[if lt IE 9]><script>document.createElement("section")</script><![endif]--> <!-- 2. 意义不明的js文件 --> <script type="text/javascript" src="4QbVtADbnLVIc/c.FxJzG50F.js%3FD9PVtGL=e5191a"></script> <script type="text/javascript"> <!-- 3. 主要加密js代码,省略 --> </script> <script type='text/javascript' r='m'> _$hr('ZoA8'); </script> <!-- 4. 这段代码的作用是重新请求当前页面,分析时注释掉 <script type="text/javascript">window.onload=function(){var _$n6=document.getElementById(_$fv('ondoal'));_$nR(_$n6.name,_$vD(_$n6,_$zb('HvzED2ExKa')));};</script> --> </body> </html> <script type="text/javascript">_$e3();</script>
2. 浏览器带着两个cookie再次请求原网页,响应码为200,得到正常页面,这次请求中cookieFSSBBIl1UgzbN7N80T的值与响应头中设置的值不一样,并且在一段时间后会自动更新。另外修改响应,删除Set-Cookie: FSSBBIl1UgzbN7N80T=...,浏览器在新的请求中仍会有FSSBBIl1UgzbN7N80T的cookie,猜测这个cookie的值是通过js计算得到,且会定时刷新。
也就是说只要破解cookie FSSBBIl1UgzbN7N80T的算法就可以了。
另外网上有人说这是用的瑞数的产品,介绍里有这样一段话
>通过对服务器网页底层代码的持续动态变换,令攻击者无法读懂攻击目标
也就是说这个产品只提供了动态混淆功能,真实的加密代码还是源网站实现的。也就是说只要提取出原来的加密逻辑就可以了。
## 准备工作
### 搭建离线分析环境
因为加密Js代码每次请求变量名都不一样,在线分析会很不方便,所以需要将文件离线下载到本地,再进行分析。
wget --mirror --page-requisites --adjust-extension --no-parent --convert-links http://wenshu.court.gov.cn/Index
python -m SimpleHTTPServer 8081
之后在浏览器中访问http://localhost:8081进行分析。
### 修改页面
为了分析方便,我们把加密js提取成独立的文件(保存为outer.js, 并在文件开头加上debugger;代码下断点), 并注释跳转代码。修改后Index.html代码如下
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> </head> <body><meta name="renderer" content="webkit"><meta id="9DhefwqGPrzGxEp9hPaoag" content="{qqqqqr0.YUQeBgClraT6.dA28Ej3I1UKSbXlJ8NIUKxHc2UmGAqqkknLqqqqhvefgTey0JSvqkxJqkqLWqqqqk130r0HMvpQpmzDUlrUpmQ83oYJA2wDVVYY1mrWY0f3s2ptVlNp1aqqqqt1073741856VIJRJg9UB18SE49UPp8fH8h5lq2Y8psLYtXTCqAg2iGacfA3Tt9GOFqTs8GTGDCQwHGz5oKqQHAe4oO7h8mEBKcWqqqqhTrB4MbBgeoxqKGKR2zkG8iNtX0bwqfYF_uGc80qqqqr0qqDe5191aqqqqhb6rmecbj1Zrr4q{WYozbAKrlMPE9mOVpEiZygKFg3hxX_u.SidW64uobWi227DF_MXET5u6NJI74gv.axiEcePMvHIL_qqqqYDUvHttHp6whZmAp7KOQtW}{tPgKp0pIU0AtpYJcsagEUlJps2JlmANpsGAEYVpUpYTioaNA1TxHIAGCFAr7xGAWwAl8k67pUumcIlQ.D6xnhSSgVfzZDmg4DfzoD6a5KcrDWfEURnTmlm9dRn2PWS7TAOwGmVRXKcx9JULM3rRrHpLfrkJrlK3DlPwMrl9cF0gtD07cUaLUV20v8YShrYWSMSRZD90l8OSvxbzc8saEAG|qqU7aWVE0A9yYlv7CMX9T4THtVhmE4UX0Fjfh_vIdsiNUgCd3JJSPa6hZsZfcgVuLJzRXgkkhAWrC4uoKF82V.vbXKITtyVsSpIJvy9.SY5zYeuUlWzJG_lkrJwYoaucRQQg6y96Ch5rsnbU71HJ.nkkD85xjOoscMWRbGCiHUXy74mDJRW00e0.UV8f7ZDsBQIzOfuDJYjeBdCbXMhlCyCjW8hzMPlbWR_w.LKKFwywal7445"><!--[if lt IE 9]><script>document.createElement("section")</script><![endif]--><script type="text/javascript" src="4QbVtADbnLVIc/c.FxJzG50F.js%3FD9PVtGL=e5191a"></script> <script type="text/javascript" src="outer.js"> </script> <script type='text/javascript' r='m'> _$hr('ZoA8'); </script> <!-- <script type="text/javascript">window.onload=function(){var _$n6=document.getElementById(_$fv('ondoal'));_$nR(_$n6.name,_$vD(_$n6,_$zb('HvzED2ExKa')));};</script> --> </body> </html> <script type="text/javascript">_$e3();</script>
### function hook
js中有几段很长的杂乱字符串,猜测可能是加密的js代码。而一般释放加密的js代码都是通过eval函数,所以我们需要hook它。在console面板中执行如下代码
orig = window.eval; window.eval=function(str){debugger;orig(str);} // 这段代码防止反hook的检测 window.eval.toString = function (){return orig.toString();}
另外为了分析meta#9DhefwqGPrzGxEp9hPaoag的content属性,执行如下代码添加断点
var dom_get_element = document.getElementById; document.getElementById = function(id){if(id=="9DhefwqGPrzGxEp9hPaoag") debugger; dom_get_element(id);}
### 监视cookie变动
在Watch面板中添加document.cookie监视cookie的变动。
## 分析过程
### 释放变量替换表
在浏览器中打开http://localhost:8081/wenshu.court.gov.cn/, 运行到out.js第一行会在debugger处触发断点。单步跟进,在第18行触发了eval函数
_$tU[_$xH(101, 118, 97, 108)](_$bY(_$fv('mScBdId1eg4by76`TeugShBfzo.rNcva2COdfAI`wtMiRgUsYisepuH`Juhsxro_m`karh`l0gGtJSFr}ne`oremihar[o)elavpnyisclut`acslre `allcdlwvt` phnt`moOacienmrtnCop`JehTdmPo`tlsston`epvTls$lt#`_omatSeooaee`___suger@eeaaultv`.hprrtSgetcijeIntmeeb``SOBe`nr`ao``rcttt`p:`oxtcdconriSiene esyaokopppyirdAxAfSOFjrcb.un`emtTdec`iAtaecaeuatl`vd`beg$M`tt`bos`uo`tcilg`fnorCsatCedo`rphl``opli``tsneltNfNizeU liBhScFiutoe`a5`Za_lgrCp0aneafXn7tjoLeba9(P{Gn8tQvFc3dK]V`1xAzi5WFtqEcDlt___`rgmrvtIlec`l_sc_a`sayretcIneo`euwe3ixtysgmbd6aV`_owtCc`latce'), _$fv('fMXn49KE26OfLRGSmXasroZMmPl16kBsoQjPPs8oqAhSbglTjrijvsmkkafjulimrcbkhgmmemcnavgjgplhmafjpiqkahdk')));
这会在浏览器中释放如下代码
var _$mM="FSSBBIl1UgzbN7N",_$gn="length",_$k9="floor",_$cE="charCodeAt",_$m6="string",_$lf="slice",_$uO="uu",_$i2="substr",_$rK="_$",_$b4="Math",_$hX="log",_$mf="toString",_$jZ="fromCharCode",_$ir="apply",_$va="split",_$mm="assert failed with condition: ",_$kG="stack",_$fL="pop",_$jR="indexOf",_$aS="Object.InjectedScript.evaluate",_$kX="@debugger",_$ss="evaluate",_$jo="charAt",_$rM="getTime",_$el="number",_$cb="FSSBA",_$ah="Array",_$gq="prototype",_$g8="execScript",_$lP="eval",_$hs="call",_$po="replace",_$jA="functioneval(){[nativecode]}",_$vS="FxJzG50F",_$ng="qrcklmDoExthWJiHAp1sVYKU3RFMQw8IGfPO92bvLNj.7zXBaSnu0TC6gy_4Ze5d",_$mT="document",_$kP="location",_$h1="random",_$kk="setTimeout",_$is="setInterval",_$jQ="$_ts",_$aP="localStorage",_$mj="___ts___",_$fo="removeItem",_$pB="__#classType",_$q6="console",_$al="wP3dxhyJgpbC6tVm_ewcCO",_$dm="match";
我们将这段释放的代码保存到文件reference_map.js。
### 释放inner.js
在out.js中删除第18行释放代码,并把得到的reference_map.js引用进Index.html。继续调试,在180行有如下代码
var _$n6 = _$zk[_$vS]; if (_$n6) { _$zk[_$vS] = false; _$tM(_$n6); }
其中_$n6的值为4QbVtADbnLVIc/c.FxJzG50F.js中那段很长的字符串。猜测_$tM函数将对这个字符串解密并释放。
在440行代码(_$tM函数中)_$mc(_$eG(_$qh.join('')));执行后,会发现cookie值被更新了。
分析发现_$eG(_$qh.join(''))的返回值一段文本形式的js代码,而_$mc函数的作用就是执行这段js。我们将这段js保存到文件inner.js。
### 调试inner.js
在Index.html中引入inner.js,去掉outer.js的断点并注释掉440行代码,调试inner.js, 发现inner.js释放了一个更大的变量替换表,其中cookie引用_$f0 = "cookie", 通过在inner.js搜索该变量,在所有更改cookie的地方下断点,进行调试可以找出cookie的计算逻辑。
cookie的值通过解密meta#9DhefwqGPrzGxEp9hPaoag的content属性得到。
在804行的 _$oc函数中有如下代码
function _$oc() { _$fM(_$sw, _$g9, _$u9); _$fM(_$sw, _$da, _$v9); _$fM(_$sw, _$kp, _$bV); _$fM(_$sw, _$aE, _$wV); _$fM(_$sw, _$vL, _$xm); _$fM(_$sw, _$sS, _$ta); _$fM(_$sw, _$s3, _$hb); _$fM(_$sw, _$an, _$xx); _$fM(_$sw, _$bJ, _$tI); _$fM(_$sw, _$qB, _$wE); _$fM(_$tU, _$is, _$sz); if (_$sw[_$bo]) { _$fM(_$sw, _$tf, _$sz); _$fM(_$sw, _$cl, _$sz); _$fM(_$sw, _$vQ, _$sz); } _$fM(_$tU, _$sl, _$vu); // window.addEventListener('load', _$tk), 即在页面加载完毕后更新一次cookie _$fM(_$tU, _$is, _$tk); // 每50秒更新一次cookie _$tU[_$hD](function() { _$uD(10); }, 50000);
根据变量替换表得出,这些代码注册了各种监听事件,并且通过interval定时更新cookie。
#### url中的MmEwMD参数
在每个请求的url后面都有个MmEwMD参数,
在console面板中执行
var xhr = new window.XMLHttpRequest(); xhr.open('POST','/hello',true); xhr.send('abc');
会在请求的Url后面自动添加上这个参数
http://localhost:8081/hello?MmEwMD=1zXMRFfiFy00rBpdSoJ2cJ4EvS6mQ1dvl5jZLrqB…oRVu5t.x8Y3GVQLmqdG.lmuE2snbEk.YHif4rmHwOtKQoTiHpODCa6HikgWg1X2S._aq8rKTLr
猜测当前页面中的xhr被hook了。
通过查找inner.js释放的变量替换表并分析,定位到inner.js的8107行代码
function _$xc(_$v8, _$vz) { var _$xB = []; var _$mE = _$qS(6); if (_$mE) { _$xB = _$xB[_$lt](_$vz); _$xB.push(_$c9(_$v8) ? 1 : 0); var _$vT = _$h6() + _$mE + _$cR(_$xB); _$vT += _$tc(_$vT); return _$y5 + '=' + _$vT; } else return _$y5 + '='; }
这段代码的作用是计算请求url中的MmEwMD参数值。通过hook XMLHttpRequest的open与send函数,在发起异步请求时在url后面自动添加这个参数。
function _$dJ() { var _$v8 = _$tU[_$ld]; var _$v8 = window.XMLHttpRequest; if (_$v8) { var _$vz = _$v8['prototype']; if (_$vz) { _$jv = _$vz['open']; _$ca = _$vz['send']; // hook open函数,在调用open时计算token并保存到sig属性 _$vz['open'] = function() { arguments[1] = _$aY(arguments[1]); this['sig'] = arguments[1]; return _$jv['apply'](this, arguments); } ; // 调用 send发起异步请求时,将sig添加到url。 _$vz['send'] = function() { arguments[0] = _$uG(arguments[0], this['sig']); return _$ca['apply'](this, arguments); } ; } }
## 总结
网站的加密逻辑大致如下:
1. outer.js释放加密参数,并解密c.FxJzG50F.js文件,得到静态混淆的js代码(inner.js)
2. 执行inner.js,解密标签meta#9DhefwqGPrzGxEp9hPaoag的content属性得到一个数组,再加上outer.js计算的两个参数计算FSSBBIl1UgzbN7N80T的值;
注册load事件在加载完毕后添加interval定时,根据已有的cookie值计算得到新cookie值(通过_$tk函数);
hook XMLHttpRequest的open与send函数,为页面中的请求自动添加token。
绕过方案:
因为cookie是通过定时自动更新的,而且请求的token也是通过hook在发起请求时自动添加的,所以如果让浏览器先加载首页,在首页中使用xhr异步请求,请求的cookie值与token会自动带上。所以我们可以找到一个可编程的浏览器就行了。
- 我的微信
- 这是我的微信扫一扫
- 我的微信公众号
- 我的微信公众号扫一扫
2018年8月28日 上午8:43 沙发
<p>老哥,666,裁判文书网我一直从安卓端爬取,但是在某个网站也遇到瑞数的js了,老哥,来看看不qq1816778518</p>
2019年12月5日 上午2:57 板凳
<p>请问有代码吗?</p>