Cocos2DX-JS 加密逆向探究解密app实战
什么是Cocos2D?cocos2d 是一个基于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
事实上,百度有,还挺多,不过文章千篇一律,你抄我我抄你。
最后踩了不少坑才了解,那些文章工具逆向解密指的都是coco2d中源代码依附于字节码中,按官方给定的规律提取出来。
而在coco2dx版本中,这项被禁用了,也就是根本用不了。
阅读一下上面的文章 、或者去研究一下官方提供的SDK,可以知道coco2dx生成的jsc并不是真正意义上的编译出来的字节码,仅仅只是做了一层xxtea加密而已。
那么我们就需要去解密,从而得到源码。
开始找秘钥
将某三国游戏apk中的 armeabi-v7a 的 libcocos2djs.so 拖进 IDA PRo
(为什么要是armeabi-v7a中的呢?因为我的手机就是这个架构的 = =,每个人都看自己手机CPU架构信息然后去拖对应的SO)
。。。。。。
。。。。。
。。。。
。。。。
(电脑有点卡,还在一直加载读取中。。。。)
。。。。
。。。。
。。。
(有点无聊。。。)
。。。。。
打开了,我们首先研究JNI_OnLoad 函数,这个是加载so必定经过的位置,说不定秘钥就在这个地方定义了,那我们不就不用干啥了吗 {:1_918:}
使用伪代码查看
然后发现有个 cocos_jni_env_init 函数,一层一层追进去
额,尴尬了,啥也没有,不过没关系,直接祭出大招。
搜索 decrypt 关键词,来搜索Function 函数 (英语不好的人可以百度 = = ,这个是解密的意思啦)
ok,很幸运 ,一下子就定位到关键位置。
然后我们打开查看
记住一下,此处的地址为 0x006672F8,这个地址是相对于这个so来说的
通过上面的文章或者百度xxtea_decrypt算法,可以知道,该函数的声明为
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代码
# -*- 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的代码为
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.readCString());
},
onLeave: function(retval){
}
});
});
},100)
这里说一下,上面找到的地址是相对于so来说的,是相对地址,也可以说是so的偏移地址,那么只需要找到so的入口地址+ 相对地址 = 在内存中的即时真实地址
然后运行,查看控制台信息
(⊙o⊙)…,好像出来了。。。。。。
解密试试看?
这里贴上一份XXTea的算法,java版本(挺恨上面的作者,竟然不分享,晚上找的好多都不完善不能用)
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 ^ 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, y, sum = 0, e;
while (q-- > 0) {
sum = sum + DELTA;
e = sum >>> 2 & 3;
for (p = 0; p < n; p++) {
y = v;
z = v += MX(sum, y, z, p, e, k);
}
y = v;
z = v += 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, sum = q * DELTA, e;
while (sum != 0) {
e = sum >>> 2 & 3;
for (p = n; p > 0; p--) {
z = v;
y = v -= MX(sum, y, z, p, e, k);
}
z = v;
y = v -= 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;
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;
result = data.length;
}
else {
result = new int;
}
n = data.length;
for (int i = 0; i < n; ++i) {
result |= (0x000000ff & data) << ((i & 3) << 3);
}
return result;
}
private static byte[] toByteArray(int[] data, boolean includeLength) {
int n = data.length << 2;
if (includeLength) {
int m = data;
n -= 4;
if ((m < n - 3) || (m > n)) {
return null;
}
n = m;
}
byte[] result = new byte;
for (int i = 0; i < n; ++i) {
result = (byte) (data >>> ((i & 3) << 3));
}
return result;
}
}
然后 写上 一个解密的类
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;
int n;
while ((n = ungzip.read(buffer)) >= 0) {
out.write(buffer, 0, n);
}
} catch (Exception e) {
e.printStackTrace();
}
return out.toByteArray();
}
}
最后运行,查看
objk了!!!
请问下楼主拿到JS后续是否可以写脱机瓜呀 如何入手呢{:301_982:} 氓之嗤嗤 发表于 2021-1-31 11:03
请问下楼主拿到JS后续是否可以写脱机瓜呀 如何入手呢
什么脱机瓜? 楼主真的厉害 学到了学到了 Python版解密
#!/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) 感谢分享,膜拜大佬。 :lol 研究一下那种真正编译成jsc的还原吧 zycode 发表于 2021-3-6 19:24
研究一下那种真正编译成jsc的还原吧
这个就是 6666学习了
页:
[1]