
Permite emular uma biblioteca nativa do Android e uma emulação experimental de iOS.
Permite emular uma biblioteca nativa do Android, além de uma emulação experimental de iOS.
Este é um projeto educacional para aprender mais sobre o formato de arquivo ELF/MachO e assembly ARM.
Use por sua conta e risco!
unidbg suporta Model Context Protocol (MCP) para depuração assistida por IA. Quando o depurador estiver ativo, digite mcp no console para iniciar um servidor MCP ao qual ferramentas de IA (ex.: Cursor) podem se conectar.
O MCP do unidbg tem dois modos de operação:
Modo 1: Depuração por Breakpoint — Anexe o depurador e execute seu código. Quando um breakpoint é atingido, Breaker.debug() pausa o emulador — digite mcp no console para iniciar o servidor MCP e deixar a IA ajudar na análise. Todas as ferramentas de depuração estão disponíveis (registradores, memória, desmontagem, stepping, rastreamento, etc.). Após retomar, se outro breakpoint for atingido, o depurador pausa novamente. Quando a execução termina sem atingir um breakpoint, o processo é encerrado e o MCP é desligado.
Debugger debugger = emulator.attach();
debugger.addBreakPoint(address);
// run your emulation logic — debugger pauses when breakpoint is hit
Modo 2: Ferramentas Personalizadas (Repetível) — Use McpToolkit para registrar ferramentas personalizadas e deixar a IA re-executar funções alvo com parâmetros diferentes. A biblioteca nativa é carregada uma única vez; após cada execução, o processo permanece ativo e o MCP continua disponível para a próxima execução.
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 o depurador pausar, digite mcp (ou mcp 9239 para especificar a porta) no console. Em seguida, adicione às configurações de MCP do Cursor:
{
"mcpServers": {
"unidbg-mcp-server": {
"url": "http://localhost:9239/sse"
}
}
}
Status e Informações
Registradores e Desmontagem
Memória
Breakpoints e Execução
Rastreamento
| Ferramenta | Descrição |
|---|---|
trace_code | Rastreia instruções com valores de leitura/escrita de registradores (regs_read, prev_write) |
trace_read / trace_write | Rastreia leituras/escritas de memória em um intervalo de endereços |
Chamadas de Função
| Ferramenta | Descrição |
|---|---|
call_function | Chama função nativa por endereço com argumentos tipados (hex, string, bytes, null). Retorna o valor com resolução de símbolos e pré-visualização de memória |
call_symbol | Chama função exportada por módulo + nome do símbolo, ex.: libc.so + malloc |
Somente iOS (disponível quando Family=iOS)
Use McpToolkit para registrar ferramentas personalizadas, cada uma implementando a interface McpTool. Isso substitui o despacho manual baseado em if-else por classes de ferramentas limpas e autocontidas. Neste ponto, a biblioteca nativa já está totalmente carregada (JNI_OnLoad / ponto de entrada já executados), então o código dentro de execute() de cada ferramenta é a lógica da função alvo a ser analisada. A IA pode definir breakpoints e traces antes de acionar uma ferramenta personalizada e, em seguida, inspecionar os resultados da execução com diferentes entradas sem reiniciar o processo.
Exemplo Android — Veja Utilities64.java para um exemplo de JNI Android com ferramentas 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());
Exemplo iOS — Veja IpaLoaderTest.java para um exemplo de carregamento de IPA iOS com ferramentas 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());
Depois que o servidor MCP é iniciado, a IA pode chamar essas ferramentas via MCP para executar emulações com parâmetros personalizados, definir breakpoints, rastrear a execução e inspecionar os resultados — tudo sem reiniciar o processo.
API de baixo nível: Você também pode usar
Debugger.addMcpTool()+Debugger.run(DebugRunnable)diretamente para controle total.McpToolkité um wrapper de nível mais alto que elimina o despacho com if-else.
Rastreia alocações de memória do lado do guest (mmap/munmap/brk) para detectar vazamentos em código nativo emulado. Use try-with-resources — o rastreamento começa na criação e o relatório de vazamentos é impresso automaticamente no fechamento.
try (MemoryTracker tracker = emulator.traceMemoryLeaks()) {
module.callFunction(emulator, "targetFunction", arg1, arg2);
}
Cada bloco vazado inclui o backtrace ARM do guest (módulo+offset+símbolo) e o stack trace Java do host. Exemplo de saída:
=== 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)
...
Você também pode acessar o relatório programaticamente antes do fechamento:
try (MemoryTracker tracker = emulator.traceMemoryLeaks()) {
module.callFunction(emulator, "targetFunction", arg1, arg2);
List<AllocationRecord> leaks = tracker.getLeaks();
assert leaks.isEmpty() : "Memory leak detected!";
}
Um pool de objetos thread-safe para reutilizar instâncias de emulador entre múltiplas threads, evitando a sobrecarga da inicialização 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
Veja TTEncryptWorker.java para um exemplo completo.
Testes simples no diretório src/test:

Mais testes:
| Ferramenta | Descrição |
|---|
check_connection | Status do emulador: Family, arquitetura, capacidades do backend, isRunning, módulos carregados |
list_modules / get_module_info | Lista módulos carregados, obtém detalhes incluindo contagem de símbolos exportados e dependências |
list_exports | Lista símbolos exportados/dinâmicos de um módulo com filtro opcional e demangling de C++ |
find_symbol | Encontra símbolo por nome ou encontra o símbolo mais próximo de um endereço |
get_threads | Lista todas as threads/tarefas no emulador |
| Ferramenta | Descrição |
|---|
get_registers / get_register / set_register | Lê/escreve registradores da CPU |
disassemble | Desmonta instruções em um endereço (alvos de branch anotados automaticamente com nomes de símbolos) |
assemble | Monta texto de instrução em código de máquina |
get_callstack | Obtém a pilha de chamadas atual (backtrace) |
| Ferramenta | Descrição |
|---|
read_memory / write_memory | Lê/escreve bytes brutos de memória |
read_string / read_std_string | Lê string C ou std::string de C++ (com detecção de SSO) |
read_pointer | Lê cadeia de ponteiros com resolução de símbolos |
read_typed | Lê memória como valores tipados (int8–int64, float, double, pointer) |
search_memory | Busca padrões de bytes na memória com filtros de escopo/permissão |
list_memory_map | Lista todos os mapeamentos de memória com permissões |
allocate_memory / free_memory / list_allocations | Aloca (malloc/mmap) com dados iniciais opcionais, libera e rastreia blocos de memória |
patch | Escreve instruções montadas na memória |
| Ferramenta | Descrição |
|---|
add_breakpoint / add_breakpoint_by_symbol / add_breakpoint_by_offset | Adiciona breakpoints por endereço, símbolo ou módulo+offset |
remove_breakpoint / list_breakpoints | Remove ou lista breakpoints (com desmontagem) |
continue_execution | Retoma a execução. Use poll_events para aguardar breakpoint_hit ou execution_completed |
step_over / step_into / step_out | Passo por cima, para dentro (N instruções) ou para fora da função |
next_block | Pausa no próximo bloco básico (somente Unicorn) |
step_until_mnemonic | Pausa na próxima instrução que corresponda ao mnemônico, ex.: bl, ret (somente Unicorn) |
poll_events | Consulta eventos de breakpoint_hit, execution_completed e trace |
| Ferramenta | Descrição |
|---|
inspect_objc_msg | Inspeciona a chamada objc_msgSend: mostra o nome da classe do receiver e o selector, ex.: -[NSString length] |
get_objc_class_name | Obtém o nome da classe ObjC de um objeto em um endereço específico (somente parsing de memória, sem mudança de estado) |
dump_objc_class | Gera dump da definição da classe ObjC (propriedades, métodos, protocolos, ivars) |
dump_gpb_protobuf | Gera dump do esquema de mensagem GPB protobuf no formato .proto (somente 64 bits) |