cocos2d棋牌类APP协议加密算法详解分析 教程 审计

admin 1月前 126

最近碰到一个IOS的棋牌app,本着学习的目的进行了一番分析,如有侵权,请联系本人或者版主删贴

这个APP用的是cocos2d框架,里面集成了很多游戏,子游戏逻辑全部用lua实现

这篇文章简单介绍协议加密算法的分析的过程,有了加密算法之后,剩下的基本只需要体力活就可以写出完整协议了

lua解密

通过抓包,发现选择一个还未下载的子游戏的时候,会从服务器下载一个zip包,解压后可以看出游戏逻辑都是lua实现的。压缩包内也有一些json配置文件,但是除了游戏名和版本号,没什么收获。

lua文件扩展名都是luac,并且是加密的,config.luac文件内容如图:

这里可以看到文件头比较特殊,以"**TCY**"开头,于是尝试在ida里查询这字符串,运气比较好,找到了

通过交叉索引,找到如下函数:(部分变量已经重命名)

__int64 __fastcall sub_100F7BBBC(const char *a1, int a2, __int64 *a3, _DWORD *a4)
{
    _DWORD *v4; // x20
    __int64 *v5; // x19
    int len; // w21
    const char *buf; // x22
    __int64 offset; // x23
    __int64 result; // x0
    int v10; // w8
    __int64 v11; // x8
    __int128 v12; // [xsp+0h] [xbp-50h]
 
    v4 = a4;
    v5 = a3;
    len = a2;
    buf = a1;
    *a3 = 0LL;
    *a4 = 0;
    v12 = xmmword_1015A9D20;
    offset = 7LL;
    if ( !strncmp(a1, "**TCY**", 7uLL) )
    {
        v10 = -7;
    }
    else
    {
        offset = 11LL;
        if ( strncmp(buf, "**TCYGZIP**", 0xBuLL) )
            return 1LL;
        v10 = -11;
    }
    v11 = sub_100D21A28((__int64)&buf[offset], (unsigned int)(v10 + len), (const unsigned __int8 *)&v12, 0x10u, v4);
    result = 0LL;
    *v5 = v11;
    return result;
}

如果是 **TCY** 开头,就跳过7个字节,然后调用sub_100D21A28函数

结合函数F5结果,以及其他大神的文章(比如 https://bbs.pediy.com/thread-216969.htm ),很容易知道v12其实就是xxtea算法的密钥

v12 = xmmword_1015A9D20; 双击可以在ida里直接看到密钥为

101014222030444A201935285248545C

Github上顺手找到了xxtea的源码,用该密钥成功解密所有.luac文件,并且实际上都是原始Lua

https://github.com/xxtea (这里有xxtea算法的多种编程语言实现)

config.luac解密后部分内容

lua源码分析

接下来就开始尝试查看lua源码,来分析协议

找到如下代码片段,猜测应该用的tcp协议,并且连的服务器的31626端口

local json = cc.load("json").json
        local s = cc.FileUtils:getInstance():getStringFromFile("AppConfig.json")
        local AppJsonObj = json.decode(s)
 
        local utils = HslUtils:create(AppJsonObj["abbr"])
        hallIp = utils:getHallSvrIp()
        if type(hallIp) ~= 'string' or hallIp:len() == 0 then
            print(hallIp)
            print('hallIp ip empty')
        end
 
        hallPort = utils:getHallSvrPort()
        if hallPort == 0 then
            hallPort = 31626
        end

抓包证实,并且数据看起来是加密的

前面两个字节总是00 02,接下来4个字节是包的长度,后面就不清楚了

找了半天,在lua代码没找到加密相关的逻辑,只找到了这么一个发包函数

function NetWorkCenter:sendRequest(INmsgId, INdata, INdataLen, INbBack)

加密算法分析

于是开始在主程序里寻找相关逻辑

通过 sendRequest 关键字,很容易找到了相关函数

__int64 __fastcall func_sendRequest(__int64 a1, __int64 msgId, __int64 data, __int64 dataLen, __int64 echoWait)
{
    __int64 l_echoWait; // x20
    __int64 l_dataLen; // x22
    __int64 l_data; // x23
    __int64 l_msgId; // x24
    __int64 v9; // x21
    std::__1::mutex *v10; // x19
    int v11; // w8
    int v12; // w25
    __int64 ts; // x0
    __int64 v14; // x20
 
    l_echoWait = echoWait;
    l_dataLen = dataLen;
    l_data = data;
    l_msgId = msgId;
    v9 = a1;
    v10 = (std::__1::mutex *)(a1 + 168);
    std::__1::mutex::lock((std::__1::mutex *)(a1 + 168));
    if ( *(_DWORD *)(v9 + 48) != -1 && *(_DWORD *)(v9 + 84) )
    {
        v11 = *(_DWORD *)(v9 + 72);
        if ( v11 <= 0xFFFFFF )
            v12 = v11 + 1;
        else
            v12 = 1;
        *(_DWORD *)(v9 + 72) = v12;
        ts = func_getUnixTimestampMs();
        sub_10063A87C(v9, v12, ts);
        func_log(
            20LL,
            "MCClientImpl(0x%x)::sendRequest, nRequest(%d), nSessionId(%d), echoWait(%d)",
            v9,
            l_msgId,
            *(unsigned int *)(v9 + 72),
            l_echoWait);
        (***(void (__fastcall ****)(_QWORD, __int64, __int64, __int64, __int64, _QWORD, __int64, __int64))(v9 + 64))(// 0x100F79EE0
            *(_QWORD *)(v9 + 64),
            l_msgId,
            l_data,
            l_dataLen,
            1LL,
            *(unsigned int *)(v9 + 72),
            l_echoWait,
            1LL);
        v14 = *(unsigned int *)(v9 + 72);
    }
    else
    {
        v14 = 0xFFFFFFFFLL;
    }
    std::__1::mutex::unlock(v10);
    return v14;
}

第二个开始的4个参数,正好能和lua里的sendRequest对应上

然后下层函数静态分析看不出,通过调试找到了对应的函数

经过分析,箭头指向的函数是关键加密函数,如果字节数多于23个,还会先用zlib压缩

注意方框标出的crc函数,后续crc值会写入封包开头

并且可以看到保存了是否压缩,以及是否加密的标记,后续打包协议头的时候有用到

zlib压缩部分很简单,跟进去可以看到如下代码

memset(&strm, 0, 0x70uLL);
    deflateInit_(&strm, -1, "1.2.11", 0x70);    // level, version, stream_size

加密部分,跟进去后,可以看到有用到如下的常量数组

很明显是aes

为了方便测试,直接hook,打印了下相关信息,包括密钥,以及加密前后的数据

可以看到加密后的数据,就是抓的封包里面,第17个字节开始的数据

协议头分析

加密部分内容解决了,还得查看协议头(例子封包里面的前面16个字节)怎么来的

通过静态分析,发现如果压缩了,会写入压缩之前的大小,然后是消息序号,以及消息号,当然还有前面截图给出的crc

00 02 
00 00 00 30  // 封包大小
93 41 6C 21 // crc
2B                // 状态标记(是否加密,压缩等)
16              // 压缩前大小
01             // 消息序号
BA EA 01     // 消息号

这里如果多对比一些封包,会发现后面三个数字不太正常,后来发现是因为这几个整数编码过了,用的是protobuf的编码方式,代码如下

__int64 __fastcall func_encodeInteger(_BYTE *a1, unsigned int val)
{
    __int64 len; // x8
    char v3; // w8
    unsigned int v4; // w10
 
    if ( val >= 0x80 )
    {
        v3 = val | 0x80;
        if ( val >> 14 )
        {
            v4 = val >> 21;
            if ( val >> 21 )
            {
                *a1 = v3;
                a1[1] = (val >> 7) | 0x80;
                a1[2] = (val >> 14) | 0x80;
                val >>= 28;
                if ( val )
                {
                    a1[3] = v4 | 0x80;
                    a1 += 4;
                    len = 5LL;
                }
                else
                {
                    a1 += 3;
                    len = 4LL;
                    LOBYTE(val) = v4;
                }
            }
            else
            {
                *a1 = v3;
                a1[1] = (val >> 7) | 0x80;
                a1 += 2;
                len = 3LL;
                val >>= 14;
            }
        }
        else
        {
            *a1++ = v3;
            val >>= 7;
            len = 2LL;
        }
    }
    else
    {
        len = 1LL;
    }
    *a1 = val;
    return len;
}

容易看出以每个字节的第一位表示是否结束,后面7位存数据,

接码只需要反向操作一遍即可,写了段代码测试,发现可以成功拿到原始的值,比如ba ea 01解码之后,就是 0x753a (30010)

var flag = 0;
var list = new List<byte>();
do
{
    list.Add(bytes[offset]);
} while ((bytes[offset++] & 0x80) > 0);
 
for (var i = list.Count - 1; i >= 0; i--)
{
    flag = (flag << 7) + (list[i] & 0x7f);
}

去lua代码里能搜索到这个消息号

GET_SERVERS                        =30010,

辅助工具

用wireshark已经可以很好的抓包,但是因为消息号是编码过的,消息体又是加密的,不太方便分析

尝试写wireshark插件,发现效果不太好(可能是自己能力不行),用wireshark的lua接包,也没那么方便

所以干脆自己写了个抓包工具,支持插件,用于实时分析不同协议,插件接口也很简单,就传入一个封包,返回封包简单内容(提示),以及详细解析数据

主要是可以实时看到每个封包的消息类型,以及选中封包之后,右下角可以显示相关信息


少客联盟- 版权声明 1、本主题所有言论和图片纯属会员个人意见,与少客联盟立场无关。
2、本站所有主题由该帖子作者发表,该帖子作者admin少客联盟享有帖子相关版权。
3、少客联盟管理员和版主有权不事先通知发贴者而删除本文。
4、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者admin少客联盟的同意。
5、帖子作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任。
6、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责。
7、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除并致以最深的歉意。
8、官方反馈邮箱:chinasuc@chinasuc.cn


上一篇:固件分析--工具、方法技巧解析
下一篇:程序简单的后门分析经验
最新回复 (2)
全部楼主
  • admin 1月前
    0 2
    顶起来
  • admin 12天前
    0 3
    • 少客联盟
      4
        登录 注册 QQ登录(停用)
返回
负责声明:本站部分资源来源于网络,如有侵权请发邮件(chinasuc@chinasuc.cn)告知我们,我们将会在24小时内处理。