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

文章正文
发布时间:2025-10-21 00:30

这篇文章主要分享 如何找key

破解思路后面有空再分享

cocos2dlua

棋牌游戏的逻辑一般是使用lua编写的,lua脚本加载一般是用 cocos2d 加载的,我的破解方向集中在这里,如果你要破解的棋牌游戏不是这个技术栈,那就不能照搬

lua脚本常见情况如下:

luac(编辑器打开脚本,前面几个字节是lua)

cocos2dlua(编辑器打开脚本,前面几个字节,不是lua,多打开几个会发现是固定的)

我这里遇到的就是 cocos2dlua (看一下lib文件有没有叫类似名字的so)

cocos2dlua 加密原理给大家简单介绍一下

写好lua脚本

使用key进行加密(加密算法叫 xxtea)

加密完成之后在最前面拼接sign

这个sign就是刚刚提到的固定字符串

解密步骤如下:

读取脚本,切掉前缀,也就是sign

使用找到的key进行解密

那么解密难点就是找key

key一般来说都在 libcocos2dlua.so里面, 有以下几种玩法:

不修改 cocos2d 代码,明文存储

修改  cocos2d 代码,明文存储

修改  cocos2d 代码,代码计算得到

不修改 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)

源码在

修改cocos2d 代码,明文存储

不修改源码,直接记事本打开就能找到key,所以一些开发者为了提升安全性,修改了 cocos2d代码,让sign和key远离(比如写一个函数获取key),增加破解难度

这里列举几个可能的变形点,依次在 ida里找就可能找到

以下均为 function

applicationDidFinishLaunching
入口点,默认sign和key在这里

setXXTEAKeyAndSign
如函数名所示,设置sign和key

xxtea_decrypt
算法解密点,因为需要传入key进行解密,按x看调用的地方,有key的踪迹

下面举一个例子

010打开lua脚本

001.png (53.4 KB, 下载次数: 1)

下载附件

2023-5-2 01:13 上传

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

002.png (132.04 KB, 下载次数: 1)

下载附件

2023-5-2 01:22 上传

搜索 setXXTEAKeyAndSign,也没有看到明文

003.png (68.59 KB, 下载次数: 2)

下载附件

2023-5-2 01:22 上传

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

004.png (9.3 KB, 下载次数: 2)

下载附件

2023-5-2 01:22 上传

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

005.png (50.65 KB, 下载次数: 1)

下载附件

2023-5-2 01:22 上传

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

006.png (108.81 KB, 下载次数: 1)

下载附件

2023-5-2 01:22 上传

到这里,key就找到了

修改  cocos2d 代码,代码计算得到

另外一种变形就是key不是明文存的,是计算得到,下面举一个例子说明

步骤类似
010打开

007.png (48.85 KB, 下载次数: 1)

下载附件

007

2023-5-2 01:25 上传

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

008.png (96.61 KB, 下载次数: 1)

下载附件

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, 下载次数: 1)

下载附件

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 < exports.length; i++) {         var name = exports[i].name;         // 不一定叫这个名字,匹配一下         if (name.indexOf("xxtea_decrypt")!=-1) {             console.log("name:", name);             console.log("exports[i]:", JSON.stringify(exports[i]));             var addr = exports[i].address;             Interceptor.attach(addr, {                 onEnter: function (args) {                     console.log('[+] args0,r0: ' + args[0]);//data数据                     console.log('[+] args1,r1: ' + args[1]);//data长度                     //密钥                     console.log('[+] args2,r2: ' + args[2]);                     console.log(Memory.readCString(args[2]));                     console.log('[+] args2,r3: ' + args[3]);//密钥长度                 } onLeave: function (retval) {                 }             }         });     } } setImmediate(xx,0);

011.png (49.24 KB, 下载次数: 2)

下载附件

2023-5-2 02:00 上传

效果

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

012.png (142.96 KB, 下载次数: 2)

下载附件

2023-5-2 02:12 上传

 

免费评分 参与人数 11威望 +1 吾爱币 +30 热心值 +10 理由
junjia215     + 1   + 1   用心讨论,共获提升!  
wykdz     + 1   + 1   我很赞同!  
烟凌     + 1     用心讨论,共获提升!  
allspark     + 1   + 1   用心讨论,共获提升!  
52pojie666z     + 1   + 1   热心回复!  
gunxsword     + 1   + 1   谢谢@Thanks!  
zlnxlzht     + 1   + 1   热心回复!  
Chenda1     + 1   + 1   我很赞同!  
RickSanchez     + 1   + 1   我很赞同!  
2016976438     + 1   + 1   谢谢@Thanks!  
正己   + 1   + 20   + 1   感谢发布原创作品,吾爱破解论坛因你更精彩!  

查看全部评分