fanssong 发表于 2024-4-9 19:56

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

本帖最后由 fanssong 于 2024-4-10 17:34 编辑

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

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

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

1.1进入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>的直接子元素,然后为这些元素绑定了点击事件。
$('.ui-question-options>li .ui-question-options-order')
    .on(
      'click',
      function() { //这里存放了选择题答案对比部分
      }
跳过上面答案对比比较部分,继续往下直达分析查看是否有上传加密。
2.2该部分提示使用exam.SaveItem()方法上传了题干信息
if (isChanged) {
    exam.SaveItem(parent.prop('id')
      .substring(2), parent.attr('code')
      .substring(4), answer, '');
}
2.2从总代码向上代码可以找到exam的赋值部分
该部分定义了使用全局变量window.__RTE.SaveItem()链条的函数
var exam = window.__RTE;
2.3想要查看全局变量可以用我写得代码来实现
利用控制台输入以下代码
if (window.__RTE && typeof window.__RTE === 'object') {
    // 遍历window.__RTE对象的所有属性
    for (let key in window.__RTE) {
      // 检查属性是否是函数
      if (typeof window.__RTE === 'function') {
            // 输出函数名称
            console.log(key);
            // 如果你还想查看函数的定义或者其它信息,你可以这样做:
            // 输出函数的定义(函数体)
            console.log(window.__RTE.toString());
            // 或者输出函数的长度(参数个数)
            console.log(window.__RTE.length);
      }
    }
} else {
    console.log('window.__RTE is not defined or not an object.');
}
2.4以此获取到全局变量函数:SaveItem()
function(qId, psqId, answer, attach) {
    function SaveItem(qId, psqId, answer, attach) {
      var self = this,
            callback = (arguments.length > 4 ? arguments : null);

      // 客户端判卷用到的方法
      function saveAnswerAndStatus(qId, psqId, qa, rightAnswers, attach) {
            var rightAnswer, len = rightAnswers.length;
            for (var i = 0; i < len; i++) {
                rightAnswer = rightAnswers;
                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混淆加密部分
bodyParams["m"] = encryption(params.userExamId, params.qId, bodyParams);
全局搜索,可以发现此方法是单独注册的一个MD5.js文件。
三、思路-实现最终自动化
1、已经获取到的加密提交信息,只需要对应输入即可
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注册引用
// 使用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机进行通信和网页操作,达到自动答题的目的
//==================================以下代码为自创===================================
// 页面加载完成后初始化

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.url //解析出url
      HuoquDaAn(DaAN_url, '', '', 5);

    } else if (xiaoxi == 6) { //获取到解析的答案页面url,用于直接获取答案自动答题
      var DaAN_url = jsondata.url //解析出url
      var DaAN_basePath = jsondata.basePath //解析上传头url
      var DaAN_userExamId = jsondata.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 === 'function') {
                  // 输出函数名称
                  console.log(key);
                  // 如果你还想查看函数的定义或者其它信息,你可以这样做:
                  // 输出函数的定义(函数体)
                  console.log(window.__RTE.toString());
                  // 或者输出函数的长度(参数个数)
                  console.log(window.__RTE.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 : 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')
}

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

Kls673M 发表于 2024-4-10 11:47

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

小人类 发表于 2024-4-10 15:14

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

狂侠先森 发表于 2024-4-9 20:50

666又学到了 感谢分享

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

思路不错,试试看先
页: [1] 2 3 4 5 6
查看完整版本: [MD5-JS逆向]某高等继续教育平台自动化答题