12306 抢票系列之只要搞定RAIL_DEVICEID的来源,从此抢票不再掉线(下)

Stella981
• 阅读 664

郑重声明: 本文仅供学习使用,禁止用于非法用途,否则后果自负,如有侵权,烦请告知删除,谢谢合作!

模拟伪装

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

值得注意的是,这种 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 抢票系列之只要搞定RAIL_DEVICEID的来源,从此抢票不再掉线(下)

回顾展望

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

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

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

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

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

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

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

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

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

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

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

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

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

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

参考资料

如果本文对你有所帮助,不用赞赏直接点赞就是最大的鼓励,顺便关注下微信公众号「 雪之梦技术驿站 」那就更好啦!

12306 抢票系列之只要搞定RAIL_DEVICEID的来源,从此抢票不再掉线(下)

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
12306 的架构到底有多牛逼?
链接|绘你一世倾城来源|https://urlify.cn/ZBRBRv每到节假日期间,一二线城市返乡、外出游玩的人们几乎都面临着一个问题:抢火车票!12306抢票,极限并发带来的思考虽然现在大多数情况下都能订到票,但是放票瞬间即无票的场景,相信大家都深有体会。尤其是春节期间,大家
Wesley13 Wesley13
3年前
12306 的架构也太 牛X 了吧!
作者:绘你一世倾城juejin.im/post/5d84e21f6fb9a06ac8248149每到节假日期间,一二线城市返乡、外出游玩的人们几乎都面临着一个问题:抢火车票!虽然现在大多数情况下都能订到票,但是放票瞬间即无票的场景,相信大家都深有体会。尤其是春节期间,大家不仅使用12306,还会考虑“智行”和其他的抢票软件,全国上下几亿
Stella981 Stella981
3年前
12306 抢票系列之只要搞定RAIL_DEVICEID的来源,从此抢票不再掉线(中)
直奔重点高楼大厦寻关键线索Js文件中关于网络请求最典型的就是异步回调,将原本简单的操作复杂化,非要你等我,我等他,他还等着他的她.最终直接结果就是整个请求流程反过来了,假设正常流程:是ABCDEF,那么异步请求很可能陷入这样的陷阱:F<E<D<C<B<A所以一层又一层的回调函数
可莉 可莉
3年前
12306 抢票系列之只要搞定RAIL_DEVICEID的来源,从此抢票不再掉线(中)
直奔重点高楼大厦寻关键线索Js文件中关于网络请求最典型的就是异步回调,将原本简单的操作复杂化,非要你等我,我等他,他还等着他的她.最终直接结果就是整个请求流程反过来了,假设正常流程:是ABCDEF,那么异步请求很可能陷入这样的陷阱:F<E<D<C<B<A所以一层又一层的回调函数
可莉 可莉
3年前
12306 抢票系列之只要搞定RAIL_DEVICEID的来源,从此抢票不再掉线(上)
郑重声明:本文仅供学习使用,禁止用于非法用途,否则后果自负,如有侵权,烦请告知删除,谢谢合作!开篇明义本文针对自主开发的抢票脚本在抢票过程中常常遇到的请求无效等问题,简单分析了12306网站的前端加密算法,更准确的说,是探究RAIL_DEVICEID的生成过程.因为该cookie值是抢票请求
可莉 可莉
3年前
12306 抢票系列之只要搞定RAIL_DEVICEID的来源,从此抢票不再掉线(下)
郑重声明:本文仅供学习使用,禁止用于非法用途,否则后果自负,如有侵权,烦请告知删除,谢谢合作!模拟伪装现在已经还原了算法的实现逻辑,下一步就是如何更好地伪造自己,本文提供临时设置的实现方式,方便在不修改之前复现代码的基础上实现扩展,当然也可以直接在还原算法源码中写入伪造代码.值得注意的是,这种Object.de
Stella981 Stella981
3年前
12306 抢票系列之只要搞定RAIL_DEVICEID的来源,从此抢票不再掉线(上)
郑重声明:本文仅供学习使用,禁止用于非法用途,否则后果自负,如有侵权,烦请告知删除,谢谢合作!开篇明义本文针对自主开发的抢票脚本在抢票过程中常常遇到的请求无效等问题,简单分析了12306网站的前端加密算法,更准确的说,是探究RAIL_DEVICEID的生成过程.因为该cookie值是抢票请求
Stella981 Stella981
3年前
Python 实现短信轰炸机
原理其实很简单,就是利用selenium包打开各种网站的注册页,输入轰炸的号码,实现轰炸。其实也算是利用了注册漏洞。申明:仅娱乐使用,禁止:u7981:️用于非法用途!若用于非法用途,后果及法律责任博主一律不承担很多人学习python,不知道从何学起。很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手。很
Stella981 Stella981
3年前
Spring Cloud Gateway限流浅析之一段脚本实现令牌桶
前言在一个分布式高并发的系统设计中,限流是一个不可忽视的功能点。如果不对系统进行有效的流量访问限制,在双十一和抢票这种流量洪峰的场景下,很容易就会把我们的系统打垮。而作为系统服务的卫兵的网关组件,作为系统服务的统一入口,更需要考虑流量的限制,直接在网关层阻断流量比在各个系统中实现更合适。SpringCloudGateway的实现