Unity开发的游戏核心代码通常位于Assembly-CSharp.dll中,而C#本身容易被反编译。不少开发者选择对Mono进行二次开发,先对Assembly-CSharp.dll文件内容使用特定算法进行加密,再于Mono加载Assembly-CSharp.dll时,在mono_image_open_from_data_with_name函数中对其进行解密。这样,暴露在文件系统中的Assembly-CSharp.dll便无法反编译。
这种方法提高了反编译Assembly-CSharp.dll的门槛,但也称不上绝对的安全,可参考的破解思路有很多,如:
- 反编译mono_image_open_from_data_with_name函数,查看加密算法以反推出解密算法。
- 附加debugger,在mono_image_open_from_data_with_name函数返回前设置断点,截取出解密后的数据。
- 直接dump内存下来分析。
- ……
还有一种更为简便也比较通用的方法,就是写个程序动态链接Mono.dll,模拟游戏调用mono_image_open_from_data_with_name来进行来获得解密后的结果。Mono本身是开源的, 在这里可以看到声明该函数的头文件中我们感兴趣的部分:
typedef enum {
MONO_IMAGE_OK,
MONO_IMAGE_ERROR_ERRNO,
MONO_IMAGE_MISSING_ASSEMBLYREF,
MONO_IMAGE_IMAGE_INVALID
} MonoImageOpenStatus;
MonoImage *mono_image_open_from_data_with_name (char *data, uint32_t data_len, mono_bool need_copy,
MonoImageOpenStatus *status, mono_bool refonly, const char *name);
mono_image_open_from_data_with_name函数给定一个dll文件的二进制数据data,以及长度data_len、文件名name等,返回一个MonoImage指针。MonoImage中包含了解密后的数据,它的定义可以在这里看到,虽然这个结构体比较长也比较复杂,不过我们感兴趣的数据都只定义在靠前的部分:
struct _MonoImage {
int ref_count;
void *raw_data_handle;
char *raw_data;
guint32 raw_data_len;
//Others...
}
这样,不论加密解密算法如何,我们都可以调用mono_image_open_from_data_with_name来获取解密后可以反编译的结果。
程序示例
这里给出了直接调用Mono.dll中的mono_image_open_from_data_with_name函数来解密Assembly-CSharp.dll、输出Assembly-CSharp.decrypted.dll的一个程序示例。实际运行的话还需要在main函数前3行指定相应的输入输出文件名。
#include <stdio.h>
#include <windows.h>
#pragma region Mono接口
typedef struct _MonoImage {
int ref_count;
void *raw_data_handle;
char *raw_data;
unsigned int raw_data_len;
//Others...
} MonoImage;
typedef enum {
MONO_IMAGE_OK,
MONO_IMAGE_ERROR_ERRNO,
MONO_IMAGE_MISSING_ASSEMBLYREF,
MONO_IMAGE_IMAGE_INVALID
} MonoImageOpenStatus;
typedef MonoImage* (*mono_image_open_from_data_with_name) (char *data, unsigned int data_len, bool need_copy, MonoImageOpenStatus *status, bool refonly, const char * name);
#pragma endregion
int main()
{
//输入输出文件
const char * pathMono = "mono.dll";
const char * pathAssembly = "Assembly-CSharp.dll";
const char * pathDecrypted = "Assembly-CSharp.decrypted.dll";
//打开Assembly-CSharp.dll
FILE *fpAssembly = fopen(pathAssembly, "rb");
if (!fpAssembly) {
printf("Open Error: Cannot Read Assembly Dll.\n");
}
else {
//读取Assembly-CSharp.dll到data
fseek(fpAssembly, 0, SEEK_END);
unsigned int len = ftell(fpAssembly);
fseek(fpAssembly, 0, SEEK_SET);
char *data = new char[len];
fread(data, 1, len, fpAssembly);
fclose(fpAssembly);
//加载Mono.dll
HMODULE handleMono = LoadLibraryA(pathMono);
if (!handleMono) {
printf("Open Error: Cannot Read Mono Dll.\n");
}
else {
//获取mono_image_open_from_data_with_name函数入口
mono_image_open_from_data_with_name func = (mono_image_open_from_data_with_name)GetProcAddress(handleMono, "mono_image_open_from_data_with_name");
if (!func) {
printf("Open Error: Cannot Find Mono Function.\n");
}
else {
//调用mono_image_open_from_data_with_name解密
MonoImageOpenStatus status;
MonoImage* result = (*func)(data, len, false, &status, false, "Assembly-CSharp.dll");
if (!result) {
printf("Open Error: Cannot Decrypt Assembly Dll. Error Code: %d.\n", status);
}
else {
//打开文件
FILE *fpDecrypted = fopen(pathDecrypted, "wb");
if (!fpDecrypted) {
printf("Open Error: Cannot Write Decrypted Assembly Dll.\n");
}
else {
//输出结果
fwrite(result->raw_data, result->raw_data_len, 1, fpDecrypted);
fclose(fpDecrypted);
}
}
}
FreeLibrary(handleMono);
}
delete data;
}
return 0;
}