
Consente di emulare una libreria nativa Android e un'emulazione iOS sperimentale.
Consente di emulare una libreria nativa Android e una sperimentale emulazione iOS.
Questo è un progetto educativo per imparare di più sul formato file ELF/MachO e sull'assembly ARM.
Usalo a tuo rischio e pericolo!
unidbg supporta il Model Context Protocol (MCP) per il debug assistito dall'AI. Quando il debugger è attivo, digita mcp nella console per avviare un server MCP a cui gli strumenti AI (es. Cursor) possono connettersi.
L'MCP di unidbg ha due modalità operative:
Modalità 1: Debug con breakpoint — Collega il debugger ed esegui il tuo codice. Quando viene raggiunto un breakpoint, Breaker.debug() mette in pausa l'emulatore: digita mcp nella console per avviare il server MCP e lascia che l'AI assista con l'analisi. Tutti gli strumenti di debug sono disponibili (registri, memoria, disassembly, step, tracciamento, ecc.). Dopo la ripresa, se viene raggiunto un altro breakpoint, il debugger si mette di nuovo in pausa. Una volta che l'esecuzione termina senza raggiungere un breakpoint, il processo esce e l'MCP si spegne.
Debugger debugger = emulator.attach();
debugger.addBreakPoint(address);
// run your emulation logic — debugger pauses when breakpoint is hit
Modalità 2: Strumenti personalizzati (ripetibili) — Usa McpToolkit per registrare strumenti personalizzati e lascia che l'AI riesegua le funzioni target con parametri diversi. La libreria nativa viene caricata una sola volta; dopo ogni esecuzione il processo resta attivo e l'MCP rimane disponibile per l'esecuzione successiva.
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());
Quando il debugger si ferma, digita mcp (oppure mcp 9239 per specificare la porta) nella console. Poi aggiungi nelle impostazioni MCP di Cursor:
{
"mcpServers": {
"unidbg-mcp-server": {
"url": "http://localhost:9239/sse"
}
}
}
Stato e informazioni
Registri e disassembly
Memoria
Breakpoint ed esecuzione
Tracciamento
| Tool | Description |
|---|---|
trace_code | Traccia le istruzioni con i valori di lettura/scrittura dei registri (regs_read, prev_write) |
trace_read / trace_write | Traccia letture/scritture di memoria in un intervallo di indirizzi |
Chiamate di funzione
| Tool | Description |
|---|---|
call_function | Chiama una funzione nativa per indirizzo con argomenti tipizzati (hex, stringa, byte, null). Restituisce il valore con risoluzione dei simboli e anteprima della memoria |
call_symbol | Chiama una funzione esportata per modulo + nome del simbolo, es. libc.so + malloc |
Solo iOS (disponibile quando Family=iOS)
Usa McpToolkit per registrare strumenti personalizzati, ognuno dei quali implementa l'interfaccia McpTool. Questo sostituisce la gestione manuale if-else con classi di strumenti pulite e autonome. A questo punto la libreria nativa è completamente caricata (JNI_OnLoad / punto di ingresso già eseguito), quindi il codice all'interno del metodo execute() di ogni strumento è la logica della funzione target da analizzare. L'AI può impostare breakpoint e tracce prima di attivare uno strumento personalizzato, quindi ispezionare i risultati dell'esecuzione su input diversi senza riavviare il processo.
Esempio Android — Vedi Utilities64.java per un esempio JNI Android con strumenti MCP personalizzati:
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());
Esempio iOS — Vedi IpaLoaderTest.java per un esempio di caricamento IPA iOS con strumenti MCP personalizzati:
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 volta avviato il server MCP, l'AI può chiamare questi strumenti tramite MCP per eseguire emulazioni con parametri personalizzati, impostare breakpoint, tracciare l'esecuzione e ispezionare i risultati, il tutto senza riavviare il processo.
API di basso livello: puoi anche usare direttamente
Debugger.addMcpTool()+Debugger.run(DebugRunnable)per il controllo completo.McpToolkitè un wrapper di livello superiore che elimina la gestione if-else.
Tieni traccia delle allocazioni di memoria lato guest (mmap/munmap/brk) per rilevare fughe di memoria nel codice nativo emulato. Usa try-with-resources: il tracciamento inizia alla creazione e il report delle fughe viene stampato automaticamente alla chiusura.
try (MemoryTracker tracker = emulator.traceMemoryLeaks()) {
module.callFunction(emulator, "targetFunction", arg1, arg2);
}
Ogni blocco leakato include il backtrace ARM guest (modulo+offset+simbolo) e lo stack trace Java host. Output di esempio:
=== 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)
...
Puoi anche accedere al report a livello di codice prima della chiusura:
try (MemoryTracker tracker = emulator.traceMemoryLeaks()) {
module.callFunction(emulator, "targetFunction", arg1, arg2);
List<AllocationRecord> leaks = tracker.getLeaks();
assert leaks.isEmpty() : "Memory leak detected!";
}
Un pool di oggetti thread-safe per riutilizzare le istanze dell'emulatore tra più thread, evitando l'overhead di un'inizializzazione ripetuta.
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
Vedi TTEncryptWorker.java per un esempio completo.
Test semplici nella directory src/test:

Altri test:
| Tool | Description |
|---|
check_connection | Stato dell'emulatore: famiglia, architettura, capacità del backend, isRunning, moduli caricati |
list_modules / get_module_info | Elenca i moduli caricati, ottieni dettagli incluso il conteggio dei simboli esportati e le dipendenze |
list_exports | Elenca i simboli esportati/dinamici di un modulo con filtro opzionale e demangling C++ |
find_symbol | Trova un simbolo per nome o trova il simbolo più vicino a un indirizzo |
get_threads | Elenca tutti i thread/attività nell'emulatore |
| Tool | Description |
|---|
get_registers / get_register / set_register | Legge/scrive i registri della CPU |
disassemble | Disassembla le istruzioni all'indirizzo (le destinazioni dei branch sono annotate automaticamente con i nomi dei simboli) |
assemble | Assembla il testo delle istruzioni in codice macchina |
get_callstack | Ottieni lo stack di chiamate corrente (backtrace) |
| Tool | Description |
|---|
read_memory / write_memory | Legge/scrive byte grezzi di memoria |
read_string / read_std_string | Legge una stringa C o std::string C++ (con rilevamento SSO) |
read_pointer | Legge una catena di puntatori con risoluzione dei simboli |
read_typed | Legge la memoria come valori tipizzati (int8–int64, float, double, pointer) |
search_memory | Cerca nella memoria pattern di byte con filtri di ambito/autorizzazioni |
list_memory_map | Elenca tutte le mappature di memoria con le autorizzazioni |
allocate_memory / free_memory / list_allocations | Alloca (malloc/mmap) con dati iniziali opzionali, libera e traccia i blocchi di memoria |
patch | Scrive istruzioni assemblate in memoria |
| Tool | Description |
|---|
add_breakpoint / add_breakpoint_by_symbol / add_breakpoint_by_offset | Aggiunge breakpoint per indirizzo, simbolo o modulo+offset |
remove_breakpoint / list_breakpoints | Rimuove o elenca i breakpoint (con disassembly) |
continue_execution | Riprende l'esecuzione. Usa poll_events per attendere breakpoint_hit o execution_completed |
step_over / step_into / step_out | Esegue passo su, dentro (N istruzioni) o fuori dalla funzione |
next_block | Interrompe al prossimo blocco di base (solo Unicorn) |
step_until_mnemonic | Interrompe alla prossima istruzione che corrisponde al mnemonico, es. bl, ret (solo Unicorn) |
poll_events | Esegue il polling di breakpoint_hit, execution_completed, eventi di traccia |
| Tool | Description |
|---|
inspect_objc_msg | Ispeziona la chiamata objc_msgSend: mostra il nome della classe del ricevente e il selettore, es. -[NSString length] |
get_objc_class_name | Ottiene il nome della classe ObjC di un oggetto a un determinato indirizzo (pura analisi della memoria, nessuna modifica dello stato) |
dump_objc_class | Estrae la definizione della classe ObjC (proprietà, metodi, protocolli, ivars) |
dump_gpb_protobuf | Estrae lo schema del messaggio protobuf GPB in formato .proto (solo 64-bit) |