美文网首页工具资源系列
12306 抢票系列之只要搞定RAIL_DEVICEID的来源,

12306 抢票系列之只要搞定RAIL_DEVICEID的来源,

作者: 雪之梦技术驿站 | 来源:发表于2020-02-18 10:36 被阅读0次

    模拟伪装

    现在已经还原了算法的实现逻辑,下一步就是如何更好地伪造自己,本文提供临时设置的实现方式,方便在不修改之前复现代码的基础上实现扩展,当然也可以直接在还原算法源码中写入伪造代码.

    值得注意的是,这种 Object.defineProperty 方式只会临时生效而且仅仅针对使用 js 代码获取对象属性的值,并不会真正修改对象属性!

    • 设置用户代理
      /**
       * 设置用户代理,检测方式: navigator.userAgent
       */
      chromeHelper.setUserAgent = function(userAgent) {
        if (!userAgent) {
          userAgent = "Mozilla5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit537.36 (KHTML, like Gecko) Chrome80.0.3987.87 Safari537.36";
        }
        Object.defineProperty(navigator, "userAgent", {
          value: userAgent,
          writable: false
        });
      }
    
    • 设置浏览器语言
      /**
       * 设置浏览器语言,检测方式: navigator.language
       */
      chromeHelper.setLanguage = function(language) {
        if (!language) {
          language = "zh-CN";
        }
        Object.defineProperty(navigator, "language", {
          value: language,
          writable: false
        });
      }
    
    • 设置浏览器语言
      /**
       * 设置浏览器语言,检测方式: navigator.languages
       */
      chromeHelper.setLanguages = function(languages) {
        if (!languages) {
          languages = ["zh-CN", "zh", "en"];
        }
        Object.defineProperty(navigator, "languages", {
          value: languages,
          writable: false
        });
      }
    
    • 设置屏幕颜色深度
      /**
       * 设置屏幕颜色深度,检测方式: screen.colorDepth
       */
      chromeHelper.setColorDepth = function(colorDepth) {
        if (!colorDepth) {
          colorDepth = 24;
        }
        Object.defineProperty(screen, "colorDepth", {
          value: colorDepth,
          writable: false
        });
      }
    
    • 设置设备像素比率
      /**
       * 设置设备像素比率,检测方式: window.devicePixelRatio
       */
      chromeHelper.setDevicePixelRatio = function(devicePixelRatio) {
        if (!devicePixelRatio) {
          devicePixelRatio = 24;
        }
        Object.defineProperty(window, "devicePixelRatio", {
          value: devicePixelRatio,
          writable: false
        });
      }
    
    • 设置屏幕宽度
      /**
       * 设置屏幕宽度,检测方式: screen.width
       */
      chromeHelper.setWidth = function(width) {
        if (!width) {
          width = 1280;
        }
        Object.defineProperty(screen, "width", {
          value: width,
          writable: false
        });
      }
    
    • 设置屏幕高度
      /**
       * 设置屏幕高度,检测方式: screen.height
       */
      chromeHelper.setHeight = function(height) {
        if (!height) {
          height = 800;
        }
        Object.defineProperty(screen, "height", {
          value: height,
          writable: false
        });
      }
    
    • 设置屏幕可用宽度
      /**
       * 设置屏幕可用宽度,检测方式: screen.availWidth
       */
      chromeHelper.setAvailWidth = function(availWidth) {
        if (!availWidth) {
          availWidth = 1280;
        }
        Object.defineProperty(screen, "availWidth", {
          value: availWidth,
          writable: false
        });
      }
    
    • 设置屏幕可用高度
      /**
       * 设置屏幕可用高度,检测方式: screen.availHeight
       */
      chromeHelper.setAvailHeight = function(availHeight) {
        if (!availHeight) {
          availHeight = 777;
        }
        Object.defineProperty(screen, "availHeight", {
          value: availHeight,
          writable: false
        });
      }
    
    • 设置Session存储
      /**
       * 设置Session存储,检测方式: !!window.sessionStorage
       */
      chromeHelper.setSessionStorage = function(sessionStorage) {
        if (!sessionStorage) {
          sessionStorage = 1;
        }
        if (sessionStorage) {
          window.sessionStorage = 1
        } else {
          delete window.sessionStorage
        }
      }
    
    • 设置Local存储
      /**
       * 设置Local存储,检测方式: !!window.localStorage
       */
      chromeHelper.setLocalStorage = function(localStorage) {
        if (!localStorage) {
          localStorage = 1;
        }
        if (localStorage) {
          window.localStorage = 1
        } else {
          delete window.localStorage
        }
      }
    
    • 设置indexedDB存储
      /**
       * 设置indexedDB存储,检测方式: !!window.indexedDB
       */
      chromeHelper.setIndexedDB = function(indexedDB) {
        if (!indexedDB) {
          indexedDB = 1;
        }
        if (indexedDB) {
          window.indexedDB = 1
        } else {
          delete window.indexedDB
        }
      }
    
    • 设置addBehavior存储
      /**
       * 设置addBehavior存储,检测方式: !!document.body.addBehavior
       */
      chromeHelper.setAddBehavior = function(addBehavior) {
        if (!addBehavior) {
          addBehavior = 1;
        }
        if (addBehavior) {
          document.body.addBehavior = 1
        } else {
          delete document.body.addBehavior
        }
      }
    
    • 设置Cpu类型
      /**
       * 设置Cpu类型,检测方式: navigator.cpuClass
       */
      chromeHelper.setCpuClass = function(cpuClass) {
        if (!cpuClass) {
          cpuClass = "unknown";
        }
        Object.defineProperty(navigator, "cpuClass", {
          value: cpuClass,
          writable: false
        });
      }
    
    • 设置平台类型
      /**
       * 设置平台类型,检测方式: navigator.platform
       */
      chromeHelper.setPlatform = function(platform) {
        if (!platform) {
          platform = "MacIntel";
        }
        Object.defineProperty(navigator, "platform", {
          value: platform,
          writable: false
        });
      }
    
    • 设置反追踪
      /**
       * 设置反追踪,检测方式: navigator.doNotTrack
       */
      chromeHelper.setDoNotTrack = function(doNotTrack) {
        if (!doNotTrack) {
          doNotTrack = "unknown";
        }
        Object.defineProperty(navigator, "doNotTrack", {
          value: doNotTrack,
          writable: false
        });
      }
    
    • 设置插件
      /**
       * 设置插件,检测方式: navigator.plugins
       */
      chromeHelper.setPlugins = function(plugins) {
    
      }
    
    • 设置Canvas
      /**
       * 设置Canvas,检测方式: TODO
       */
      chromeHelper.setCanvas = function(canvas) {
    
      }
    
    • 设置Webgl
      /**
       * 设置Webgl,检测方式: TODO
       */
      chromeHelper.setWebgl = function(webgl) {
    
      }
    
    • 设置AdBlock
      /**
       * 设置AdBlock,检测方式: TODO
       */
      chromeHelper.setAdBlock = function(AdBlock) {
    
      }
    
    • 设置AdBlock
      /**
       * 设置AdBlock,检测方式: TODO
       */
      chromeHelper.setAdBlock = function(AdBlock) {
    
      }
    
    • 设置字体
      /**
       * 设置字体,检测方式: TODO
       */
      chromeHelper.setFonts = function(fonts) {
    
      }
    
    • 设置最多触控点
      /**
       * 设置最多触控点,检测方式: navigator.maxTouchPoints
       */
      chromeHelper.setMaxTouchPoints = function(maxTouchPoints) {
        if (!maxTouchPoints) {
          maxTouchPoints = 0;
        }
        Object.defineProperty(navigator, "maxTouchPoints", {
          value: maxTouchPoints,
          writable: false
        });
      }
    
    • 设置ontouchstart事件
      /**
       * 设置ontouchstart事件,检测方式: "ontouchstart" in window
       */
      chromeHelper.setTouchEvent = function(ontouchstart) {
        if (!ontouchstart) {
          ontouchstart = false;
        }
        if (ontouchstart) {
          window.ontouchstart = true
        } else {
          delete window.ontouchstart
        }
      }
    
    • 设置app代码名称代码
      /**
       * 设置app代码名称代码,检测方式: navigator.appCodeName.toString()
       */
      chromeHelper.setAppCodeName = function(appCodeName) {
        if (!appCodeName) {
          appCodeName = "Mozilla";
        }
        Object.defineProperty(navigator, "appCodeName", {
          value: appCodeName,
          writable: false
        });
      }
    
    • 设置app代码名称代码
      /**
       * 设置app代码名称代码,检测方式: navigator.appName.toString()
       */
      chromeHelper.setAppName = function(appName) {
        if (!appName) {
          appName = "Netscape";
        }
        Object.defineProperty(navigator, "appName", {
          value: appName,
          writable: false
        });
      }
    
    • 设置Java是否启用
      /**
       * 设置Java是否启用,检测方式: navigator.javaEnabled()
       */
      chromeHelper.setJavaEnabled = function(javaEnabled) {
    
      }
    
    • 设置媒体类型
      /**
       * 设置媒体类型,检测方式: navigator.mimeTypes
       */
      chromeHelper.setMimeTypes = function(mimeTypes) {
    
      }
    
    • 设置cookie是否启用
      /**
       * 设置cookie是否启用,检测方式: navigator.cookieEnabled
       */
      chromeHelper.setCookieEnabled = function(cookieEnabled) {
        if (!cookieEnabled) {
          cookieEnabled = true;
        }
        Object.defineProperty(navigator, "cookieEnabled", {
          value: cookieEnabled,
          writable: false
        });
      }
    
    • 设置是否在线
      /**
       * 设置是否在线,检测方式: navigator.onLine.toString()
       */
      chromeHelper.setOnLine = function(onLine) {
        if (!onLine) {
          onLine = true;
        }
        Object.defineProperty(navigator, "onLine", {
          value: onLine,
          writable: false
        });
      }
    
    • 添加历史记录
      /**
       * 添加历史记录,检测方式: window.history
       */
      chromeHelper.pushHistory = function(newUrls) {
        for (url in newUrls) {
          history.pushState(null, '', url);
        }
      }
    

    使用示例

    亲测构造请求 /otn/HttpZF/logdevice时,关于参数 algID 经常性发生变化,因此无法提供静态的请求方法,建议根据实际情况实时改变.

    通过翻阅源码实现,最终发现关于发送请求的代码是这样的:

    e = c.hashAlg(m, a, e);
    a = e.key;
    e = e.value;
    a += "\x26timestamp\x3d" + (new Date).getTime();
    $a.getJSON("https://kyfw.12306.cn/otn/HttpZF/logdevice" + ("?algID\x3dmBxuYhGXYR\x26hashCode\x3d" + e + a), null, function(a) {
        var b = JSON.parse(a);
        void 0 != mb && mb.postMessage(a, r.parent);
        for (var d in b)
            "dfp" == d ? G("RAIL_DEVICEID") != b[d] && (V("RAIL_DEVICEID", b[d], 1E3),
            c.deviceEc.set("RAIL_DEVICEID", b[d])) : "exp" == d ? V("RAIL_EXPIRATION", b[d], 1E3) : "cookieCode" == d && (c.ec.set("RAIL_OkLJUJ", b[d]),
            V("RAIL_OkLJUJ", "", 0))
    })
    

    其中,参数 a 表示的是加密后的浏览器指纹信息,(new Date).getTime() 是当前时间戳,而 algID\x3dmBxuYhGXYR\x26hashCode\x3d 这部分的 algID 算法参数是暂时性静态的,比如今天一段时间都是 mBxuYhGXYR 而第二天这个值就变成其他值了.

    hashCode 参数的值就是程序运行结果的 value 值,最后面的变量 a 代表的是剩下的浏览器指纹信息,即运行结果的 key.

    假设此时此刻为例,演示如何使用该 js 文件:

    function ajax(req){
        var xhr=new XMLHttpRequest();
        xhr.onreadystatechange=function(){
            if(xhr.readyState===4){
                req.success&&req.success(xhr.responseText,xhr.status);
            }
        }
        req.method=req.method?req.method.toUpperCase():'GET';
        var data=null;
        var url=req.url;
        if(req.data){
            var arg='';
            for(var n in req.data){
                arg+=n+'='+encodeURIComponent(req.data[n])+'&'
            }
            arg=arg.slice(0,-1);
            if(req.method==='GET'){
                url=url+'?'+arg;
            }else{
                data=arg;
            }
        }
        if(req.headers){
            for(var h in req.headers){
                var v=req.headers[h];
                xhr.setRequestHeader(h,v);
            }
        }
        xhr.open(req.method,url);
        xhr.send(data);
    }
    
    e = chromeHelper.prototype.encryptedFingerPrintInfo();
    a = e.key;
    e = e.value;
    a += "\x26timestamp\x3d" + (new Date).getTime();
    ajax({
        url:"https://kyfw.12306.cn/otn/HttpZF/logdevice" + ("?algID\x3dmBxuYhGXYR\x26hashCode\x3d" + e + a),
        success:function(data){
            console.log("data",data);
    
            startIndex = "callbackFunction('".length;
            endIndex = data.lastIndexOf("')");
            jsonStrData = data.substring(startIndex,endIndex);
            console.log("jsonStrData",jsonStrData);
    
            jsonData = JSON.parse(jsonStrData);
            console.log("jsonData",jsonData);
    
            exp = jsonData.exp;
            cookieCode = jsonData.cookieCode;
            dfp = jsonData.dfp;
            console.log("RAIL_DEVICEID::: "+dfp+" RAIL_EXPIRATION::: " + exp +" RAIL_OkLJUJ::: " + cookieCode);
        }
    });
    
    12306-algorithm-web-js-website-console-ajax.png

    回顾展望

    在还原算法实现过程中,充分复习了 web 前端开发的调试技巧,针对通篇无意义的变量命名方式,有效的应对方式就是采用正则表达式精确匹配进行查找.

    同时,为了验证自己的猜想是否正确,还需要结合断点调试,如果存在反调试手段,那么只能靠自己硬啃压缩混淆代码了,我只能说:考验真正技术的时候,到了!

    本文给我们留下了不少启发供后续工作学习使用,从开发者的角度上来讲:

    • 不完全依靠现成加密技术,哪怕真正加密时没自己实现也要在加密前后实现自己的混淆逻辑.

    例如重新打乱字符串,将字符串分隔成三份,按照首尾中或者尾中首等反人类次序重新排序等.

    • 重复使用同一数据时也不一定要抽象成同一个方法,不同对象调用不同的处理逻辑,更是让人防不胜防,陷入思维惯性误区.

    例如获取用户代理采用不同的正则表达式进行替换,获取浏览器语言时采用另外的途径验证上一步获取结果是否造假等.

    • 无序更胜似有序,看似规整优美的代码是给开发人员看的而不该给机器看,一定不能使用源码上线而是要加密处理或者其他处理.

    只有要基本的开发经验很容易一叶知秋,进而判断相关技术栈,因为技术都是通用的方案,非常容易复制,打造独特的技术流会增大破解成本,进而吓退一部分菜鸟小白.

    • 前端和后端需要密切配合协同协作,缺少统一指挥难以避免一方或者多方偷懒进而暴露我方阵地.

    重要的业务逻辑肯定是放在后端进行处理,哪怕前端已经处理过相同逻辑也不能偷懒,更要保证前后端处理逻辑的一致性.

    攻防是矛与盾,作为攻克方要做的一点就是打铁还需自身硬,多了解不同的技术栈才能做到有的放矢而不至于临阵脱逃,望而却步!

    最后祝大家抢票愉快,需要买票时人人都有票,再也不需要抢票回家,人生苦短何必浪费生命去抢票?

    再次声明,本文仅供学习研究,一切用作它途的行为均与本人无关,如有侵权,烦请告知,谢谢合作.

    参考资料

    如果本文对你有所帮助,不用赞赏,点赞鼓励一下就是最大的认可,顺便也可以关注下微信公众号「 雪之梦技术驿站 」哟!

    雪之梦技术驿站.png

    相关文章

      网友评论

        本文标题:12306 抢票系列之只要搞定RAIL_DEVICEID的来源,

        本文链接:https://www.haomeiwen.com/subject/bczwfhtx.html