[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:这样偷懒是不好的行为,哈哈,多学习才能学到更多有用的知识
喜欢的话可以给本帖免费评分++++ 感谢分享,
大佬这类的答案你在怎么搞到手的?
是提交cookie反向获取答案吗? 还是原有题目里面就包含答案了? 眼熟这个网站,之前做过自动化处理,md5应该就是我刷太多才加的,后面也解了:lol,这是一个系列的网站,很多地方的成人教育都是使用这一套模板, 666又学到了 感谢分享 可以,感谢分享,学习思路了! 不错嘛,给我就是找不到关键函数
感谢分享 咋用啊 我不会 不错的,学习了,感谢分享! 哈哈,偷懒是社会进步的动力 我喜欢你的注释 思路不错,试试看先