
Permite emular una librería nativa de Android, y una emulación experimental de iOS.
Permite emular una librería nativa de Android, además de una emulación experimental de iOS.
Este es un proyecto educativo para aprender más sobre el formato de archivo ELF/MachO y el ensamblador ARM.
¡Úsalo bajo tu propio riesgo!
unidbg es compatible con Model Context Protocol (MCP) para la depuración asistida por IA. Cuando el depurador está activo, escribe mcp en la consola para iniciar un servidor MCP al que las herramientas de IA (p. ej., Cursor) puedan conectarse.
El MCP de unidbg tiene dos modos de funcionamiento:
Modo 1: Depuración con puntos de interrupción — Adjunta el depurador y ejecuta tu código. Cuando se alcanza un punto de interrupción, Breaker.debug() pausa el emulador: escribe mcp en la consola para iniciar el servidor MCP y deja que la IA ayude con el análisis. Todas las herramientas de depuración están disponibles (registros, memoria, desensamblado, ejecución paso a paso, trazado, etc.). Tras reanudar, si se alcanza otro punto de interrupción, el depurador se pausa de nuevo. Cuando la ejecución finaliza sin alcanzar ningún punto de interrupción, el proceso termina y MCP se cierra.
Debugger debugger = emulator.attach();
debugger.addBreakPoint(address);
// run your emulation logic — debugger pauses when breakpoint is hit
Modo 2: Herramientas personalizadas (repetibles) — Usa McpToolkit para registrar herramientas personalizadas y deja que la IA vuelva a ejecutar las funciones objetivo con diferentes parámetros. La librería nativa se carga una sola vez; después de cada ejecución, el proceso permanece vivo y MCP sigue activo para la siguiente ejecución.
McpToolkit toolkit = new McpToolkit();
toolkit.addTool(new McpTool() {
@Override public String name() { return "encrypt"; }
@Override public String description() { return "Run encryption"; }
@Override public String[] paramNames() { return new String[]{"input"}; }
@Override public void execute(String[] params) {
String input = params.length > 0 ? params[0] : "default";
// call encryption with input
}
});
toolkit.run(emulator.attach());
Cuando el depurador se detenga, escribe mcp (o mcp 9239 para especificar el puerto) en la consola. A continuación, añádelo a la configuración de MCP de Cursor:
{
"mcpServers": {
"unidbg-mcp-server": {
"url": "http://localhost:9239/sse"
}
}
}
Estado e información
Registros y desensamblado
Memoria
Puntos de interrupción y ejecución
Trazado
| Herramienta | Descripción |
|---|---|
trace_code | Traza instrucciones con valores de lectura/escritura de registros (regs_read, prev_write) |
trace_read / trace_write | Traza lecturas/escrituras de memoria en un rango de direcciones |
Llamadas a funciones
| Herramienta | Descripción |
|---|---|
call_function | Llama a una función nativa por dirección con argumentos tipados (hex, string, bytes, null). Devuelve el valor con resolución de símbolos y vista previa de memoria |
call_symbol | Llama a una función exportada por módulo + nombre de símbolo, p. ej. libc.so + malloc |
Solo iOS (disponible cuando Family=iOS)
Usa McpToolkit para registrar herramientas personalizadas, cada una de las cuales implementa la interfaz McpTool. Esto sustituye el despacho manual if-else por clases de herramientas limpias y autocontenidas. En este punto, la librería nativa está completamente cargada (JNI_OnLoad / punto de entrada ya ejecutado), por lo que el código dentro del execute() de cada herramienta es la lógica de la función objetivo que se va a analizar. La IA puede establecer puntos de interrupción y trazas antes de invocar una herramienta personalizada y, a continuación, inspeccionar los resultados de la ejecución con diferentes entradas sin reiniciar el proceso.
Ejemplo de Android — Consulta Utilities64.java para ver un ejemplo de JNI de Android con herramientas MCP personalizadas:
DalvikModule dm = vm.loadLibrary(new File("libtmessages.29.so"), true);
dm.callJNI_OnLoad(emulator);
cUtilities = vm.resolveClass("org/telegram/messenger/Utilities");
McpToolkit toolkit = new McpToolkit();
toolkit.addTool(new McpTool() {
@Override public String name() { return "aesCbc"; }
@Override public String description() { return "Run AES-CBC encryption on input data"; }
@Override public String[] paramNames() { return new String[]{"input"}; }
@Override public void execute(String[] params) {
byte[] input = params.length > 0 ? params[0].getBytes() : new byte[16];
aesCbcEncryptionByteArray(input);
}
});
toolkit.addTool(new McpTool() {
@Override public String name() { return "aesCtr"; }
@Override public String description() { return "Run AES-CTR decryption on input data"; }
@Override public String[] paramNames() { return new String[]{"input"}; }
@Override public void execute(String[] params) {
byte[] input = params.length > 0 ? params[0].getBytes() : new byte[16];
aesCtrDecryptionByteArray(input);
}
});
toolkit.addTool(new McpTool() {
@Override public String name() { return "pbkdf2"; }
@Override public String description() { return "Run PBKDF2 key derivation"; }
@Override public String[] paramNames() { return new String[]{"password", "iterations"}; }
@Override public void execute(String[] params) {
String password = params.length > 0 ? params[0] : "123456";
int iterations = params.length > 1 ? Integer.parseInt(params[1]) : 100000;
pbkdf2(password.getBytes(), iterations);
}
});
toolkit.run(emulator.attach());
Ejemplo de iOS — Consulta IpaLoaderTest.java para ver un ejemplo de carga de IPA en iOS con herramientas MCP personalizadas:
IpaLoader ipaLoader = new IpaLoader64(ipa, new File("target/rootfs/ipa"));
LoadedIpa loader = ipaLoader.load(this);
emulator = loader.getEmulator();
loader.callEntry();
module = loader.getExecutable();
McpToolkit toolkit = new McpToolkit();
toolkit.addTool(new McpTool() {
@Override public String name() { return "dumpClass"; }
@Override public String description() { return "Dump an ObjC class definition by name"; }
@Override public String[] paramNames() { return new String[]{"className"}; }
@Override public void execute(String[] params) {
String className = params.length > 0 ? params[0] : "AppDelegate";
IClassDumper classDumper = ClassDumper.getInstance(emulator);
System.out.println("dumpClass(" + className + "):\n" + classDumper.dumpClass(className));
}
});
toolkit.addTool(new McpTool() {
@Override public String name() { return "readVersion"; }
@Override public String description() { return "Read the TelegramCoreVersionString from the executable"; }
@Override public void execute(String[] params) {
Symbol sym = module.findSymbolByName("_TelegramCoreVersionString");
if (sym != null) {
Pointer pointer = UnidbgPointer.pointer(emulator, sym.getAddress());
if (pointer != null) {
System.out.println("_TelegramCoreVersionString=" + pointer.getString(0));
}
}
}
});
toolkit.run(emulator.attach());
Una vez iniciado el servidor MCP, la IA puede llamar a estas herramientas mediante MCP para ejecutar emulaciones con parámetros personalizados, establecer puntos de interrupción, trazar la ejecución e inspeccionar los resultados, todo ello sin reiniciar el proceso.
API de bajo nivel: También puedes usar
Debugger.addMcpTool()+Debugger.run(DebugRunnable)directamente para tener un control total.McpToolkites un envoltorio de alto nivel que elimina el despacho if-else.
Realiza el seguimiento de las asignaciones de memoria del lado del invitado (mmap/munmap/brk) para detectar fugas en código nativo emulado. Usa try-with-resources: el seguimiento comienza con la creación y el informe de fugas se imprime automáticamente al cerrar.
try (MemoryTracker tracker = emulator.traceMemoryLeaks()) {
module.callFunction(emulator, "targetFunction", arg1, arg2);
}
Cada bloque con fuga incluye el backtrace ARM del invitado (module+offset+symbol) y el stack trace Java del anfitrión. Salida de ejemplo:
=== Memory Leak Report ===
Tracking duration: 42ms
Total allocations: 5
Total deallocations: 3
Leaked blocks: 2
Total leaked size: 32768 bytes (32.0 KB)
--- Leak #1 ---
Address: 0x40001000, Size: 16384 (16.0 KB), Perms: rw-
Guest Backtrace:
#0 0x40123456 libexample.so+0x3456 (malloc+0x12)
#1 0x40124000 libexample.so+0x4000 (doSomething+0x48)
Host Stack Trace:
com.github.unidbg.linux.AndroidElfLoader.mmap2(AndroidElfLoader.java:785)
...
También puedes acceder al informe mediante programación antes de cerrar:
try (MemoryTracker tracker = emulator.traceMemoryLeaks()) {
module.callFunction(emulator, "targetFunction", arg1, arg2);
List<AllocationRecord> leaks = tracker.getLeaks();
assert leaks.isEmpty() : "Memory leak detected!";
}
Un pool de objetos seguro para hilos que reutiliza instancias del emulador en múltiples hilos, evitando la sobrecarga de la inicialización repetida.
public class MyWorker implements Worker {
private final AndroidEmulator emulator;
public MyWorker() {
emulator = AndroidEmulatorBuilder.for64Bit().build();
// load .so, call JNI_OnLoad, etc.
}
@Override
public void destroy() {
emulator.close();
}
public byte[] doWork(byte[] input) {
// call native methods and return the result
}
}
// Create a worker pool (max = CPU cores, lazy-initialized)
WorkerPool pool = WorkerPoolFactory.create(MyWorker::new);
// Or specify max workers explicitly
// WorkerPool pool = WorkerPoolFactory.create(MyWorker::new, 4);
// Optional: customize idle timeout (default 10 minutes, minimum 1 minute)
pool.setIdleTimeout(30); // idle workers destroyed after 30 minutes
// Optional: customize minimum kept-alive workers (default 1, minimum 1)
pool.setMinIdle(2); // always keep at least 2 workers alive
// Optional: pre-create workers eagerly (default 0, fully lazy)
pool.setInitialSize(4); // eagerly create 4 workers on startup
// Concurrent invocation from multiple threads
ExecutorService executor = Executors.newFixedThreadPool(100);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
try (WorkerLoan<MyWorker> loan = pool.borrow(1, TimeUnit.MINUTES)) {
if (loan != null) {
byte[] result = loan.get().doWork(input);
}
} // worker is automatically returned to the pool
});
}
executor.shutdown();
executor.awaitTermination(10, TimeUnit.MINUTES);
pool.close(); // destroy all workers and release resources
Consulta TTEncryptWorker.java para ver un ejemplo completo.
Pruebas sencillas en el directorio src/test:

Más pruebas:
| Herramienta | Descripción |
|---|
check_connection | Estado del emulador: Family, arquitectura, capacidades del backend, isRunning, módulos cargados |
list_modules / get_module_info | Lista los módulos cargados y obtén detalles, incluidos el número de símbolos exportados y las dependencias |
list_exports | Lista los símbolos exportados/dinámicos de un módulo con filtro opcional y demangling de C++ |
find_symbol | Busca un símbolo por nombre o el símbolo más cercano a una dirección |
get_threads | Lista todos los hilos/tareas del emulador |
| Herramienta | Descripción |
|---|
get_registers / get_register / set_register | Lee/escribe registros de la CPU |
disassemble | Desensambla instrucciones en una dirección (los destinos de salto se anotan automáticamente con nombres de símbolos) |
assemble | Ensambla texto de instrucción en código máquina |
get_callstack | Obtén la pila de llamadas actual (backtrace) |
| Herramienta | Descripción |
|---|
read_memory / write_memory | Lee/escribe bytes de memoria en bruto |
read_string / read_std_string | Lee una cadena C o un std::string de C++ (con detección de SSO) |
read_pointer | Lee la cadena de punteros con resolución de símbolos |
read_typed | Lee memoria como valores tipados (int8–int64, float, double, puntero) |
search_memory | Busca en la memoria patrones de bytes con filtros de alcance/permisos |
list_memory_map | Lista todos los mapeos de memoria con permisos |
allocate_memory / free_memory / list_allocations | Asigna (malloc/mmap) con datos iniciales opcionales, libera y realiza el seguimiento de los bloques de memoria |
patch | Escribe instrucciones ensambladas en memoria |
| Herramienta | Descripción |
|---|
add_breakpoint / add_breakpoint_by_symbol / add_breakpoint_by_offset | Añade puntos de interrupción por dirección, símbolo o módulo+offset |
remove_breakpoint / list_breakpoints | Elimina o lista puntos de interrupción (con desensamblado) |
continue_execution | Reanuda la ejecución. Usa poll_events para esperar breakpoint_hit o execution_completed |
step_over / step_into / step_out | Paso por encima, paso hacia dentro (N instrucciones) o paso fuera de una función |
next_block | Pausa en el siguiente bloque básico (solo Unicorn) |
step_until_mnemonic | Pausa en la siguiente instrucción que coincida con el mnemónico, p. ej. bl, ret (solo Unicorn) |
poll_events | Consulta eventos como breakpoint_hit, execution_completed y trace events |
| Herramienta | Descripción |
|---|
inspect_objc_msg | Inspecciona la llamada objc_msgSend: muestra el nombre de la clase receptora y el selector, p. ej. -[NSString length] |
get_objc_class_name | Obtén el nombre de la clase ObjC de un objeto en una dirección determinada (análisis de memoria puro, sin cambio de estado) |
dump_objc_class | Vuelca la definición de la clase ObjC (propiedades, métodos, protocolos, ivars) |
dump_gpb_protobuf | Vuelca el esquema de mensaje GPB protobuf en formato .proto (solo 64 bits) |