90

微信 “跳一跳” 分析笔记

 6 years ago
source link: http://mp.weixin.qq.com/s/Amch6Vnhi1oYXik86tKNHQ
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

微信 “跳一跳” 分析笔记

洪荒之力&obaby 看雪学苑 2018-01-05 10:00 Posted on

Image

0x00 引子


目前网上已经有模拟发包上传分数的方案了,图形识别和人肉丈量都是不错的选择,此外,也有基于安卓 adb 实现的。下面给出一些这方面的分析,没别的意思,就是纯粹好玩。

0x01 抓包


抓包通常可以用 Charles 或者 Fiddler 抓小程序 https 数据,这里说下另一种方法,从安卓代码入手,找到 https 明文发包点截取封包。

从微信的 log 中看到,每次游戏发包时都会打印 "AppBrandNetworkRequest",从 Tag 命名上猜测这是小程序代码通过微信 sdk 发包的接口,反编译微信根据该关键词定位到负责小程序 https 通信的类是 Lcom/tencent/mm/plugin/appbrand/i/c;。

分析下代码可以发现最后使用过 Lcom/tencent/mm/sdk/f/e;->post 发包的,打印调用这行代码的函数的各个参数就可以截取小程序通过 https 发送的 json 明文数据了。

0x02 改包


在 1 中明文发包点可以直接修改发送的数据,但是敏感数据是加密通信的,比如上传分数的接口 https://mp.weixin.qq.com/wxagame/wxagame_settlement 中 action_data 字段就是在小程序内加密后再通过微信发出去的。

既然数据是在本地加密的,加密算法肯定也在本地可以找到,所以"跳一跳"作弊的关键就是找到小程序源码。

0x03 寻找小程序源码


从微信小程序文档中可以知道,小程序的核心逻辑通常是用 js 写的。搜索手机中的文件并没有找到后缀为 ".js" 的源码,源码只能从内存中 dump 了。

微信 log 中搜索 ".js",可以发现有如下 log 打印:
I/MicroMsg.WxaPkgRuntimeReader(12426): [, , 12554]:openRead, appId = wx7c8d593b2c3a7703, reqURL = /game.js, null(FALSE), type = java.lang.String, cost = 6ms

从关键词 WxaPkgRuntimeReader 可以猜测这是 wxapp 源码加载的相关代码,通过该关键词反编译微信定位到加载 js 代码的类:Lcom/tencent/mm/plugin/appbrand/appcache/ai;
分析代码可知该类下的 public static String a(e eVar, String str) 方法返回的就是js源码,把返回字串 dump 下来得到 "跳一跳" 核心源码 game.js。

0x04 实现作弊功能

有了源码,就可以根据上传分数的相关代码模拟上报分数了:

{
    key: "requestSettlement",
    value: function() {
        var t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : 0,
        e = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : 0,
        i = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : function() {},
        n = arguments.length > 3 && void 0 !== arguments[3] ? arguments[3] : {};
        if (a.default.sessionId) {
            var r = {
                score: t,
                times: e,
                game_data: JSON.stringify(n)
            },
            o = {
                base_req: {
                    session_id: a.default.sessionId,
                    fast: 1
                },
                action_data: (0, s.encrypt)(r, a.default.sessionId)
            };
            wx.request({
                url:
                h.AJAX_URL + "/wxagame/wxagame_settlement",
                method: "POST",
                data: o,
                success: function(t) {
                    i(200 === t.statusCode ? 0 === t.data.base_resp.errcode ? !0 : !1 : !1)
                },
                fail: function(t) {
                    i(!1)
                }
            })
        } else i(!1)
    }
},

加密部分:

e.encrypt = function(t, e) {
    var e = e.slice(0, 16),
    i = n.default.enc.Utf8.parse(e),
    r = n.default.enc.Utf8.parse(e),
    a = t;
    a = JSON.stringify(a);
    var o = n.default.AES.encrypt(a, i, {
        iv: r,
        mode: n.default.mode.CBC,
        padding: n.default.pad.Pkcs7
    });
    return o = o.toString()
};

既然源码在手,直接修改源码就可以实现作弊功能了。实现的方法是修改 3 中的 js 源码载入函数的返回字符串,替换相应 js 代码。
比如修改每次跳跃加分 32,把 "this.score+=t" 替换成 "this.score+=32" 等等。

0x05 基于触动精灵来实现


想到了下面的识别办法:

1. 逐行进行扫描来识别要跳转的目标坐标。为了提高效率可以适当增加扫描步进。定义一个矩形区域,要跳转的目标相对来说位置都比较固定。

2. 获取小人的位置,通过触动精灵的查找颜色功能进行定位坐标,虽然有一定误差,但是只要能获取到坐标,用来计算还是基本没问题的。

3. 计算跳跃距离,通过直接三角形的勾股定理进行计算。按压时间需要根据距离进行修正,我在小米 5s上测试用的1.3 基本还算可以。

已知问题

1. 通过触动精灵进行颜色匹配搜索坐标的做法效率较低,需要比较长的时间。

2. 运行一段时间之后,找色函数和获取小人坐标的函数会发生错误,导致无法获取到真正的坐标。我加了几个判断,出现问题的时候直接重新启动脚本就可以了。

3. 由于是基于颜色进行匹配的,因而相对来时识别的坐标的准确度比上面的python版本要低很多。

改进方式

1. 针对搜索坐标的函数进行匹配,折半查找,如果小人在左侧,直接搜索右侧。如果小人在右侧直接搜索左侧。

2. 匹配到错误之后直接重启脚本,使用触动精灵的循环运行功能

3. 其他未知的功能修改?我也不知道有啥。哈哈

脚本文件:

require "TSLib"
require("math")
-----------------------------------------------
-- Auto jump scripy
-- Code by obaby
-- http://www.h4ck.org.cn
-- Findu App http://findu.co
-----------------------------------------------

-- define scan zone with (x1,y1) (x2,y2)
scanZone_x1 = 50;
scanZone_y1 = 600;
scanZone_x2 = 1000;
scanZone_y2 = 922;


-- get the target object position
function getDestXY()
    -- body
    mSleep(1000);
    isfound = 0;
    dest_x = 0;
    dest_y = 0;
    for y = scanZone_y1,scanZone_x2,30 do
        for x =scanZone_x1,scanZone_x2,30 do
            colorb = getColor(x, y)
            colorc = getColor(x-30, y)
            colord = getColor(x+50, y)
            delta = math.abs(colorb -colorc)
            delta2 = math.abs(colord - colorb)
            --toast(delta.." :x:"..x.." :y:"..y,1)
            --mSleep(100)
            if delta >1000 then
                --toast(delta.." :x:"..x.." :y:"..y,1)
                nLog("COLO TO::ColorB:"..colorb.." ColorC:"..colorc);
                isfound = 1;
                dest_x = x;
                dest_y = y;
                --mSleep(5000);
                break;
            end

--dialog("ColorB:"..colorb.."ColorC:"..colorc, 3);
            --mSleep(3000)
            --x= x+10

end
        --y= y+10
        if isfound ==1 then

break;
        end

end
    return dest_x, dest_y
end

-- get the
function getDistance(dest_x, dest_y)
    -- body
    --mSleep(1000);
    x, y = findColorInRegionFuzzy( 0x39375f , 80, 0, 926, 1070, 1370);
    if x == -1 then
        x, y = findColorInRegionFuzzy( 0x39375f , 70, 0, 926, 1070, 1370);
    end
    if x == -1 then
        x, y = findColorInRegionFuzzy( 0x39375f , 80, 0, 926, 1070, 1370);
    end
    if x ==-1 then
        return 0;
    end

nLog("JUMP FR::src_x:"..x.." src_y:"..y);
    distance = math.sqrt(math.pow(dest_x - x,2) + math.pow(dest_y-y,2));

if math.abs(dest_y - y) < (1116-940) then
        return (1116-940)*1.4;
    end


    return distance;
end


while (true) do
    -- body
    if (isColor( 581, 1626, 0xffffff, 85) and  isColor( 558, 1714, 0x262628, 85)) then
        break;
    end

dest_x , dest_y = getDestXY();
    dist = getDistance(dest_x,dest_y);
    toast("dest_x:"..dest_x.." dest_y:"..dest_y.." distance:"..math.floor(dist),3);
    --toast(dist,1)
    --can not get dest position or can not get the source position
    nLog("JUMP TO::dst_x:"..dest_x.." dst_y:"..dest_y.." distance:"..math.floor(dist));
    if dest_x ==scanZone_x1 or dist == 0 or dest_y == scanZone_y1 or dest_x ==scanZone_x2 then
        toast("Get posison error",1);
        nLog("ERRO ER:: Get position error")
        break;
    end

touchDown(dest_x, dest_y);
    mSleep(dist*1.3);
    touchUp(dest_x, dest_y);

end

Image

Image

本文由看雪论坛 洪荒之力&obaby 原创

转载请注明来自看雪社区

点击阅读原文/read,

更多干货等着你~

Image

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK