489 字
1 分钟
Unity3dWeb金课堂的逆向
开端

学校在校内部署了一个非常神秘的学习小游戏,发现他是unity引擎所制作。
利用之前在远古版本解包过原神的经验,于是就有了这篇文章
这次实现了能够直接一键完成的操作。
本文只提供学习,不要用于盈利
第一步 抓引擎加载文件
通过抓包得到 xxxx.js.unityweb , 一共三个文件,这里的 xxx 有:
- framework 资源文件
- wasm 二进制代码
- 忘记了
第二步 解包分析
通过7-zip右键打开压缩包,发现内部还藏了一个东西。
通过 unityweb 和 uwdtool 进行解包,得到 Assembly-Csharp.dll 文件,使用dnSpy打开后发现了 wocao

咳咳咳,打岔了,我们发现用户的信息是通过另一个接口获得的

还有玩家在完成一定的任务下,会向后端上传当前玩家数据

第三步 抓包

通过抓包分析,我们可以知道会存档信息就是一个Base64后的encode Json,所以我们需要一个即将通过的存档,
才能伪造全100分的存档。

于是经过九九八十一难,我终于到达最后一关,并把数据抓了下来。
第三步 编写脚本
通过分析游戏数据包,我们可以发现,每次打开游戏会向服务器请求得到自己的存档
于是我们可以通过拦截这个请求的响应数据,让他返回一个几乎完成的存档
完成以上这些我们可以直接做到在 30秒 内完成这一建模差劲的游戏

脚本内容:
脚本具体就是重写window.fetch,通过更改他的响应数据
期间尝试了重写多个请求,最后发现 unity web 调用的请求是走的window.fetch
// ==UserScript==// @name 一键完成// @namespace http://tampermonkey.net/// @version 202x-1x-0x// @description try to take over the world!// @author You// @match *://172.xx.0.xx:xxxx/*// @icon https://www.google.com/s2/favicons?sz=64&domain=0.10// @grant none// ==/UserScript==
// 具体原理:// 每次启动拟真都会读档,读档则会想服务器请求存档数据// 我们只需要修改响应数据更改存档数据,即可一键完成,// 只需要加载脚本,随后完成最后一步即可。。。(function() { 'use strict';
const originalFetch = window.fetch;
const gradeData = { "loginTime": "17x1xxxxxx", "startTime": "17x1xxxxxx", "dataList": [ // 这里有敏感数据 不方便展示 ], "finishNum": 13, "cameraPosX": 25.4873600006104, "cameraPosY": 1.35499978065491, "cameraPosZ": -30.6442451477051, "cameraRotX": 0.0, "cameraRotY": 251.015045166016, "cameraRotZ": 0.0, "studyList": [ true, true, true, true, false, false, true, true, true, true, true, true, true, true, true, true, true, true, false ], "doorState": [ false, false, false, false, false, false ], "hdFinish": [ false, true, true, true, true ], "doorIndex": 5, "hdState": [ false, false, true, false ], "videoCount": 1, "countHD1": 4, "guideIndex": 32 };
const hoursAgo = 6 + Math.random(); // 2-3小时 const referenceTime = Math.floor(Date.now() / 1000) - Math.floor(hoursAgo * 3600);
gradeData.loginTime = referenceTime.toString(); gradeData.startTime = referenceTime.toString();
const totalCourses = gradeData.dataList.length;
const firstCourseStart = referenceTime;
gradeData.dataList.forEach((course, index) => { const minDuration = 600; // 5分钟 const maxDuration = 600 * 4; // 10分钟 const duration = Math.floor(Math.random() * (maxDuration - minDuration) + minDuration);
if (index === 0) { course.startTime = firstCourseStart; course.endTime = firstCourseStart + duration; } else { // 后续课程在前一个课程结束后开始 const prevCourse = gradeData.dataList[index - 1]; // 课程之间的间隔为15-25分钟 const minInterval = 900; const maxInterval = 1500; const interval = Math.floor(Math.random() * (maxInterval - minInterval) + minInterval);
course.startTime = prevCourse.endTime + interval; course.endTime = course.startTime + duration; } });
function toBinaryStr(str) { const encoder = new TextEncoder(); const charCodes = encoder.encode(str); return String.fromCharCode(...charCodes); }
window.fetch = async function(...args) { const [input, init = {}] = args; const url = typeof input === 'string' ? input : input.url;
const targetApis = [ 'http://172.xx.0.xx:xxxx/', ];
const shouldIntercept = targetApis.some(api => url.includes(api));
if (!shouldIntercept) { return originalFetch.apply(this, args); }
const response = await originalFetch.apply(this, args); const clonedResponse = response.clone();
try { const data = await clonedResponse.json();
if (url.includes('/zydx-web/trans/vr/temp/get')) { const courseData = data.data; // 当前保存的课程信息 data.endflag = "1" try { const encrypt = btoa(toBinaryStr(JSON.stringify(gradeData))); console.log(gradeData); data.data = encrypt; }catch(e1) { console.error(e1); return response; } }
return new Response(JSON.stringify(data), { status: response.status, statusText: response.statusText, headers: response.headers });
} catch (error) { return response; } };
console.log('已启用');})(); Unity3dWeb金课堂的逆向
https://blog.cutebaka.cloud/posts/jkt/ 部分信息可能已经过时









