Cómo ofuscar código en U3D
Resumen del contenido: debido a algunas particularidades del motor de Unity, la protección del código bajo el motor de Unity es relativamente compleja de implementar. Actualmente, no existe ningún programa listo para usar en la industria nacional. y en el extranjero. A través de intentos prácticos en el proyecto "QQ Orchestra", el autor ha resumido un conjunto de soluciones específicas y factibles que pueden proteger eficazmente la lógica del código. Me gustaría compartir esto con amigos que estén interesados en el motor Unity, con la esperanza de brindarles alguna referencia.
Antecedentes
Los programas en el motor Unity se ejecutan en el tiempo de ejecución Mono. El formato de ensamblaje compilado por el tiempo de ejecución Mono es el mismo que el de .NET Framework. C# es el principal lenguaje de desarrollo del motor Unity. Tiene muchas características de lenguaje de alto nivel, como reflexión, metadatos, serialización integrada, etc. Pero C# también es muy fácil de usar. Sin embargo, C# también es un lenguaje fácil de descompilar si no se toman medidas de protección, el código recompilado se puede obtener fácilmente utilizando herramientas comunes (.NET Reflector). Reflector .NET. Esto trae grandes riesgos a la operación del proyecto.
Un método de protección común para la plataforma .NET es ofuscar los ensamblados compilados. Dotfuscator, la herramienta de ofuscación que viene con Visual Studio, puede ofuscar ensamblados. Sus funciones incluyen modificación de nombres, ofuscación de procesos, cifrado de cadenas, etc. Una vez que Dotfuscator ofusca el ensamblaje, puede evitar que las herramientas de descompilación ordinarias lo descifren. La expresividad de las variables se destruye y el flujo interno de la función se ofusca (ver [B1] a continuación). Esta es una forma muy eficaz de proteger el código fuente.
publicclass181: 218
{
// Campo
publicuint0;
publicushort1;
publicstaticreadonlyuint2;
publicstaticreadonlyuint3;
/ método
static181();
public181();
public95.02();
public95.02(ref515A_0,uintA_1);
public95.02(79A_0,refuintA_1);
public95.02 (ref79A_0,uintA_1);
public95.04(refbyte[] A_0, intA_1, refintA_2, uintA_3);
}
public95.00(refsbyteA_0, intA_1)
{
/// Este elemento está ofuscado y no se puede traducir.
ir a Label_0006;
if(1!= 0)
{
}
95.0local= 95.0.0;
bytenum= 0;
local = this.0(refnum,A_1);
A_0 = (sbyte) num;
returnlocal;
Bajo el motor Unity, Mono compilará ensamblados. Dado que adopta el mismo formato estándar que .NET, estos ensamblados se pueden mostrar directamente en formato obfile. Dado que el estándar de formato es el mismo que el de .NET, Dotfuscator puede ofuscar directamente estos ensamblados. Sin embargo, existen algunas características especiales del motor Unity que hacen que la ofuscación funcione de manera diferente a los programas .NET comunes. La sección 3 resaltará estos puntos especiales.
Funciones especiales de ofuscación de código en el motor Unity
Los recursos hacen referencia al código [B2].
Una característica de diseño clave de las capacidades de edición visual de Unity es que el código se puede adjuntar a instancias de recursos como componentes. A diferencia de los juegos tradicionales, los dos recursos de Unity (escenas y prefabricados) incluyen no solo datos, sino también objetos de clase adjuntos a los recursos. En otras palabras, los formatos de almacenamiento de estos dos tipos de recursos tienen datos que identifican de forma única el tipo de código. El proceso de ofuscación no puede destruir esta correspondencia, por lo que la lógica del código del recurso se puede ejecutar correctamente. (La importancia de esta forma de diseñar Unity no es el tema central de este artículo. Detallaré mi comprensión personal de la edición visual de Unity en otro artículo).
Los proyectos de Unity publicados en la web agrupan los pasos para compilar el ensamblado y empaquetarlo en una interfaz que genera el ejecutable del reproductor (*.unity). No podemos ofuscar el ensamblado compilado y escribirlo en el ejecutable del reproductor como un programa .NET normal.
UnityEngine se llama con el nombre de función.MonoBehaviour, que es una clase base de componente importante del motor Unity. Unity puede acceder a muchos de estos métodos a través de nombres de métodos, como Awake, Start, Update, etc. Si se cambia el nombre de estos métodos durante la ofuscación, la llamada al método fallará. Este problema es relativamente fácil de resolver; la función de cambio de nombre de Dotfuscator proporciona una configuración de exclusión. Una vez que tengamos todos los tipos que heredan de MonoBehaviour, podemos generar una configuración de exclusión que le indique a Dotfuscator que no cambie el nombre de estos métodos.
El extracto de configuración generado es el siguiente [B3]:
<.method name=".*" regex= "true" /> < nombre de campo=".*"regex="true" />
< escriba nombre =".*"excludetype="false" regex="true">
Idea
¿Cuándo surge la confusión? Dado que los procesos de compilación y empaquetado de proyectos web están agrupados, no se proporciona oficialmente ninguna interfaz separada. (He tenido comentarios con el funcionario antes, pero el funcionario aún no ha proporcionado planes específicos). No es factible ni muy científico analizar usted mismo el formato de embalaje oficial. La única forma que queda es compilar el código en una DLL, ofuscarlo y agregarlo al proyecto de Unity.
Siguiendo esta idea, la probé en el proyecto QQ Orchestra. Eliminé todo el código relacionado con la ejecución en el proyecto (excluyendo el código de extensión del editor), especifiqué las dependencias relevantes de Unity, lo compilé en una DLL y copié la DLL en el proyecto original. Luego copie la DLL en el proyecto original. Sucedió algo inesperado: se perdieron todas las referencias de código al recurso en el proyecto. Para encontrar la relación de mapeo entre recursos y código, ajusté la configuración del editor de Unity, cambié el formato de serialización de los recursos a formato de texto y los comparé y analicé. Resulta que el recurso corresponde a un código específico mediante un GUID[B4].
(Imagen a continuación)
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject .{fileID: 100000}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, directriz: 8ae38faa3fc9f91418a5a9872bcc4b0f, tipo: 1} p>
m_GameObject: 1}
m_Name:
mInt: 1
mFloat: .5
El tipo en Ofuscado , pero el GUID ha cambiado. Después de reemplazar el nuevo GUID en el archivo de recursos, la relación de referencia se restauró.
Se pueden resolver problemas específicos bajo el motor Unity. Por lo tanto, siguiendo esta idea, se desarrollaron varias herramientas para obtener la correspondencia entre los GUID anteriores y los anteriores y escanear todos los recursos para reemplazar el GUID. Por otro lado, después de la ofuscación, el nombre de la variable del tipo ha cambiado y al nombre de la variable en el recurso también se le ha asignado un valor específico, por lo que el nombre de la variable en el recurso debe reemplazarse para que corresponda al ofuscado. nombre de la variable. Fue necesario mucho esfuerzo y finalmente se fabricó la herramienta.
Sin embargo, el problema que llevó a que esta solución fuera un callejón sin salida fue un problema imprevisto: una falla en la forma en que el motor de Unity manejaba los tipos de plantillas en las DLL; los tipos de plantillas en las DLL no tienen GUID, por lo que No se puede hacer referencia a los recursos. Hay una pequeña cantidad de comentarios sobre este problema en el sitio web oficial de Unity. El funcionario reconoció la vulnerabilidad pero no proporcionó una solución. El proyecto QQ Orchestra hace un uso más amplio de los tipos de plantillas en las operaciones de la interfaz de usuario, y eliminar el uso de plantillas no es trivial. Así, una pregunta tan casual puso fin al rumbo de este intento.
"Bailando con grilletes" describe un estado de búsqueda de soluciones tanto como sea posible bajo diversas limitaciones. Resumiendo los fallos anteriores, finalmente encontramos un plan de mejora práctico y lo aplicamos con éxito a la versión web y a la versión de microcliente de "QQ Band".
La idea final es superponer proyectos. Creamos una "capa lógica" a la que no hacen referencia los recursos, que contiene los módulos de sistema y análisis de protocolo más sensibles. El código en la capa lógica se compila de forma independiente en una DLL, se ofusca y se incluye en el proyecto. El código fuera de la capa lógica consiste principalmente en recursos a los que hacen referencia los módulos del sistema o definiciones de interfaz parciales de contenido menos sensible, lo que llamamos "capa de comportamiento". Para que la capa lógica se compile de forma independiente, requerimos que la capa lógica pueda hacer referencia a la capa de comportamiento, y que la capa de comportamiento solo pueda acceder a la capa lógica a través de la interfaz de la capa lógica retenida en la capa de comportamiento. De esta manera podemos proteger el código más importante y evitar problemas con el código de referencia de recursos.
Esta solución impone ciertos requisitos en la arquitectura del proyecto. En primer lugar, requiere que el código y los recursos confidenciales permanezcan separados y requiere un marco para cargar módulos individuales en lugar de adjuntar directamente el código del módulo a los recursos del objeto de escena. En segundo lugar, requiere una jerarquía clara y no permite dependencias inversas. La buena noticia para el proyecto QQ Orchestra es que QQ Orchestra ha implementado un enfoque más claro para la gestión de la arquitectura desde el principio. Entonces, después de pasar algún tiempo superponiendo e implementando mecanismos de acceso a la interfaz, la solución se implementó con éxito.
Pasos de ofuscación reales. QQ Orchestra utiliza VisualBuild para realizar el proceso de creación y lanzamiento de la versión. A continuación se describe el proceso relacionado con la ofuscación durante el proceso de compilación de la versión:
Copie el directorio de código de la capa lógica (CodeGameLogic) del directorio Assets del proyecto Unity, así como el código de extensión del editor (para evitar la ofuscación del código de extensión poseditor (errores de compilación debido a dependencias faltantes en la capa lógica).
Llame a la línea de comando de Unity.exe para compilar la capa de comportamiento restante:
Esta función realmente se ejecuta:
BuildPipeline.BuildPlayer(new string[] { "Assets /obfuscated.unity" }, " WebPlayerObfuscated",
BuildTarget.WebPlayer, BuildOptions.None);
El ensamblaje del editor (es decir, el ensamblaje de extensión del editor) no se puede construir en tiempo de compilación Compilación, interrumpiendo así el proceso de compilación para evitar limpiar la DLL generada por la compilación al final del proceso de BuildPlayer.
Copie la DLL de la capa de comportamiento generada en el directorio de compilación de la capa lógica. La ruta a la DLL de la capa de comportamiento se encuentra en Biblioteca/ScriptAssemblies en el proyecto, donde hay dos archivos: Assembly-CSharp.dll y Assembly-CSharp-firstpass.dll. Además, copie otras DLL de las que depende la capa lógica en el directorio de compilación, incluido UnityEngine.dll y las bibliotecas dependientes en el directorio de complementos del proyecto.
Llame al compilador mcs de Mono para compilar la capa lógica DLL CodeGameLogic.dll. El comando de compilación es el siguiente:
Generar el archivo de configuración de DotObfuscator "WebCfg.xml". Aquí hay una herramienta que escribí yo mismo que escanea los tipos en CodeGameLogic.dll en busca de nombres de métodos y tipos no ofuscados y los agrega a la lista de emisiones en el archivo de configuración. Como se muestra en el inciso "III.3".
Llame a DotObfuscator para ofuscar CodeGameLogic.dll y obtener el CodeGameLogic.dll ofuscado:
Copie el CodeGameLogic.dll ofuscado al proyecto y luego genere el proyecto. Tenga en cuenta que si está creando un proyecto web, deberá copiar el dll al directorio de complementos. Si está creando un proyecto independiente (es decir, cliente), puede copiar directamente el dll al directorio de Activos. Además, no se permiten errores de compilación en esta compilación, por lo que la primera parte requiere eliminar el código de la extensión del editor en el directorio del Editor.
El siguiente paso es fusionar el proyecto construido con los recursos para obtener una versión ofuscada completa.
Resumen:
Decodificar el código de un proyecto de Unity es relativamente fácil. Debe tener cuidado con la ofuscación del código.
La implementación de soluciones de ofuscación de código para proyectos de Unity está muy restringida. Hasta donde yo sé, el esquema presentado en este artículo es el único esquema de ofuscación disponible actualmente. Existen requisitos obligatorios para la estratificación arquitectónica de proyectos. Es una buena idea pensar en cómo colocar capas en el proyecto al principio y colocar lo que se necesita proteger en las capas que se deben ofuscar.