Simplify ejecuta virtualmente una aplicación para comprender su comportamiento y luego intenta optimizar el código para que se comporte de manera idéntica pero sea más fácil de entender para un humano. Cada tipo de optimización es simple y genérico, por lo que no importa qué tipo específico de ofuscación se utilice.
El código de la izquierda es una descompilación de una aplicación ofuscada y el código de la derecha ha sido desofuscado.
El proyecto consta de tres partes: smalivm, simplifica y la aplicación de demostración.
if
o switch
con un valor desconocido da como resultado que se tomen ambas ramas. usage: java -jar simplify.jar <input> [options]
deobfuscates a dalvik executable
-et,--exclude-types <pattern> Exclude classes and methods which include REGEX, eg: "com/android", applied after include-types
-h,--help Display this message
-ie,--ignore-errors Ignore errors while executing and optimizing methods. This may lead to unexpected behavior.
--include-support Attempt to execute and optimize classes in Android support library packages, default: false
-it,--include-types <pattern> Limit execution to classes and methods which include REGEX, eg: ";->targetMethod("
--max-address-visits <N> Give up executing a method after visiting the same address N times, limits loops, default: 10000
--max-call-depth <N> Do not call methods after reaching a call depth of N, limits recursion and long method chains, default: 50
--max-execution-time <N> Give up executing a method after N seconds, default: 300
--max-method-visits <N> Give up executing a method after executing N instructions in that method, default: 1000000
--max-passes <N> Do not run optimizers on a method more than N times, default: 100
-o,--output <file> Output simplified input to FILE
--output-api-level <LEVEL> Set output DEX API compatibility to LEVEL, default: 15
-q,--quiet Be quiet
--remove-weak Remove code even if there are weak side effects, default: true
-v,--verbose <LEVEL> Set verbosity to LEVEL, default: 0
La compilación requiere la instalación del Java Development Kit 8 (JDK).
Debido a que este proyecto contiene submódulos para marcos de Android, clone con --recursive
:
git clone --recursive https://github.com/CalebFenton/simplify.git
O actualice los submódulos en cualquier momento con:
git submodule update --init --recursive
Luego, para construir un único frasco que contenga todas las dependencias:
./gradlew fatjar
El jar de Simplify estará en simplify/build/libs/
. Puede probar que funciona simplificando la aplicación de ejemplo ofuscada proporcionada. Así es como lo ejecutarías (es posible que tengas que cambiar simplify.jar
):
java -jar simplify/build/libs/simplify.jar -it " org/cf/obfuscated " -et " MainActivity " simplify/obfuscated-app.apk
Para comprender qué se está desofuscando, consulte el archivo README de la aplicación Obfuscated.
Si Simplificar falla, pruebe estas recomendaciones, en orden:
-it
.--max-address-visits
, --max-call-depth
y --max-method-visits
superiores.-v
o -v 2
e informe el problema con los registros y un hash de DEX o APK.Si compila en Windows y la compilación falla con un error similar al siguiente:
No se pudo encontrar herramientas.jar. Compruebe que C:Program FilesJavajre1.8.0_151 contenga una instalación JDK válida.
Esto significa que Gradle no puede encontrar una ruta JDK adecuada. Asegúrese de que el JDK esté instalado, configure la variable de entorno JAVA_HOME
en su ruta JDK y asegúrese de cerrar y volver a abrir el símbolo del sistema que utiliza para compilar.
No seas tímido. Creo que la ejecución virtual y la desofuscación son problemas fascinantes. Cualquiera que esté interesado automáticamente es genial y las contribuciones son bienvenidas, incluso si es solo para corregir un error tipográfico. No dude en hacer preguntas en los números y enviar solicitudes de extracción.
Incluya un enlace al APK o DEX y el comando completo que está utilizando. Esto hace que sea mucho más fácil reproducir (y, por tanto, solucionar ) su problema.
Si no puede compartir la muestra, incluya el hash del archivo (SHA1, SHA256, etc.).
Si una operación coloca un valor de un tipo que se puede convertir en una constante, como una cadena, un número o un valor booleano, esta optimización reemplazará esa operación con la constante. Por ejemplo:
const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
invoke-static {v0}, Lmy/string/Decryptor;->decrypt(Ljava/lang/String;)Ljava/lang/String;
# Decrypts to: "Tell me of your homeworld, Usul."
move-result v0
En este ejemplo, una cadena cifrada se descifra y se coloca en v0
. Dado que las cadenas son "constantizables", el move-result v0
se puede reemplazar con una const-string
:
const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
invoke-static {v0}, Lmy/string/Decryptor;->decrypt(Ljava/lang/String;)Ljava/lang/String;
const-string v0, "Tell me of your homeworld, Usul."
El código está inactivo si eliminarlo no puede alterar el comportamiento de la aplicación. El caso más obvio es si el código es inalcanzable, por ejemplo if (false) { // dead }
). Si se puede acceder al código, se puede considerar muerto si no afecta ningún estado fuera del método, es decir, no tiene efectos secundarios . Por ejemplo, el código no puede afectar el valor de retorno del método, alterar ninguna variable de clase ni realizar ninguna IO. Esto es difícil de determinar en el análisis estático. Por suerte, smalivm no tiene por qué ser inteligente. Simplemente ejecuta estúpidamente todo lo que puede y asume que hay efectos secundarios si no puede estar seguro. Considere el ejemplo de Propagación constante:
const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
invoke-static {v0}, Lmy/string/Decryptor;->decrypt(Ljava/lang/String;)Ljava/lang/String;
const-string v0, "Tell me of your homeworld, Usul."
En este código, invoke-static
ya no afecta el valor de retorno del método y supongamos que no hace nada extraño como escribir bytes en el sistema de archivos o en un socket de red, por lo que no tiene efectos secundarios. Simplemente se puede quitar.
const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
const-string v0, "Tell me of your homeworld, Usul."
Finalmente, la primera const-string
asigna un valor a un registro, pero ese valor nunca se usa, es decir, la asignación está muerta. También se puede quitar.
const-string v0, "Tell me of your homeworld, Usul."
¡Hurra!
Uno de los principales desafíos del análisis estático de Java es la reflexión. Simplemente no es posible saber que los argumentos son a favor de los métodos de reflexión sin realizar un análisis cuidadoso del flujo de datos. Hay formas inteligentes de hacer esto, pero smalivm lo hace simplemente ejecutando el código. Cuando encuentra una invocación de método reflejada como:
invoke-virtual {v0, v1, v2}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
Puede conocer los valores de v0
, v1
y v2
. Si está seguro de cuáles son los valores, puede reemplazar la llamada a Method.invoke()
con una invocación de método real no reflejada. Lo mismo se aplica a las búsquedas de clases y campos reflejados.
Para todo lo que no encaja claramente en una categoría particular, existen optimizaciones de mirilla. Esto incluye eliminar operaciones check-cast
inútiles, reemplazar llamadas Ljava/lang/String;-><init>
con const-string
, etc.
.method public static test1 () I
.locals 2
new-instance v0 , L java/lang/Integer ;
const/4 v1 , 0x1
invoke-direct { v0 , v1 }, L java/lang/Integer ; -> <init> ( I ) V
invoke-virtual { v0 }, L java/lang/Integer ; -> intValue () I
move-result v0
return v0
.end method
Todo lo que esto hace es v0 = 1
.
.method public static test1 () I
.locals 2
new-instance v0 , L java/lang/Integer ;
const/4 v1 , 0x1
invoke-direct { v0 , v1 }, L java/lang/Integer ; -> <init> ( I ) V
invoke-virtual { v0 }, L java/lang/Integer ; -> intValue () I
const/4 v0 , 0x1
return v0
.end method
El move-result v0
se reemplaza con const/4 v0, 0x1
. Esto se debe a que sólo hay un valor de retorno posible para intValue()I
y el tipo de retorno se puede convertir en una constante. Los argumentos v0
y v1
son inequívocos y no cambian. Es decir, existe un consenso de valores para cada ruta de ejecución posible en intValue()I
. Otros tipos de valores que se pueden convertir en constantes:
const/4
, const/16
, etc.const-string
const-class
.method public static test1 () I
.locals 2
const/4 v0 , 0x1
return v0
.end method
Debido a que el código anterior const/4 v0, 0x1
no afecta el estado fuera del método (sin efectos secundarios), se puede eliminar sin cambiar el comportamiento. Si hubo una llamada a un método que escribió algo en el sistema de archivos o en la red, no se pudo eliminar porque afecta el estado fuera del método. O si test()I
tomé un argumento mutable, como LinkedList
, cualquier instrucción que accediera a él no podría considerarse muerta.
Otros ejemplos de código muerto:
if (false) { dead_code(); }
Esta herramienta está disponible bajo una licencia dual: una comercial adecuada para proyectos de código cerrado y una licencia GPL que se puede utilizar en software de código abierto.
Dependiendo de tus necesidades, deberás elegir uno de ellos y seguir sus políticas. Un detalle de las políticas y acuerdos para cada tipo de licencia está disponible en los archivos LICENSE.COMMERCIAL y LICENSE.GPL.