Charles 抓包破解「羊了个羊」

2022-12-02 11:23:54 浏览数 (1)

最近“羊了个羊”非常火爆,在朋友圈和微信小程序都能看见这只可爱的羊的身影。被它的外表吸引过去才发现,这只羊真的太难过了!

点进去你会发现,第一关太简单了,这不是有手就行?可是到了第二关,随着“难度飙升”四个字飘过,你就会陷入其中不能自拔,转眼就到了第二天清晨。甚至不由得让人怀疑这游戏在 PUA 你。

为了探究这个游戏背后的秘密,我们可以使用抓包工具查看它在游戏过程中究竟干了什么。

首先需要下载一个抓包工具,传统的抓包工具有WireShark、TcpDump以及Charles等,不过WireShark不能查看加密的HTTPS请求的数据,并且WireShark和TcpDump几乎会查看所有包,难以定位。相对来说,Charles的抓人眼球的青花瓷Icon以及其界面更加优雅好用,我们这里就使用Charles进行抓包。在Charles官网

代码语言:javascript复制
https://www.charlesproxy.com

可以获取到应用,下载的时候会有 30 天免费试用,后续如有需要可以在数码荔枝(不是广告)购买正版,价格为 199 元人民币。

安装完成之后,首先需要安装根证书,使Charles能够设置代理。打开软件之后,依次选择 Help -> SSL Proxying -> Install Charles Root Certificate,这里以 Mac Book Pro 为例,需要将证书安装到钥匙串(Keychain)。安装好了之后需要打开钥匙串并点击“始终信任”此证书。在默认状态下,Charles不能进行HTTPS的抓包,需要在 Proxy -> Recording Settings -> Include 中添加 HTTPS,选择 HTTPS 协议,在 Host 中填入通配符 "*",在 Port 中填入端口号 443,如下图所示。

之后可以使用快捷键 Shift Command P 或者 Proxy -> macOS Proxy 打开 Mac 的代理,这时打开浏览器任意网页,就可以看到 Charles 在工作。下一步为了能在iPhone手机端也能使用 Charles 进行抓包,需要在手机上也安装根证书,路径为 Help -> SSL Proxying -> Install Charles Root Certificate on a Mobile Device or Remote Browser

在手机上点击无线局域网,选择当前网络右边的感叹号拉到最下面有一个HTTP 代理(Configure Proxy)的设置,选择手动(Manual);输入上面图片的代理地址,将 "10.105.40.67" 输入到服务器(Server)中,"8888" 输入到端口号(Port)中;点击存储(Save)即可。然后打开浏览器访问上方图片的地址 "chls.pro/ssl" 选择“允许(Approve)”会下载根证书到手机上。打开通用(General)-> VPN 中会找到刚刚下载的根证书,输入手机锁屏密码进行安装,安装完成之后选择关于本机(About)拉到最下方,找到 Charles 开头的根证书点击开关打开,选择“继续(continue)”即可。现在就完成了抓包工具的配置工作。

将手机和电脑都连接到同一个网络环境下以使用 Charles 代理。下一步是查看游戏发送的包。进入微信并点击“羊了个羊”小程序,在 Charles 中我们可以看到它向一个网址发起了请求。

其中 map 开头的两个请求就是关卡。我们可以在 Charles 的右边界面点进去看它有一些什么参数。

这里的 "err_code" 表示错误代码,0表示正常,"err_msg" 表示错误信息,"data"表示它加载的数据,可以通过另一个请求找到这串代码对应的请求。

里面可以看到每一块的坐标以及高度,第一关只有 2 层,所以 3 层对应的为空,"blockTypeData" 中的数字表示对应种类有多少组,每组 3 个块。通过上面的 "data" 参数选择本来是第二关的请求,将第一关的请求中的 "80001" 修改为第二关对应的 "levelKey",一般来说是今天的日期,比如说 "90018"。然后右键点击请求,选择 "Map Local" 将文件替换。

需要注意这时 Charles 的 Tool 中需要选中 Map Local

之后闯过第一关,第二关就会自动替换成第一关。

这样就可以轻松过关。但也别着急,这里过关的时候我们也可以看看它到底向服务器发送了什么请求。

可以看到它实际上就是向服务器发送了一个这样的请求,来表示你已经通关。那么如果你想增加通关次数,其实只要向这个地址一直发送请求即可。发送请求的代码可以点击下方的阅读原文查看。其中有个参数 "t" 表示你自己的 token,你可以在任何一次向服务器发起的请求中找到这个参数,一般是以 "ey" 开头。

那这个游戏到底能不能顺利通关呢?那么就要从它的源代码出发找寻答案。从上面抓包中可以知道其实每一种类别的块的数量以及每一层的块的位置是游戏刚开始就已经确定的。那么它是如何确定每一块的层数和位置的呢?我们可以在

代码语言:javascript复制
https://github.com/SleepyAsh0191/sheep-n-sheep-front/blob/main/assets/main/index.0df91.js

找到对应的源码。通过格式化美化压缩后的代码。定位到 3926 行处,有下面一段代码。

代码语言:javascript复制
e.prototype.createBlockTypeObj = function() {
    var t = this.nowLevelData.blockTypeData;
    for (var e in this.blockTypeArr = [], this.nowLevelBlockObj = {}, t)
    for (var o = 3 * t[e], n = 0; n < o; n  ) this.blockTypeArr.push(e);
    console.log("PUSH=> block ", this.blockTypeArr.length), this.blockTypeArr = c.default.shuffle(this.blockTypeArr), p.default.blacksInfo.blockCurCount = this.blockTypeArr.length, p.default.blacksInfo.blockAllCount = this.blockTypeArr.length, console.log("blockCurCount = "   p.default.blacksInfo.blockCurCount   " blockAllCount = "   p.default.blacksInfo.blockAllCount)
}

这段代码简单的说就是将所有种类的块的数量先乘以 3,然后进行随机洗牌,这样就产生了所有的块的数组。再接下来会将这个数组中的元素插入到每一层中,像下面这样。

代码语言:javascript复制
e.prototype.initBlockNodeLayer = function(t) {
    p.default.getInstance().cookieDict.cookieCurCount = 0;
    var e = this.nowLevelData.levelData;
    for (var o in e)
        for (var n in e[o]) t ? this.addBlockFunc(e[o][n], cc.winSize.height) : this.addBlockFunc(e[o][n], 0);
            t ? this.playInitBlockAnimate() : this.noPlayAnimate()
}

这里面涉及到了一个 "addBlockFunc()" 的函数,跳转到它的定义,我们就会发现它实际上就是做了一个工作,当前位置的类型没有确定的时候,也就是说当前位置还没有分配块,就把数组中的最后一个元素赋予这个位置。

代码语言:javascript复制
e.prototype.addBlockFunc = function(t, e) {
    var o = cc.instantiate(this.blockPrefab);
    this.blockArea.addChild(o);
    var n = this.nowLevelData.widthNum * this.minBlockNum,
    a = this.node.width / n,
    i = this.node.width / this.nowLevelData.widthNum,
    c = i / this.blockMaxWidth;
    c *= this.scaleRate, o.scale = c;
    var s = t.rolNum * a   i / 2,
    l = -(t.rowNum * a   c * this.blockMaxHeight / 2);
    if (o.x = s, o.y = l   e, 0 == t.type) {
        var u = this.blockTypeArr.pop();
        t.type = u
    }
    this.cookieBlockType == t.type ? t.cookie = 1 : t.cookie = 0, t.cookieType = this.cookieBlockType, o.getComponent(r.default).addBlockNode(t, this.removeCardNode.bind(this), this.refreshIndex.bind(this)), this.addBlockDataObj(o, t)
}

那么实际上就是完全随机的将块加到每一层的位置。并且通过地图请求我们可以发现,这个地图它实际上是一个两头小中间大的纺锤形状。所以在一开始的时候,你会觉得非常顺利,而到后面难度会陡然提升。并且最后几层甚至块是完全重合的,导致不用洗牌道具就容易卡住。而这个游戏将洗牌功能做成了一个广告,导致非常难以通关。

这个游戏的漏洞也在提示我们程序员,在做 token 验证的时候,需要对 token 的存活时间进行设置,否则就可能由于 token 的泄露而造成安全隐患。

0 人点赞