吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5117|回复: 54
收起左侧

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

  [复制链接]
fanssong 发表于 2024-4-9 19:56
本帖最后由 fanssong 于 2024-4-10 17:34 编辑

实现某高等继续教育平台自动化答题
一、前言-本期主要讲解实现自动化

最近考取一建,发现对口专业问题,增考一个成人学历,入学后发现需要完成学校规定的【在线】课件视频学习时长、平时作业、和期末考试。
视频课件相信有过经历的同学都知道:拖拽滑条不计算学习时长。
平时作业、期末考试 需要在线进行作答,提交后查看分数,可进行多次修改。
屏幕截图 2024-04-09 162759.png
ps:因为时间不允许花费大量时间在这上面,就想着能不能分析后台js代码实现自动答题和视频课件拖拽。
二、开始分析
1、从网页元素分析-从答案提交按钮查找线索,登录教学平台,进入考试页面,F12进入开发者页面,等待提交试卷查看数据流。
屏幕截图 2024-04-09 165647.png
提交后发现提交数据流中并未有任何关于题干信息,
猜测:答案是根据点击选项时即时上传到服务器的。
利用开发者面板的选择工具(Ctrl+Shift+C),定位元素再查看该元素的click点击绑定事件
屏幕截图 2024-04-09 170647.png
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的直接子元素<li>的直接子元素,然后为这些元素绑定了点击事件。
[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 < len; i++) {
                rightAnswer = rightAnswers[i];
                if (rightAnswer.questionId == qId) {
                    break;
                }
            }
            var score = 0,
                right = false;
            if (rightAnswer) {
                if (rightAnswer.type == 1) {
                    /*单选题*/
                    if (rightAnswer.answer == qa) {
                        score = rightAnswer.score;
                        right = true;
                    }
                } else if (rightAnswer.type == 2) {
                    /*多选替*/
                    var tmpAnswer = rightAnswer.answer;
                    var breaked = false;
                    for (var i = 0; i < qa.length; i++) {
                        var a = qa.charAt(i);
                        if (tmpAnswer.indexOf(a) < 0) {
                            breaked = true;
                            break;
                        }
                        tmpAnswer = tmpAnswer.replace(a, '');
                    }
                    if (!breaked && tmpAnswer.length == 0) {
                        score = rightAnswer.score;
                        right = true;
                    }
                }
            }
            saveAnswerFunc({
                qId: qId,
                userExamId: self.userExamId,
                body: {
                    psqId: psqId,
                    answer: qa,
                    attach: attach,
                    score: score,
                    right: right
                }
            });
        }

        //保存答案
        function saveAnswerFunc(params) {
            const saveAnswerRemote = self.basePath + '/myanswer/clientSave/' + self.userExamId + '/' + params.qId;
            const saveAnswerService = self.basePath + '/myanswer/newSave/' + self.userExamId + '/' + params.qId;
            const bodyParams = params.body;
            bodyParams["m"] = encryption(params.userExamId, params.qId, bodyParams);
            try {
                $.post(saveAnswerService, bodyParams, function(data) {
                    if (data && data.success) {
                        if (callback && $.isFunction(callback)) {
                            callback.apply(self);
                        }
                        if (data.leftTime) {
                            self.leftTime = data.leftTime;
                        }
                        saveUserAnswerCallback(qId);
                    } else {
                        const message = "保存答案失败," + data.errMsg;
                        self.Message(message, 'err', [{
                            label: '刷新',
                            func: function() {
                                window.location.reload()
                            }
                        }], true);
                    }
                }, 'json')
            } finally {
                try {
                    bodyParams["httpUrl"] = saveAnswerRemote;
                    cefobj.localSaveAnswer(JSON.stringify(bodyParams))
                } catch (e) {
                    console.log("保存本地出错: " + e)
                }
            }
        }

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

免费评分

参与人数 14威望 +1 吾爱币 +33 热心值 +12 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
涛之雨 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
lxzlxz + 1 鼓励转贴优秀软件安全工具和文档!
Kls673M + 1 + 1 热心回复!
笨笨家的唯一 + 1 + 1 我很赞同!
鸠山一茶 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
haxcode + 1 + 1 用心讨论,共获提升!
shengruqing + 1 我很赞同!
xyzliuin + 1 + 1 谢谢@Thanks!
Yukin0shita + 1 + 1 我很赞同!
gqdsc + 1 真是高手啊
liuxuming3303 + 1 + 1 用心讨论,共获提升!
mizhi + 1 + 1 我很赞同!
wystudio + 2 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Kls673M 发表于 2024-4-10 11:47
感谢分享,
大佬这类的答案你在怎么搞到手的?
是提交cookie反向获取答案吗? 还是原有题目里面就包含答案了?
小人类 发表于 2024-4-10 15:14
眼熟这个网站,之前做过自动化处理,md5应该就是我刷太多才加的,后面也解了,这是一个系列的网站,很多地方的成人教育都是使用这一套模板,
狂侠先森 发表于 2024-4-9 20:50
Scan 发表于 2024-4-9 20:58
可以,感谢分享,学习思路了!
lemonatalk952 发表于 2024-4-9 21:49
不错嘛,给我就是找不到关键函数
q12569463 发表于 2024-4-9 22:56

感谢分享
qilin570 发表于 2024-4-9 23:25
咋用啊 我不会
52PJ070 发表于 2024-4-9 23:43
不错的,学习了,感谢分享!
Lty20000423 发表于 2024-4-10 07:40
哈哈,偷懒是社会进步的动力
jituidadada 发表于 2024-4-10 08:41
我喜欢你的注释
photocs 发表于 2024-4-10 08:41
思路不错,试试看先
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-12 20:49

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表