[MD5-JS逆向]某高等继续教育平台自动化答题

查看 103|回复 11
作者:fanssong   
实现某高等继续教育平台自动化答题
一、前言-本期主要讲解实现自动化

最近考取一建,发现对口专业问题,增考一个成人学历,入学后发现需要完成学校规定的【在线】课件视频学习时长、平时作业、和期末考试。
视频课件相信有过经历的同学都知道:拖拽滑条不计算学习时长。
平时作业、期末考试 需要在线进行作答,提交后查看分数,可进行多次修改。


屏幕截图 2024-04-09 162759.png (54.74 KB, 下载次数: 0)
下载附件
2024-4-9 16:28 上传

ps:因为时间不允许花费大量时间在这上面,就想着能不能分析后台js代码实现自动答题和视频课件拖拽。
二、开始分析
1、从网页元素分析-从答案提交按钮查找线索,登录教学平台,进入考试页面,F12进入开发者页面,等待提交试卷查看数据流。


屏幕截图 2024-04-09 165647.png (54.87 KB, 下载次数: 0)
下载附件
2024-4-9 17:02 上传

提交后发现提交数据流中并未有任何关于题干信息,
猜测:答案是根据点击选项时即时上传到服务器的。
利用开发者面板的选择工具(Ctrl+Shift+C),定位元素再查看该元素的click点击绑定事件


屏幕截图 2024-04-09 170647.png (36.61 KB, 下载次数: 0)
下载附件
2024-4-9 17:10 上传

1.1进入javascript代码后,跟踪分析发现果然是点击选项即时上传服务器的操作
[JavaScript] 纯文本查看 复制代码$('.ui-question-options>li .ui-question-options-order')
    .on('click', function() {
        /*选择题选项处理*/
        var t = $(this)
            .parent(),
            parent = t.parent()
            .parent(),
            answer = '',
            isChanged = false;
        while (!!parent) {
            if (parent.hasClass('ui-question')) {
                break;
            } else {
                parent = parent.parent();
            }
        }
        if (!!parent) {
            if (parent.hasClass('undone')) {
                parent.removeClass('undone');
            }
            if (parent.hasClass('ui-question-1')) {
                /*单选题*/
                if (!t.hasClass('ui-option-selected')) {
                    $('.ui-question-options>li.ui-option-selected', parent)
                        .removeClass('ui-option-selected');
                    t.addClass('ui-option-selected');
                    answer = t.attr('code');
                    isChanged = true;
                }
            } else if (parent.hasClass('ui-question-2')) {
                /*多选题*/
                isChanged = true;
                t.toggleClass('ui-option-selected');
                $('.ui-question-options>li.ui-option-selected', parent)
                    .each(function() {
                        answer = answer + $(this)
                            .attr('code');
                    });
            }
            if (isChanged) {
                exam.SaveItem(parent.prop('id')
                    .substring(2), parent.attr('code')
                    .substring(4), answer, '');
            }
        }
    });
2、分析3-分析javascript代码,发现是用jQuery语法来完成选择器与事件绑定。
2.1发现选择器与事件绑定:
【重要部分,后续会用到】该方法指定了所有带有类名.ui-question-options-order的元素,并且筛选了必须是.ui-question-options的直接子元素
  • 的直接子元素,然后为这些元素绑定了点击事件。
    [JavaScript] 纯文本查看 复制代码$('.ui-question-options>li .ui-question-options-order')
        .on(
            'click',
            function() { //这里存放了选择题答案对比部分
            }
    跳过上面答案对比比较部分,继续往下直达分析查看是否有上传加密。
    2.2该部分提示使用exam.SaveItem()方法上传了题干信息
    [JavaScript] 纯文本查看 复制代码if (isChanged) {
        exam.SaveItem(parent.prop('id')
            .substring(2), parent.attr('code')
            .substring(4), answer, '');
    }
    2.2从总代码向上代码可以找到exam的赋值部分
    该部分定义了使用全局变量window.__RTE.SaveItem()链条的函数
    [JavaScript] 纯文本查看 复制代码var exam = window.__RTE;
    2.3想要查看全局变量可以用我写得代码来实现
    利用控制台输入以下代码
    [JavaScript] 纯文本查看 复制代码if (window.__RTE && typeof window.__RTE === 'object') {
        // 遍历window.__RTE对象的所有属性
        for (let key in window.__RTE) {
            // 检查属性是否是函数
            if (typeof window.__RTE[key] === 'function') {
                // 输出函数名称
                console.log(key);
                // 如果你还想查看函数的定义或者其它信息,你可以这样做:
                // 输出函数的定义(函数体)
                console.log(window.__RTE[key].toString());
                // 或者输出函数的长度(参数个数)
                console.log(window.__RTE[key].length);
            }
        }
    } else {
        console.log('window.__RTE is not defined or not an object.');
    }
    2.4以此获取到全局变量函数:SaveItem()
    [JavaScript] 纯文本查看 复制代码function(qId, psqId, answer, attach) {
        function SaveItem(qId, psqId, answer, attach) {
            var self = this,
                callback = (arguments.length > 4 ? arguments[4] : null);
            // 客户端判卷用到的方法
            function saveAnswerAndStatus(qId, psqId, qa, rightAnswers, attach) {
                var rightAnswer, len = rightAnswers.length;
                for (var i = 0; i
    检查发现,果然有涉及到MD5混淆加密部分
    [JavaScript] 纯文本查看 复制代码bodyParams["m"] = encryption(params.userExamId, params.qId, bodyParams);
    全局搜索,可以发现此方法是单独注册的一个MD5.js文件。

    三、思路-实现最终自动化

    1、已经获取到的加密提交信息,只需要对应输入即可
    [JavaScript] 纯文本查看 复制代码bodyParams["m"] = encryption(params.userExamId, params.qId, bodyParams);
    1.1、qId=题目id值,psqId=为题目考试编号,根据数据流分析得到userExamId=考试编号+用户编号+时间戳
    1.2、bodyParams={psqId: psqId, answer: answer, attach: attach},其中answer为选项的小写英文编号(如abcd),attach为空文本
    2、解决MD5.js注册引用
    [JavaScript] 纯文本查看 复制代码// 使用loadScript函数加载MD5.js
    loadScript('https://XXX.XXX..com/exam/statics/scripts/MD5.js')
        .then(() => {
            console.log('https://XXX.XXX.com/exam/statics/scripts/MD5.js has been loaded and executed');
        })
        .catch(error => {
            console.error('There was an error loading https://XXX.XXX.com/exam/statics/scripts/MD5.js:', error);
        });
    3、整合以上部分,编写自己的代码
    利用在最初我标记重要的地方插入自己javascript代码来实现自动化答题了以下为我自己编写的javascript代码,用于和pc机进行通信和网页操作,达到自动答题的目的
    [JavaScript] 纯文本查看 复制代码//==================================以下代码为自创===================================
    // 页面加载完成后初始化
    window.onload = init;
    var 是否主动断开 = false;
    const serverIP = '192.168.1.90'; // IP地址,请替换为实际的IP
    const serverPort = 8888; // 端口号,请替换为实际的端口号
    const wsUri = `ws://${serverIP}:${serverPort}`;
    var kehuduan = null; // 在全局作用域中定义        
    function init() {
        kehuduan = new WebSocket(wsUri);
        kehuduan.onopen = onOpen;
        kehuduan.onclose = onClose;
        kehuduan.onmessage = onMessage;
        kehuduan.onerror = onError;
    }
    function onOpen(evt) {
        console.log("连接已打开!");
        var referrerURL = document.referrer;
        console.log(referrerURL);
        //向本地服务器发送url
        var data = {
            code: "0",
            url: referrerURL
        };
        var jsonString = JSON.stringify(data); /// 将对象转换为JSON字符串
        kehuduan.send(jsonString); // 发送JSON字符串到服务器
    }
    function onClose(evt) {
        console.log("连接已关闭!", evt);
    }
    function onError(evt) {
        console.log('WebSocket发生错误');
    }
    function onMessage(evt) {
        console.log('收到消息:' + evt.data);
        //code的值:0时为:上一页,1时为:下一页,2时为:刷新页面,3时为:发送url,4时为:收到答案
        var jsonString = evt.data;
        var jsonObject = JSON.parse(jsonString);
        var xiaoxi = jsonObject.code;
        var jsondata = jsonObject.data;
        console.log('检测到代码命令:' + xiaoxi);
        if (xiaoxi === "0") { //上一题https://www.52pojie.cn/index.php
            __offsetQuestion(-1);
        } else if (xiaoxi == 1) { //下一题        
            __offsetQuestion(1);
        } else if (xiaoxi == 2) { //刷新页面
            location.reload();
            //javascript:location.reload(location.href);
        } else if (xiaoxi == 3) { //服务器要主动断开连接!
            kehuduan.close();
            是否主动断开 = true;
            console.log("服务器解析到URL主动断开连接!");
        } else if (xiaoxi == 4) { //收到手动答案,开始自动答题                        
            ZhengliDaAm(jsondata);
        } else if (xiaoxi == 5) { //获取到解析的答案页面url,用于直接获取答案自动答题
            var DaAN_url = jsondata[0].url //解析出url
            HuoquDaAn(DaAN_url, '', '', 5);
        } else if (xiaoxi == 6) { //获取到解析的答案页面url,用于直接获取答案自动答题
            var DaAN_url = jsondata[0].url //解析出url
            var DaAN_basePath = jsondata[1].basePath //解析上传头url
            var DaAN_userExamId = jsondata[2].userExamId //解析用户id和时间戳
            HuoquDaAn(DaAN_url, DaAN_basePath, DaAN_userExamId, 6);
        } else if (xiaoxi == 7) { //自定义数据,用于测试代码
            if (window.__RTE && typeof window.__RTE === 'object') {
                // 遍历window.__RTE对象的所有属性
                for (let key in window.__RTE) {
                    // 检查属性是否是函数
                    if (typeof window.__RTE[key] === 'function') {
                        // 输出函数名称
                        console.log(key);
                        // 如果你还想查看函数的定义或者其它信息,你可以这样做:
                        // 输出函数的定义(函数体)
                        console.log(window.__RTE[key].toString());
                        // 或者输出函数的长度(参数个数)
                        console.log(window.__RTE[key].length);
                    }
                }
            } else {
                console.log('window.__RTE is not defined or not an object.');
            }
        };
    }
    //HuoquDaAn(DaAN_url,DaAN_basePath,DaAN_userExamId,6);
    function HuoquDaAn(DaAN_url, DaAN_basePath, DaAN_userExamId, DaAN_code) { //用于从学校服务器获取正确答案
        $.get(DaAN_url, {}, function(data) {
            if (data && data.success) {
                console.log(data.answers);
                console.log(data.success);
                var jsonString = data.answers;
                ZhengliDaAm(jsonString, DaAN_code, DaAN_basePath, DaAN_userExamId);
            } else {
                console.log('错误:从服务器端获取答案失败!');
            }
        }, 'json');
    }
    function ZhengliDaAm(jsondata, DaAN_code, DaAN_basePath, DaAN_userExamId) {
        var allspan = $('.ui-question-options>li>span.ui-question-options-order');
        console.log('所有直接子元素带有 ui-question-options-order 类的元素:', allspan);
        if (allspan.length > 0) {
            jsondata.forEach(item => { //使用JavaScript的forEach方法
                const id = item.questionId;
                const answer = item.answer;
                const score = item.score;
                // 使用.each()方法遍历allspan中的每一个元素        
                allspan.each(function(index, element) { //element是每次遍历到的值
                    var t = $(element)
                        .parent();
                    var parent = t.parent()
                        .parent();
                    if (id == parent.prop('id')
                        .substring(2)) {
                        if (DaAN_code == 5) {
                            var exam = window.__RTE; //获取赋值全局变量window的rte函数给exam
                            exam.SaveItem(id, parent.attr('code')
                                .substring(4), answer, ''); //向学校服务器保送答案,保存答案
                        } else {
                            SaveItem(id, parent.attr('code')
                                .substring(4), answer, '', DaAN_basePath, DaAN_userExamId, score); //向学校服务器保送答案,保存答案
                        }
                        console.log('已向服务器提交数据:', id, parent.attr('code')
                            .substring(4), answer, '');
                        //console.log(parent.prop('id').substring(2), parent.attr('code').substring(4), answer);
                        return false; //跳出当前.each()遍历                                                        
                    }
                });
            });
        } else {
            console.log('没有找到任何匹配的元素');
        }
    }
    function SaveItem(qId, psqId, answer, attach, DaAN_basePath, DaAN_userExamId, DaAN_score) {
        var right = true,
            callback = (arguments.length > 7 ? arguments[7] : null);
        saveAnswerFunc({
            qId: qId,
            userExamId: DaAN_userExamId,
            body: {
                psqId: psqId,
                answer: answer,
                attach: attach
            }
        }, DaAN_basePath);
        //body: {psqId: psqId, answer: answer, attach: attach, score: DaAN_score, right: right}
        //},DaAN_basePath);
    }
    function saveAnswerFunc(params, DaAN_basePath) {
        const saveAnswerRemote = DaAN_basePath + '/myanswer/clientSave/' + params.userExamId + '/' + params.qId;
        const saveAnswerService = DaAN_basePath + '/myanswer/newSave/' + params.userExamId + '/' + params.qId;
        const bodyParams = params.body;
        bodyParams["m"] = encryption(params.userExamId, params.qId, bodyParams);
        $.post(saveAnswerService, bodyParams, function(data) {
            if (data && data.success) {
                console.log('答案提交成功');
                if (data.leftTime) {
                    self.leftTime = data.leftTime;
                }
            } else {
                console.log('保存答案失败', data.errMsg);
            }
        }, 'json')
    }


    屏幕截图 2024-04-09 194112.png (74.95 KB, 下载次数: 0)
    下载附件
    2024-4-9 19:51 上传

    以上代码是根据原有代码进行了现状修改,增加了一些个人习惯的功能,参考使用
    下一期将分享如何实现该网站拖拽视频进度条,或干脆逆向MD5进行直接模拟提交进度信息
    ps:这样偷懒是不好的行为,哈哈,多学习才能学到更多有用的知识
    喜欢的话可以给本帖免费评分++++

    代码, 答案

  • Kls673M   

    感谢分享,
    大佬这类的答案你在怎么搞到手的?
    是提交cookie反向获取答案吗? 还是原有题目里面就包含答案了?
    小人类   

    眼熟这个网站,之前做过自动化处理,md5应该就是我刷太多才加的,后面也解了,这是一个系列的网站,很多地方的成人教育都是使用这一套模板,
    狂侠先森   

    666又学到了 感谢分享
    Scan   

    可以,感谢分享,学习思路了!
    lemonatalk952   

    不错嘛,给我就是找不到关键函数
    q12569463   

    感谢分享
    qilin570   

    咋用啊 我不会
    52PJ070   

    不错的,学习了,感谢分享!
    Lty20000423   

    哈哈,偷懒是社会进步的动力
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部