吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7074|回复: 8
收起左侧

[Android 原创] Cocos2DX-JS 加密逆向探究解密app实战

  [复制链接]
xieqing520 发表于 2021-1-30 16:26
什么是Cocos2D?
cocos2d [1]  是一个基于MIT协议的开源框架,用于构建游戏、应用程序和其他图形界面交互应用。可以让你在创建自己的多平台游戏时节省很多的时间。
Cocos2D也拥有几个主要版本,包括Cocos2D-iPhone、Cocos2D-X,以及被社区普遍看好的Cocos2D-HTML5和JavaScript bindings for Cocos2D-X。同时也拥有了非常优秀的编辑器(独立编辑器),例如SpriteSheet Editors、Particle Editors 、Font Editors 、 Tilemap Editors。
另外,2012年发布的CocoStudio工具集是开源游戏引擎Cocos2d-x开发团队官方推出的游戏开发工具,目前已经进入稳定版。CocoStudio吸取了他们自己在游戏制作中的经验,为移动游戏开发者和团队量身定做,旨在降低游戏开发的门槛,提高开发效率,同时也为Cocos2D-X的进一步发展打下基础。以上信息来自于百度百科。
Cocos2D和Cocos2Dx主要区别?
cocos2d是OC写的bai,cocos2dx是c++写的
cocos2d只能在duios下运行,cocos2dx是跨平台的,zhiios和android平台都可以dao运行
cocos2d是外国人搞zhuan的,cocos2dx是中国人搞的。shu
cocos2dx是cocos2d的C++写法,但是游戏架构是一样的,都包含了精灵,导演,场景,动作等概念,他们是一脉相承的东西。你可以直接研究cocos2dx,没有什么障碍。虽然是有了cocos2d才有的cocos2dx,但是cocos2dx包含了cocos2d的主要思想,因此可以直接研究cocos2dx。

以上采纳于百度回答
研究目标以及环境工具介绍
  • IDA Pro 7.0      - -----  交互式反汇编器专业版(Interactive Disassembler Professional),人们常称其为IDA Pro,或简称为IDA。是目前最棒的一个静态反编译软件,为众多0day世界的成员和ShellCode安全分析人士不可缺少的利器!
  • Frida环境   
  • Frida是一款基于Python + JavaScript 的Hook框架,本质是一种动态插桩技术,可以用于Android、Windows、iOS等各大平台。由于是基于脚本的交互,因此相比xposed来说更加便捷。

    ​ frida与xposed对比:frida适用于全平台,xposed只能用于Android平台;frida可Hook java层和native层,而xposed只能Hook java层;frida通过编写js代码实现,xposed必须编写插件(类似写Android app);

    ​ 动态插桩技术:在程序运行时实时地插入额外代码和数据,对可执行文件没有任何永久性的改变。


4.一台Root的手机,模拟器理论上也可以使用frida,本次实践没有尝试:rggrg
优秀文章
半夜逆向一个Cocos2D-JS做的棋牌App,解密Jsc拿下Authorization算法
https://www.52pojie.cn/thread-1307664-1-1.html
(出处: 吾爱破解论坛)
本想就着发扬伸手党的伟大精神直接拿,可惜作者并不讲解关键具体经过以及成品代码提供下载,对于刚入门或不了解IDA的朋友提供的帮助聊胜于无~~~

开始实践首先百度一番jsc逆向为js,万一有呢;www
image.png

事实上,百度有,还挺多,不过文章千篇一律,你抄我我抄你。
最后踩了不少坑才了解,那些文章工具逆向解密指的都是coco2d中源代码依附于字节码中,按官方给定的规律提取出来。
而在coco2dx版本中,这项被禁用了,也就是根本用不了。



阅读一下上面的文章 、或者去研究一下官方提供的SDK,可以知道coco2dx生成的jsc并不是真正意义上的编译出来的字节码,仅仅只是做了一层xxtea加密而已。
那么我们就需要去解密,从而得到源码。

开始找秘钥
将某三国游戏apk中的 armeabi-v7a 的 libcocos2djs.so 拖进 IDA PRo
(为什么要是armeabi-v7a中的呢?因为我的手机就是这个架构的 = =,每个人都看自己手机CPU架构信息然后去拖对应的SO)

。。。。。。
。。。。。

。。。。
。。。。
(电脑有点卡,还在一直加载读取中。。。。)


。。。。
。。。。
。。。
(有点无聊。。。)


。。。。。

打开了,我们首先研究JNI_OnLoad 函数,这个是加载so必定经过的位置,说不定秘钥就在这个地方定义了,那我们不就不用干啥了吗 {:1_918:}
使用伪代码查看
image.png

然后发现有个 cocos_jni_env_init 函数,一层一层追进去


image.png

额,尴尬了,啥也没有,不过没关系,直接祭出大招。
搜索 decrypt 关键词,来搜索Function 函数 (英语不好的人可以百度 = = ,这个是解密的意思啦)

image.png

ok,很幸运 ,一下子就定位到关键位置。
然后我们打开查看

image.png


记住一下,此处的地址为 0x006672F8,这个地址是相对于这个so来说的

通过上面的文章或者百度xxtea_decrypt算法,可以知道,该函数的声明为
[C++] 纯文本查看 复制代码
1
uint8_t* data = xxtea_decrypt((uint8_t*)fileData.getBytes(), (uint32_t)fileData.getSize(), (uint8_t*)xxteaKey.c_str(), (uint32_t)xxteaKey.size(), &dataLen);


那么我们需要hook一下这个第三个参数,就可以直接获取到秘钥了~~~

let go

打开编辑器,编辑一下frida代码
[Python] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# -*- coding: utf-8 -*-
# adb forward tcp:27043 tcp:27043
# adb forward tcp:27042 tcp:27042
import frida
import sys
 
 
def on_message(message, data):
    if message["type"] == 'send':
        print(u"[*] {0}".format(message['payload']))
    else:
        print(message)
 
 
 
device = frida.get_remote_device()
 
pid = device.spawn(["xxxxx某游戏的包名"])
session = device.attach(pid)
device.resume(pid)
 
 
 
jscode = open('hook.js', 'r',encoding='utf-8').read()
script = session.create_script(jscode)
script.on("message", on_message)
script.load()
sys.stdin.read()

hook.js的代码为
[JavaScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
setTimeout(function(){
    Java.perform(function(){
        var baseAddress = Module.findBaseAddress("libcocos2djs.so");
        send("libcocos2djs.so baseAddress is: "+baseAddress)
         
        Interceptor.attach(baseAddress.add(0x6672f8),{
            onEnter: function(args) {
                console.log("Param:"+args[2].readCString());
            },
            onLeave: function(retval){
            }
        });
    });
},100)


这里说一下,上面找到的地址是相对于so来说的  ,是相对地址,也可以说是so的偏移地址,那么只需要找到so的入口地址  + 相对地址   = 在内存中的即时真实地址

然后运行,查看控制台信息
image.png


(⊙o⊙)…,好像出来了。。。。。。

解密试试看?

这里贴上一份XXTea的算法,java版本(挺恨上面的作者,竟然不分享,晚上找的好多都不完善不能用)
[Java] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
package com.Test;
 
import java.io.UnsupportedEncodingException;
 
public final class XXTEA {
 
    private static final int DELTA = 0x9E3779B9;
 
    private static int MX(int sum, int y, int z, int p, int e, int[] k) {
        return (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
    }
 
    private XXTEA() {}
 
    public static final byte[] encrypt(byte[] data, byte[] key) {
        if (data.length == 0) {
            return data;
        }
        return toByteArray(
                encrypt(toIntArray(data, true), toIntArray(fixKey(key), false)), false);
    }
    public static final byte[] encrypt(String data, byte[] key) {
        try {
            return encrypt(data.getBytes("UTF-8"), key);
        }
        catch (UnsupportedEncodingException e) {
            return null;
        }
    }
    public static final byte[] encrypt(byte[] data, String key) {
        try {
            return encrypt(data, key.getBytes("UTF-8"));
        }
        catch (UnsupportedEncodingException e) {
            return null;
        }
    }
    public static final byte[] encrypt(String data, String key) {
        try {
            return encrypt(data.getBytes("UTF-8"), key.getBytes("UTF-8"));
        }
        catch (UnsupportedEncodingException e) {
            return null;
        }
    }
    public static final String encryptToBase64String(byte[] data, byte[] key) {
        byte[] bytes = encrypt(data, key);
        if (bytes == null) return null;
        return Base64.encode(bytes);
    }
    public static final String encryptToBase64String(String data, byte[] key) {
        byte[] bytes = encrypt(data, key);
        if (bytes == null) return null;
        return Base64.encode(bytes);
    }
    public static final String encryptToBase64String(byte[] data, String key) {
        byte[] bytes = encrypt(data, key);
        if (bytes == null) return null;
        return Base64.encode(bytes);
    }
    public static final String encryptToBase64String(String data, String key) {
        byte[] bytes = encrypt(data, key);
        if (bytes == null) return null;
        return Base64.encode(bytes);
    }
    public static final byte[] decrypt(byte[] data, byte[] key) {
        if (data.length == 0) {
            return data;
        }
        return toByteArray(
                decrypt(toIntArray(data, false), toIntArray(fixKey(key), false)), true);
    }
    public static final byte[] decrypt(byte[] data, String key) {
        try {
            return decrypt(data, key.getBytes("UTF-8"));
        }
        catch (UnsupportedEncodingException e) {
            return null;
        }
    }
    public static final byte[] decryptBase64String(String data, byte[] key) {
        return decrypt(Base64.decode(data), key);
    }
    public static final byte[] decryptBase64String(String data, String key) {
        return decrypt(Base64.decode(data), key);
    }
    public static final String decryptToString(byte[] data, byte[] key) {
        try {
            byte[] bytes = decrypt(data, key);
            if (bytes == null) return null;
            return new String(bytes, "UTF-8");
        }
        catch (UnsupportedEncodingException ex) {
            return null;
        }
    }
    public static final String decryptToString(byte[] data, String key) {
        try {
            byte[] bytes = decrypt(data, key);
            if (bytes == null) return null;
            return new String(bytes, "UTF-8");
        }
        catch (UnsupportedEncodingException ex) {
            return null;
        }
    }
    public static final String decryptBase64StringToString(String data, byte[] key) {
        try {
            byte[] bytes = decrypt(Base64.decode(data), key);
            if (bytes == null) return null;
            return new String(bytes, "UTF-8");
        }
        catch (UnsupportedEncodingException ex) {
            return null;
        }
    }
    public static final String decryptBase64StringToString(String data, String key) {
        try {
            byte[] bytes = decrypt(Base64.decode(data), key);
            if (bytes == null) return null;
            return new String(bytes, "UTF-8");
        }
        catch (UnsupportedEncodingException ex) {
            return null;
        }
    }
 
    private static int[] encrypt(int[] v, int[] k) {
        int n = v.length - 1;
 
        if (n < 1) {
            return v;
        }
        int p, q = 6 + 52 / (n + 1);
        int z = v[n], y, sum = 0, e;
 
        while (q-- > 0) {
            sum = sum + DELTA;
            e = sum >>> 2 & 3;
            for (p = 0; p < n; p++) {
                y = v[p + 1];
                z = v[p] += MX(sum, y, z, p, e, k);
            }
            y = v[0];
            z = v[n] += MX(sum, y, z, p, e, k);
        }
        return v;
    }
 
    private static int[] decrypt(int[] v, int[] k) {
        int n = v.length - 1;
 
        if (n < 1) {
            return v;
        }
        int p, q = 6 + 52 / (n + 1);
        int z, y = v[0], sum = q * DELTA, e;
 
        while (sum != 0) {
            e = sum >>> 2 & 3;
            for (p = n; p > 0; p--) {
                z = v[p - 1];
                y = v[p] -= MX(sum, y, z, p, e, k);
            }
            z = v[n];
            y = v[0] -= MX(sum, y, z, p, e, k);
            sum = sum - DELTA;
        }
        return v;
    }
 
    private static byte[] fixKey(byte[] key) {
        if (key.length == 16) return key;
        byte[] fixedkey = new byte[16];
        if (key.length < 16) {
            System.arraycopy(key, 0, fixedkey, 0, key.length);
        }
        else {
            System.arraycopy(key, 0, fixedkey, 0, 16);
        }
        return fixedkey;
    }
 
    private static int[] toIntArray(byte[] data, boolean includeLength) {
        int n = (((data.length & 3) == 0)
                ? (data.length >>> 2)
                : ((data.length >>> 2) + 1));
        int[] result;
 
        if (includeLength) {
            result = new int[n + 1];
            result[n] = data.length;
        }
        else {
            result = new int[n];
        }
        n = data.length;
        for (int i = 0; i < n; ++i) {
            result[i >>> 2] |= (0x000000ff & data[i]) << ((i & 3) << 3);
        }
        return result;
    }
 
    private static byte[] toByteArray(int[] data, boolean includeLength) {
        int n = data.length << 2;
 
        if (includeLength) {
            int m = data[data.length - 1];
            n -= 4;
            if ((m < n - 3) || (m > n)) {
                return null;
            }
            n = m;
        }
        byte[] result = new byte[n];
 
        for (int i = 0; i < n; ++i) {
            result[i] = (byte) (data[i >>> 2] >>> ((i & 3) << 3));
        }
        return result;
    }
}


然后 写上 一个解密的类
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.Test;
 
import org.json.JSONObject;
 
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Map;
import java.util.zip.GZIPInputStream;
 
public class Coco2dx {
    public static void main(String[] args) throws Exception {
        String filePath = "C:\\Users\\清明\\Desktop\\某三国游戏\\assets";
        decode(new File(filePath));
    }
 
    private static void decode(File path){
        if (path.isDirectory()){
            File[] files = path.listFiles();
            for (File file:files) {
                decode(file);
            }
        }else{
            if (path.getAbsolutePath().indexOf(".jsc")>0){
                System.out.println("正在解密:"+path);
                byte[] d = XXTEA.decrypt(FileIOUtils.readFile2BytesByChannel(path),"162a81f0-7b82-44");
                d = uncompress(d);
                FileIOUtils.writeFileFromBytesByStream(path.getAbsolutePath().replace("幻想三国2.0","幻想三国解密")
                        .replace(".jsc",".js"),d);
            }else{
                System.out.println(path);
            }
        }
    }
 
    public static byte[] uncompress(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ByteArrayInputStream in = new ByteArrayInputStream(bytes);
        try {
            GZIPInputStream ungzip = new GZIPInputStream(in);
            byte[] buffer = new byte[256];
            int n;
            while ((n = ungzip.read(buffer)) >= 0) {
                out.write(buffer, 0, n);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return out.toByteArray();
    }
}



最后运行,查看
image.png


objk了!!!

免费评分

参与人数 8吾爱币 +11 热心值 +8 收起 理由
正己 + 3 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
fcl101 + 1 + 1 谢谢@Thanks!
石碎大胸口 + 1 + 1 我很赞同!
blue_sky_1 + 1 + 1 我很赞同!
bihaiorg + 1 热心回复!
染个我 + 1 + 1 谢谢@Thanks!
漁滒 + 3 + 1 我很赞同!
深水夜藏 + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

氓之嗤嗤 发表于 2021-1-31 11:03
请问下楼主拿到JS后续是否可以写脱机瓜呀 如何入手呢
 楼主| xieqing520 发表于 2021-1-31 12:08
氓之嗤嗤 发表于 2021-1-31 11:03
请问下楼主拿到JS后续是否可以写脱机瓜呀 如何入手呢

什么脱机瓜?
Rainsup 发表于 2021-2-1 19:50
Ag2S 发表于 2021-2-27 21:32
Python版解密

[Python] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/usr/bin/python3
# -*- coding: utf-8 -*-
##运行需求
##pip install cffi
##pip install xxtea-py
import os
import xxtea
import zlib
 
##获取当前目录下所有jsc文件
def getFileList():
    fs=[]
    dirpath='./'
    for root,dirs,files in os.walk(dirpath):
        for file in files:
            if(file.endswith('.jsc')):
                fs.append(os.path.join(root,file))
    return fs
   
def Fix(path,key):
    f1=open(path,'rb').read()
    print("正在解密:%s"%(path))
    d1=xxtea.decrypt(f1,key)
    d1=zlib.decompress(d1,16+zlib.MAX_WBITS)
    print("解密完成:%s"%(path))
    f2=open(path.replace('.jsc','.js'),'wb')
    f2.write(d1)
     
def run(key):
    for f in getFileList():
        #print(f)
        Fix(f,key)
         
 
key = "162a81f0-7b82-44"
run(key)
fcl101 发表于 2021-3-5 19:07
感谢分享,膜拜大佬。
zycode 发表于 2021-3-6 19:24
研究一下那种真正编译成jsc的还原吧
 楼主| xieqing520 发表于 2021-3-10 15:57
zycode 发表于 2021-3-6 19:24
研究一下那种真正编译成jsc的还原吧

这个就是
keeslient 发表于 2021-3-10 17:32
6666学习了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-10 03:06

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表