Unity: 调用Mono.dll中的mono_image_open_from_data_with_name函数解密Assembly-CSharp.dll

Unity开发的游戏核心代码通常位于Assembly-CSharp.dll中,而C#本身容易被反编译,不少开发者选择对Assembly-CSharp.dll文件内容使用特定算法进行加密、在Mono加载时再进行解密。然鹅这种方法也算不算绝对的安全。

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的门槛,但也称不上绝对的安全,可参考的破解思路有很多,如:

  1. 反编译mono_image_open_from_data_with_name函数,查看加密算法以反推出解密算法。
  2. 附加debugger,在mono_image_open_from_data_with_name函数返回前设置断点,截取出解密后的数据。
  3. 直接dump内存下来分析。
  4. ……

还有一种更为简便也比较通用的方法,就是写个程序动态链接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;
}

 

称谓(*)
邮箱
留言(*)