martes, 3 de junio de 2025

Android Research IV: Java For Malware IV

Lista de ejercicios para practicar


A.1. Tipos de Datos y Variables

  1. Calculadora de Almacenamiento de Datos Primitivos:

    • Declara variables para cada uno de los tipos de datos primitivos enteros (byteshortintlong) e inicialízalas con valores que se acerquen a sus límites (tanto positivos como negativos). Imprime cada valor.
    • Haz lo mismo para los tipos de punto flotante (floatdouble).
    • Declara una variable char e inicialízala con un carácter Unicode (p.ej., un símbolo o una letra no inglesa). Imprímela.
    • Declara una variable boolean e imprímela.
    • Reflexión: ¿Cómo se comportaría un int de C++ que intente almacenar un valor mayor al de un int de Java? ¿Qué pasa si intentas asignar un valor muy grande a un byte en Java?
  2. Simulador de Pila y Heap (Conceptual):

    • Escribe un pequeño programa donde declares algunas variables locales primitivas dentro de un método.
    • Luego, crea un objeto simple (p.ej., Object obj = new Object();) y un array de enteros (int[] numeros = new int[3];).
    • Dibuja en un papel cómo crees que se verían la pila (stack) y el montículo (heap) en ese momento, indicando dónde residirían las variables primitivas, las referencias y los objetos/arrays en sí.
    • Reflexión: Si asignas una variable de array a otra (int[] copiaNumeros = numeros;), ¿qué sucede en la memoria? ¿Estás copiando el array o la referencia?
  3. Manejo de Posibles "unsigned int":

    • Imagina que recibes un valor de 32 bits de una fuente externa que conceptualmente es un "unsigned int" (p.ej., 0xFFFFFFFF). En Java, esto se interpretaría como -1 si se almacena en un int.
    • Lee este valor como un int. Luego, usa un long para almacenar su representación "sin signo" correcta (realiza un casting y una máscara con 0xFFFFFFFFL si es necesario). Imprime ambos valores.
    • Reflexión: ¿Por qué es importante 0xFFFFFFFFL (con la L) en la máscara?

A.2. Operadores

  1. Calculadora Básica y Asignaciones:

    • Declara dos variables enteras y dos double.
    • Realiza operaciones de suma, resta, multiplicación, división y módulo con ellas. Presta atención al resultado de la división entera vs. la división con double.
    • Utiliza los operadores de asignación compuesta (+=, -=, *=, /=, %=) para modificar los valores de estas variables. Imprime los resultados en cada paso.
  2. Comparador de Referencias vs. Contenido:

    • Crea dos objetos String con el mismo contenido literal (p.ej., String s1 = "test"; String s2 = "test";). Compara usando == y .equals().
    • Crea otros dos objetos String usando new String("test") (p.ej., String s3 = new String("test"); String s4 = new String("test");). Compara usando == y .equals().
    • Crea una tercera referencia que apunte a uno de los objetos anteriores (p.ej., String s5 = s3;). Compara s3 == s5.
    • Imprime todos los resultados y explica por qué son diferentes.
    • Reflexión: ¿Cómo podría un malware usar incorrectamente == con Strings para evadir una detección o tomar una decisión incorrecta?
  3. Lógica de Cortocircuito:

    • Escribe una expresión if que use && donde la segunda condición podría causar un NullPointerException si se evaluara, pero no lo hace debido al cortocircuito. (P.ej., String str = null; if (str != null && str.length() > 0) { ... }).
    • Haz lo mismo con || donde la segunda condición no se evalúa porque la primera es true.
  4. Manipulación de Bits para Ofuscación Simple:

    • Toma un char (p.ej., 'A'). Conviértelo a int.
    • Realiza una operación XOR con una "clave" int (p.ej., 0xAA). Imprime el resultado.
    • Realiza la operación XOR nuevamente con la misma clave sobre el resultado anterior. ¿Qué obtienes?
    • Usa operadores de desplazamiento (<<>>>>>) sobre un número entero (positivo y negativo) e imprime los resultados. Observa la diferencia entre >> y >>> con números negativos.
    • Reflexión: ¿Cómo podría usarse XOR para ocultar cadenas o datos en un payload?
  5. Verificación de Tipo con instanceof:

    • Crea un Object que sea en realidad un String (p.ej., Object obj = "Soy una cadena";).
    • Usa instanceof para verificar si obj es una instancia de String, de Integer, y de Object. Imprime los resultados.
    • Si es un String, haz un casting seguro a String y llama a algún método de String (p.ej., length()).

A.3. Declaraciones de Flujo de Control

  1. Clasificador de Números:

    • Pide al usuario (o define una variable) un número entero.
    • Usa una estructura if-else if-else para determinar si el número es positivo, negativo o cero.
    • Usa una instrucción switch para realizar una acción diferente basada en si un número (del 1 al 3) es 1 ("Opción Uno"), 2 ("Opción Dos"), o 3 ("Opción Tres"), con un caso default para otros valores. Intenta usar String en el switch (si tu versión de Java lo permite y es relevante para el texto).
  2. Iteraciones y Factorial:

    • Calcula el factorial de un número usando un bucle for.
    • Calcula el mismo factorial usando un bucle while.
    • Calcula el mismo factorial usando un bucle do-while.
    • Imprime los resultados.
  3. Procesamiento de Comandos (Simulado):

    • Crea un array de String con algunos "comandos" (p.ej., "START""PROCESS_DATA""PAUSE""STOP""UNKNOWN_CMD").
    • Itera sobre el array usando un bucle for-each.
    • Dentro del bucle, usa if o switch para simular la ejecución de cada comando.
    • Si encuentras el comando "STOP", usa break para salir del bucle.
    • Si encuentras un comando "SKIP_NEXT_IF_PAUSE" y el siguiente es "PAUSE", usa continue para saltar el procesamiento de "PAUSE" (esto es un poco más avanzado, podrías necesitar un for clásico para mirar adelante o una bandera).
    • Reflexión: ¿Cómo puede break y continue (especialmente etiquetados) ser usados para crear flujos de control complejos o difíciles de seguir?
  4. Búsqueda en Matriz con Salida Etiquetada:

    • Crea una matriz (array bidimensional) de enteros.
    • Busca un valor específico en la matriz usando bucles anidados.
    • Si encuentras el valor, usa break <etiqueta>; para salir de ambos bucles inmediatamente. Imprime la posición donde lo encontraste o un mensaje si no se encontró.

A.4. Arrays y Cadenas

  1. Gestor de Tareas Simple:

    • Declara un array de String para almacenar una lista de tareas. Inicialízalo con algunas tareas.
    • Imprime el número de tareas usando la propiedad length.
    • Itera sobre el array e imprime cada tarea con su índice.
    • Intenta acceder a un índice fuera de los límites y observa la ArrayIndexOutOfBoundsException (puedes usar un try-catch si ya lo conoces, o simplemente dejar que ocurra para ver el mensaje).
  2. Manipulador de Nombres de Usuario:

    • Crea una String que contenga un nombre de usuario, posiblemente con espacios al inicio/final y en mayúsculas/minúsculas mezcladas (p.ej., " TeStUser123 ").
    • Usa métodos de String para:
      • Eliminar espacios al inicio y al final (trim()).
      • Convertir todo a minúsculas (toLowerCase()).
      • Verificar si contiene un número (contains() o matches() con una expresión regular simple).
      • Obtener una subcadena (p.ej., los primeros 4 caracteres).
      • Reemplazar "User" por "Admin" (si existe).
    • Imprime el resultado de cada transformación.
    • Reflexión: ¿Por qué la inmutabilidad de String es importante aquí? ¿Qué pasa si concatenas Strings repetidamente en un bucle?
  3. Constructor de Cadenas Eficiente:

    • Usa un bucle para construir una cadena concatenando números del 0 al 9, primero usando el operador + con String.
    • Luego, haz lo mismo usando StringBuilder y su método append().
    • Aunque no medirás el rendimiento directamente en un ejercicio básico, reflexiona sobre por qué StringBuilder es más eficiente.
  4. Codificador/Decodificador XOR Simple para Cadenas:

    • Toma una String (p.ej., "HolaSecreto").
    • Conviértela a un byte[] (p.ej., usando getBytes("UTF-8")).
    • "Cifra" cada byte del array realizando una operación XOR con un byte clave (p.ej., 0x55).
    • Crea una nueva String a partir del byte[] cifrado (podrías necesitar codificarla en Base64 para que sea imprimible de forma segura, o manejar la posibilidad de caracteres no imprimibles).
    • "Descifra" el byte[] realizando la operación XOR nuevamente con la misma clave.
    • Convierte el byte[] descifrado de nuevo a una String e imprímela.
    • Reflexión: ¿Qué problemas pueden surgir al convertir byte[] arbitrarios directamente a String sin una codificación adecuada como Base64? ¿Por qué es crucial especificar un Charset como "UTF-8"?

B.1. Clases, Objetos, Métodos y Constructores

  1. Clase MalwareComponent:

    • Crea una clase llamada MalwareComponent.
    • Campos: Añade los siguientes campos (variables de instancia):
      • String componentName (nombre del componente, ej: "NetworkListener")
      • int version (versión del componente)
      • boolean isActive (indica si el componente está activo)
    • Métodos:
      • Un método void displayStatus() que imprima el nombre, versión y si está activo el componente. Por ejemplo: "Componente: NetworkListener, Versión: 1, Estado: Activo".
      • Un método void activate() que ponga isActive a true.
      • Un método void deactivate() que ponga isActive a false.
    • Relevancia en Malware: Esta clase podría representar un módulo genérico dentro de una pieza de malware.
  2. Objetos y this:

    • En tu método main (o en una clase de prueba), crea dos objetos (instancias) de la clase MalwareComponent.
      • componenteRed = new MalwareComponent();
      • componentePersistencia = new MalwareComponent();
    • Asigna valores a los campos de componenteRed directamente (si no los has hecho privados aún).
    • Modifica el método displayStatus() en MalwareComponent para usar this explícitamente al referirse a los campos (ej. this.componentName).
    • Llama al método activate() para componenteRed y displayStatus() para ambos objetos para ver sus estados.
  3. Constructores para MalwareComponent:

    • Constructor por defecto: Si no has añadido ningún constructor, observa cómo puedes crear un objeto. Luego, añade un constructor sin parámetros que inicialice componentName a "GenericComponent", version a 1, y isActive a false.
    • Constructor parametrizado: Añade un constructor que acepte String name y int ver como parámetros. Este constructor debe inicializar componentName y version con los valores recibidos, e isActive a false.
    • Constructor sobrecargado con this(): Añade un tercer constructor que solo acepte String name. Este constructor debe llamar al constructor parametrizado (el que toma name y ver) usando this(name, 1);, asignando una versión por defecto de 1.
    • En tu main, crea objetos de MalwareComponent usando los diferentes constructores que has definido y llama a displayStatus() para cada uno.
    • Relevancia en Malware: Los constructores aseguran que cada "componente" del malware se inicialice con una configuración base necesaria (ej. un C2Connector que necesita URL y puerto).

B.2.a. Encapsulación y Paquetes

  1. Modificadores de Acceso y Getters/Setters para MalwareComponent:

    • Modifica la clase MalwareComponent (del ejercicio B.1.1):
      • Cambia los modificadores de acceso de todos sus campos (componentNameversionisActive) a private.
      • Crea métodos public getters para cada campo (ej. getComponentName()getVersion()isActive() o isIsActive()).
      • Crea métodos public setters para componentName y version (ej. setComponentName(String name)). En el setVersion(int ver), añade una pequeña validación: si la versión es menor que 1, se debe establecer a 1.
    • En tu main, intenta acceder a los campos directamente (deberías obtener un error) y luego usa los getters y setters para interactuar con los objetos MalwareComponent.
  2. Paquetes y Visibilidad default (package-private):

    • Crea una estructura de paquetes:
      • com.malware.core
      • com.malware.utils
    • Mueve tu clase MalwareComponent al paquete com.malware.core. Asegúrate de que la clase MalwareComponent y sus métodos públicos (getters/setters) sigan siendo public.
    • Dentro de MalwareComponent (en com.malware.core), añade un nuevo campo sin modificador de acceso (package-private): String internalId; y un método también package-private: void assignInternalId(String id) { this.internalId = id; }.
    • Prueba 1 (Mismo Paquete): Crea una nueva clase CoreManager en el mismo paquete com.malware.core. Dentro de CoreManager, crea un objeto MalwareComponent e intenta acceder a internalId y llamar a assignInternalId(). Debería funcionar.
    • Prueba 2 (Diferente Paquete): Crea una clase ExternalTool en el paquete com.malware.utils. Dentro de ExternalTool, importa com.malware.core.MalwareComponent. Intenta crear un objeto MalwareComponent e intenta acceder a internalId o assignInternalId(). No debería funcionar. Explica por qué.
    • Relevancia en Malware: La encapsulación y los paquetes ayudan a estructurar el malware, ocultando detalles internos de ciertos módulos a otros, o a código externo (como herramientas de análisis si no usan reflexión).

B.2.b. Herencia

  1. Clase Base Payload y Subclase CommandPayload:
    • Crea una clase Payload en el paquete com.malware.core.
      • Campos: protected String payloadType; y protected int executionPriority;.
      • Constructor: Un constructor que acepte payloadType y executionPriority para inicializar los campos.
      • Método: Un método public void displayInfo() que imprima el tipo y la prioridad del payload.
      • Método (para sobrescribir): Un método public void executeAction() que imprima "Ejecutando acción genérica del payload."
    • Crea una clase CommandPayload en el mismo paquete que extienda de Payload.
      • Campo Adicional: private String commandToExecute;.
      • Constructor: Un constructor que acepte payloadTypeexecutionPriority, y commandToExecute. Debe llamar al constructor de la superclase (Payload) usando super() para inicializar payloadType y executionPriority, y luego inicializar commandToExecute.
      • Sobrescritura (@Override): Sobrescribe el método executeAction(). La nueva implementación debe primero llamar a super.executeAction() y luego imprimir "Ejecutando comando específico: " seguido del valor de commandToExecute.
      • Método Adicional: Un getter public String getCommandToExecute().
    • En tu main, crea un objeto de Payload y un objeto de CommandPayload. Llama a displayInfo() y executeAction() en ambos.
    • Relevancia en Malware: Permite definir un comportamiento base para diferentes tipos de "cargas útiles" o acciones, y luego especializarlas (ej. StealDataPayloadRansomEncryptPayload podrían heredar de Payload).

B.2.c. Polimorfismo

  1. Manejo Polimórfico de Payloads:

    • Usando las clases Payload y CommandPayload del ejercicio anterior:
    • En tu main, crea un array o ArrayList de tipo PayloadPayload[] payloads = new Payload[2];
    • Asigna al primer elemento un nuevo objeto Payload (ej., new Payload("INFO", 1)) y al segundo elemento un nuevo objeto CommandPayload (ej., new CommandPayload("CMD_EXEC", 10, "cat /etc/passwd")).
    • Itera sobre el array usando un bucle for-each. Dentro del bucle, para cada payload en payloads:
      • Llama a payload.displayInfo().
      • Llama a payload.executeAction(). Observa cómo se ejecuta el método executeAction correcto para cada objeto (el de Payload para el primer objeto, y el sobrescrito en CommandPayload para el segundo). Este es el polimorfismo en acción.
  2. instanceof y Casting:

    • Dentro del mismo bucle del ejercicio anterior, después de llamar a executeAction():
      • Usa if (payload instanceof CommandPayload) para verificar si el objeto actual es una instancia de CommandPayload.
      • Si es verdadero, haz un downcast del objeto payload a CommandPayloadCommandPayload cmdPayload = (CommandPayload) payload;
      • Luego, llama al método específico de CommandPayloadSystem.out.println("Comando a ejecutar (obtenido por casting): " + cmdPayload.getCommandToExecute());
    • Relevancia en Malware: El polimorfismo permite que el malware maneje diferentes tipos de tareas o módulos de forma genérica. Por ejemplo, un "despachador" de tareas podría tener una lista de objetos Task (superclase) y llamar a un método run() en cada uno, donde cada subclase de Task implementa run() de manera diferente. instanceof podría usarse para tareas que requieren un manejo especial.

C.1. Framework de Colecciones de Java (JCF)

  1. Listas: ArrayList y LinkedList (Gestión de Objetivos y Comandos)

    • ArrayList para Objetivos:
      • Crea un ArrayList<String> llamado targetFiles para almacenar nombres de archivos que el malware podría buscar (ej., "passwords.txt", "credentials.db", "photos/DCIM_001.jpg").
      • Añade varios nombres de archivo.
      • Imprime el número de archivos objetivo.
      • Obtén y muestra el primer y último archivo de la lista.
      • Elimina un archivo específico por su nombre (primero encuéntralo si es necesario, o por índice si lo conoces).
      • Verifica si un archivo "secret_key.dat" está en la lista.
    • LinkedList para Cola de Comandos C2:
      • Crea una LinkedList<String> llamada c2CommandQueue para simular una cola de comandos recibidos de un servidor C&C.
      • Añade (usando offer o addLast) los siguientes comandos: "UPLOAD_CONTACTS", "GET_LOCATION", "RECORD_AUDIO_5MIN".
      • Procesa los comandos: en un bucle, mientras la cola no esté vacía, obtén y elimina el primer comando (usando poll o pollFirst) e imprime "Procesando comando: [comando]".
  2. Sets: HashSet y LinkedHashSet (IDs Únicos y Acciones Ordenadas)

    • HashSet para IDs Únicos:
      • Crea un HashSet<String> llamado infectedDeviceRegistry para almacenar IDs de dispositivos ya "infectados" o procesados.
      • Añade varios IDs, incluyendo algunos duplicados (ej., "device_alpha", "device_beta", "device_alpha").
      • Imprime el conjunto de IDs (observa que los duplicados no se añaden).
      • Verifica si "device_gamma" ya ha sido registrado usando contains().
    • LinkedHashSet para Pasos de un Ataque:
      • Crea un LinkedHashSet<String> llamado attackSteps para almacenar una secuencia de pasos de un ataque, donde el orden de inserción es importante y no debe haber pasos duplicados.
      • Añade: "1. Reconocimiento", "2. Acceso Inicial", "3. Escalada de Privilegios", "2. Acceso Inicial" (de nuevo), "4. Exfiltración".
      • Itera sobre attackSteps e imprime cada paso, observando que se mantiene el orden de la primera inserción y no hay duplicados.
  3. Maps: HashMap y LinkedHashMap (Configuración y Datos Robados)

    • HashMap para Configuración de Malware:
      • Crea un HashMap<String, String> llamado malwareConfig.
      • Inserta los siguientes pares clave-valor: ("c2_url", "http://evilhost.example/api"), ("retry_interval_seconds", "60"), ("debug_mode", "false").
      • Obtén e imprime la URL del C2.
      • Verifica si la configuración contiene una clave "proxy_enabled".
      • Elimina la entrada "debug_mode".
      • Itera sobre entrySet() del mapa e imprime cada par clave-valor.
    • LinkedHashMap para Credenciales Ordenadas:
      • Crea un LinkedHashMap<String, String> llamado stolenCredentials para almacenar pares servicio/credencial (ej., "servicio_web" -> "usuario:contraseña").
      • Añade varias credenciales.
      • Itera sobre el mapa e imprime las credenciales. Observa que se mantiene el orden en que las añadiste (lo cual podría ser útil para un log de exfiltración ordenado).
  4. Iteradores (Recorriendo y Modificando Colecciones)

    • Toma el ArrayList<String> targetFiles del ejercicio 1.
    • Obtén un Iterator<String> para esta lista.
    • Usa el iterador para recorrer la lista. Imprime cada nombre de archivo.
    • Durante la iteración, si un nombre de archivo contiene la palabra "credentials", elimínalo de la lista usando iterator.remove().
    • Después del bucle, imprime la lista targetFiles para ver los cambios.
    • Opcional: Si usaste ArrayList, obtén un ListIterator y prueba a recorrerla hacia atrás.
  5. Clase Collections (Ordenando y Sincronizando)

    • Crea un ArrayList<String> llamado payloadModules con nombres de módulos de payload (ej., "DataStealer", "KeyLogger", "AudioRecorder", "NetworkSniffer").
    • Usa Collections.shuffle(payloadModules) para barajarlos aleatoriamente e imprime la lista.
    • Usa Collections.sort(payloadModules) para ordenarlos alfabéticamente e imprime la lista.
    • Imprime el número de veces que "KeyLogger" aparece en la lista usando Collections.frequency().
    • Crea una versión "inmodificable" de esta lista usando Collections.unmodifiableList() y intenta añadir un nuevo elemento (debería lanzar UnsupportedOperationException, puedes envolverlo en un try-catch para demostrarlo).
    • Conceptual: Describe brevemente por qué un malware podría necesitar Collections.synchronizedList() si tuviera múltiples hilos accediendo a una lista de objetivos.

C.2. Manejo de Excepciones

  1. try-catch-finally (Manejo de Archivos y Recursos)

    • Escribe un método void attemptToReadConfig(String filePath) que intente crear un FileInputStream para el filePath dado.
    • Dentro de un bloque try, intenta leer el primer byte (simplemente fis.read();).
    • Añade un bloque catch para FileNotFoundException que imprima "Error: Archivo de configuración '[filePath]' no encontrado."
    • Añade otro bloque catch para IOException genérico que imprima "Error de E/S al leer '[filePath]'."
    • Añade un bloque finally que siempre imprima "Intento de lectura de configuración finalizado para '[filePath]'." y que intente cerrar el FileInputStream (si no es null), manejando también una posible IOException al cerrar dentro del finally.
    • Prueba tu método con un nombre de archivo que exista (puedes crear un archivo vacío para probar) y con uno que no exista.
  2. try-with-resources (Simplificando el Manejo de Recursos)

    • Reescribe el método attemptToReadConfig del ejercicio anterior usando la sentencia try-with-resources para el FileInputStream.
    • Observa cómo ya no necesitas el bloque finally explícito para cerrar el stream (aunque aún puedes tener un finally para otras acciones si es necesario). Los bloques catch seguirían siendo iguales.
  3. throw y throws (Lanzando y Declarando Excepciones)

    • Crea una clase PayloadDeployer.
    • Dentro, escribe un método void deployPayload(String payloadName, String targetSystemInfo) throws DeploymentException.
    • Dentro de este método:
      • Si payloadName es null o está vacío, lanza throw new IllegalArgumentException("El nombre del payload no puede ser nulo o vacío.");.
      • Si targetSystemInfo contiene la subcadena "honeypot" (simulando detección de un entorno de análisis), lanza throw new DeploymentException("Despliegue abortado: Posible entorno de análisis detectado.");.
      • Si no, imprime "Desplegando [payloadName] en [targetSystemInfo]".
    • Define la excepción personalizada class DeploymentException extends Exception { public DeploymentException(String message) { super(message); } }.
    • En tu main, llama a deployPayload varias veces con diferentes entradas, manejando IllegalArgumentException y DeploymentException en un bloque try-catch.
  4. Excepciones Personalizadas y Robustez del Malware

    • Define una excepción no verificada class C2ConnectionTimeoutException extends RuntimeException { public C2ConnectionTimeoutException(String message, Throwable cause) { super(message, cause); } }.
    • Crea un método String fetchDataFromC2(String command) que simule una comunicación con el C2.
      • Dentro de un try block, simula una operación de red que podría tardar mucho o fallar (puedes usar Thread.sleep() y luego simular un error).
      • Si ocurre un error "de red" (simulado), captura la excepción original (ej. una IOException simulada) y lanza throw new C2ConnectionTimeoutException("Timeout al intentar ejecutar comando: " + command, originalException);.
      • Si tiene éxito, devuelve "Datos para: " + command.
    • En el código que llama a WorkspaceDataFromC2, usa un try-catch para C2ConnectionTimeoutException. En el catch, el "malware" no debe crashear, sino registrar internamente el error (imprimir un mensaje como "LOG INTERNO: Fallo C2: [mensaje de excepción], causa: [causa]") y quizás intentar una acción alternativa o continuar.
    • Discusión: ¿Por qué un malware preferiría usar excepciones no verificadas para ciertos fallos internos y cómo el manejo de excepciones contribuye a su sigilo operacional?
  5. Bloque catch (Throwable t) y Evasión (Conceptual)

    • Escribe un pequeño fragmento de código que simule el bucle principal de una tarea de malware (ej. un while(true) que intenta realizar una "acción maliciosa").
    • Envuelve la "acción maliciosa" (que podría ser una llamada a un método que a veces falla con diferentes excepciones) dentro de un try-catch (Throwable t).
    • Dentro del bloque catch (Throwable t):
      • No imprimas t.printStackTrace().
      • Opcionalmente, haz un log muy discreto o simplemente pon un comentario // Error ignorado para continuar operación.
    • Pregunta para reflexionar: ¿Cuáles son las implicaciones (tanto para la funcionalidad del malware como para un analista) de usar un bloque catch (Throwable t) tan genérico y silencioso? ¿Cómo podría esto ayudar al malware a evadir una detección simple o un análisis superficial?

D.1. Reflexión de Java (Java Reflection)

  1. Obteniendo Objetos Class:

    • Escribe un fragmento de código en Java que obtenga el objeto Class para la clase java.util.ArrayList de las siguientes tres maneras:
      1. Usando el literal de clase: ArrayList.class.
      2. Creando una instancia de ArrayList y usando obj.getClass().
      3. Usando Class.forName("java.util.ArrayList").
    • Para la tercera opción, asegúrate de manejar la ClassNotFoundException con un bloque try-catch. Imprime el nombre de la clase obtenido de cada objeto Class para verificar.
  2. Inspeccionando una Clase "Secreta":

    • Crea una clase simple llamada AgentProfile con los siguientes miembros:
      package com.example.secrets;
      
      public class AgentProfile {
          private String realName = "John Doe";
          private int agentId = 7;
          public String coverName = "Operator X";
      
          private AgentProfile(String realName, int agentId) {
              this.realName = realName;
              this.agentId = agentId;
          }
      
          public AgentProfile(String coverName) {
              this.coverName = coverName;
          }
      
          private void revealSecretMission() {
              System.out.println("Mission: Infiltrate target system. Real name: " + realName);
          }
      
          public void reportStatus() {
              System.out.println("Agent " + coverName + " (ID: " + agentId + ") reporting: All clear.");
          }
      }
    • En otra clase (tu clase de prueba), obtén el objeto Class para AgentProfile.
    • Usa reflexión para:
      • Listar e imprimir los nombres de todos los campos declarados (getDeclaredFields()) en AgentProfile, incluyendo los privados.
      • Listar e imprimir los nombres de todos los métodos declarados (getDeclaredMethods()) en AgentProfile, incluyendo los privados y constructores.
      • Listar e imprimir solo los constructores públicos (getConstructors()).
  3. Accediendo y Modificando Miembros Privados con Reflexión:

    • Continuando con la clase AgentProfile del ejercicio anterior:
    • Instanciación con Constructor Privado:
      • Obtén el constructor privado de AgentProfile que toma String y int como parámetros (getDeclaredConstructor(String.class, int.class)).
      • Usa constructor.setAccessible(true) para hacerlo accesible.
      • Crea una instancia de AgentProfile usando constructor.newInstance("Jane Smith", 8).
    • Acceso a Campos Privados:
      • Obtén el campo privado realName de la instancia que acabas de crear.
      • Usa field.setAccessible(true).
      • Lee e imprime su valor usando field.get(instance).
      • Cambia el valor del campo realName a "Alex Rider" usando field.set(instance, "Alex Rider").
    • Invocación de Métodos Privados:
      • Obtén el método privado revealSecretMission().
      • Usa method.setAccessible(true).
      • Invoca el método en la instancia usando method.invoke(instance). Observa la salida.
    • Maneja las excepciones que puedan surgir (NoSuchFieldExceptionNoSuchMethodExceptionIllegalAccessExceptionInvocationTargetExceptionInstantiationException).
  4. Ofuscación de Llamadas a APIs (Simulación):

    • Imagina que quieres invocar el método System.getProperty("java.version") pero usando reflexión para "ocultar" las cadenas "java.lang.System" y "getProperty".
    • Construye estas cadenas (puedes hacerlo directamente por ahora, pero piensa cómo un malware podría obtenerlas de forma ofuscada, ej. decodificando desde Base64, concatenando partes, etc.).
    • Usa Class.forName() para obtener la clase System.
    • Usa clazz.getMethod() para obtener el método getProperty que toma un String como parámetro.
    • Invoca el método (method.invoke(null, "java.version") - null porque es un método estático) e imprime la versión de Java.
    • Pregunta de Reflexión: ¿Por qué esta técnica dificulta el análisis estático del código malicioso? ¿Qué buscaría un analista en el código si sospecha de este tipo de ofuscación?

D.2. Serialización de Java

  1. Creando una Clase Serializable para Datos de Malware:

    • Define una clase C2Packet que implemente java.io.Serializable.
    • Añade los siguientes campos:
      • private static final long serialVersionUID = 12345L;
      • String deviceId;
      • String commandType; // ej. "CONFIG_UPDATE", "DATA_EXFIL"
      • java.util.Map<String, String> dataPayload; // Para enviar datos variados
      • transient String networkSessionToken; // No debe ser serializado
    • Añade un constructor para inicializar estos campos y un método toString() para mostrar su contenido.
  2. Serializando y Deserializando un C2Packet:

    • En tu método main:
      • Crea una instancia de C2Packet. Ponle algunos datos de ejemplo, incluyendo un networkSessionToken y un HashMap para dataPayload.
      • Imprime el objeto original.
      • Serializa el objeto a un archivo llamado "c2_packet.dat" usando ObjectOutputStream y FileOutputStream.
      • Deserializa el objeto desde "c2_packet.dat" a una nueva instancia de C2Packet usando ObjectInputStream y FileInputStream.
      • Imprime el objeto deserializado. Observa y explica qué valor tiene el campo networkSessionToken después de la deserialización y por qué.
    • Asegúrate de manejar IOException y ClassNotFoundException.
  3. Control Personalizado (Simulación writeObject/readObject):

    • Modifica tu clase C2Packet.
    • Añade un método private void writeObject(java.io.ObjectOutputStream out) throws IOException. Dentro de este método:
      • Imprime "LOG: Cifrando dataPayload antes de serializar..."
      • Simula el cifrado: Modifica el dataPayload de alguna manera simple (ej., añade un prefijo "encrypted_" a cada valor en el mapa) ANTES de llamar a out.defaultWriteObject(); (que serializa los campos no transitorios y no estáticos normalmente).
      • Después de defaultWriteObject(), puedes revertir el cambio si es solo una simulación para el objeto en memoria.
    • Añade un método private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException. Dentro de este método:
      • Llama a in.defaultReadObject(); PRIMERO.
      • Imprime "LOG: Descifrando dataPayload después de deserializar..."
      • Simula el descifrado: Revierte la modificación que hiciste en writeObject (ej., quita el prefijo "encrypted_").
    • Vuelve a ejecutar el ejercicio 2 para ver los mensajes de log y el efecto de tus métodos personalizados. (Nota: para un cifrado real, los datos se cifrarían y escribirían byte a byte, o se cifraría el Map y se escribiría con out.writeObject()).
  4. Discusión sobre Deserialización Insegura:

    • Basándote en el texto, explica con tus propias palabras:
      • ¿Qué es una vulnerabilidad de "deserialización insegura"?
      • ¿Cómo podría un malware explotar esta vulnerabilidad en otra aplicación instalada en el dispositivo Android?
      • ¿Qué son los "gadgets" en el contexto de estos ataques?
      • ¿Por qué la deserialización de java.io.ObjectInputStream es considerada inherentemente peligrosa si los datos provienen de una fuente no confiable?

D.3. Interfaz Nativa de Java (JNI)

Dado que configurar un entorno NDK completo está fuera del alcance de estos ejercicios de Java puro, nos centraremos en la comprensión de los conceptos del lado de Java y la interacción.

  1. Declaración de Métodos Nativos en Java:

    • Crea una clase Java llamada MalwareNativeInterface en un paquete de tu elección (ej. com.evilcorp.bridge).
    • Dentro de esta clase, declara los siguientes métodos nativos:
      • public native boolean checkRootStatus();
      • private native String getHiddenData(String key); (nota que es privado)
      • public static native int executeShellCommand(String command);
    • Añade un bloque estático para cargar una biblioteca nativa llamada "droid_ops_lib".
      static {
          try {
              System.loadLibrary("droid_ops_lib"); // Nombre de la lib sin "lib" ni ".so"
          } catch (UnsatisfiedLinkError e) {
              System.err.println("FALLO CRÍTICO: No se pudo cargar la biblioteca nativa droid_ops_lib. " + e.getMessage());
              // El malware podría tener una lógica de fallback o terminar sigilosamente aquí.
          }
      }
    • En tu main, crea una instancia de MalwareNativeInterface e intenta llamar a checkRootStatus() (solo para ver si compila y se ejecuta la parte Java; lanzará UnsatisfiedLinkError si la biblioteca no existe, lo cual es esperado sin el NDK).
  2. Convención de Nombres de Funciones JNI:

    • Para los tres métodos nativos que declaraste en MalwareNativeInterface en el paquete com.evilcorp.bridge en el ejercicio anterior:
      • checkRootStatus()
      • getHiddenData(String key)
      • executeShellCommand(String command)
    • Escribe los nombres exactos de las funciones C/C++ que la ART (Android Runtime) esperaría encontrar en la biblioteca nativa "droid_ops_lib.so".
  3. Interacción JNIEnv* y Tipos de Datos (Conceptual):

    • Imagina que estás implementando la función nativa C/C++ para private native String getHiddenData(String key);.
    • El parámetro key llega a tu función C++ como un jstring.
    • Tu lógica nativa produce un resultado que es un const char* secret_value.
    • Describe conceptualmente (no necesitas código C++ exacto) qué funciones del puntero JNIEnv* necesitarías usar para:
      1. Convertir el jstring key en un const char* utilizable en C++.
      2. Liberar cualquier recurso asociado a esa conversión una vez que ya no necesites el const char* key_c.
      3. Convertir tu const char* secret_value resultante de C++ de nuevo a un jstring para devolverlo a Java.