Lista de ejercicios para practicar
A.1. Tipos de Datos y Variables
Calculadora de Almacenamiento de Datos Primitivos:
- Declara variables para cada uno de los tipos de datos primitivos enteros (
byte
,short
,int
,long
) 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 (
float
,double
). - 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 unint
de Java? ¿Qué pasa si intentas asignar un valor muy grande a unbyte
en Java?
- Declara variables para cada uno de los tipos de datos primitivos enteros (
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?
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 unint
. - Lee este valor como un
int
. Luego, usa unlong
para almacenar su representación "sin signo" correcta (realiza un casting y una máscara con0xFFFFFFFFL
si es necesario). Imprime ambos valores. - Reflexión: ¿Por qué es importante
0xFFFFFFFFL
(con laL
) en la máscara?
- Imagina que recibes un valor de 32 bits de una fuente externa que conceptualmente es un "unsigned int" (p.ej.,
A.2. Operadores
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.
- Declara dos variables enteras y dos
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
usandonew 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;
). Comparas3 == s5
. - Imprime todos los resultados y explica por qué son diferentes.
- Reflexión: ¿Cómo podría un malware usar incorrectamente
==
conStrings
para evadir una detección o tomar una decisión incorrecta?
- Crea dos objetos
Lógica de Cortocircuito:
- Escribe una expresión
if
que use&&
donde la segunda condición podría causar unNullPointerException
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 estrue
.
- Escribe una expresión
Manipulación de Bits para Ofuscación Simple:
- Toma un
char
(p.ej., 'A'). Conviértelo aint
. - 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?
- Toma un
Verificación de Tipo con
instanceof
:- Crea un
Object
que sea en realidad unString
(p.ej.,Object obj = "Soy una cadena";
). - Usa
instanceof
para verificar siobj
es una instancia deString
, deInteger
, y deObject
. Imprime los resultados. - Si es un
String
, haz un casting seguro aString
y llama a algún método deString
(p.ej.,length()
).
- Crea un
A.3. Declaraciones de Flujo de Control
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 casodefault
para otros valores. Intenta usarString
en elswitch
(si tu versión de Java lo permite y es relevante para el texto).
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.
- Calcula el factorial de un número usando un bucle
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
oswitch
para simular la ejecución de cada comando. - Si encuentras el comando
"STOP"
, usabreak
para salir del bucle. - Si encuentras un comando
"SKIP_NEXT_IF_PAUSE"
y el siguiente es"PAUSE"
, usacontinue
para saltar el procesamiento de"PAUSE"
(esto es un poco más avanzado, podrías necesitar unfor
clásico para mirar adelante o una bandera). - Reflexión: ¿Cómo puede
break
ycontinue
(especialmente etiquetados) ser usados para crear flujos de control complejos o difíciles de seguir?
- Crea un array de
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
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 untry-catch
si ya lo conoces, o simplemente dejar que ocurra para ver el mensaje).
- Declara un array de
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()
omatches()
con una expresión regular simple). - Obtener una subcadena (p.ej., los primeros 4 caracteres).
- Reemplazar "User" por "Admin" (si existe).
- Eliminar espacios al inicio y al final (
- Imprime el resultado de cada transformación.
- Reflexión: ¿Por qué la inmutabilidad de
String
es importante aquí? ¿Qué pasa si concatenasString
s repetidamente en un bucle?
- Crea una
Constructor de Cadenas Eficiente:
- Usa un bucle para construir una cadena concatenando números del 0 al 9, primero usando el operador
+
conString
. - Luego, haz lo mismo usando
StringBuilder
y su métodoappend()
. - Aunque no medirás el rendimiento directamente en un ejercicio básico, reflexiona sobre por qué
StringBuilder
es más eficiente.
- Usa un bucle para construir una cadena concatenando números del 0 al 9, primero usando el operador
Codificador/Decodificador XOR Simple para Cadenas:
- Toma una
String
(p.ej., "HolaSecreto"). - Conviértela a un
byte[]
(p.ej., usandogetBytes("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 delbyte[]
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 unaString
e imprímela. - Reflexión: ¿Qué problemas pueden surgir al convertir
byte[]
arbitrarios directamente aString
sin una codificación adecuada como Base64? ¿Por qué es crucial especificar unCharset
como "UTF-8"?
- Toma una
B.1. Clases, Objetos, Métodos y Constructores
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 pongaisActive
atrue
. - Un método
void deactivate()
que pongaisActive
afalse
.
- Un método
- Relevancia en Malware: Esta clase podría representar un módulo genérico dentro de una pieza de malware.
- Crea una clase llamada
Objetos y
this
:- En tu método
main
(o en una clase de prueba), crea dos objetos (instancias) de la claseMalwareComponent
.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()
enMalwareComponent
para usarthis
explícitamente al referirse a los campos (ej.this.componentName
). - Llama al método
activate()
paracomponenteRed
ydisplayStatus()
para ambos objetos para ver sus estados.
- En tu método
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, yisActive
afalse
. - Constructor parametrizado: Añade un constructor que acepte
String name
yint ver
como parámetros. Este constructor debe inicializarcomponentName
yversion
con los valores recibidos, eisActive
afalse
. - Constructor sobrecargado con
this()
: Añade un tercer constructor que solo acepteString name
. Este constructor debe llamar al constructor parametrizado (el que tomaname
yver
) usandothis(name, 1);
, asignando una versión por defecto de 1. - En tu
main
, crea objetos deMalwareComponent
usando los diferentes constructores que has definido y llama adisplayStatus()
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).
- 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
B.2.a. Encapsulación y Paquetes
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 (
componentName
,version
,isActive
) aprivate
. - Crea métodos
public
getters para cada campo (ej.getComponentName()
,getVersion()
,isActive()
oisIsActive()
). - Crea métodos
public
setters paracomponentName
yversion
(ej.setComponentName(String name)
). En elsetVersion(int ver)
, añade una pequeña validación: si la versión es menor que 1, se debe establecer a 1.
- Cambia los modificadores de acceso de todos sus campos (
- 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 objetosMalwareComponent
.
- Modifica la clase
Paquetes y Visibilidad
default
(package-private):- Crea una estructura de paquetes:
com.malware.core
com.malware.utils
- Mueve tu clase
MalwareComponent
al paquetecom.malware.core
. Asegúrate de que la claseMalwareComponent
y sus métodos públicos (getters/setters) sigan siendopublic
. - Dentro de
MalwareComponent
(encom.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 paquetecom.malware.core
. Dentro deCoreManager
, crea un objetoMalwareComponent
e intenta acceder ainternalId
y llamar aassignInternalId()
. Debería funcionar. - Prueba 2 (Diferente Paquete): Crea una clase
ExternalTool
en el paquetecom.malware.utils
. Dentro deExternalTool
, importacom.malware.core.MalwareComponent
. Intenta crear un objetoMalwareComponent
e intenta acceder ainternalId
oassignInternalId()
. 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).
- Crea una estructura de paquetes:
B.2.b. Herencia
- Clase Base
Payload
y SubclaseCommandPayload
:- Crea una clase
Payload
en el paquetecom.malware.core
.- Campos:
protected String payloadType;
yprotected int executionPriority;
. - Constructor: Un constructor que acepte
payloadType
yexecutionPriority
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."
- Campos:
- Crea una clase
CommandPayload
en el mismo paquete que extienda dePayload
.- Campo Adicional:
private String commandToExecute;
. - Constructor: Un constructor que acepte
payloadType
,executionPriority
, ycommandToExecute
. Debe llamar al constructor de la superclase (Payload
) usandosuper()
para inicializarpayloadType
yexecutionPriority
, y luego inicializarcommandToExecute
. - Sobrescritura (
@Override
): Sobrescribe el métodoexecuteAction()
. La nueva implementación debe primero llamar asuper.executeAction()
y luego imprimir "Ejecutando comando específico: " seguido del valor decommandToExecute
. - Método Adicional: Un getter
public String getCommandToExecute()
.
- Campo Adicional:
- En tu
main
, crea un objeto dePayload
y un objeto deCommandPayload
. Llama adisplayInfo()
yexecuteAction()
en ambos. - Relevancia en Malware: Permite definir un comportamiento base para diferentes tipos de "cargas útiles" o acciones, y luego especializarlas (ej.
StealDataPayload
,RansomEncryptPayload
podrían heredar dePayload
).
- Crea una clase
B.2.c. Polimorfismo
Manejo Polimórfico de Payloads:
- Usando las clases
Payload
yCommandPayload
del ejercicio anterior: - En tu
main
, crea un array oArrayList
de tipoPayload
:Payload[] payloads = new Payload[2];
- Asigna al primer elemento un nuevo objeto
Payload
(ej.,new Payload("INFO", 1)
) y al segundo elemento un nuevo objetoCommandPayload
(ej.,new CommandPayload("CMD_EXEC", 10, "cat /etc/passwd")
). - Itera sobre el array usando un bucle
for-each
. Dentro del bucle, para cadapayload
enpayloads
:- Llama a
payload.displayInfo()
. - Llama a
payload.executeAction()
. Observa cómo se ejecuta el métodoexecuteAction
correcto para cada objeto (el dePayload
para el primer objeto, y el sobrescrito enCommandPayload
para el segundo). Este es el polimorfismo en acción.
- Llama a
- Usando las clases
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 deCommandPayload
. - Si es verdadero, haz un downcast del objeto
payload
aCommandPayload
:CommandPayload cmdPayload = (CommandPayload) payload;
- Luego, llama al método específico de
CommandPayload
:System.out.println("Comando a ejecutar (obtenido por casting): " + cmdPayload.getCommandToExecute());
- Usa
- 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étodorun()
en cada uno, donde cada subclase deTask
implementarun()
de manera diferente.instanceof
podría usarse para tareas que requieren un manejo especial.
- Dentro del mismo bucle del ejercicio anterior, después de llamar a
C.1. Framework de Colecciones de Java (JCF)
Listas:
ArrayList
yLinkedList
(Gestión de Objetivos y Comandos)ArrayList
para Objetivos:- Crea un
ArrayList<String>
llamadotargetFiles
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.
- Crea un
LinkedList
para Cola de Comandos C2:- Crea una
LinkedList<String>
llamadac2CommandQueue
para simular una cola de comandos recibidos de un servidor C&C. - Añade (usando
offer
oaddLast
) 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
opollFirst
) e imprime "Procesando comando: [comando]".
- Crea una
Sets:
HashSet
yLinkedHashSet
(IDs Únicos y Acciones Ordenadas)HashSet
para IDs Únicos:- Crea un
HashSet<String>
llamadoinfectedDeviceRegistry
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()
.
- Crea un
LinkedHashSet
para Pasos de un Ataque:- Crea un
LinkedHashSet<String>
llamadoattackSteps
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.
- Crea un
Maps:
HashMap
yLinkedHashMap
(Configuración y Datos Robados)HashMap
para Configuración de Malware:- Crea un
HashMap<String, String>
llamadomalwareConfig
. - 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.
- Crea un
LinkedHashMap
para Credenciales Ordenadas:- Crea un
LinkedHashMap<String, String>
llamadostolenCredentials
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).
- Crea un
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 unListIterator
y prueba a recorrerla hacia atrás.
- Toma el
Clase
Collections
(Ordenando y Sincronizando)- Crea un
ArrayList<String>
llamadopayloadModules
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 lanzarUnsupportedOperationException
, puedes envolverlo en untry-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.
- Crea un
C.2. Manejo de Excepciones
try-catch-finally
(Manejo de Archivos y Recursos)- Escribe un método
void attemptToReadConfig(String filePath)
que intente crear unFileInputStream
para elfilePath
dado. - Dentro de un bloque
try
, intenta leer el primer byte (simplementefis.read();
). - Añade un bloque
catch
paraFileNotFoundException
que imprima "Error: Archivo de configuración '[filePath]' no encontrado." - Añade otro bloque
catch
paraIOException
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 elFileInputStream
(si no esnull
), manejando también una posibleIOException
al cerrar dentro delfinally
. - 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.
- Escribe un método
try-with-resources
(Simplificando el Manejo de Recursos)- Reescribe el método
attemptToReadConfig
del ejercicio anterior usando la sentenciatry-with-resources
para elFileInputStream
. - Observa cómo ya no necesitas el bloque
finally
explícito para cerrar el stream (aunque aún puedes tener unfinally
para otras acciones si es necesario). Los bloquescatch
seguirían siendo iguales.
- Reescribe el método
throw
ythrows
(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
esnull
o está vacío, lanzathrow 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), lanzathrow new DeploymentException("Despliegue abortado: Posible entorno de análisis detectado.");
. - Si no, imprime "Desplegando [payloadName] en [targetSystemInfo]".
- Si
- Define la excepción personalizada
class DeploymentException extends Exception { public DeploymentException(String message) { super(message); } }
. - En tu
main
, llama adeployPayload
varias veces con diferentes entradas, manejandoIllegalArgumentException
yDeploymentException
en un bloquetry-catch
.
- Crea una clase
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 usarThread.sleep()
y luego simular un error). - Si ocurre un error "de red" (simulado), captura la excepción original (ej. una
IOException
simulada) y lanzathrow new C2ConnectionTimeoutException("Timeout al intentar ejecutar comando: " + command, originalException);
. - Si tiene éxito, devuelve "Datos para: " + command.
- Dentro de un
- En el código que llama a
WorkspaceDataFromC2
, usa untry-catch
paraC2ConnectionTimeoutException
. En elcatch
, 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?
- Define una excepción no verificada
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
.
- No imprimas
- 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?
- Escribe un pequeño fragmento de código que simule el bucle principal de una tarea de malware (ej. un
D.1. Reflexión de Java (Java Reflection)
Obteniendo Objetos
Class
:- Escribe un fragmento de código en Java que obtenga el objeto
Class
para la clasejava.util.ArrayList
de las siguientes tres maneras:- Usando el literal de clase:
ArrayList.class
. - Creando una instancia de
ArrayList
y usandoobj.getClass()
. - Usando
Class.forName("java.util.ArrayList")
.
- Usando el literal de clase:
- Para la tercera opción, asegúrate de manejar la
ClassNotFoundException
con un bloquetry-catch
. Imprime el nombre de la clase obtenido de cada objetoClass
para verificar.
- Escribe un fragmento de código en Java que obtenga el objeto
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
paraAgentProfile
. - Usa reflexión para:
- Listar e imprimir los nombres de todos los campos declarados (
getDeclaredFields()
) enAgentProfile
, incluyendo los privados. - Listar e imprimir los nombres de todos los métodos declarados (
getDeclaredMethods()
) enAgentProfile
, incluyendo los privados y constructores. - Listar e imprimir solo los constructores públicos (
getConstructors()
).
- Listar e imprimir los nombres de todos los campos declarados (
- Crea una clase simple llamada
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 tomaString
yint
como parámetros (getDeclaredConstructor(String.class, int.class)
). - Usa
constructor.setAccessible(true)
para hacerlo accesible. - Crea una instancia de
AgentProfile
usandoconstructor.newInstance("Jane Smith", 8)
.
- Obtén el constructor privado de
- 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" usandofield.set(instance, "Alex Rider")
.
- Obtén el campo privado
- 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.
- Obtén el método privado
- Maneja las excepciones que puedan surgir (
NoSuchFieldException
,NoSuchMethodException
,IllegalAccessException
,InvocationTargetException
,InstantiationException
).
- Continuando con la clase
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 claseSystem
. - Usa
clazz.getMethod()
para obtener el métodogetProperty
que toma unString
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?
- Imagina que quieres invocar el método
D.2. Serialización de Java
Creando una Clase
Serializable
para Datos de Malware:- Define una clase
C2Packet
que implementejava.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.
- Define una clase
Serializando y Deserializando un
C2Packet
:- En tu método
main
:- Crea una instancia de
C2Packet
. Ponle algunos datos de ejemplo, incluyendo unnetworkSessionToken
y unHashMap
paradataPayload
. - Imprime el objeto original.
- Serializa el objeto a un archivo llamado "c2_packet.dat" usando
ObjectOutputStream
yFileOutputStream
. - Deserializa el objeto desde "c2_packet.dat" a una nueva instancia de
C2Packet
usandoObjectInputStream
yFileInputStream
. - Imprime el objeto deserializado. Observa y explica qué valor tiene el campo
networkSessionToken
después de la deserialización y por qué.
- Crea una instancia de
- Asegúrate de manejar
IOException
yClassNotFoundException
.
- En tu método
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 aout.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_").
- Llama a
- 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 conout.writeObject()
).
- Modifica tu clase
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?
- Basándote en el texto, explica con tus propias palabras:
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.
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 deMalwareNativeInterface
e intenta llamar acheckRootStatus()
(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).
- Crea una clase Java llamada
Convención de Nombres de Funciones JNI:
- Para los tres métodos nativos que declaraste en
MalwareNativeInterface
en el paquetecom.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".
- Para los tres métodos nativos que declaraste en
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 unjstring
. - 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:- Convertir el
jstring key
en unconst char*
utilizable en C++. - Liberar cualquier recurso asociado a esa conversión una vez que ya no necesites el
const char* key_c
. - Convertir tu
const char* secret_value
resultante de C++ de nuevo a unjstring
para devolverlo a Java.
- Convertir el
- Imagina que estás implementando la función nativa C/C++ para