Una de las debilidades de Java (y otros lenguajes que compilan a bytecode) es que se puede decompilar o descompilar, como le quieras llamar. O decompile, si hablas inglés.
Hoy vamos a ver un decompilador de aplicaciones de android, es decir, de apps con extensiones APK.
Estuve probando el decompilador y funciona de maravilla, obtiene casi el mismo código que el original.
Para hacer esta demostración y no hacer nada ilegal, voy a usar el CRUD de SQLite con Android cuyo código fuente está aquí, y su APK está aquí.
Descargo de responsabilidad
Ya deberías saber que esto debe tener algo de ilegal, así que no lo hagas en casa. Si lo haces, es bajo tu propia responsabilidad, no me hago cargo de nada.
Se supone que estos decompiladores son útiles cuando existe una app maliciosa y se quiere analizar su comportamiento.
Obtener código fuente original de APK
El decompilador del que hablo es deguard cuyo sitio está aquí. En el sitio dice:
DeGuard invierte el proceso de ofuscación realizado por las herramientas de ofuscación de Android. Esto permite numerosos análisis de seguridad, incluyendo inspección de código y predicción de bibliotecas.
Para comenzar hay que seleccionar un archivo en donde dice “Select APK File” y luego hacer click en Upload.
Cuando la app que vamos a decompilar se haya subido, comenzará el proceso de decompilación en el servidor.
Al terminar se mostrará una interfaz amigable. A la izquierda estará un árbol de directorios, el cual contiene los archivos del código fuente.
A la derecha aparece el código fuente del archivo seleccionado.
Los archivos que deberían importarnos son aquellos que tienen el id de quien lo creó, no los que tienen el prefijo de android, ya que son las librerías propias del sistema.
En mi caso voy a analizar el paquete “in.parzibyte.crudsqlite.controllers” pues parece que no es de android.
El archivo es MascotasController.java (incluso supo cuál era el nombre) y si lo comparamos con el original veremos que no hay muchos cambios, solo optimizaciones de código que el compilador hace por nosotros.
En la parte de abajo podemos descargar el código fuente, la lista de librerías, entre otros.
Nota: no solo podemos ver ese archivo, podemos ver todo el código; en este caso solo analicé un archivo, pero los demás están ahí, decompilados.
Conclusión
Si quieres ver la diferencia en profundidad, aquí dejo ambos archivos.
El original:
package me.parzibyte.crudsqlite.controllers;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import java.util.ArrayList;
import me.parzibyte.crudsqlite.AyudanteBaseDeDatos;
import me.parzibyte.crudsqlite.modelos.Mascota;
public class MascotasController {
private AyudanteBaseDeDatos ayudanteBaseDeDatos;
private String NOMBRE_TABLA = "mascotas";
public MascotasController(Context contexto) {
ayudanteBaseDeDatos = new AyudanteBaseDeDatos(contexto);
}
public int eliminarMascota(Mascota mascota) {
SQLiteDatabase baseDeDatos = ayudanteBaseDeDatos.getWritableDatabase();
String[] argumentos = {String.valueOf(mascota.getId())};
return baseDeDatos.delete(NOMBRE_TABLA, "id = ?", argumentos);
}
public long nuevaMascota(Mascota mascota) {
// writable porque vamos a insertar
SQLiteDatabase baseDeDatos = ayudanteBaseDeDatos.getWritableDatabase();
ContentValues valoresParaInsertar = new ContentValues();
valoresParaInsertar.put("nombre", mascota.getNombre());
valoresParaInsertar.put("edad", mascota.getEdad());
return baseDeDatos.insert(NOMBRE_TABLA, null, valoresParaInsertar);
}
public int guardarCambios(Mascota mascotaEditada) {
SQLiteDatabase baseDeDatos = ayudanteBaseDeDatos.getWritableDatabase();
ContentValues valoresParaActualizar = new ContentValues();
valoresParaActualizar.put("nombre", mascotaEditada.getNombre());
valoresParaActualizar.put("edad", mascotaEditada.getEdad());
// where id...
String campoParaActualizar = "id = ?";
// ... = idMascota
String[] argumentosParaActualizar = {String.valueOf(mascotaEditada.getId())};
return baseDeDatos.update(NOMBRE_TABLA, valoresParaActualizar, campoParaActualizar, argumentosParaActualizar);
}
public ArrayList<Mascota> obtenerMascotas() {
ArrayList<Mascota> mascotas = new ArrayList<>();
// readable porque no vamos a modificar, solamente leer
SQLiteDatabase baseDeDatos = ayudanteBaseDeDatos.getReadableDatabase();
// SELECT nombre, edad, id
String[] columnasAConsultar = {"nombre", "edad", "id"};
Cursor cursor = baseDeDatos.query(
NOMBRE_TABLA,//from mascotas
columnasAConsultar,
null,
null,
null,
null,
null
);
if (cursor == null) {
/*
Salimos aquí porque hubo un error, regresar
lista vacía
*/
return mascotas;
}
// Si no hay datos, igualmente regresamos la lista vacía
if (!cursor.moveToFirst()) return mascotas;
// En caso de que sí haya, iteramos y vamos agregando los
// datos a la lista de mascotas
do {
// El 0 es el número de la columna, como seleccionamos
// nombre, edad,id entonces el nombre es 0, edad 1 e id es 2
String nombreObtenidoDeBD = cursor.getString(0);
int edadObtenidaDeBD = cursor.getInt(1);
long idMascota = cursor.getLong(2);
Mascota mascotaObtenidaDeBD = new Mascota(nombreObtenidoDeBD, edadObtenidaDeBD, idMascota);
mascotas.add(mascotaObtenidaDeBD);
} while (cursor.moveToNext());
// Fin del ciclo. Cerramos cursor y regresamos la lista de mascotas :)
cursor.close();
return mascotas;
}
}
El decompilado:
package in.parzibyte.crudsqlite.controllers;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import in.parzibyte.crudsqlite.AyudanteBaseDeDatos;
import in.parzibyte.crudsqlite.modelos.Mascota;
import java.util.ArrayList;
public class MascotasController
{
private String NOMBRE_TABLA = "mascotas";
private AyudanteBaseDeDatos ayudanteBaseDeDatos;
public MascotasController(Context paramContext)
{
ayudanteBaseDeDatos = new AyudanteBaseDeDatos(paramContext);
}
public int eliminarMascota(Mascota paramMascota)
{
SQLiteDatabase localSQLiteDatabase = ayudanteBaseDeDatos.getWritableDatabase();
long l = paramMascota.getId();
return localSQLiteDatabase.delete(NOMBRE_TABLA, "id = ?", new String[] { String.valueOf(l) });
}
public int guardarCambios(Mascota paramMascota)
{
SQLiteDatabase localSQLiteDatabase = ayudanteBaseDeDatos.getWritableDatabase();
ContentValues localContentValues = new ContentValues();
localContentValues.put("nombre", paramMascota.getNombre());
localContentValues.put("edad", Integer.valueOf(paramMascota.getEdad()));
long l = paramMascota.getId();
return localSQLiteDatabase.update(NOMBRE_TABLA, localContentValues, "id = ?", new String[] { String.valueOf(l) });
}
public long nuevaMascota(Mascota paramMascota)
{
SQLiteDatabase localSQLiteDatabase = ayudanteBaseDeDatos.getWritableDatabase();
ContentValues localContentValues = new ContentValues();
localContentValues.put("nombre", paramMascota.getNombre());
localContentValues.put("edad", Integer.valueOf(paramMascota.getEdad()));
return localSQLiteDatabase.insert(NOMBRE_TABLA, null, localContentValues);
}
public ArrayList obtenerMascotas()
{
ArrayList localArrayList = new ArrayList();
Cursor localCursor = ayudanteBaseDeDatos.getReadableDatabase().query(NOMBRE_TABLA, new String[] { "nombre", "edad", "id" }, null, null, null, null, null);
if (localCursor == null) {
return localArrayList;
}
if (!localCursor.moveToFirst()) {
return localArrayList;
}
do
{
localArrayList.add(new Mascota(localCursor.getString(0), localCursor.getInt(1), localCursor.getLong(2)));
} while (localCursor.moveToNext());
localCursor.close();
return localArrayList;
}
}
Con esto queda claro que nuestro código de Android puede ser leído por cualquier persona que conozca herramientas para decompilar, así que nunca pongas claves o cosas sensibles en tu código.
Te invito a probarlo por ti mismo con la app que puse al inicio.
Por cierto, no he probado con Kotlin pero supongo que también se puede, ya que ambos (Java y Kotlin) compilan para la JVM.
y para compilarla como le hago?
Hola esto me puede servir para extraer código fuente de una App que me han hecho unos indios y ahora desaparecieron sin acabar la app y para terminar me hace falta el código fuente para dárselo a un nuevo programador, espero respuesta Gracias
Yo creo que sí, funciona en la mayoría de casos
Gracias voy a probarlo Saludos
Muy bueno el sitio, me ha servido mucho, gracias