某棋牌游戏lua逆向破解修改(一)

查看 93|回复 7
作者:yellowtail   
背景
最近朋友在玩几个棋牌游戏,我花了点时间研究了一下,虽然破解失败,但是过程感觉还是有收获,分享给大家
这篇文章主要分享 如何找key
破解思路后面有空再分享
cocos2dlua
棋牌游戏的逻辑一般是使用lua编写的,lua脚本加载一般是用 cocos2d 加载的,我的破解方向集中在这里,如果你要破解的棋牌游戏不是这个技术栈,那就不能照搬
lua脚本常见情况如下:
  • luac(编辑器打开脚本,前面几个字节是lua)
  • cocos2dlua(编辑器打开脚本,前面几个字节,不是lua,多打开几个会发现是固定的)

    我这里遇到的就是 cocos2dlua (看一下lib文件有没有叫类似名字的so)
    cocos2dlua 加密原理给大家简单介绍一下
    [ol]
  • 写好lua脚本
  • 使用key进行加密(加密算法叫 xxtea)
  • 加密完成之后在最前面拼接sign
    [/ol]
    这个sign就是刚刚提到的固定字符串
    解密步骤如下:
    [ol]
  • 读取脚本,切掉前缀,也就是sign
  • 使用找到的key进行解密
    [/ol]
    那么解密难点就是找key
    key一般来说都在 libcocos2dlua.so里面, 有以下几种玩法:
    [ol]
  • 不修改 cocos2d 代码,明文存储
  • 修改  cocos2d 代码,明文存储
  • 修改  cocos2d 代码,代码计算得到
    [/ol]
    不修改 cocos2d 代码,明文存储
    这种最简单了,编辑器打开lua脚本,直接搜前缀,key就在附近
    为什么在附近?因为源码要求sign和key作为参数一起传
    bool AppDelegate::applicationDidFinishLaunching()
    {
        // set default FPS
        Director::getInstance()->setAnimationInterval(1.0 / 60.0f);
        // register lua module
        auto engine = LuaEngine::getInstance();
        ScriptEngineManager::getInstance()->setScriptEngine(engine);
        lua_State* L = engine->getLuaStack()->getLuaState();
        lua_module_register(L);
        register_all_packages();
        LuaStack* stack = engine->getLuaStack();
        stack->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA"));
    上面的 2dxlua 就是sign,XXTEA 就是 key (加密算法也叫 xxtea)
    源码在 github
    修改cocos2d 代码,明文存储
    不修改源码,直接记事本打开就能找到key,所以一些开发者为了提升安全性,修改了 cocos2d代码,让sign和key远离(比如写一个函数获取key),增加破解难度
    这里列举几个可能的变形点,依次在 ida里找就可能找到
    以下均为 function
    [ol]
  • applicationDidFinishLaunching
    入口点,默认sign和key在这里
  • setXXTEAKeyAndSign
    如函数名所示,设置sign和key
  • xxtea_decrypt
    算法解密点,因为需要传入key进行解密,按x看调用的地方,有key的踪迹
    [/ol]
    下面举一个例子
    010打开lua脚本


    001.png (53.4 KB, 下载次数: 0)
    下载附件
    2023-5-2 01:13 上传

    看到了一个前缀
    ida分析 libcocos2dlua.so
    搜索 applicationDidFinishLaunching,没有看到明文


    002.png (132.04 KB, 下载次数: 0)
    下载附件
    2023-5-2 01:22 上传

    搜索 setXXTEAKeyAndSign,也没有看到明文


    003.png (68.59 KB, 下载次数: 0)
    下载附件
    2023-5-2 01:22 上传

    搜索 xxtea_decrypt,出现两个,有戏,因为一般是一个


    004.png (9.3 KB, 下载次数: 0)
    下载附件
    2023-5-2 01:22 上传

    依次按x看调用的地方,第一个比较正经,是正常调用关系
    第二个是自定义的函数,进去看看


    005.png (50.65 KB, 下载次数: 0)
    下载附件
    2023-5-2 01:22 上传

    经过分析源码,得知 key 是 图中的 v21 (这里就不贴明文了,避免被搜索到,哈哈)


    006.png (108.81 KB, 下载次数: 0)
    下载附件
    2023-5-2 01:22 上传

    到这里,key就找到了
    修改  cocos2d 代码,代码计算得到
    另外一种变形就是key不是明文存的,是计算得到,下面举一个例子说明
    步骤类似
    010打开


    007.png (48.85 KB, 下载次数: 0)
    下载附件
    007
    2023-5-2 01:25 上传

    搜索 applicationDidFinishLaunching,看到明文了,但是有好几个字符串,到底是哪一个呢?


    008.png (96.61 KB, 下载次数: 0)
    下载附件
    2023-5-2 01:33 上传

    上面出现好几个字符串的那一行,实际调用的是函数 setXXTEAKeyAndSign
    搜索 setXXTEAKeyAndSign,


    009.png (93.99 KB, 下载次数: 0)
    下载附件
    2023-5-2 01:33 上传

    经过阅读代码,慢慢扣偏移,最后得知 key 是 v21 (上面的例子也是v21,这个只是巧合,不用在意)
    当然了,这里也不用慢慢扣代码,可以写代码梭哈,输入4个字符串,前两个用 0x2019%s%s 组合起来了,后面两个也是一样的组合方式,加起来6个可能,依次遍历,看看到底是哪个就行
    这里是梭哈的golang代码


    010.png (51.53 KB, 下载次数: 0)
    下载附件
    2023-5-2 01:33 上传

    解密
    上面是找key的方法,找到之后就要解密了
    推荐自己写,我找了一些成品,只能说差强人意
    推荐用golang写,无需乱七八糟的依赖,写完就运行
    对golang不熟悉,也可以用 python,但是要依赖 特定版本 visual studio, 有多恶心,我就不多说了
    以下是 golang代码
    package main
    import (
            "flag"
            "fmt"
            "io/ioutil"
            "os"
            "path/filepath"
            "time"
            "github.com/xxtea/xxtea-go/xxtea"
    )
    type CMD struct {
            encrypt_file string
            decrypt_file string
    }
    var prefix string = "xxxx"
    var lua_key string = "xxxx"
    func main() {
            cmd, r := initFlag()
            if r {
                    return
            }
            if cmd.decrypt_file != "" {
                    err := decrypt(cmd.decrypt_file)
                    if err != nil {
                            fmt.Println(err)
                    }
                    return
            }
            if cmd.encrypt_file != "" {
                    err := encrypt(cmd.encrypt_file)
                    if err != nil {
                            fmt.Println(err)
                    }
                    return
            }
            fmt.Println("error, empty input")
    }
    func encrypt(path string) error {
            by, err := os.ReadFile(path)
            if err != nil {
                    fmt.Println("read fail", err)
                    return err
            }
            encrypt_data := xxtea.Encrypt(by, []byte(lua_key))
            // 拼接前缀
            data := append([]byte(prefix), encrypt_data...)
            // fileInfo, err := os.Stat(path)
            // if err != nil {
            //         return err
            // }
            name := filepath.Base(path)
            x1 := name[:len(name)-len(filepath.Ext(name))]
            newName := x1 + "_" + time.Now().Format("2006-01-02_15-04-05") + ".luac"
            fmt.Println("output file: ", newName)
            err = os.WriteFile(newName, data, 0666)
            if err != nil {
                    fmt.Println("write fail")
            }
            return nil
    }
    func decrypt(path string) error {
            by, err := ioutil.ReadFile(path)
            if err != nil {
                    fmt.Println("read fail")
                    return err
            }
            // 去除前缀
            data := by[len(prefix):]
            decrypt_data := string(xxtea.Decrypt(data, []byte(lua_key)))
            fmt.Println(decrypt_data)
            return nil
    }
    func initFlag() (CMD, bool) {
            var decrypt_file = flag.String("d", "", "解密path")
            var encrypt_file = flag.String("e", "", "加密path")
            var help = flag.Bool("h", false, "help")
            flag.Parse()
            if *help {
                    flag.Usage()
                    return CMD{}, true
            }
            if *decrypt_file == "" && *encrypt_file == "" {
                    flag.Usage()
                    return CMD{}, true
            }
            c := CMD{*encrypt_file, *decrypt_file}
            return c, false
    }
    再贴一个python的(只有解密的,文件是写死的,所以代码行数少很多)
    #coding: utf-8
    # lua 解密单个文件
    import os
    import sys
    import xxtea
    import logging
    import pathlib
    a = "xxxx"
    b = "0x201xxxx"
    def decrypt(filename):
        f = open(filename, mode='rb')
        data = f.read()
        data2 = data[len(a):]
        data3 = xxtea.decrypt(data2, b)
        return data3
    inputName = r"X:\007-project\003-lua\xxx\main.lua"
    x = decrypt(inputName)
    print(x)
    frida
    以上是通过 ida 分析得到key,比较花时间,也看运气,因为可以修改的点太多了
    这里再介绍一个釜底抽薪的方式
    经过上面的分析得知,只要开发者没有丧心病狂的去修改 xxtea 算法,那么最后key一定会传到 xxtea_decrypt 里面,我们可以通过 frida 观察入参就能得到 key
    function xx() {
        console.log("===============================================================");
        var coco = Process.findModuleByName("libcocos2dlua.so");
        var exports = coco.enumerateExports();
        for(var i = 0; i


    011.png (49.24 KB, 下载次数: 0)
    下载附件
    2023-5-2 02:00 上传

    效果
    这里贴一下反编译之后的效果图


    012.png (142.96 KB, 下载次数: 0)
    下载附件
    2023-5-2 02:12 上传

    下载次数, 代码

  • xavier001   

    学习了,感谢大佬分享,膜拜啊
    zxcv75429   

    厉害了谢谢分享
    makmak79   

    真是厉害!高手
    q197843   

    666666666666
    aa2923821a   

    可以可以  感谢分享
    骇客之技术   

    楼主提示下样本链接
    fscc无误   

    真是厉害!高手
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部