警告:该随笔内容仅用于合法范围下的学习,不得用于任何商业和非法用途,不得未经授权转载,否则后果自负。

首先是需要解密的网站:https://www.aqistudy.cn/historydata/monthdata.php?city=%E5%8C%97%E4%BA%AC,在这个网址尝试按鼠标右键/F12会跳出禁止调试对话框。因此为了进入F12界面,可以右键后再按f12.

然而该网站会检测到f12的打开,并立即改写网页内容。而在f12里面,会频繁跳出debugger,只能查出一段含有["constructor"]("debugger")())的混淆后的SOURCE代码,如下(后面我会我用省略号省去大多数加密后的枯燥的内容)

var debugflag = false;
  document.onkeydown = function() {
    if ((e.ctrlKey) && (e.keyCode == 83)) {
      alert("检测到非法调试,CTRL + S被管理员禁用");
      return false;
    }
  }
  document.onkeydown = function() {
    var e = window.event || arguments[0];
    if (e.keyCode == 123) {
      alert("检测到非法调试,F12被管理员禁用");
      return false;
    }
  }
  document.oncontextmenu = function() {
    alert('检测到非法调试,右键被管理员禁用');
    return false;
  }
  !function () {
    if (window.outerWidth - window.innerWidth > 210 ||
     window.outerHeight - window.innerHeight > 210) {
      $('#body').html('检测到非法调试, 请关闭调试终端后刷新本页面重试!<br/>Welcome for People, Not Welcome for Machine!<br/>');
      debugflag = true;
    }
    const handler = setInterval(() => {
      if (window.outerWidth - window.innerWidth > 210 ||
       window.outerHeight - window.innerHeight > 210) {
        $('#body').html('检测到非法调试, 请关闭调试终端后刷新本页面重试!<br/>Welcome for People, Not Welcome for Machine!<br/>');
        debugflag = true;
      }
      const before = new Date();
      (function() {}
        ["constructor"]("debugger")())
      const after = new Date();
      const cost = after.getTime() - before.getTime();
      if (cost > 50) {
        debugflag = true;
        document.write('检测到非法调试, 请关闭调试终端后刷新本页面重试!<br/>');
        document.write("Welcome for People, Not Welcome for Machine!<br/>");
      }

    }, 2000)
  }();

所以无法得知产生debugger的根本源码在哪儿。

经过一番折腾,最终猜测https://www.aqistudy.cn/historydata/resource/js/deJvi1NlJdQpH.min.js?t=1631459101这个代码就是要找的。代码内容如下:

eval(function(p,a,c,k,e,d){e=function(c){return c};if(!''.replace(/^/,String)){while(c--){d[c]=k[c]||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}('2(0(0(\'1\')))',3,3,'dweklxde|WT省略号XA5|eval'.split('|'),0,{}))

这是一段超长的代码,连我的油猴编辑器都变卡了。现在分析一下简化后的代码(把eval去掉了):

(function ANM(p, a, c, k, e, d) {
        e = function(c) {
            return c
        };
        if (!''.replace(/^/, String)) {
            while (c--) {
                d[c] = k[c] || 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
    })('2(0(0(\'1=\')))', 3, 3, 'dweklxde|WT省略号Dk|eval'.split('|'), 0, {})

这是一个传入6个形参的匿名函数,定义后就立即执行(传入实参,实例化)。经过在StackOverflow上的检索,我知道了if (!''.replace(/^/, String)) 的作用,大致是判断replace在当前浏览器的兼容性如何的,这不是重点,当下还是解密为重。把原来的密文传入后,运行的结果为:

"eval(dweklxde(dweklxde('WT省略号Dk=')))"

代码中再次出现了eval,此外还出现了两次dweklxde这段申必代码。故我在网站全套源码里检索了这个申必代码,并惊喜的发现了其定义:

function dweklxde(tsdx){
    var b=new Base64();
    return b.decode(tsdx)
}

把eval去掉后,在原网页控制台执行dweklxde(dweklxde(残余的密文)),得到了以下输出:

const  askhMzDQ7qNo = "aTtcZsDM6Q4W30w5";//AESkey,可自定义
const  asium9nuSIrA = "bf4STpo4157Vwqwb";//密钥偏移量IV,可自定义

const  ackDrdNRViAM = "dbxOAFBzWKAtR7G8";//AESkey,可自定义
const  aciyyIkBh3oq = "fNEcReKtHOhYss8e";//密钥偏移量IV,可自定义

const  dskcfiXeCyq7 = "h2cTaMqHmG86zGgs";//DESkey,可自定义
const  dsiRXgqLSp5I = "xbnl4T59FFDkdlBc";//密钥偏移量IV,可自定义

const  dckUcY5kLrAk = "oihJrNyLdihbAQpu";//DESkey,可自定义
const  dcihFBYD1foW = "pGOltTq5bcJfOBBY";//密钥偏移量IV,可自定义

const aes_local_key = 'emhlbnFpcGFsbWtleQ==';
const aes_local_iv = 'emhlbnFpcGFsbWl2';

var BASE64 = {
    encrypt: function(text) {
        var b = new Base64();
        return b.encode(text);
    },
    decrypt: function(text) {
        var b = new Base64();
        return b.decode(text);
    }
};

var DES = {
 encrypt: function(text, key, iv){
    var secretkey = (CryptoJS.MD5(key).toString()).substr(0, 16);
    var secretiv = (CryptoJS.MD5(iv).toString()).substr(24, 8);
    secretkey = CryptoJS.enc.Utf8.parse(secretkey);
    secretiv = CryptoJS.enc.Utf8.parse(secretiv);
    var result = CryptoJS.DES.encrypt(text, secretkey, {
      iv: secretiv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    });
    return result.toString();
 },
 decrypt: function(text, key, iv){
    var secretkey = (CryptoJS.MD5(key).toString()).substr(0, 16);
    var secretiv = (CryptoJS.MD5(iv).toString()).substr(24, 8);
    secretkey = CryptoJS.enc.Utf8.parse(secretkey);
    secretiv = CryptoJS.enc.Utf8.parse(secretiv);
    var result = CryptoJS.DES.decrypt(text, secretkey, {
      iv: secretiv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    });
    return result.toString(CryptoJS.enc.Utf8);
  }
};

var AES = {
  encrypt: function(text, key, iv) {
    var secretkey = (CryptoJS.MD5(key).toString()).substr(16, 16);
    var secretiv = (CryptoJS.MD5(iv).toString()).substr(0, 16);
    // console.log('real key:', secretkey);
    // console.log('real iv:', secretiv);
    secretkey = CryptoJS.enc.Utf8.parse(secretkey);
    secretiv = CryptoJS.enc.Utf8.parse(secretiv);
    var result = CryptoJS.AES.encrypt(text, secretkey, {
      iv: secretiv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    });
    return result.toString();
  },
  decrypt: function(text, key, iv) {
    var secretkey = (CryptoJS.MD5(key).toString()).substr(16, 16);
    var secretiv = (CryptoJS.MD5(iv).toString()).substr(0, 16);
    secretkey = CryptoJS.enc.Utf8.parse(secretkey);
    secretiv = CryptoJS.enc.Utf8.parse(secretiv);
    var result = CryptoJS.AES.decrypt(text, secretkey, {
      iv: secretiv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    });
    return result.toString(CryptoJS.enc.Utf8);
  }
};

var localStorageUtil = {
  save: function(name, value) {
    var text = JSON.stringify(value);
    text = BASE64.encrypt(text);
    text = AES.encrypt(text, aes_local_key, aes_local_iv);
    try {
      localStorage.setItem(name, text);
    } catch (oException) {
      if (oException.name === 'QuotaExceededError') {
        console.log('Local limit exceeded');
        localStorage.clear();
        localStorage.setItem(name, text);
      }
    }
  },
  check: function(name) {
    return localStorage.getItem(name);
  },
  getValue: function(name) {
    var text = localStorage.getItem(name);
    var result = null;
    if (text) {
      text = AES.decrypt(text, aes_local_key, aes_local_iv);
      text = BASE64.decrypt(text);
      result = JSON.parse(text);
    }
    return result;
  },
  remove: function(name) {
    localStorage.removeItem(name);
  }
};

// console.log('base64', BASE64.encrypt('key'));

function dAel1E5FtZ8(pf5OVah) {
  pf5OVah = DES.decrypt(pf5OVah, dskcfiXeCyq7, dsiRXgqLSp5I);
  return pf5OVah;
}

function dcLpsq6sB7(pf5OVah) {
  pf5OVah = AES.decrypt(pf5OVah, askhMzDQ7qNo, asium9nuSIrA);
  return pf5OVah;
}

function gEE7JrZUSW45M88T(key, period) {
    if (typeof period === 'undefined') {
        period = 0;
    }
    var d = DES.encrypt(key);
    d = BASE64.encrypt(key);
    var data = localStorageUtil.getValue(key);
    if (data) { // 判断是否过期
        const time = data.time;
        const current = new Date().getTime();
        if (new Date().getHours() >= 0 && new Date().getHours() < 5 && period > 1) {
            period = 1;
        }
        if (current - (period * 60 * 60 * 1000) > time) { // 更新
           data = null;
        }
        // 防止1-5点用户不打开页面,跨天的情况
        if (new Date().getHours() >= 5 && new Date(time).getDate() !== new Date().getDate() && period === 24) {
           data = null;
        }
    }
    return data;
}

function oshxcxc8Lo(obj) {
    var newObject = {};
    Object.keys(obj).sort().map(function(key){
      newObject[key] = obj[key];
    });
    return newObject;
}
function dhv7s6TfbwDPIepj215O(data) {
    data = BASE64.decrypt(data);
    data = DES.decrypt(data, dskcfiXeCyq7, dsiRXgqLSp5I);
    data = AES.decrypt(data, askhMzDQ7qNo, asium9nuSIrA);
    data = BASE64.decrypt(data);
    return data;
}
var pNOZW4pEhNH = (function(){

function oshxcxc8Lo(obj){
    var newObject = {};
    Object.keys(obj).sort().map(function(key){
        newObject[key] = obj[key];
    });
    return newObject;
}
return function(mOAJWRjCd, oyNl2g){
    var aRT5 = '3945282e47e176b3af7c4cf62edf0cf5';
    var cXlfM = 'WEB';
    var t1PYHUr = new Date().getTime();

    var pf5OVah = {
      appId: aRT5,
      method: mOAJWRjCd,
      timestamp: t1PYHUr,
      clienttype: cXlfM,
      object: oyNl2g,
      secret: hex_md5(aRT5 + mOAJWRjCd + t1PYHUr + cXlfM + JSON.stringify(oshxcxc8Lo(oyNl2g)))
    };
    pf5OVah = BASE64.encrypt(JSON.stringify(pf5OVah));
    pf5OVah = DES.encrypt(pf5OVah, dckUcY5kLrAk, dcihFBYD1foW);
    return pf5OVah;
};
})();

function spRLIQarqGIFjayN1z(mOAJWRjCd, o8WGj0NwLH, c6yqLt8Q0, pVBXfIU) {
    const krya = hex_md5(mOAJWRjCd + JSON.stringify(o8WGj0NwLH));

    const dqlC4 = gEE7JrZUSW45M88T(krya, pVBXfIU);
    if (!dqlC4) {
        var pf5OVah = pNOZW4pEhNH(mOAJWRjCd, o8WGj0NwLH);
        $.ajax({
            url: 'api/historyapi.php',
            data: { hHUxtR6cG: pf5OVah },
            type: "post",
            success: function (dqlC4) {
                dqlC4 = dhv7s6TfbwDPIepj215O(dqlC4);
                oyNl2g = JSON.parse(dqlC4);
                if (oyNl2g.success) {
                    if (pVBXfIU > 0) {
                      oyNl2g.result.time = new Date().getTime();
                      localStorageUtil.save(krya, oyNl2g.result);
                    }
                    c6yqLt8Q0(oyNl2g.result);
                } else {
                    console.log(oyNl2g.errcode, oyNl2g.errmsg);
                }
            }
        });
    } else {
        c6yqLt8Q0(dqlC4);
    }
}

虽然现在看不懂解密后的代码什么意思,但是感觉很厉害的样子!

真是悲剧啊,刚解密了前几天的代码,今儿一看,又更新了新的密文。

我现在掌握的最新情报是,这个网页每隔600秒就变更一次该加密的js文件,而且我已经发现了3种不同的加密模式(但解密后的密文根本上都和这个一致)

记载火狐浏览器下的一次新手级的js解密工作的更多相关文章

  1. 解决文件上传插件Uploadify在火狐浏览器下,Session丢失的问题

    因为在火狐浏览器下Flash发送的请求不会带有cookie,所以导致后台的session失效. 解决的方法就是手动传递SessionID到后台. $("#fileresultfiles&qu ...

  2. laydate时间组件在火狐浏览器下有多时间输入框时只能给第一个输入框赋值的问题

    遇到的问题: laydate时间组件在火狐浏览器下有多时间输入框时只能给第一个输入框赋值的问题(safari下也有同样问题); 解决办法: 给laydate绑定id; 解决前代码: <input ...

  3. 谷歌、火狐浏览器下实现JS跨域iframe高度自适应的完美解决方法,跨域调用JS不再是难题!

    谷歌.火狐浏览器下实现JS跨域iframe高度自适应的解决方法 导读:今天开发的时候遇到个iframe自适应高度的问题,相信大家对这个不陌生,但是一般我们都是在同一个项目使用iframe嵌套页面,这个 ...

  4. 火狐浏览器下点击a标签时出现虚线的解决方案

    1.兼容性问题 火狐浏览器下点击a标签时出现虚线 2.解决方案 a:focus { outline: none;}

  5. firebreath 在谷歌和火狐浏览器下的调试 以及打包

    在寻找插件开发资料的过程中找到了一个开发浏览器插件的开源项目——firebreath firebreath的安装以及测试我就不再叙述了,可以参考大神的文章 . http://www.blogjava. ...

  6. 火狐浏览器下使用jquery修改img的src

    onUploadComplete': function (file, data) { //$("#submit").removeAttr("disabled") ...

  7. (原创)解决.net 下使用uploadify,在火狐浏览器下的error 302

    简单粗劣说下哈,通过uploadify中flash在火狐下上传,造成了erroe 302, 是因为其session丢失,并修改了其sessionID. 网上有很多案列,可并没有这么直接.感觉绕了点弯. ...

  8. JS在火狐浏览器下如何关闭标签?

    首先,要确定火狐设置是否允许通过JS代码window.close()方法关闭标签. 确定方式如下: 在Firefox地址栏里输入 about:config 在配置列表中找到dom.allow_scri ...

  9. css firefox火狐浏览器下的兼容性问题

    1.DOCTYPE 影响 CSS 处理 2.FF: div 设置 margin-left, margin-right 为 auto 时已经居中, IE 不行 3.FF: body 设置 text-al ...

  10. JS 在火狐浏览器下关闭弹窗

    1.首先,要确定火狐设置是否允许通过JS代码window.close()方法关闭标签. 确定方式如下:      在Firefox地址栏里输入 about:config    在配置列表中找到dom. ...

随机推荐

  1. Map中经常被忽略但又非常好用的方法

    1. 简介 map是我们日常开发中常会的集合类之一, 但是我们除了常用的get和put之外,其他的方法好像很少会用到,接下来我们就介绍一下几个经常被忽略但又很好用的方法. 2. Quick Start ...

  2. 【转】史上最详细的 JDK 1.8 HashMap 源码解析

    HashMap的源码应该是我看过最多变的JDK源码,没有之一,自己也写过一些帖子来记录自己的感悟,虽然其中涉及数据结构以及实现方式也都有所掌握,但是每次看都有不一样的收获,尤其是源码作者的编码思路以及 ...

  3. MySQL为Null导致的四大坑

    "兵马未动粮草先行",看完了相关的配置之后,我们先来创建一张测试表和一些测试数据. -- 如果存在 person 表先删除 DROP TABLE IF EXISTS person; ...

  4. centos8网络配置问题

    由于RHEL8与centos8基本一样,所以以下方法同样适用于RHEL8 在centos8上进行网络配置时,出现以下问题: 意思是无法找到network.service 出现错误的原因是centos8 ...

  5. 面向对象-下(复习:关键字static、单例模式、main()的使用说明、类的结构代码块、属性的赋值顺序、关键字final)

    一.关键字:static static:静态的1.可以用来修饰的结构:主要用来修饰类的内部结构属性.方法.代码块.内部类2.static修饰属性:静态变量(或类变量) 2.1 属性,是否使用stati ...

  6. AI赋能软件测试:未来已来,你准备好了吗?

    ps:文末有福利领取哦 引言 在数字化转型的浪潮中,软件测试作为保障产品质量的关键环节,正面临着前所未有的挑战. 传统的测试方法已难以满足快速迭代和复杂场景的需求,而人工智能(AI)的引入,则为软件测 ...

  7. Kubernetes 轻松管理资源

    资源管理介绍 在kubernetes中,所有的内容都抽象为资源,用户需要通过操作资源来管理kubernetes. kubernetes的本质上就是一个集群系统,用户可以在集群中部署各种服务,所谓的部署 ...

  8. kubernets学习笔记一

    了解kubernets Docker作为单一的容器技术工具并不能很好地定义容器的"组织方式"和"管理规范",难以独立地支撑起生产级大规模容器化部署的要求..因此 ...

  9. 本地部署DeepSeek-R1并使用自定义的知识库AnythingLLM

    一.基础信息 1.概述 以下是私有化部署方案的优势: 性能卓越:提供媲美商业模型的对话交互体验 环境隔离:完全离线运行,杜绝数据外泄风险 数据可控:完全掌控数据资产,符合合规要求 2.硬件环境 CPU ...

  10. kali linux脚本小子速成

    $如果你耐心看十分钟,你会惊奇的发现我讲的是一堆废话,别急.kali linux博大精深,绝对不是十分钟就能学的完,真正的好东西永远都是夹在屎里,想学你想要的,拿出你的决心来. kali linux用 ...