海底捞APP游戏中心JS破解

根据抓包得到的域名来看,海底捞的游戏是完全外包给了51h5.com这个公司来做,外包是没有前途的,还是建议自己招人做吧

前段时间海底捞游戏的参数很简单,就是先访问开始游戏的接口,然后再给结束游戏的接口发一条数据,其中包括三条数据:

1
2
3
4
5
data = {
"token": self.gametoken,
"gkey": self.gkey,
"score": self.gameScore
}

token是访问 https://api-hdl.51h5.com/hdl/game/init 这个接口得到的

gkey是游戏初始化的时候得到的,也就是开始游戏的接口 https://api-hdl.51h5.com/hdl/game/begin

score是这局游戏的得分

很明显,之前的参数就很简单,随便弄一弄就能够成功伪造一个游戏数据

在海底捞经历了被几万小号刷分之后,外包商终于有了动静,调整了参数,我抓了个包,随便玩了玩游戏,目前的参数是这样的

1
2
3
4
5
6
token: self.gametoken
gkey: self.gkey
score: 5
pg: 1553147847020-1553147847032-1553147849134-227738-229486|1553147856490-567810-570930|1553147857335-1553147858090-113524-114295|1553147860104-226980-228668
gs: 1553147860180-1553147866258-226246-226248|0-113119-113119|0-113119-113119|0-113119-113119
gd: 226246-226244-226246-226240-226238-226240-226240-226238-226238

多了 pg gs gd 这三个参数,看起来挺复杂的,因为这是一个js游戏,游戏过程中完全没有数据传输,那这个加密就是写在js里了,那我们就来看看这些参数是怎么生成的吧。

首先,通过抓包,我发现了几个小游戏的debug地址….应该是开发人员为了方便自己调试而准备的,开发人员上点心吧…

// sgamelist: ["https://debug.ews.m.jaeapp.com/ajl/FTLMS/",
//     "https://debug2.ews.m.jaeapp.com/hyk/hdl_fly/",
//     "https://debug.ews.m.jaeapp.com/ajl/QSDZZ/"],

那我们这就很好调试了,有了web端的游戏地址,还不需要cookie,F12启动(这里以第一个小游戏为例进行说明):

打开Chrome F12控制台,然后把这些都勾上,模拟一下iphone X

1.png

刷新一下,准备解析参数

我之前抓过包,所以知道结束游戏的接口是/end,那就直接搜索一下这个接口,应该就能找到pg gs gd这三个参数了

2.png

在main.min.js里面

3.png

然后发现pg是从s里面取出来的属性值,在上面可以看到 s = secret.instance.end(),于是我们去找这个secret看看

4.png

应该就是这里了,定义了一万个参数,再向下翻下,可以看到pg gs gt的计算公式了

1
2
3
4
5
var e = this.gotoT + "-" + this.startLoad + "-" + this.endLoad + "-" + this.vxy(this.endLoad, this.statew) + "-" + this.vxy(this.endLoad, this.stateh) + "|" + this.startT + "-" + this.vxy(this.startT, this.startX) + "-" + this.vxy(this.startT, this.startY) + "|" + this.noTIpsT + "-" + this.knowT + "-" + this.vxy(this.knowT, this.knowX) + "-" + this.vxy(this.knowT, this.knowY) + "|" + this.knowQDT + "-" + this.vxy(this.knowQDT, this.knowQDX) + "-" + this.vxy(this.knowQDT, this.knowQDY)

i = this.gameST + "-" + this.gameET + "-" + this.vxy(this.gameST, this.gameC) + "-" + this.vxy(this.gameST, this.gameScore) + "|" + this.prop1T + "-" + this.vxy(this.prop1T, this.prop1X) + "-" + this.vxy(this.prop1T, this.prop1Y) + "|" + this.prop2T + "-" + this.vxy(this.prop2T, this.prop2X) + "-" + this.vxy(this.prop2T, this.prop2Y) + "|" + this.prop3T + "-" + this.vxy(this.prop3T, this.prop3X) + "-" + this.vxy(this.prop3T, this.prop3Y)

s = this.vxy(this.gameST, this.pillarC) + "-" + this.vxy(this.gameST, this.birdC) + "-" + this.vxy(this.gameST, this.gBirdC) + "-" + this.vxy(this.gameST, this.propC) + "-" + this.vxy(this.gameST, this.hPropC) + "-" + this.vxy(this.gameST, this.add1) + "-" + this.vxy(this.gameST, this.add2) + "-" + this.vxy(this.gameST, this.add3) + "-" + this.vxy(this.gameST, this.add4);

然后我们再去找这个vxy函数,这肯定是要用到的。

1
2
3
t.prototype.vxy = function(t, e) {
return this.getTimeNum(t) * (this.getGkeyNum() + e)
}

看到了用到了getTimeNum和getGkeyNum函数,再去找一下

1
2
3
4
5
6
7
8
9
10
11
t.prototype.getTimeNum = function(t) {
return t += "",
Number(t.charAt(10)) + 1
}
,
t.prototype.getGkeyNum = function() {
var t = window.sessionStorage.getItem("gkey")
, e = this.charToNum(t.charAt(t.length - 3))
, i = this.charToNum(t.charAt(t.length - 5));
return Number(e + i)
}

又一个charToNum,再去找找。。

1
2
3
4
5
t.prototype.charToNum = function(t) {
for (var e = "", i = 0; i < t.length; i++)
e += t.charAt(i).charCodeAt();
return e
}

到这里就差不多懂了,我们需要找到那一万个参数是如何生成的,然后伪造参数,那就可以用它的计算公式来计算pg啥的了

断点是个好东西,具体用法是在F12的sources栏里找到对应的源码,然后在行号那里打断点

5.png

然后再刷新页面,重新开始游戏,到了断点的位置就会自动停下来了。

经过一段时间的调试,我大概总结出了大部分参数所代表的含义:

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
gotoT:1552699042463
startLoad:1552699047534 # 画面载入
endLoad:1552699059366 # 画面载入完成
statew:750 # 画面宽度
stateh:1797 #画面高度
startT:1552699149502 #点击开始游戏按钮的时间
noTIpsT:0
knowT:0
knowY:0
knowQDT:1552699415153 #选择道具按钮点击时间
knowQDX:457 #选择道具的框的大小X
knowQDY:1300 #选择道具的框的大小Y
gameST:1552699415593 #游戏正式开始的时间
gameET:1552699423235 #游戏正式结束的时间
gameC:5 #游戏中总点击的次数
gameScore:18 #游戏得分
prop1T:0
prop1X:0
prop1Y:0
prop2T:0
prop2X:0
prop2Y:0
prop3T:0
prop3X:0
prop3Y:0
pillarC:6 #跳到柱子的次数
birdC:5 #全局鸟的数量
gBirdC:5 #捕获的鸟的数量
propC:2 # 随机数递增
hPropC:1 # 比propC小
add1:0 #加1分的时候add1++ 加4分的时候 add2++ 加8分的时候 add3++ 加15分的时候 add4++
add2:1

好了,现在就是这些参数了,然后开始准备伪造数据发包试试看。

中间遇到了很多坑,我把之前找到的那几个函数用python复写了一遍,最后应该是这样的:

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

def vxy(self, t, e, gkey):
tmp1 = self.get_time_num(t)
tmp2 = self.get_gkey_num(gkey) + int(e)
return str(tmp1 * tmp2)

def get_time_num(self, t):
try:
return int(str(t)[10]) + 1
except IndexError:
return 1

def get_gkey_num(self, gkey):
return int(str(self.char_to_num(gkey[len(gkey) - 3])) + str(self.char_to_num(gkey[len(gkey) - 5])))

def char_to_num(self, t):
num = 0
for i in range(0, len(t)):
num = num + ord(t[i])
return num

其中get_time_num会遇到越界的问题,所以捕获一下异常

然后准备随机生成参数:

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
self.gotoT = str(CurrentTime())
self.startLoad = str(CurrentTime())
self.endLoad = str(CurrentTime() + random.randint(2000, 4000))
self.statew = "700"
self.stateh = "1760"
self.startT = "0"
self.startX = "0"
self.startY = "0"
self.noTIpsT = "0"
self.knowT = "0"
self.knowX = "0"
self.knowY = "0"
self.knowQDT = str(CurrentTime() + random.randint(1000, 2000))
self.knowQDX = "460"
self.knowQDY = "1400"
self.prop1T = "0"
self.prop1X = "0"
self.prop1Y = "0"
self.prop2T = "0"
self.prop2X = "0"
self.prop2Y = "0"
self.prop3T = "0"
self.prop3X = "0"
self.prop3Y = "0"
self.gameST = str(int(self.knowQDT) + random.randint(3000, 6000))
self.gameET = str(int(self.gameST) + random.randint(1000, 2000))
self.propC = "0"
self.hPropC = "0"
self.add1 = str(random.randint(60, 100))
self.add2 = str(random.randint(40, 60))
self.add3 = str(random.randint(20, 40))
self.add4 = str(random.randint(10, 20))
self.gameC = str(int(self.add1) + int(self.add2) + int(self.add3) + int(self.add4) + int(random.randint(1, 20)))
self.pillarC = str(int(self.gameC) + random.randint(30, 50))
self.birdC = str(int(self.gameC) + random.randint(15, 50))
self.gBirdC = str(self.gameC)
self.gameScore = 100

这里一开始我模拟完后出现了异常回显

1
{"status":1,"data":{"credit":0,"rank":[]}}

没有排行榜数据呀…说明这是被后端检测到这是异常数据了

经过不断的调试,最终发现了问题出在分数上,add1 add2 add3 add4分别代表了分数增加的情况,而我把gameScore写死成了100,这就导致总分和add增加的分数总和不一致的情况,于是改写成

1
self.gameScore = str(int(self.add1) + 4 * int(self.add2) + 8 * int(self.add3) + 15 * int(self.add4))

果然问题出在这里。。。第一个游戏的破解大概就是这样

脚本成功的回显:

1
2
3
4
{'status': 1, 'data': {'credit': 10, 'rank': [{'rank': 118, 'score': 793, 'uid':
'', 'avatar': 'https://app-images.kiwa-tech.com/app/user/header/3.jpg'}, {'rank': 119, 'score': 791, 'uid': 'n-xxxxxx'
, 'avatar': 'https://app-images.kiwa-tech.com/app/user/header/42.jpg'}, {'rank': 120, 'score': 791, 'uid': '', 'avatar': '
https://app-images.kiwa-tech.com/app/user/header/1.jpg'}]}}

附上一段计算三个参数的沙雕代码..抄的我脑子疼:

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
def caclulate_pg_for_g1(self):
return self.gotoT + "-" + self.startLoad + "-" + self.endLoad + "-" + self.vxy(self.endLoad, self.statew,
self.gkey) + "-" + self.vxy(
self.endLoad, self.stateh, self.gkey) + "|" + self.startT + "-" + self.vxy(self.startT, self.startX,
self.gkey) + "-" + self.vxy(
self.startT, self.startY, self.gkey) + "|" + str(self.noTIpsT) + "-" + str(
self.knowT) + "-" + self.vxy(self.knowT, self.knowX, self.gkey) + "-" + self.vxy(self.knowT,
self.knowY,
self.gkey) + "|" + str(
self.knowQDT) + "-" + self.vxy(self.knowQDT, self.knowQDX, self.gkey) + "-" + self.vxy(self.knowQDT,
self.knowQDY,
self.gkey)

def caclulate_gs_for_g1(self):
return self.gameST + "-" + self.gameET + "-" + self.vxy(self.gameST, self.gameC,
self.gkey) + "-" + self.vxy(self.gameST,
self.gameScore,
self.gkey) + "|" + self.prop1T + "-" + self.vxy(
self.prop1T, self.prop1X, self.gkey) + "-" + self.vxy(self.prop1T, self.prop1Y,
self.gkey) + "|" + self.prop2T + "-" + self.vxy(
self.prop2T, self.prop2X, self.gkey) + "-" + self.vxy(self.prop2T, self.prop2Y,
self.gkey) + "|" + self.prop3T + "-" + self.vxy(
self.prop3T,
self.prop3X,
self.gkey) + "-" + self.vxy(
self.prop3T, self.prop3Y, self.gkey)

def caclulate_gd_for_g1(self):
return self.vxy(self.gameST, self.pillarC, self.gkey) + "-" + self.vxy(self.gameST, self.birdC,
self.gkey) + "-" + self.vxy(
self.gameST, self.gBirdC, self.gkey) + "-" + self.vxy(self.gameST, self.propC,
self.gkey) + "-" + self.vxy(self.gameST,
self.hPropC,
self.gkey) + "-" + self.vxy(
self.gameST, self.add1, self.gkey) + "-" + self.vxy(self.gameST, self.add2,
self.gkey) + "-" + self.vxy(self.gameST,
self.add3,
self.gkey) + "-" + self.vxy(
self.gameST, self.add4, self.gkey)

作者

Dawnnnnnn

发布于

2019-03-21

更新于

2022-07-06

许可协议

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

评论