Decompilar APK (app de Android) y obtener código fuente (casi) original

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.

1 – Seleccionar app

Cuando la app que vamos a decompilar se haya subido, comenzará el proceso de decompilación en el servidor.

2 – Decompilando apk

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.

3 – Resultados de decompilación

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.

4 – Código del controlador decompilado

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.

5 – Código original

En la parte de abajo podemos descargar el código fuente, la lista de librerías, entre otros.

6 – Descargar código decompilado

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.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

5 comentarios en “Decompilar APK (app de Android) y obtener código fuente (casi) original”

  1. 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

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *