
يتيح لك محاكاة مكتبة أندرويد أصلية، ومحاكاة iOS تجريبية.
يتيح لك محاكاة مكتبة أندرويد أصلية (native library)، بالإضافة إلى محاكاة تجريبية لنظام iOS.
هذا مشروع تعليمي لمعرفة المزيد عن تنسيق ملفات ELF/MachO ولغة تجميع ARM.
استخدمه على مسؤوليتك الخاصة!
يدعم unidbg Model Context Protocol (MCP) لتصحيح الأخطاء بمساعدة الذكاء الاصطناعي. عندما يكون المصحح نشطًا، اكتب mcp في وحدة التحكم لبدء خادم MCP يمكن لأدوات الذكاء الاصطناعي (مثل Cursor) الاتصال به.
يحتوي unidbg MCP على وضعي تشغيل:
الوضع 1: تصحيح نقاط التوقف — اربط المصحح وشغّل الكود الخاص بك. عند الوصول إلى نقطة توقف، يقوم Breaker.debug() بإيقاف المحاكي مؤقتًا — اكتب mcp في وحدة التحكم لبدء خادم MCP ودع الذكاء الاصطناعي يساعد في التحليل. جميع أدوات التصحيح متاحة (السجلات، الذاكرة، التفكيك، التنقل خطوة بخطوة، التتبّع، إلخ). بعد الاستئناف، إذا تم الوصول إلى نقطة توقف أخرى، يتوقف المصحح مرة أخرى. بمجرد اكتمال التنفيذ دون الوصول إلى نقطة توقف، تنتهي العملية ويتم إيقاف تشغيل MCP.
Debugger debugger = emulator.attach();
debugger.addBreakPoint(address);
// run your emulation logic — debugger pauses when breakpoint is hit
الوضع 2: أدوات مخصصة (قابلة للتكرار) — استخدم McpToolkit لتسجيل أدوات مخصصة ودع الذكاء الاصطناعي يعيد تشغيل الدوال المستهدفة بمعاملات مختلفة. يتم تحميل المكتبة الأصلية مرة واحدة فقط؛ وبعد كل تنفيذ تبقى العملية حية ويظل MCP نشطًا للتشغيل التالي.
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());
عندما يتوقف المصحح، اكتب mcp (أو mcp 9239 لتحديد المنفذ) في وحدة التحكم. ثم أضِف إلى إعدادات MCP في Cursor:
{
"mcpServers": {
"unidbg-mcp-server": {
"url": "http://localhost:9239/sse"
}
}
}
الحالة والمعلومات
السجلات والتفكيك
الذاكرة
نقاط التوقف والتنفيذ
التتبّع
| الأداة | الوصف |
|---|---|
trace_code | تتبّع التعليمات مع قيم قراءة/كتابة السجلات (regs_read, prev_write) |
trace_read / trace_write | تتبّع قراءات/كتابات الذاكرة في نطاق عناوين |
استدعاءات الدوال
| الأداة | الوصف |
|---|---|
call_function | استدعاء دالة أصلية حسب العنوان مع وسائط بنمط محدد (hex, string, bytes, null). يُرجع القيمة مع حل الرموز ومعاينة الذاكرة |
call_symbol | استدعاء دالة مُصدَّرة حسب الوحدة + اسم الرمز، مثل libc.so + malloc |
iOS فقط (متاح عندما تكون العائلة Family=iOS)
| الأداة |
|---|
استخدم McpToolkit لتسجيل أدوات مخصصة، حيث تنفّذ كل أداة واجهة McpTool. يستبدل هذا التوزيع اليدوي if-else بفئات أدوات نظيفة ومكتفية ذاتيًا. في هذه المرحلة تكون المكتبة الأصلية محمّلة بالكامل (تم تنفيذ JNI_OnLoad / نقطة الدخول بالفعل)، لذا فإن الكود داخل execute() لكل أداة هو منطق الدالة المستهدفة المراد تحليلها. يمكن للذكاء الاصطناعي تعيين نقاط توقف وتتبعات قبل تشغيل أداة مخصصة، ثم فحص نتائج التنفيذ عبر مدخلات مختلفة دون إعادة تشغيل العملية.
مثال أندرويد — راجع Utilities64.java للحصول على مثال JNI لأندرويد مع أدوات MCP مخصصة:
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());
مثال iOS — راجع IpaLoaderTest.java للحصول على مثال لتحميل IPA على iOS مع أدوات MCP مخصصة:
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());
بمجرد بدء تشغيل خادم MCP، يمكن للذكاء الاصطناعي استدعاء هذه الأدوات عبر MCP لتشغيل عمليات محاكاة بمعاملات مخصصة، وتعيين نقاط توقف، وتتبّع التنفيذ، وفحص النتائج — كل ذلك دون إعادة تشغيل العملية.
واجهة برمجة منخفضة المستوى: يمكنك أيضًا استخدام
Debugger.addMcpTool()+Debugger.run(DebugRunnable)مباشرةً للتحكم الكامل.McpToolkitهو غلاف عالي المستوى يلغي توزيع if-else.
تتبّع عمليات تخصيص الذاكرة على جانب الضيف (mmap/munmap/brk) لكشف التسريبات في الكود الأصلي المُحاكى. استخدم try-with-resources — يبدأ التتبّع عند الإنشاء، ويُطبع تقرير التسريب تلقائيًا عند الإغلاق.
try (MemoryTracker tracker = emulator.traceMemoryLeaks()) {
module.callFunction(emulator, "targetFunction", arg1, arg2);
}
تتضمن كل كتلة مسرّبة تتبّعًا خلفيًا ARM للضيف (الوحدة+الإزاحة+الرمز) وتتبّع مكدس Java للمضيف. مثال على الإخراج:
=== 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)
...
يمكنك أيضًا الوصول إلى التقرير برمجيًا قبل الإغلاق:
try (MemoryTracker tracker = emulator.traceMemoryLeaks()) {
module.callFunction(emulator, "targetFunction", arg1, arg2);
List<AllocationRecord> leaks = tracker.getLeaks();
assert leaks.isEmpty() : "Memory leak detected!";
}
تجمع كائنات آمن للخيوط لإعادة استخدام مثيلات المحاكي عبر خيوط متعددة، مع تجنب الحمل الزائد للتهيئة المتكررة.
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
راجع TTEncryptWorker.java للحصول على مثال كامل.
اختبارات بسيطة ضمن دليل src/test:

مزيد من الاختبارات:
| الأداة | الوصف |
|---|
check_connection | حالة المحاكي: العائلة، البنية، إمكانيات الواجهة الخلفية، isRunning، الوحدات المحمّلة |
list_modules / get_module_info | سرد الوحدات المحمّلة والحصول على التفاصيل بما في ذلك عدد الرموز المُصدَّرة والتبعيات |
list_exports | سرد الرموز المُصدَّرة/الديناميكية لوحدة مع فلتر اختياري وإزالة تشويش أسماء C++ |
find_symbol | العثور على رمز بالاسم أو العثور على أقرب رمز عند عنوان |
get_threads | سرد جميع سلاسل العمليات/المهام في المحاكي |
| الأداة | الوصف |
|---|
get_registers / get_register / set_register | قراءة/كتابة سجلات وحدة المعالجة المركزية |
disassemble | تفكيك التعليمات عند عنوان (تُضاف تلقائيًا تعليقات توضيحية بأسماء الرموز إلى أهداف الفروع) |
assemble | تجميع نص تعليمة إلى كود آلي |
get_callstack | الحصول على مكدس الاستدعاءات الحالي (التتبّع الخلفي) |
| الأداة | الوصف |
|---|
read_memory / write_memory | قراءة/كتابة وحدات البايت الخام للذاكرة |
read_string / read_std_string | قراءة سلسلة C أو سلسلة C++ std::string (مع كشف SSO) |
read_pointer | قراءة سلسلة مؤشرات مع حل الرموز |
read_typed | قراءة الذاكرة كقيم بنمط محدد (int8–int64, float, double, pointer) |
search_memory | البحث في الذاكرة عن أنماط بايت مع عوامل تصفية للنطاق/الأذونات |
list_memory_map | سرد جميع تعيينات الذاكرة مع الأذونات |
allocate_memory / free_memory / list_allocations | تخصيص (malloc/mmap) مع بيانات أولية اختيارية، وتحرير، وتتبّع كتل الذاكرة |
patch | كتابة تعليمات مُجمَّعة إلى الذاكرة |
| الأداة | الوصف |
|---|
add_breakpoint / add_breakpoint_by_symbol / add_breakpoint_by_offset | إضافة نقاط توقف حسب العنوان أو الرمز أو الوحدة+الإزاحة |
remove_breakpoint / list_breakpoints | إزالة أو سرد نقاط التوقف (مع التفكيك) |
continue_execution | استئناف التنفيذ. استخدم poll_events لانتظار breakpoint_hit أو execution_completed |
step_over / step_into / step_out | تجاوز، أو الدخول إلى (N من التعليمات)، أو الخروج من الدالة |
next_block | التوقف عند الكتلة الأساسية التالية (Unicorn فقط) |
step_until_mnemonic | التوقف عند التعليمة التالية المطابقة للرمز التذكيري، مثل bl، ret (Unicorn فقط) |
poll_events | الاستعلام عن breakpoint_hit و execution_completed وأحداث التتبّع |
| الوصف |
|---|
inspect_objc_msg | فحص استدعاء objc_msgSend: عرض اسم فئة المستقبِل والمُحدِّد، مثل -[NSString length] |
get_objc_class_name | الحصول على اسم فئة ObjC لكائن في عنوان معين (تحليل ذاكرة خالص، دون تغيير الحالة) |
dump_objc_class | تفريغ تعريف فئة ObjC (الخصائص والطرق والبروتوكولات و ivars) |
dump_gpb_protobuf | تفريغ مخطط رسالة GPB protobuf بصيغة .proto (64-bit فقط) |