web端的八门神器开发指南
简介
知道八门神器吗?知道的人想必已经30岁开外了,在那个移动互联网刚刚起步的时代,有很多有趣又好玩的单机游戏,之前的游戏开发还不成熟,对于金币等数值类类型的存储并咩有加密,此时八门神器出现了。
他的原理非常简单,比如你想修改金币余额,你只需要搜索当前余额的数值,可能会出现上万个地址都是该数值,不用担心,你在游戏中花一点钱,再从这上万的数值中搜索新的余额,此时数量就会大幅度减少或者只剩下一个,重复该操作直至到一个结果,那该结果的内存地址就是余额的内存地址,修改该地址的数值,即可篡改余额。
web端的八门神器开发指南
可以借鉴下类似的方式,开发一款web端适用的八门神器。
因为web开源的特性和数值的局限性,我们可以不通过检索的方式来修改具体的数值,
直接拦截类的实例方法是一个不错的主意,因为多数实例都是通过call方法来生成的,我们直接拦截call方法,通过设定一些规则,即可实现更加通用的修改器功能。
这是一款功能强大的浏览器插件,旨在帮助开发者捕捉、读取和修改网页中的对象实例。通过自定义规则,用户可以灵活地操作网页中的对象,适用于个人研究学习使用。
功能特性
- 实例化捕捉规则:通过自定义函数或属性数组,捕捉符合条件的对象实例。
- 实体读取规则:定义需要读取的对象属性,输出以供选择。
- 实体设置规则:修改对象实例的属性值,实现动态更新。
实现原理
插件通过content-script.js
脚本注入到网页中,利用Function.prototype.call
方法拦截对象实例的调用,并根据用户定义的规则进行捕捉、读取和修改。
源码如下
manifest.json
配置文件
{
"name": "八门神器",
"version": "1.0.0",
"manifest_version": 2,
"description": "八门神器",
"icons": {
"16": "logo.png",
"48": "logo.png",
"128": "logo.png"
},
"permissions": [
"tabs", "*://*/*","storage"
],
"browser_action": {
"default_title": "八门神器",
"default_icon": "logo.png",
"default_popup":"index.html"
},
"web_accessible_resources": ["injected-script.js"],
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["content-script.js"],
"all_frames": true,
"run_at":"document_start"
}
]
}
index.html
当前文件为弹出层的页面,主要用来接收用户的配置
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>说明文档</title>
<style>
*{padding: 0;margin: 0}
body{width: 650px;}
label{display: inline-block}
label input{vertical-align: middle}
textarea{resize: none}
textarea:focus{outline: none}
textarea::placeholder{color: #999}
.tip{padding: 15px;display: flex;flex-direction: row}
.openUrl{width: 150px;line-height: 33px;border: none;outline: none;background-color:green;color: white;height: 33px;cursor: pointer}
.openUrl:hover{background-color: #005b00;box-shadow: 5px 5px 5px #ccc}
</style>
</head>
<body>
<div class="tip">
<div>
<div style="color: red;width: 350px;margin-bottom: 10px">用前须知:本工具只限于个人研究学习使用,请勿用于其它任意非法及商业用途。</div>
<div><b>1. </b><b>实例化捕捉规则</b></div>
<label><textarea id="instanceCatchRule" style="width: 350px;height: 80px" placeholder="例子1自定义函数:instance.name !== void 0 && instance.age !== void 0 代表该类拥有name和age属性,instance是注入进来的变量 例子2属性数组:["name","age"] 缺陷是不能识别原型上的方法和继承来的属性"></textarea></label>
<div><b>2. </b><b>实体读取规则</b></div>
<label><textarea id="instanceReadRule" style="width: 350px;height: 80px" placeholder="例子:[name,age],代表输出name和age以供选择"></textarea></label>
<div><b>2. </b><b>实体设置规则</b></div>
<label><textarea id="instanceUpdateRule" style="width: 350px;height: 80px" placeholder="例子:{name:1}"></textarea></label>
</div>
<div style="display: flex;justify-content: center;flex-direction: column;margin-left: 45px">
<div style="position: absolute;margin-top: -50px;" id="tip"></div>
<button class="openUrl" id="save">设置</button>
</div>
<div style="position: absolute;right: 5px;bottom: 5px;color: #666">联系QQ:xxx</div>
<script src="indexPop.js"></script>
</div>
</body>
</html>
indexPop.js
当前文件为弹出层的js,将输入的配置存储起来,给注入的js用
function init() {
document.getElementById('save').addEventListener('click',function () {
save();
});
chrome.storage.sync.get({'8-door': {instanceCatchRule:'',instanceReadRule:'',instanceUpdateRule:''}}, function(data) {
data = data['8-door'];
document.getElementById('instanceCatchRule').value = data.instanceCatchRule || '';
document.getElementById('instanceReadRule').value = data.instanceReadRule || '';
document.getElementById('instanceUpdateRule').value = data.instanceUpdateRule || '';
});
}
function save() {
const instanceCatchRule = document.getElementById('instanceCatchRule').value;
const instanceReadRule = document.getElementById('instanceReadRule').value;
const instanceUpdateRule = document.getElementById('instanceUpdateRule').value;
chrome.storage.sync.set({'8-door': {instanceCatchRule,instanceReadRule,instanceUpdateRule}}, function() {
tip('设置成功!刷新网页后生效');
});
}
function tip(msg) {
document.getElementById('tip').innerText = msg;
setTimeout(()=>{
document.getElementById('tip').innerText = '';
},3000);
}
init();
content-script.js
核心逻辑,负责读取配置,拦截call方法,通过配置篡改参数用的
let door8 = {};
function viewInitFn() {
let classArray = [];
let showed = true;
let timer = null;
function exe(exp,globalData) {
try {
return new window.Function('globalData','data','instance','with(globalData){return ' + exp + '}')(globalData,globalData,globalData);
}catch (e) {
console.error(e);
return e.toString();
}
}
function parseMethods(methods) {
let methodDefault = null;
let methodExt = null;
if(methods){
for(const key in methods){
if(!Object.prototype.hasOwnProperty.apply(methods,[key]))continue;
const method = methods[key];
if(/EXT$/.test(key)){
if(!methodExt)methodExt = {};
methodExt[key] = method;
}else {
if(!methodDefault)methodDefault = {};
methodDefault[key] = method;
}
}
}
return [methodDefault,methodExt];
}
function mergeMethodExt(methods,methodsExt){
for(const key in methodsExt){
if(!Object.prototype.hasOwnProperty.call(methodsExt,[key]))continue;
const methodExt = methodsExt[key];
const reg = /([ABCD])?EXT$/;
const content = key.match(reg);
const opportunity = content ? content[1] : 'C';
const methodName = key.replace(reg,'');
const method = methods[methodName];
if(!method)return;
switch (opportunity){
case 'A':
methods[methodName] = function () {
methodExt.apply(methods,[...arguments,method.apply(methods,[...arguments])]);
};
break;
case 'D':
delete methods[methodName];
break;
case 'B':
methods[methodName] = function () {
methodExt.apply(methods,[...arguments]);
method.apply(methods,[...arguments]);
};
break;
case 'C':
default:
methods[methodName] = methodExt;
break;
}
}
return methods;
}
function merge(parentVal, childVal) {
const [parentMethodDefault,parentMethodExt] = parseMethods(parentVal);
const [childMethodDefault,childMethodExt] = parseMethods(childVal);
if (parentMethodDefault){
Object.assign(parentVal, parentMethodDefault);
}
if (childMethodDefault){
Object.assign(parentVal, childMethodDefault);
}
if(parentMethodExt){
mergeMethodExt(parentVal,parentMethodExt);
}
if(childMethodExt){
mergeMethodExt(parentVal,childMethodExt);
}
return parentVal;
}
function initCall() {
const call = Function.prototype.call;
let {instanceCatchRule,instanceUpdateRule} = door8;
if(instanceCatchRule && instanceCatchRule[0] === '['){
try {
instanceCatchRule = JSON.parse(instanceCatchRule);
}catch (e) {
console.error(e);
}
}else {
instanceCatchRule = new window.Function('globalData','data','instance','with(globalData){return ' + instanceCatchRule + '}')
}
Function.prototype.call = function (obj,...args) {
const push = ()=>{
if(classArray.indexOf(obj) === -1){
if(instanceCatchRule && instanceUpdateRule){
try {
const data = exe(instanceUpdateRule,obj);
merge(obj,data)
}catch (err){
console.error(err);
}
}
classArray.push(obj);
}
};
if(!isObject(obj)){
return this.apply(obj,args);
}
if(!instanceCatchRule){
push();
}else if(typeof instanceCatchRule === 'function'){
try {
if(instanceCatchRule(obj,obj,obj,obj)){
push();
}
}catch (e) {
}
}else {
for(let i = 0;i < instanceCatchRule.length;i++){
const cur = instanceCatchRule[i];
if(!cur || typeof cur !== 'string' || Object.getOwnPropertyNames(obj).indexOf(cur) === -1){
return this.apply(obj,args);
}
}
push();
}
return this.apply(obj,args);
}
}
function initView() {
initCall();
setTimeout(()=>{
renderFixed();
timer = setInterval(function () {
updateDom();
},1000);
},document.body ? 0 : 1000)
}
function renderFixed() {
const html = `<div class="door-8-wrap" style="display: none">
<style>
.door-8-wrap{z-index:999999999;background-color: #fff;box-shadow: 0 0 5px #000;position: fixed;right: 10px;top: 50%;margin-top: -24px;line-height: 16px;padding: 10px 6px;min-width: 120px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap}
.door-8-close{position: absolute;right: 3px;top: 3px;cursor: pointer;}
.door-8-close:hover{color: red}
.door-8-result{max-height: 300px;overflow-y: scroll;overflow-x: hidden}
.door-8-result-row{border-bottom: 1px solid #d7d7d7}
.door-8-result-col{line-height: 16px;color: #666;position: relative}
.door-8-result-col>span{display: inline-block;max-width: 300px;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;vertical-align: middle;color: #666}
.door-8-result-col>.p-copy{color: #333}
.door-8-tip{font-size: 12px;text-align: center;width: 100%;line-height: 14px;color: #999;margin-top: -6px}
.door-8-copy-tip:after{content: '复制成功';transform: scale(0.7);margin-left: 3px;color: red;position: absolute;right: 0;top: 0;background-color: #fff;padding: 2px 4px}
</style>
<div class="door-8-tip p-copy-all" title="单击文本可直接复制(点我可复制全部,直接粘贴至excel)">单击文本可直接复制</div>
<div class="door-8-close" id="door-8-close">X</div>
<div id="door-8-result" class="door-8-result"></div>
</div>`;
let parentEle = document.getElementById('door-8-wrap');
if(!parentEle){
parentEle = document.createElement('div');
parentEle.innerHTML = html;
parentEle.children[0].id = 'door-8-wrap';
document.body.append(parentEle.children[0]);
}
document.getElementById('door-8-close').addEventListener('click',function () {
clearInterval(timer);
document.getElementById('door-8-wrap').style.display = 'none';
classArray = [];
});
document.getElementById('door-8-wrap').addEventListener('click',function (event) {
const target = event.target;
if(target.classList.contains('p-copy')){
if(copy(target.innerText)){
tip('复制成功!',target);
}
}else if(target.classList.contains('p-copy-all')){
if(copyAll()){
tip('复制全部成功!',target);
}
}
})
}
function tip(msg,ele) {
if(!ele.classList.contains('door-8-copy-tip')){
ele.classList.add('door-8-copy-tip');
setTimeout(()=>{
ele.classList.remove('door-8-copy-tip');
},1000)
}
}
function updateDom() {
const parentElement = document.getElementById('door-8-result');
if(!parentElement)return;
if(classArray.length === 0 || !door8.instanceReadRule){
parentElement.innerHTML = '';
return;
}
let html = ``;
classArray.forEach(item=>{
const instanceReadRule = door8.instanceReadRule;
if(!instanceReadRule){
let result = stringify(item);
html += `<div class="door-8-result-row"><div class="door-8-result-col"><span>实例:</span><span class="p-copy" title="${htmlEncodeByRegExp(stringify(item,null,' '))}">${result}</span></div></div>`;
return;
}
let list = [];
try {
list = exe(instanceReadRule,item);
}catch (e) {
html += `<div class="door-8-result-row"><div class="door-8-result-col"><span>读取失败:</span><span class="p-copy" title="${htmlEncodeByRegExp(e.toString())}">${e.toString()}</span></div></div>`;
return;
}
let tr = '';
if(!list || Object.prototype.toString.apply(list) !== '[object Array]'){
list = [list];
}
list.forEach((item,index)=>{
tr += `<div class="door-8-result-col"><span>${index+1}.</span><span class="p-copy" title="${htmlEncodeByRegExp(stringify(item,null,' '))}">${stringify(item)}</span></div>`;
});
html += `<div class="door-8-result-row">${tr}</div>`;
});
parentElement.innerHTML = html;
const wrapElement = document.getElementById('door-8-wrap');
if(wrapElement && wrapElement.style.display !== 'block'){
wrapElement.style.display = 'block';
}
}
function copyAll() {
if(classArray.length === 0)return false;
const parentElement = document.getElementById('door-8-result');
if(!parentElement)return false;
return copy([...parentElement.querySelectorAll('.door-8-result-row')].map(row=>{
return [...row.querySelectorAll('.door-8-result-col .p-copy')].map(ele=>ele.innerText).join('\t')
}).join('\n'))
}
function isObject(obj) {
return Object.prototype.toString.apply(obj) === '[object Object]'
}
function stringify(data,replace,space) {
if(typeof data === 'number' || typeof data === 'string')return data;
try {
return JSON.stringify(data,replace,space)
}catch (e) {
return e.toString();
}
}
function htmlEncodeByRegExp(str) {
if(str === null || typeof str === 'undefined')return str;
str = typeof str !== 'string' ? String(str) : str;
if(str.length === 0) return '';
return str.replace(/&/g,'&')
.replace(/</g,'<')
.replace(/>/g,'>')
.replace(/ /g,' ')
.replace(/'/g,''')
.replace(/[\r\n]+/g,' ')
.replace(/"/g,'"');
}
function copy(text) {
if(text && typeof text !== 'string'){
try {
text = JSON.stringify(text,null,' ');
}catch (e) {
}
}
const textarea = document.createElement('textarea');
textarea.style.position = 'fixed';
textarea.style.top = '0';
textarea.style.left = '0';
textarea.style.width = '1px';
textarea.style.height = '1px';
textarea.style.padding = '0';
textarea.style.border = 'none';
textarea.style.outline = 'none';
textarea.style.boxShadow = 'none';
textarea.style.background = 'transparent';
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
let error = null;
try {
const successful = document.execCommand('copy');
error = !successful;
} catch (err) {
console.warn('Unable to copy.');
error = true;
}
document.body.removeChild(textarea);
return !error;
}
initView();
}
function init() {
chrome.storage.sync.get('8-door',(data)=>{
setTimeout(()=>{
door8 = data['8-door'] || {};
const scripts=[viewInitFn];
scripts.forEach(function (viewInitFn) {
const script= document.createElement("script");
script.innerHTML = `const door8 = ${JSON.stringify(door8)};(${viewInitFn.toString()})()`;
script.onload=function () {
this.parentNode.removeChild(this);
};
document.head.appendChild(script);
});
console.log(data);
})
})
}
init();
crx文件下载
附件里面应该有,或者通过上面的源码自行在浏览器中打包下即可
使用方法
- 安装插件:将插件添加到浏览器中。
- 设置规则:在插件的弹出页面中,输入实例化捕捉规则、实体读取规则和实体设置规则。
- 应用规则:点击“设置”按钮,规则将保存并生效。
- 查看结果:刷新网页后,插件将根据规则捕捉、读取和修改对象实例,并在页面中显示结果。
配置事例
// 实例化捕捉规则
const instanceCatchRule = 'instance.name !== void 0 && instance.age !== void 0';
// 实体读取规则
const instanceReadRule = '[name, age]';
// 实体设置规则
const instanceUpdateRule = '{name: "John", age: 30}';
能用来做啥?
读取部分视频网站全部的m3u8链接?其他的妙用等待开发者自行挖掘哈哈
注意事项
- 本工具仅限个人研究学习使用,请勿用于非法及商业用途。
- 使用前请仔细阅读说明文档,确保理解各项规则的含义和用法。