0%

编译型语言的特点

c , c++ , 打包成操作系统机器码,然后再执行

解释型语言的特点

Javascript,一边编译成机器码一边执行

V8引擎

V8引擎是谷歌浏览器和NodeJS内置的 Javascript引擎

比如你在一个网页中加载一个calc.js文件,如图:D:\个人博客\blog\source_posts\Javascript介绍\image-20220501212609819

这个文件里的每个字节都会被转化成符号,比如ASCII或者UTF-8字节D:\个人博客\blog\source_posts\Javascript介绍\image-20220501212604289谷歌浏览器会识别这些关键词符号,直至将文件读取完毕

D:\个人博客\blog\source_posts\Javascript介绍\image-20220501213009153然后检查语法并生成抽象树D:\个人博客\blog\source_posts\Javascript介绍\image-20220501213141154

之后交给V8引擎的ignition解释器,生成字节码

D:\个人博客\blog\source_posts\Javascript介绍\image-20220501213318435

ignition解释器然后会运行这些字节码,转化成cpu运行的机器码

对于一些被调用多次的函数,turbofan优化器会存留之前生成的机器码,跳过解释直接运行机器码,啊v要注意函数的参数个数,类型一定要是一样的D:\个人博客\blog\source_posts\Javascript介绍\image-20220501214321900

致谢

https://www.youtube.com/watch?v=p-iiEDtpy6I&t=634s

https://dev.to/lydiahallie/javascript-visualized-the-javascript-engine-4cdf

Scope

比如说执行 getperson

不含 city

local context for getperson

不会被阻塞

圆周运动周期

T 代表周期

F 频率,一秒多少周

$\omega$ = 2 $\pi$/T

v= 2 $\pi$r/T = $\omega$r

向心加速度 v2/r

取模的定义

模除(又稱模数取模操作取模運算等,英語:modulo 有时也称作 modulus)得到的是一个数除以另一个数的余数。

给定两个正整数:被除数 a除数 na modulo n (缩写为 a mod n)得到的是使用欧几里德除法a/n 的余数。

取模的性质

对于正整数a,k,m,n,如果有 a = m (mod n),那么 m* k (mod n) = [ a* k (mod n) ]mod n

费马小定理的证明

举例证明

如果p是一个素数(质数),a是一个正整数且与p的最大公因数为1,那么对于数列:

a, 2a, 3a, 4a, 5a, (p-1)a

将每个项都除去p得出余数, 得出的结果是一个包含正整数1到p-1的数列,且每个整数都只出现一次

举例,a=8,p=11,可得:

8, 0*11+ 8
16,1*11+ 5
24,2*11+ 2
32,2*11+ 10
40,3*11+ 7
48,4*11+ 4
56,5*11+ 1
64,5*11+ 9
72,6*11+ 6
80, 7*11+ 3

可以看到,余数是打乱后的数列 1,2,3,4,5,6,7,8,9,10

将数列逐项相称,a *2a *3a *4a *5a … *(p-1)a,

根据乘法的交换律,其结果就等于 1 * 2 * 3 … * (p-1) (mod p)

也就是说
$$
a^{p-1}(p-1)! \equiv (p-1)!(\mod p)
$$
接着根据取模的性质,两边消去(p-1),可得:
$$
a^{p-1} \equiv 1(\mod p)
$$

串珠证明

取一个整数a,一个素数p

一个珠子串共有p个珠子,每个珠子有a种颜色可能,那么这个珠子串共有ap种可能,

从所有可能的珠子串中减去都为同样的颜色珠子串(共有a个),得到的珠子串为ap-a 个

因为每个珠子串共p个珠子,所以ap-a 能够整除p

也就是
$$
a^{p}-a (\mod p)\equiv 0
$$
转化得
$$
a^{p}\equiv a (\mod p)
$$

$$
a^{p-1}\equiv 1 (\mod p)
$$

费马小定理解决扑克牌问题

给你一副按大小顺序排好的扑克牌(52张不包括大小王),将牌平分为上下两组,取第二组的一张牌,再取第一组的一张牌,循环直至所有牌都拿完

再次循环上面的操作,将牌组再平分为上下两组,取第二组一张,再取第一组一张,直至取完

如此循环,需要多少次循环才能使牌组重新回到顺序排好的状态呢?

www.youtube.com_watch_v=YcYmbcwmRwQ&t=3312s这里就可以用模算术,我们假设一张牌最初的位置是x

很容易发现规律:经过一次洗牌后,牌的位置变成了2x(mod 53) ,比如 27*2 mod53可得1,52*2 mod53可得51

那么进行n次洗牌,原本x位置的牌会变成 x* 2^n (mod 53),注意 x *2(mod53)*2(mod53)…*2(mod53)=x*2*2…*2(mod53)

要想n次洗牌后位置不变,则 x*2^n(mod53)=x

等式两边消去x,得2n(mod53)=1,变换写法就是2n=1 (mod53)

也就是说找到次幂n能够使2^n-1整除53

根据费马小定理,因为53是素数且和2之间没有公因数(公约数),则n=52

费马小定理到欧拉定理

未完待续…

视频参考

有兴趣的小伙伴可以进入文章页面,在底部评论 >_<

如何在关闭中查找变量

如下函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let obj = function() {
let i = 0

return {
setI(k) {
i = k
},

getI() {
return i
},
}
}

let x = obj()

console.log(x)

代码会打印出一个对象,里边有两个函数

image-20220403203258480

1.不启用本地替代,打开开发者工具,正常启动赛尔号

image-20220211233642647

在网络里搜索关键词zzz_conf会找到env.%7B开头的一个js文件,可以清楚的看到,这里有一行,写着游戏版本控制文件

2.打开服务器项目,(从gitee下载)

image-20220211234300923

找到env%7B-103这个文件,搜索关键词zzz_conf找到注释的版本文件位置,并修改成上一步中获取的url地址,(不要忘了保存修改)如下图:

image-20220211234556543

3.启动服务器项目

如下图,启动服务器

QQ图片20220211235159

启动后如下图:

image-20220211235430981

4.打开本地替代,重新进入赛尔号

打开本地替代

image-20220211235535173

刷新浏览器

发现这次版本更新,mapManager这个文件也更新了,这也好办

image-20220211235815559

关掉本地替代,重新打开一个网页,网址栏输入s.61.com,回车

image-20220212000141106

在网络里搜索mapmanager找到如下文件

image-20220212000401051

在这里右键,选在新标签打开屏幕截图(8)

进入新标签,按键盘,ctrl+s,将这个保存到服务器项目static/resouce/app/mapManager里,在这里新建文本文档

屏幕截图(9)

在vscode里,右键文件夹,可以快速打开文件所在目录

image-20220212001952528

将新文档重命名为网址最后的名字

image-20220212002132165

保存修改,重启服务器

image-20220212002212795

image-20220212002246897

启动本地替代,重进赛尔号

成功进入,但是😭,这次更新把js文件都改了,服务器里的js一个也没加载

image-20220212002535196

5. 本地替代修改游戏js文件

先找发包的js文件,网络里搜索控制对战速度的js文件battle,得到如下,记住这个文件名

image-20220212012614524

在源代码–>页面–>无域名里,找到这个文件

image-20220212012810056

右键,选择保存为本地替代,效果如图,有一个紫色的圆点image-20220212013001576

点击代码美化

image-20220212013058845

在美化后代码里,ctrl+f,搜索关键词animation.play,找到如下:

image-20220212013329053

修改为:

得来全不费工夫

在了解firda之前,我曾经接触过xposed,当时也是对这样的东西存在感到十分吃惊,Reverse Engineer的学习道路上,必不可少各种各样的工具。这些前辈们给我们留下的工具是我们工作的基础。如果说安卓应用和windows应用原本就像一个黑盒子,我们无法窥探它内部的工作,而frida和Ollydbug这些工具,就是我们打开黑盒面纱的钥匙。

我们要实现的功能:

破解vip才能使用的变声功能

付费语音包的使用

Jadx反编译,窥探APP源码,定位关键类

通过jadx反编译,找到应用包名,在Mainactivity里搜索关键词VIP,就找到了关键函数gotoBuyVIPUI

image-20220125234730911

IntelliJ smalidea 插件,动态调试APP

用Android killer 拆apk后,用IntelliJ打开项目。找到我们之前找到关键函数,在这下断点

image-20220125235703838

在APP上点击一个付费VIP才能使用的语音包,果然,在断点处停了下来。说明我们找对地方了,这个就是检测是否VIP的函数

改写APKsamli代码或者frida动态hook,实现vip功能的使用

由于对samli语法不熟悉,经过一番尝试无果

我选择简单粗暴的frida

以下为python注入代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import frida, sys

def on_message(message, data):#控制台打印
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)


with open('weiyin.js', 'r', encoding='UTF-8') as file:#打开js脚本文件
result = file.read()
jscode = result

process = frida.get_usb_device(1).attach('com.bsqrj.numfirst')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()

sys.stdin.read()

以下为weiyin.js 的脚本代码

1
2
3
4
5
6
7

Java.perform(function () {

Java.use('com.xmb.voicechange.db.LocalCache').isVIP.overload('android.content.Context').implementation=function(v){
return true}
})

注入脚本后,即可愉快使用vip功能,不过缺点是每次重启app后,都要重新注入,可惜不会samli语法啊

网页游戏的核心

网页网游的游戏界面绝大多数是通过canvas实现的

网页网游的通信绝大多数是通过websocket实现的

如何找到赛尔号的通信函数

首先打开一个空白页,再打开浏览器的调试工具,快捷键f12

这时输入网址s.61.com,因为我们提前打开了调试工具,就能截获到从页面html开始之后的所有的HTTP请求

image-20210802013616839

如图,281次请求,这些请求主要是加载页面素材的,真正的游戏通信在画红线的websocket里

点开ws,再点开左侧的某一个protocol,这时就能看到和服务器的通信了

image-20210802013833049

这时的消息主要是登录时的请求,并没有什么重要性,所以我们进游戏后再看protocol里的变化

image-20210802014201812

登录进去后发现另一个protocal里出现了很多消息,那么这个protocal就是我们和服务器建立的ws连接

image-20210802014345700

我们随意在游戏里进行一些操作,然后选中最近的一个收到的消息(红色),点击上方发起程序,

然后我们在调用堆栈里选择这个e.connect,点击右侧进入源代码位置

image-20210802014810999

image-20210802015122325然后点击左下角的美化,这时自动定位到了发起程序的源码位置

image-20210802015242539

点击左侧空栏,下断点,如图,我下在第1045行image-20210802015338965

这时回到游戏进行操作,发现仍能操作,这说明这一行代码现在并没有在运行

往下发现一个sendMessage函数,在这里下断点后,游戏瞬间暂停

image-20210802020418753

在右方监视输入t._cmdt._body,就可以实时分别地看到通信的协议号和包体

image-20210802020718632

此时的协议279代表从服务器获取当前时间,包体为空

此时的协议4353代表角色移动,包体x,y代表移动的坐标

image-20210802020959588

找到通信函数后如何制作脚本呢?

归根到底有几种办法:

  1. 每次打开进入游戏后,在控制台写入脚本
  2. 直接写入html源代码中,这样每次打开不用手动输入脚本
  3. 制作浏览器扩展插件

1.控制台脚本

hook 负责发包的js对象

既然我们已经找到了发包的函数,直接将尝试将其打包

首先注意prototype关键词,prototype是原型的意思,也就是说我们现在是在复杂的函数e内的一个叫sendMessage的原型函数中,要想能够完整还原函数e的全部功能,我们需要整个e,而不仅仅是它的sendMessage函数,这时使用this关键字就能在一个原型函数内获得其外部的整体

image-20210803021230357

封装的发包函数=this就是一行代码这么简单!!我们新建了一个全局变量封装的发包函数并将其指向了this实例

然后再封装我们想要模拟发送的包,也就是函数的参数上图括号里的t

image-20210803014801726

这么简单,三个字符的代码包=t

然后只需要每次发包时对包进行我们需要的更改(下图的4353是玩家移动的指令,包._body.x是移动的横坐标),然后使用封装的发包函数.sendMessage(包)就可以模拟真人操作了

image-20210803015159642

视频演示

small

虽然本地没看到用户移动,但是服务器确实收到了包,因为其他账号可以看到

接下来的时间我会制作一些其他发包的脚本,欢迎加入赛尔号启航战队混元~太极门,学习交流

2.本地替代

chrome浏览器有一个强大功能——本地替代。本地替代可以使你随心所欲的修改任意一个网页,其原理是在请求远程网站时会先查看是否存在本地替代的改写文件。

本地替代只有打开F12时才会生效。所以在打开目标网页之前需要先打开任意网页并打开F12然后再跳转到目标网页。查看s.61.com的网页源代码后,会发现整个游戏的代码都在BootStrapV2.min.js?(后跟时间戳)里,而经过我的测试,发现不论什么时间,返回的包都是一样的,虽然文件名带有不断变化的时间戳,但完全给它一个固定的文件名并进行本地改写。以下代码,先定义了一个网络请求的函数sendasgetRequest,然后使用这个函数来加载名为bootStart.js的文件,这个文件其实就是BootStrapV2.min.js的替代。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

async function sendasgetRequest(url) {
const Http = new XMLHttpRequest();

Http.open("GET", url);
Http.send();

if (Http.readyState === XMLHttpRequest.DONE) {
return Http;
}

let res;
const p = new Promise((r) => res = r);
Http.onreadystatechange = () => {
if (Http.readyState === XMLHttpRequest.DONE) {
res(Http);
}
}
return p;
}


document.body.ontouchstart = function (e) { e.preventDefault && e.preventDefault(); };
setTimeout(async function () {
var oHead = document.getElementsByTagName('HEAD').item(0);
// var script = document.createElement("script");
// script.type = "text/javascript";
// script.src = 'BootStrapV2.min.js?' + ~~(Date.now() / 1000);
// oHead.appendChild(script);
// var response=await sendasgetRequest( 'BootStrapV2.min.js?' + ~~(Date.now() / 1000) );
var response=await sendasgetRequest( 'bootStart.js' );

var script =

// ' debugger; ' +
response.response+'\n'
// + '//# sourceURL=s.61.com/bootStart.js'
// ;


var el = document.createElement('script');
el.type = 'text/javascript';
el.text = script
;
oHead.appendChild(el);



接下来分析网页的主要代码bootStart.js,可以发现游戏加载了如图所示的几个重要的js文件,在原本的index.html中你会发现一个奇怪的单词egret,经过研究,赛尔号启航使用的就是这个叫egret的游戏引擎制作的。在控制台输入egret,你会发现的确它是一个已经存在的全局变量。而图上的几个js文件就是赛尔号个性化修改后的egret的引擎文件。

接下来再通过修改这几个重要的引擎文件,就能实现游戏脚本的制作。那么接下来首先要找到这些引擎文件的请求是在哪里发起的。在网络选项=》发起程序中找到其请求是由bootStart.js中如下代码发起的

1
2
3
4
5
o.$value=e(n.$value)}catch(e){t=!0,o.$error=e}n.$error=null,n.$value=null,n.$next=null,o.$next?o.$next():t&&setTimeout(function(t){console.error(t)},0,o.$error)},o},t}();t.SimplePromise=e,egret.registerClass(e,"bootStrap.SimplePromise");var r=e,n=function(){function t(){}return __define,t.prototype,t.get=function(t,e,n,o){return void 0===e&&(e="text"),void 0===n&&(n="GET"),void 0===o&&(o=6e4),new r(function(r,i){var a,u,c=/^([\w-]+:)\/\//.test(t)?RegExp.$1:w.location.protocol;if(w.XMLHttpRequest)a=new w.XMLHttpRequest;else try{a=new ActiveXObject("MSXML2.XMLHTTP")}catch(t){a=new ActiveXObject("Microsoft.XMLHTTP")}a.onreadystatechange=function(){if(4==a.readyState)if(u&&clearTimeout(u),a.onreadystatechange=null,a.status>=200&&a.status<300||304==a.status||0==a.status&&"file:"==c)r(a);else{var e=new Error(t+" 加载失败! xhr.status:"+a.status);e.url=t,e.status=a.status,i(e)}},u=setTimeout(function(){clearTimeout(u),a.abort(),i(new Error('"'+t+'"加载超时,用时'+o/1e3+"s"))},o);try{a.responseType=e}catch(t){console.error(t)}

a.open(n,t,!0),
a.send()}).then(function(t){if(void 0!=t.response)return t.response;if(/msie

如果你在最后一行打上断点,你会发现这里的a其实是一个XMLHttpRequest,既然发送的请求在这里,那么处理请求的函数应该也在附近。往下找就发现了一句代码document.createElement("script"),这就是关键了,通过修改这里的script,就能实现修改引擎文件。我筛选了4个重要的引擎文件,以下图是通过访问本机的3009端口获取本地的4个引擎文件从而实现对引擎文件进行本地修改。当然,这样就需要本机在3009端口开启cors的服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
let List=[
'bootStrap/libs.{-1300278934}.BootStrapV2.min.js?-1300278934'
// , 'bootStrap/base.{-116702590}.BootStrapV2.min.js?-116702590'
,'bootStrap/base.%7B-116702590%7D.BootStrapV2.min.js?-116702590'.replace('%7B','{').replace('%7D','}')
// ,'bootStrap/env.%7B-1033305282%7D.BootStrapV2.min.js?-1033305282'.replace('%7B','{').replace('%7D','}')
,'bootStrap/env.%7B-2093208555%7D.BootStrapV2.min.js?-2093208555'.replace('%7B','{').replace('%7D','}')
// ,'bootStrap/libs.%7B-1300278934%7D.BootStrapV2.min.js?-1300278934'.replace('%7B','{').replace('%7D','}')
// ,'bootStrap/libs.%7B1442564649%7D.BootStrapV2.min.js?1442564649'.replace('%7B','{').replace('%7D','}')
,'bootStrap/start.%7B-792005426%7D.BootStrapV2.min.js?-792005426'.replace('%7B','{').replace('%7D','}')
]
// .replace('%7B','{').replace('%7D','}')

let judgr= ()=>{return List.some((ur)=>{if (ur==e){
return true
}})}
if(judgr()){
await fetch("http://localhost:3009/"+encodeURI(e).replaceAll('%','%25').replaceAll('?','%253f'), {

mode: "cors",
method: "GET",
headers: {
"Accept": "application/javascript; charset=UTF-8"
}
})
.then(response => response.text())
.then((response) => {
// alert(response)
f.text=response
document.head.appendChild(f).parentNode.removeChild(f) })

这里我为什么不继续使用chrome的本地替代功能,而要大费周章从自建服务器获取文件呢?

因为有BUG,chrome的本地替代有bug,修改index.html和bootStart还好,因为它们都在s.61.com的域名下,而这里的/bootStrap/不知为何变成了未知域名网址,网页就不会自动加载这些本地文件,仍然从远端获取。

egret引擎的介绍

egret是一个2D的游戏引擎,也就是说游戏里所有的2D图像本质上都是egret里的“显示物体”和“显示容器”,而所有这些物体和容器都存在于“舞台”之内,在控制台输入MFC.stage你就能得到这个”舞台“指向的 Object。

有了这个舞台 Object,你会发现它有一个children属性,这个属性就是它所有的子对象,有些子对象还会有自己的子对象,如此递进,就能得到”舞台“中任意的对象,也就是egret里的“显示物体”和“显示容器”。

1