瑞数(裁判文书)js的加密分析

  • A+
所属分类:脚本语言

抓了半年多的裁判文书突然停了,查看log发现HTTP响应码全是202,分析页面发现原网站在cookie上做了加密。经分析正确的请求流程如下
1. 请求首页,返回两个set-cookie响应头,cookie名称分别为FSSBBIl1UgzbN7N80SFSSBBIl1UgzbN7N80T,响应码为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会自动带上。所以我们可以找到一个可编程的浏览器就行了。

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

目前评论:0   其中:访客  0   博主  0

    • 游客 6

      <p>老哥,666,裁判文书网我一直从安卓端爬取,但是在某个网站也遇到瑞数的js了,老哥,来看看不qq1816778518</p>

      • 游客 6

        <p>请问有代码吗?</p>