Unity Editor Encryption Scheme Based on Mono

Keywords: Unity Windows github encoding

Introduction

This blog is written to game developers who need to protect their intellectual property rights. The encryption method of this article is relatively simple and easy to understand, but the effect is enough to deter most of the small white stealers.

Windows Encryption Chapter

Unity2017-based

I. preparation

1.Mono source code (download the source code according to your Unity version): https://github.com/Unity-Technologies/mono
2. Visual Studio 2010 (Mono version 2017 needs to be compiled with this VS version)
3. encryption algorithm (arbitrarily, based on C encoding).

Two. Text

1. Modify the load source code (the first few parts are relatively simple, no more details, directly into the topic)
  • Principle: C# is able to load DLL(Assembly.LoadFile) dynamically. We mainly do some articles here.
    • After reading mono source code, we can know
    • Mon_image_open_from_data_with_name:
MonoImage *
mono_image_open_from_data_with_name (char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, const char *name)
{
	MonoCLIImageInfo *iinfo;
	MonoImage *image;
	char *datac;

	if (!data || !data_len) {
		if (status)
			*status = MONO_IMAGE_IMAGE_INVALID;
		return NULL;
	}

	// The data here is the data after reading the DLL
	// Here we can decrypt our encrypted DLL by filtering names.

	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);
	}

	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);
}
  • Read mono_assembly_open_full with DLL code assembly.c
MonoAssembly *
mono_assembly_open_full (const char *filename, MonoImageOpenStatus *status, gboolean refonly)
{
	MonoImage *image;
	MonoAssembly *ass;
	MonoImageOpenStatus def_status;
	gchar *fname;
	gchar *new_fname;
	
	g_return_val_if_fail (filename != NULL, NULL);

	if (!status)
		status = &def_status;
	*status = MONO_IMAGE_OK;

	if (strncmp (filename, "file://", 7) == 0) {
		GError *error = NULL;
		gchar *uri = (gchar *) filename;
		gchar *tmpuri;

		/*
		 * MS allows file://c:/... and fails on file://localhost/c:/... 
		 * They also throw an IndexOutOfRangeException if "file://"
		 */
		if (uri [7] != '/')
			uri = g_strdup_printf ("file:///%s", uri + 7);
	
		tmpuri = uri;
		uri = mono_escape_uri_string (tmpuri);
		fname = g_filename_from_uri (uri, NULL, &error);
		g_free (uri);

		if (tmpuri != filename)
			g_free (tmpuri);

		if (error != NULL) {
			g_warning ("%s\n", error->message);
			g_error_free (error);
			fname = g_strdup (filename);
		}
	} else {
		fname = g_strdup (filename);
	}

	mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_ASSEMBLY,
			"Assembly Loader probing location: '%s'.", fname);

	new_fname = NULL;
	if (!mono_assembly_is_in_gac (fname))
		new_fname = mono_make_shadow_copy (fname);
	if (new_fname && new_fname != fname) {
		g_free (fname);
		fname = new_fname;
		mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_ASSEMBLY,
			    "Assembly Loader shadow-copied assembly to: '%s'.", fname);
	}
	
	image = NULL;
	// The data read in will be analyzed here.
	// When bundles are encrypted, the original DLL data format is changed
	// So we need to deal with it here so that the read DLL data is not filtered.
	
	FILE * pFile;
	long lSize;
	char * buffer;
	size_t result;
	char*name;
	name = g_path_get_basename(filename); // Get the DLL name
	if (strstr(name, "MyXXX.dll" != NULL) // Determine whether the required decryption conditions are met (here is the name of the DLL)
	{
		pFile = fopen(filename, "rb");
		fseek(pFile, 0, SEEK_END);
		lSize = ftell(pFile);
		rewind(pFile);
		buffer = (char*) malloc (sizeof(char)*lSize);
		result=fread(buffer, 1, lSize, pFile);
		fclose(pFile);
		image = mono_image_open_from_data_with_name(buffer, lSize, FALSE, status, refonly, name);
		free(buffer);
	}
	else if (bundles != NULL)
		image = mono_assembly_open_from_bundle (fname, status, refonly);

	if (!image)
		image = mono_image_open_full (fname, status, refonly);

	if (!image){
		if (*status == MONO_IMAGE_OK)
			*status = MONO_IMAGE_ERROR_ERRNO;
		g_free (fname);
		return NULL;
	}

	if (image->assembly) {
		/* Already loaded by another appdomain */
		mono_assembly_invoke_load_hook (image->assembly);
		mono_image_close (image);
		g_free (fname);
		return image->assembly;
	}

	ass = mono_assembly_load_from_full (image, fname, status, refonly);

	if (ass) {
		mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_ASSEMBLY,
				"Assembly Loader loaded assembly from location: '%s'.", filename);
		if (!refonly)
			mono_config_for_assembly (ass->image);
	}

	/* Clear the reference added by mono_image_open */
	mono_image_close (image);
	
	g_free (fname);

	return ass;
}

Later, VS2010 is used for compilation and substitution to take effect.
After 2018, the method is different. After I update it again, the principle is basically the same.

Posted by phanh on Wed, 09 Oct 2019 04:41:26 -0700