Unity Android DLL 热更新与加密

Unity Android DLL 热更新与加密

摘要

本文主要介绍,在安卓环境下,通过更换或解密 Unity-Mono 加载的 Assembly-CSharp.dll 数据文件的方法,达到更新或加密代码的目的。首先介绍如何编译生成 libmono.so,然后讲述通过修改 mono - image.c - mono_image_open_from_data_with_name 方法达到修改 DLL 的目的。

工具与环境

Xcode Command Line Tools 安装方法 xcode-select --install

autoconf automake libtool pkg-config 四项可以使用 Homebrew 安装。

  • 获取 Homebrew /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

  • 安装 brew install autoconf automake libtool pkg-config

正文

编译 libmono.so

【步骤一】选择 Unity 版本对应的 Mono 分支,例如本文选择 Unity 5.5.0f3 则对应 Mono 的版本是 unity-5.5。进入 mono 根目录,mono 根目录是编译 Mono 的工作目录。

【步骤二】修改./external/buildscripts/build_runtime_android.sh./external/buildscripts/build_runtime_android_x86.sh

build_runtime_android_x86.sh主要负责编译 x86 架构下的 libmono.so。

build_runtime_android.sh主要负责编译 arm 架构下的 libmono.so,然后调用build_runtime_android_x86.sh

build_runtime_android_x86.sh去掉-g,去掉调试符号。

build_runtime_android.sh将 CFLAGS 下的-g修改为-O2,去掉调试符号,并增加优化符号。

-g 调试符号

-O2 优化符号

详见 https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html

注意:“O”是英文第15个字母的大写,不是零。

build_runtime_android.sh删除编译armv5armv6_vfp,只剩下armv7a

【步骤三】在 mono 根目录下,执行./external/buildscripts/build_runtime_android.sh

编译成功后,终端会提示:

Build SUCCESS!
Build failed? Android STATIC/SHARED library cannot be found... Found        4 libs under builds/embedruntimes/android
total 0
drwxr-xr-x  4 lbs  staff  136 12 30 11:40 armv7a
drwxr-xr-x  4 lbs  staff  136 12 30 11:42 x86

./builds/embedruntimes/android目录下,会有armv7ax86架构下的libmono.so

DLL 热更新与加密原理

打开./mono/metadata/image.c文件,找到mono_image_open_from_data_with_name (char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, const char *name)函数。

参数说明

char *data DLL 数据的指针

guint32 data_len DLL 数据的长度

const char *name DLL 名

使用glib库的g_message方法,在 Android Logcat 中输出 Assembly-CSharp.dll 的 name = /data/app/包名-1.apk/assets/bin/Data/Managed/Assembly-CSharp.dll

从参数可以看出,进入mono_image_open_from_data_with_name函数后,通过辨认name,可以对datadata_len做出修改,从而影响实际加载的 DLL。

DLL 加密与解密

从 Unity 导出 Android 工程后,可以拿到Assembly-CSharp.dll,在 Unity 中编写的大多数代码都会在这个动态链接库下。通过一些加密算法对此 DLL 进行加密后,初学者使用一些反编译工具就无法看到源代码了。示例工程中,首先将Assembly-CSharp.dll的首字节+1(加密),然后在mono_image_open_from_data_with_name中将Assembly-CSharp.dll的首字节-1(解密),从而实现了对 DLL 的加密过程。

DLL 热更新

首先在mono_image_open_from_data_with_name函数上面补充两个函数。实现从可读写路径中读取 DLL 的操作。

static FILE *OpenFileWithPath(const char *path)
{
    const char *fileMode = "rb";
    return fopen (path, fileMode);
}

static char *ReadStringFromFile(const char *pathName, int *size)
{
    FILE *file = OpenFileWithPath (pathName);
    if (file == NULL)
    {
        return 0;
    }
    fseek (file, 0, SEEK_END);
    int length = ftell(file);
    fseek (file, 0, SEEK_SET);
    if (length < 0)
    {
        fclose (file);
        return 0;
    }
    *size = length;
    char *outData = g_try_malloc (length);
    int readLength = fread (outData, 1, length, file);
    fclose(file);
    if (readLength != length)
    {
        g_free (outData);
        return 0;
    }
    return outData;
}

改写mono_image_open_from_data_with_name函数,当发现可读写路径中存在/data/data/包名/files/Assembly-CSharp.dll时,加载新的 DLL。

MonoImage *
mono_image_open_from_data_with_name (char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, const char *name)
{

	////////Modify Begin////////
	int datasize = 0;
	if(name != NULL && strstr (name, "Assembly-CSharp.dll"))
	{
		//重新计算路径,这里假设包名以“com.”开头。
		const char *_pack = strstr (name, "com.");
		const char *_pfie = strstr (name, "-");
		char _name[512];
		memset(_name, 0, 512);
		int _len0 = (int)(_pfie - _pack);
		memcpy(_name, "/data/data/", 11);	//_name = "/data/data/"
		memcpy(_name + 11, _pack, _len0);	//_name = "/data/data/com.Company.ProductName"
		memcpy(_name + 11 + _len0, "/files/Assembly-CSharp.dll", 26);	//_name = "/data/data/com.Company.ProductName/files/Assembly-CSharp.dll"
		char *bytes = ReadStringFromFile (_name, &datasize);
		if (datasize > 0)
		{
			data = bytes;
			data_len = datasize;
		}
	}
	////////Modify End////////

	MonoCLIImageInfo *iinfo;
	MonoImage *image;
	char *datac;

	if (!data || !data_len) {
		if (status)
			*status = MONO_IMAGE_IMAGE_INVALID;
		return NULL;
	}
	datac = data;
	if (need_copy) {
		datac = g_try_malloc (data_len);
		if (!datac) {
			if (status)
				*status = MONO_IMAGE_ERROR_ERRNO;
			return NULL;
		}
		memcpy (datac, data, data_len);
	}

	////////Modify Begin////////
	if(datasize > 0 && data != 0)
	{
		g_free (data);	//释放新 DLL 的数据
	}
	////////Modify End////////

	image = g_new0 (MonoImage, 1);
	image->raw_data = datac;
	image->raw_data_len = data_len;
	image->raw_data_allocated = need_copy;
	image->name = (name == NULL) ? g_strdup_printf ("data-%p", datac) : g_strdup(name);
	iinfo = g_new0 (MonoCLIImageInfo, 1);
	image->image_info = iinfo;
	image->ref_only = refonly;
	image->ref_count = 1;

	image = do_mono_image_load (image, status, TRUE, TRUE);
	if (image == NULL)
		return NULL;

	return register_image (image);
}

热更新 DLL 版本详情 on Github

后续工作

修改代码后,重新编译 Mono。将./builds/embedruntimes/android目录中对应armv7ax86架构下的libmono.so分别替换 Unity 导出的 Android 工程中的libmono.so。然后就可以进行安卓打包了。

技术支持

Unity-Technologies/mono

Android NDK

参考资料

Unity3D-重新编译Mono加密DLL

Unity Android 动态更新 Assembly-CSharp.dll

Unity3D研究院之Android加密DLL与破解DLL .SO

posted @   Domefy  阅读(892)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示