根据抓包得到的域名来看,海底捞的游戏是完全外包给了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

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

在main.min.js里面

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

应该就是这里了,定义了一万个参数,再向下翻下,可以看到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栏里找到对应的源码,然后在行号那里打断点

然后再刷新页面,重新开始游戏,到了断点的位置就会自动停下来了。
经过一段时间的调试,我大概总结出了大部分参数所代表的含义:
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: , 'avatar': 'https: https:
|
附上一段计算三个参数的沙雕代码..抄的我脑子疼:
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)
|