本文将介绍Cocos2d-x项目的反编译,破解和导出功能。
需要使用到:

使用Frida可以下断点,改参数,改返回值,dump内存内容等各种操作。Python Binding使用起来比较上手。

Python头

1
2
3
4
5
6
7
8
9
10
# -*- coding: utf-8 -*-
import sys
import frida
import sys
import time
import os
import errno

reload(sys)
sys.setdefaultencoding('utf8')

连接设备上的Frida

1
2
3
4
device = frida.get_usb_device(1)
device.enable_spawn_gating() #捕获所有子进程
pid = device.spawn(["com.google.chrome"]) #目标应用处于暂停状态
session = device.attach(pid)

准备JS脚本
下面的脚本打印了目标应用的所有模块和模块内的所有导出函数

1
2
3
4
5
6
7
8
9
10
11
12
source = """
Java.perform(function() {
var mo = Process.enumerateModulesSync()
mo.forEach(function(n) {
console.log(n.name)
var ex = Module.enumerateExportsSync(n.name)
ex.forEach(function(node) {
console.log(node.name)
})
})
})
"""

如果运行失败可以在Java.perform外层包一个settimeout(func,0),但这样会失去第一帧捕获系统调用和Activity创建的时机。

愉快的跑起来

1
2
3
4
5
script = session.create_script(source)
script.on("message", on_message)
script.load()
device.resume(pid) #恢复目标应用的执行
sys.stdin.read()

到这一步Frida的基础流程就跑通了,但像上面提到的一样,时机很重要。对Cocos2dx游戏而言,抓住libcocos2dlua.so的加载时机可以确保大多数函数都可以被截住。可以使用捕获libc.sodlopen来找到libcocos2dlua.so的加载时机,让主代码附在applicationDidFinishLaunching:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Interceptor.attach(Module.findExportByName("libc.so", "dlopen"), {
onEnter: function(args) {
this._path = Memory.readUtf8String(args[0])
},

onLeave: function(retval) {
if (this._path.indexOf('libcocos2dlua.so') != -1) {
// safe to intercept with libcocos2dlua.so here
Interceptor.attach(
Module.findExportByName("libcocos2dlua.so","_ZN11AppDelegate29applicationDidFinishLaunchingEv"), {
onEnter: function(args){
Java.perform(function() {
// load additional library if needed
var System = Java.use('java.lang.System');
System.loadLibrary('mb-lib')
// send message to host logic (python here)
send('ready-to-inject')
})
}
}
)
}
}
});

snippet
std::string的支持有限,可以加载自己的库来实现相应的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var mb_string_new = new NativeFunction(Module.findExportByName("libmb-lib.so", "_Z13mb_string_newPKc"), 'pointer', ['pointer'])
var mb_string_delete = new NativeFunction(Module.findExportByName("libmb-lib.so", "_Z16mb_string_deletePSs"), 'void', ['pointer'])
var mb_string_assign = new NativeFunction(Module.findExportByName("libmb-lib.so", "_Z16mb_string_assignPSsPKc"), 'void', ['pointer', 'pointer'])
var mb_string_cstr = new NativeFunction(Module.findExportByName("libmb-lib.so", "_Z14mb_string_cstrPSs"), 'pointer', ['pointer'])

function newStdString(str) {
return mb_string_new(Memory.allocUtf8String(str))
}

function deleteStdString(ptrStr) {
mb_string_delete(ptrStr)
}

function assignStdString(ptrStr, str) {
// may crash when call this function in huge loop, use new/delete instead
mb_string_assign(ptrStr, Memory.allocUtf8String(str))
}

function readStdString(ptrStr) {
return Memory.readUtf8String(mb_string_cstr(ptrStr))
}

解析PNG

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var ptrInitWithImageFile = Module.findExportByName("libcocos2dlua.so","_ZN7cocos2d5Image17initWithImageFileERKSs") 
var funcInitWithImageFile = new NativeFunction(ptrInitWithImageFile, 'int', ['pointer','pointer']);
var ptrInitWithPngData = Module.findExportByName("libcocos2dlua.so","_ZN7cocos2d5Image15initWithPngDataEPKhl")
var funcInitWithPngData = new NativeFunction(ptrInitWithPngData, 'int', ['pointer', 'pointer', 'int'])

Interceptor.replace( ptrInitWithImageFile, new NativeCallback( function(image, filename) {
send( { action: 'filename', filename: readStdString(filename) })
return funcInitWithImageFile(image, filename)
}, 'int', ['pointer', 'pointer'])
);

Interceptor.replace( ptrInitWithPngData, new NativeCallback( function(image, data, length ) {
var content = Memory.readByteArray(data, parseInt(length))
send( { action: 'content', length: length }, content)
return funcInitWithPngData(image, data, length)
}, 'int', ['pointer', 'pointer', 'int'])
);

在Update函数里执行主动解析相关操作,注意到一些使用的Interceptor.replace,一些使用的Interceptor.attach
使用replace的函数处理中调用其他可被捕获的方法时,依然可以触发目标方法的捕获。
但使用attach的函数调用其他可被捕获的方法时,不会触发目标方法的捕获。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var ptrImageNew = Module.findExportByName("libcocos2dlua.so","_ZN7cocos2d5ImageC2Ev") 
var funcImageNew = new NativeFunction(ptrImageNew, 'pointer', [])
var ptrSchedulerUpdate = Module.findExportByName("libcocos2dlua.so", "_ZN7cocos2d9Scheduler6updateEf")
var funcSchedulerUpdate = new NativeFunction(ptrSchedulerUpdate, 'void', ['pointer', 'float'])

Interceptor.replace( ptrSchedulerUpdate, new NativeCallback( function(scheduler, delta) {
var ret = funcSchedulerUpdate(scheduler, delta)
var current_image = funcImageNew()

for (index = 0; index < file_list.length; index++) {
var real_index = current_index + index
var filename = file_list[real_index]
var strFilename = newStdString(filename)
funcInitWithImageFile(current_image, strFilename)
deleteStdString(strFilename)
}

Interceptor.revert(ptrSchedulerUpdate)
Interceptor.revert(ptrInitWithImageFile)
Interceptor.revert(ptrInitWithPngData)

console.log( '--> DONE' )
return ret
}, 'void', ['pointer', 'float'])
);

解析lua

1
2
3
4
5
6
7
8
9
Interceptor.attach(
Module.findExportByName("libcocos2dlua.so","luaL_loadbuffer"),{
onEnter: function(args){
var content = Memory.readUtf8String(args[1])
var name = Memory.readUtf8String(args[3])
send( { name: name, content: content } )
}
}
);

解析plist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Interceptor.attach(
Module.findExportByName("libcocos2dlua.so","_ZN7cocos2d9SAXParser5parseERKSs"),{
onEnter: function(args){
send( { action: 'filename', filename: readStdString(args[1]) })
}
}
);

Interceptor.attach(
Module.findExportByName("libcocos2dlua.so","_ZN7cocos2d9SAXParser5parseEPKcj"),{
onEnter: function(args){
var content = Memory.readByteArray(args[1], parseInt(args[2]))
send( { action: 'content' }, content)
}
}
);

解析Cocos2d::Data

1
2
3
4
5
6
7
8
9
function readDataBytesArray(ptrToData) {
var getbytesptr = Module.findExportByName("libcocos2dlua.so", "_ZNK7cocos2d4Data8getBytesEv");
var getsizeptr = Module.findExportByName("libcocos2dlua.so", "_ZNK7cocos2d4Data7getSizeEv");
var getbytes = new NativeFunction(getbytesptr,'pointer',['pointer']);
var getsize = new NativeFunction(getsizeptr,'int',['pointer']);

var bytesarray = Memory.readByteArray(getbytes(ptrToData), getsize(ptrToData))
return bytesarray
}

Python中处理Message

1
2
3
4
5
6
7
8
9
10
11
# script.on("message", on_message) 
# -> send(message, data)
# -> on_message(message, data)
def on_message(message, data):
# message := { 'type': 'error|send|receive', 'payload': any-object }
# data := binary-object
if message['type'] == 'error':
print(message)
return
payload = message['payload']
...