
إطار ربط وظائف نواة iOS للأجهزة المتوافقة مع checkra1n'able

مخرجات من سجل النواة بعد تجميع وتشغيل example/open1_hook.c
xnuspy هي وحدة pongoOS تقوم بتثبيت استدعاء نظام جديد، xnuspy_ctl، والذي يسمح لك بربط دوال النواة من مساحة المستخدم. وهي تدعم iOS 13.x و iOS 14.x و iOS 15.x على checkra1n 0.12.2 وما فوق. أجهزة 4K غير مدعومة.
هذه الوحدة تعطل تمامًا KTRR/KPP وتجعل من الممكن إنشاء ذاكرة RWX داخل EL1. لا تستخدم هذا على جهازك الأساسي.
يتطلب libusb: brew install libusb
قم بتشغيل make في الدليل الجذري. سيبني المحمل والوحدة.
أضف هذه قبل make.
XNUSPY_DEBUG=1
kprintf).XNUSPY_SERIAL=1
IOLog.XNUSPY_LEAKED_PAGE_LIMIT=n
64. يمكن العثور على مزيد من المعلومات تحت تصحيح أخطاء Kernel Panics.XNUSPY_TRAMP_PAGES=n
XNUSPY_DEBUG و XNUSPY_SERIAL لا يعتمدان على بعضهما البعض.
بعد أن قمت ببناء كل شيء، اجعل checkra1n يقوم بتشغيل جهازك إلى قشرة pongo: /Applications/checkra1n.app/Contents/MacOS/checkra1n -p
في نفس الدليل الذي بنيت فيه المحمل والوحدة، قم بتنفيذ loader/loader module/xnuspy. بعد القيام بذلك، سيقوم xnuspy بعمله وفي بضع ثوانٍ سيتم تشغيل جهازك. سينتظر loader بضع ثوانٍ إضافية بعد إصدار xnuspy-getkernelv في حال احتاج SEPROM إلى الاستغلال.
أحيانًا يتوقف بعض من هواتفي عند "Booting" بعد تشغيل KPF الخاص بـ checkra1n. لم أتمكن بعد من معرفة سبب ذلك، ولكن إذا حدث، حاول مرة أخرى. أيضًا، إذا توقف الجهاز بعد bootx، حاول مرة أخرى. أخيرًا، تعليم الكود المترجم لـ xnuspy_ctl كقابل للتنفيذ على iPhone X الخاص بي الذي يعمل بنظام iOS 13.3.1 هو أمر غير مستقر بعض الشيء، لكنه ينجح بنسبة 100% على هواتفي الأخرى. إذا حدث هلع (panic) مع خطأ إجهاض جلب التعليمات من النواة عند تنفيذ برنامج الربط الخاص بك، حاول مرة أخرى.
سيقوم xnuspy بتصحيح استدعاء نظام enosys للإشارة إلى xnuspy_ctl_tramp. هذا هو trampoline صغير يقوم بتعليم الكود المترجم لـ xnuspy_ctl كقابل للتنفيذ والتفرع إليه. يمكنك العثور على تنفيذ xnuspy_ctl في module/el1/xnuspy_ctl/xnuspy_ctl.c وأمثلة في دليل example.
داخل include/xnuspy/ يوجد xnuspy_ctl.h، ملف رأس يعرف ثوابت لـ xnuspy_ctl. من المفترض أن يتم تضمينه في جميع البرامج التي تقوم بربط دوال النواة.
يمكنك استخدام sysctlbyname لمعرفة أي استدعاء نظام تم تصحيحه:```
size_t oldlen = sizeof(long);
long SYS_xnuspy_ctl = 0;
sysctlbyname("kern.xnuspy_ctl_callnum", &SYS_xnuspy_ctl, &oldlen, NULL, 0);
هذه الاستدعاءات النظامية تأخذ أربع وسائط: `flavor`، `arg1`، `arg2`، و `arg3`.
يمكن أن يكون النكهة إما `XNUSPY_CHECK_IF_PATCHED`، `XNUSPY_INSTALL_HOOK`،
`XNUSPY_REGISTER_DEATH_CALLBACK`، `XNUSPY_CALL_HOOKME`، `XNUSPY_CACHE_READ`،
`XNUSPY_KREAD`، `XNUSPY_KWRITE`، أو `XNUSPY_GET_CURRENT_THREAD`.
يعتمد معنى الوسائط الثلاثة التالية على النكهة.
## `XNUSPY_CHECK_IF_PATCHED`
هذا موجود لتتمكن من التحقق من وجود `xnuspy_ctl`. استدعاؤه بهذه النكهة
سيؤدي إلى إرجاع `999`. يتم تجاهل قيم الوسائط الأخرى.
## `XNUSPY_INSTALL_HOOK`
صممت هذه النكهة لتتوافق مع واجهة برمجة تطبيقات [`MSHookFunction`](http://www.cydiasubstrate.com/api/c/MSHookFunction/).
`arg1` هو عنوان *غير مزاح (UNSLID)* لوظيفة النواة التي ترغب في ربطها. إذا
قدمت عنوانًا مزاحًا، فمن المرجح أن يحدث ذعر (panic). `arg2` هو مؤشر إلى
وظيفة الاستبدال المتوافقة مع ABI الخاصة بك. `arg3` هو مؤشر لـ `xnuspy_ctl` لـ
`copyout` عنوان النقلة (trampoline) الذي يمثل وظيفة النواة الأصلية.
يمكن أن يكون `NULL` إذا كنت لا تنوي استدعاء الوظيفة الأصلية.
## `XNUSPY_REGISTER_DEATH_CALLBACK`
تسمح لك هذه النكهة بتسجيل "استدعاء موت" اختياري (death callback)، وهي وظيفة سيقوم
xnuspy باستدعائها عند خروج برنامج الربط الخاص بك. يمنحك فرصة لتنظيف أي شيء
أنشأته من ربطات النواة الخاصة بك. إذا قمت بإنشاء أي خيوط نواة، فستطلب
منها الإنهاء في هذه الوظيفة.
لا يتم استدعاء الاستدعاء الخاص بك بشكل غير متزامن، لذا إذا قمت بحظر (block)، فأنت تمنع
خيط جمع القمامة (garbage collection thread) الخاص بـ xnuspy من التنفيذ.
`arg1` هو مؤشر إلى وظيفة الاستدعاء الخاصة بك. يتم تجاهل قيم الوسائط الأخرى.
## `XNUSPY_CALL_HOOKME`
`hookme` هو كعب صغير بلغة التجميع (assembly stub) يقوم xnuspy بتصديره من خلال ذاكرة التخزين المؤقت (cache) لـ xnuspy
لربطه. استدعاء `xnuspy_ctl` بهذه النكهة سيؤدي إلى استدعاء `hookme`،
مما يوفر لك طريقة لتحقيق تنفيذ كود النواة بسهولة دون الحاجة إلى ربط وظيفة نواة فعلية.
`arg1` هو وسيطة سيتم تمريرها إلى `hookme` عند استدعائها.
يمكن أن يكون `NULL`.
## `XNUSPY_CACHE_READ`
تمنحك هذه النكهة طريقة للقراءة من ذاكرة التخزين المؤقت لـ xnuspy. تحتوي على العديد من الأشياء
المفيدة مثل `kprintf`، `current_proc`، `kernel_thread_start`، بعض وظائف libc،
وإزاحة (slide) النواة حتى لا تضطر إلى العثور عليها بنفسك. للحصول على قائمة كاملة
بمعرّفات (IDs) ذاكرة التخزين المؤقت، اطّلع على `example/xnuspy_ctl.h`.
`arg1` هو أحد معرّفات ذاكرة التخزين المؤقت المحددة في `xnuspy_ctl.h` و `arg2` هو
مؤشر لـ `xnuspy_ctl` لـ `copyout` العنوان أو القيمة لما طلبته.
يتم تجاهل قيم الوسائط الأخرى.
## `XNUSPY_KREAD`
تمنحك هذه النكهة طريقة سهلة لقراءة ذاكرة النواة من مساحة المستخدم (userspace) دون
tfp0.
`arg1` هو عنوان افتراضي للنواة، `arg2` هو عنوان مخزن مؤقت (buffer) في مساحة المستخدم،
و `arg3` هو حجم ذلك المخزن المؤقت. سيتم كتابة `arg3` بايت
من `arg1` إلى `arg2`.
## `XNUSPY_KWRITE`
تمنحك هذه النكهة طريقة سهلة للكتابة إلى ذاكرة النواة من مساحة المستخدم دون
tfp0.
`arg1` هو عنوان افتراضي للنواة، `arg2` هو عنوان مخزن مؤقت في مساحة المستخدم،
و `arg3` هو حجم ذلك المخزن المؤقت. سيتم كتابة `arg3` بايت
من `arg2` إلى `arg1`.
## `XNUSPY_GET_CURRENT_THREAD`
توفر هذه النكهة لمساحة المستخدم عنوان النواة للخيط المتصل (calling thread).
`arg1` هو مؤشر لـ `xnuspy_ctl` لـ `copyout` القيمة المُرجَعة من
`current_thread`. يتم تجاهل قيم الوسائط الأخرى.
### الأخطاء (Errors)
بالنسبة لجميع النكهات باستثناء `XNUSPY_CHECK_IF_PATCHED`، يتم إرجاع `0` عند النجاح.
عند حدوث خطأ، يتم إرجاع `-1` ويتم تعيين `errno`. لا يعيد `XNUSPY_CHECK_IF_PATCHED`
أي أخطاء. يتم استخدام `mach_to_bsd_errno` في XNU لتحويل
`kern_return_t` إلى `errno المناسب`.
#### الأخطاء المتعلقة بـ `XNUSPY_INSTALL_HOOK`
يتم تعيين `errno` إلى...
- `EEXIST` إذا:
- يوجد ربط بالفعل لوظيفة النواة غير المزاحة المشار إليها بـ `arg1`.
- `ENOMEM` إذا:
- أعاد `unified_kalloc` القيمة `NULL`.
- `ENOSPC` إذا:
- لا توجد بنيات `xnuspy_tramp` حرة، وهي بنية بيانات داخلية
لـ xnuspy. لا ينبغي أن يحدث هذا إلا إذا كنت تقوم بربط مئات وظائف النواة
*في نفس الوقت*. إذا كنت بحاجة إلى المزيد من ربطات الوظائف، اطّلع على [الحدود (Limits)](#limits).
- `ENOTSUP` إذا:
- المتصل ليس من ملف تنفيذي (Mach-O) أو مكتبة ديناميكية.
- `ENOENT` إذا:
- لم يتمكن `mh_for_addr` من تحديد رأس (header) Mach-O المقابل لـ
`arg2` داخل مساحة عنوان المتصل.
- `EFAULT` إذا:
- رأس Mach-O المحدد ليس في الواقع رأس Mach-O. هذا على الأرجح
لن يحدث أبدًا.
- `EIO` إذا:
- لم يُرجع `mach_make_memory_entry_64` إدخال ذاكرة لكامل
قطاعي `__TEXT` و `__DATA` لرأس Mach-O المحدد.
يعتمد `errno` أيضًا على القيمة المُرجَعة من `vm_map_wire_external`،
`mach_vm_map_external`، `mach_make_memory_entry_64`، `copyin`، `copyout`، و
إذا كان ذلك مناسبًا، وظيفة التهيئة لمرة واحدة.
إذا أرجع هذه النكهة خطأً، فلن يتم ربط وظيفة النواة المستهدفة.
إذا مررت مؤشرًا غير `NULL` لـ `arg3`، فقد يكون أو لا يكون قد تم
تهيئته. من غير الآمن استخدامه إذا كان قد تم تهيئته.
#### الأخطاء المتعلقة بـ `XNUSPY_REGISTER_DEATH_CALLBACK`
يتم تعيين `errno` إلى...
- `ENOENT` إذا:
- عملية المتصل لم تقم بربط أي وظائف نواة.
إذا أرجع هذه النكهة خطأً، فلن يتم تسجيل استدعاء الموت الخاص بك.
#### الأخطاء المتعلقة بـ `XNUSPY_CALL_HOOKME`
يتم تعيين `errno` إلى...
- `ENOTSUP` إذا:
- `hookme` بعيد جدًا عن الذاكرة التي تحتوي على بنيات `xnuspy_tramp`
. يتم تحديد ذلك داخل pongoOS، ولا يمكن أن يحدث إلا إذا
اضطر xnuspy إلى الرجوع إلى كود غير مستخدم موجود بالفعل داخل kernelcache.
في هذه الحالة، من شبه المؤكد أن استدعاء `hookme` سيسبب ذعرًا في النواة،
وسيتعين عليك إيجاد وظيفة نواة أخرى لربطها.
إذا أرجع هذه النكهة خطأً، فلن يتم استدعاء `hookme`.
#### الأخطاء المتعلقة بـ `XNUSPY_CACHE_READ`
يتم تعيين `errno` إلى...
- `EINVAL` إذا:
- الثابت المشار إليه بـ `arg1` لا يمثل أي شيء في ذاكرة التخزين المؤقت.
- كان `arg1` هو `IO_LOCK`، ولكن النواة هي iOS 14.4.2 أو أقل أو iOS 15.x.
- كان `arg1` هو `IPC_OBJECT_LOCK`، ولكن النواة هي iOS 15.x.
- كان `arg1` هو `IPC_PORT_RELEASE_SEND`، ولكن النواة هي iOS 14.5 أو أعلى.
- كان `arg1` هو `IPC_PORT_RELEASE_SEND_AND_UNLOCK`، ولكن النواة هي iOS 14.4.2 أو أقل.
- كان `arg1` هو `KALLOC_CANBLOCK`، ولكن النواة هي iOS 14.x أو أعلى.
- كان `arg1` هو `KALLOC_EXTERNAL`، ولكن النواة هي iOS 13.x.
- كان `arg1` هو `KFREE_ADDR`، ولكن النواة هي iOS 14.x أو أعلى.
- كان `arg1` هو `KFREE_EXT`، ولكن النواة هي iOS 13.x.
- كان `arg1` هو `PROC_REF`، ولكن النواة هي iOS 14.8 أو أقل.
- كان `arg1` هو `PROC_REF_LOCKED`، ولكن النواة هي iOS 15.x.
- كان `arg1` هو `PROC_RELE`، ولكن النواة هي iOS 14.8 أو أقل.
- كان `arg1` هو `PROC_RELE_LOCKED`، ولكن النواة هي iOS 15.x.
- كان `arg1` هو `VM_MAP_UNWIRE`، ولكن النواة هي iOS 15.x.
- كان `arg1` هو `VM_MAP_UNWIRE_NESTED`، ولكن النواة هي iOS 14.8 أو أقل.
يعتمد `errno` أيضًا على القيمة المُرجَعة من `copyout` وإذا كان ذلك مناسبًا،
القيمة المُرجَعة من وظيفة التهيئة لمرة واحدة.
إذا أرجع هذه النكهة خطأً، فإن المؤشر الذي مررته لـ `arg2` لم يتم
تهيئته.
#### الأخطاء المتعلقة بـ `XNUSPY_KREAD` و `XNUSPY_KWRITE`
يتم تعيين `errno` إلى...
- `EFAULT` إذا:
- فشلت ترجمة العنوان (address translation) لـ `arg1` أو `arg2`. إذا قمت بالتجميع باستخدام
`XNUSPY_DEBUG=1`، يتم طباعة رسالة حول ذلك في سجل النواة.
إذا أرجع هذه النكهة خطأً، لم تتم قراءة/كتابة ذاكرة النواة.
#### الأخطاء المتعلقة بـ `XNUSPY_GET_CURRENT_THREAD`
إذا فشل `copyout`، يتم تعيين `errno` إلى قيمته المُرجَعة.
# معلومات هامة (Important Information)
### المزالق الشائعة (Common Pitfalls)
أثناء كتابة وظائف الاستبدال، كان من السهل نسيان أنني كنت أكتب
كود نواة. إليك بعض الأمور التي يجب وضعها في الاعتبار عند كتابة ربطات:
- *لا يمكنك تنفيذ أي كود من مساحة المستخدم يوجد خارج قطاع `__TEXT` لبرنامجك*.
ستحدث ذعر (panic) إذا قمت، على سبيل المثال، باستدعاء `printf` عن طريق الخطأ
بدلاً من `kprintf`. تحتاج إلى إعادة تنفيذ أي وظيفة libc تريد استدعائها
إذا كانت هذه الوظيفة غير متوفرة بالفعل عبر `XNUSPY_CACHE_READ`.
يمكنك إنشاء مؤشرات وظائف لوظائف نواة أخرى واستدعاءها، على الرغم من ذلك.
- *العديد من وحدات الماكرو (macros) المستخدمة بشكل شائع في كود مساحة المستخدم غير آمنة للنواة.* على
سبيل المثال، `PAGE_SIZE` يتم توسيعه إلى `vm_page_size`، وليس ثابتًا. تحتاج
إلى تعطيل PAN (على A10+، وهو الأمر الذي لا أوصي به أيضًا) قبل قراءة هذا
المتغير وإلا ستحدث ذعر.
- *تأكد من تجميع كودك باستخدام `-fno-stack-protector` و `-D_FORTIFY_SOURCE=0`*. في بعض الحالات،
سيحتاج الجهاز إلى قراءة `___stack_chk_guard` عن طريق إلغاء الإشارة لمؤشر آخر في مساحة المستخدم،
مما سيحدث ذعرًا على A10+.
- *للأمان فقط، لا تقم بتجميع برامج الربط الخاصة بك مع تحسينات المحول البرمجي (compiler optimizations).*
يُنصح أيضًا بتصفح https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/KernelProgramming/style/style.html.
### تصحيح أخطاء ذعر النواة (Debugging Kernel Panics)
الأخطاء أمر لا مفر منه عند كتابة الكود، لذا في النهاية ستسبب
ذعرًا في النواة. الذعر لا يعني بالضرورة وجود خطأ في xnuspy، لذا
قبل فتح مشكلة (issue)، يرجى التأكد من أنك لا تزال تتعرض للذعر عندما تفعل
لا شيء سوى استدعاء الوظيفة الأصلية وإرجاع قيمتها (إذا لزم الأمر). إذا
كنت لا تزال تتعرض للذعر، فمن المحتمل أن يكون خطأ في xnuspy (ويُرجى فتح مشكلة)،
ولكن إذا لم يكن الأمر كذلك، فهناك خطأ ما في وظيفة الاستبدال الخاصة بك.
نظرًا لأن xnuspy لا يعيد توجيه التنفيذ فعليًا إلى صفحات EL0، فإن تصحيح
أخطاء الذعر ليس بالأمر المباشر. افتح `module/el1/xnuspy_ctl/xnuspy_ctl.c`،
وقبل الاستدعاء الوحيد لـ `kwrite_instr` في `xnuspy_install_hook`،
أضف استدعاءً لـ `IOSleep` لبضع ثوانٍ. يتم ذلك للتأكد من وجود
وقت كافٍ قبل أن يحدث ذعر للجهاز حتى تنتشر السجلات. أعد تجميع xnuspy باستخدام
`XNUSPY_DEBUG=1 make -B` وقم بتحميل الوحدة مرة أخرى. بعد تحميل الوحدة،
إذا لم تكن قد قمت بذلك بالفعل، قم بتجميع `klog` من `klog/`. ارفعه إلى جهازك
وقم بتنفيذ `stdbuf -o0 ./klog | grep shared_mapping_kva`. قم بتشغيل برنامج الربط الخاص بك مرة أخرى
وراقب ظهور سطر من `klog` يبدو هكذا:
`shared_mapping_kva: dist 0x7af4 uaddr 0x104797af4 umh 0x104790000 kmh 0xfffffff00c90c000`
إذا كنت تقوم بتثبيت أكثر من ربط واحد، فسيكون هناك أكثر من حدوث واحد.
في هذه الحالة، ستختلف `dist` و `uaddr`، لكن `umh` و `kmh` لن تختلف. `kmh`
تشير إلى بداية تعيين النواة لقطاع `__TEXT` لبرنامجك.
ضع برنامج الربط الخاص بك في المفكك (disassembler) المفضل لديك وأعد تحديد أساسه (rebase) بحيث يكون رأس Mach-O الخاص به
في عنوان `kmh`. بالنسبة لـ IDA Pro، هذا هو `Edit -> Segments -> Rebase
program...` مع تفعيل `Image base`. بعد أن يحدث ذعر لجهازك ويعيد التشغيل مرة أخرى،
إذا كانت هناك عناوين تتوافق مع تعيين النواة لاستبدالك
في سجل الذعر، فسوف تتطابق مع المفكك. إذا لم يكن هناك أي منها، فمن المحتمل
أن يكون لديك نوع ما من تلف الذاكرة الخفي (subtle memory corruption) داخل استبدالك.
لا يمتلك xnuspy أيضًا طريقة لمعرفة ما إذا كان خيط النواة لا يزال ينفذ (أو سينفذ)
على تعيين النواة لقطاع `__TEXT` لبرنامجك بعد إلغاء تثبيت ربطاتك.
أحد الأشياء التي يفعلها xnuspy للتعامل مع هذا هو عدم إلغاء تخصيص هذا التعيين فورًا
بعد موت برنامج الربط الخاص بك. بدلاً من ذلك، يتم إضافته إلى نهاية قائمة انتظار.
بمجرد أن يلاحظ خيط جمع القمامة الخاص بـ xnuspy تجاوز حد معين فيما يتعلق بعدد الصفحات (pages) من التعيينات المحتجزة
في تلك القائمة، سيبدأ في إلغاء التخصيص من مقدمة القائمة وسيستمر حتى لا يتم تجاوز ذلك الحد. بشكل افتراضي، هذا الحد هو 1 ميجابايت،
أو 64 صفحة.
بينما يساعد هذا بشكل كبير، كلما زاد حجم قطاعي `__TEXT` و `__DATA`
لبرنامج الربط الخاص بك، كلما قلت احتمالية فوز xnuspy بهذا السباق. إذا كنت
تتعرض للذعر بانتظام وكان لديك برنامج ربط كبير إلى حد ما، جرب زيادة
هذا الحد عن طريق إضافة `XNUSPY_LEAKED_PAGE_LIMIT=n` قبل `make`. سيؤدي هذا إلى تعيين
هذا الحد إلى `n` صفحة بدلاً من 64.
### الحدود (Limits)
يحتفظ xnuspy بصفحة واحدة من ذاكرة النواة الثابتة قبل تشغيل XNU لبنياته
`xnuspy_tramp`، مما يتيح لك ربط حوالي 225 وظيفة نواة في نفس الوقت. إذا كنت تريد
المزيد، يمكنك إضافة `XNUSPY_TRAMP_PAGES=n` قبل `make`. سيعطي هذا تعليمات لـ xnuspy
باحتجاز `n` صفحة من الذاكرة الثابتة لهياكل `xnuspy_tramp`. ومع ذلك، إذا
اضطر xnuspy إلى الرجوع إلى كود غير مستخدم موجود بالفعل داخل kernelcache، فسيتم تجاهل هذا.
متى يحدث هذا موضح في [كيف يعمل](#how-it-works).
### التسجيل (Logging)
لسبب ما، لا تظهر السجلات من `os_log_with_args` في التدفق
الناتج من أداة سطر الأوامر `oslog`. سجلات `kprintf` لا تصل
إلى هناك أيضًا، لكنها *يمكن* رؤيتها باستخدام `dmesg`. ومع ذلك، `dmesg`
ليس بثًا مباشرًا، لذا كتبت `klog`، وهي أداة تعرض سجلات `kprintf`
في الوقت الفعلي. تجدها في `klog/`. أوصي بشدة باستخدام ذلك بدلاً
من إرسال `dmesg` بشكل متكرر لرسائل `kprintf` الخاصة بك.
إذا حصلت على `open: Resource busy` بعد تشغيل `klog`، قم بتشغيل هذا الأمر
`launchctl unload /System/Library/LaunchDaemons/com.apple.syslogd.plist`
وحاول مرة أخرى.
لسوء الحظ، لن تتمكن من رؤية أي `NSLog` إذا كان
`atm_diagnostic_config=0x20000000` مضبوطًا في bootargs الخاص بـ XNU. يعتمد `klog`
على وجود وسيط التشغيل (boot argument) هذا. إذا كنت تريد إعادة `NSLog`، قم بإزالة
وسيط التشغيل هذا من `pongo_send_command` داخل `loader.c`.
### إلغاء تثبيت الربط (Hook Uninstallation)
سيدير xnuspy هذا لك. بمجرد خروج عملية، يتم إلغاء تثبيت جميع ربطات النواة
التي تم تثبيتها بواسطة تلك العملية في غضون ثانية أو نحو ذلك.
### وظائف النواة القابلة للربط (Hookable Kernel Functions)
معظم أطر ربط الوظائف لديها حد أدنى لطول معين يجعل وظيفة معينة قابلة للربط.
لدى xnuspy هذا الحد *فقط* إذا كنت تخطط لاستدعاء الوظيفة الأصلية
*و* التعليمات الأولى للوظيفة المربوطة ليست `B`. في هذه الحالة،
الحد الأدنى للطول هو ثمانية بايتات. خلاف ذلك، لا يوجد حد أدنى للطول.
يستخدم xnuspy `X16` و `X17` لنقلاته (trampolines)، لذا وظائف النواة التي
تتوقع استمرار هذه التسجيلات عبر استدعاءات الوظائف لا يمكن ربطها (لا يوجد
الكثير منها التي تتوقع ذلك). إذا كانت الوظيفة التي تريد ربطها تبدأ بـ `BL`،
وتعتزم استدعاء الأصلية، يمكنك فقط القيام بذلك إذا كان تنفيذ
الوظيفة الأصلية لا يعدل `X17`.
### سلامة الخيوط (Thread-safety)
سوف يقوم `xnuspy_ctl` بإجراء تهيئة لمرة واحدة في المرة الأولى التي يتم استدعاؤها
بعد التشغيل الجديد. هذا هو الجزء الوحيد من xnuspy الذي يمكن أن يحدث له سباق (raceable) حيث
لا يمكنني تهيئة قفل القراءة/الكتابة الذي أستخدمه بشكل ثابت. بعد أن يعود
الاستدعاء الأول، يتم ضمان أن أي استدعاءات مستقبلية آمنة من حيث الخيوط.
# كيف يعمل (How It Works)
هذا مبسط، لكنه يلتقط الفكرة الرئيسية جيدًا. ربط وظيفة في xnuspy
هو بنية توجد على ذاكرة نواة قابلة للكتابة والتنفيذ. في معظم الحالات،
هذه هي الذاكرة التي يعيدها `alloc_static` داخل pongoOS. يمكن اختصارها
إلى هذا:```
struct {
uint64_t replacement;
uint32_t tramp[2];
uint32_t orig[10];
};
حيث replacement هو العنوان الافتراضي للنواة (سيتم شرحه لاحقًا) لدالة الاستبدال، و tramp هو ترامبولين صغير يعيد توجيه التنفيذ إلى replacement، و orig هو ترامبولين أكبر وأكثر تعقيدًا يمثل الدالة الأصلية.
أحد أول الأشياء التي يقوم بها xnuspy هو تحديد مكان وجود استبدال EL0 داخل مساحة عنوان العملية المستدعية. يتم ذلك حتى يمكن ربط دوال النواة من المكتبات الديناميكية. يتم حفظ رأس Mach-O الذي يتوافق مع عنوان ذلك الاستبدال.
بعد ذلك، يتم إنشاء تعيين مشترك بين المستخدم والنواة لقطاعي __TEXT و __DATA لذلك الرأس (بالإضافة إلى أي قطاع بينهما، إن وجد). __TEXT مشترك حتى تتمكن من استدعاء دوال أخرى من خطافاتك. __DATA مشترك حتى تكون التغييرات على المتغيرات العالمية مرئية لكل من EL1 و EL0.
نظرًا لأن هذا التعيين هو نسخة واحد إلى واحد من __TEXT و __DATA، فمن السهل معرفة عنوان دالة الاستبدال الخاصة بالمستخدم عليه. بالنظر إلى عنوان رأس Mach-O للعملية المستدعية u، وعنوان بداية التعيين المشترك k، وعنوان دالة الاستبدال الخاصة بالمستخدم r، نطبق الصيغة التالية: replacement = k + (r - u)
بعد ذلك، replacement هو العنوان الافتراضي للنواة لدالة الاستبدال الخاصة بالمستخدم على التعيين المشترك ويتم كتابته في بنية خطاف الدالة. لا يقوم xnuspy بإعادة توجيه التنفيذ إلى عنوان EL0 لدالة الاستبدال لأن ذلك غير آمن للغاية: لا يضعنا ذلك تحت رحمة المجدول فحسب، بل لا يمنحنا أي سيطرة على السيناريو الذي تموت فيه عملية بها خطاف نواة بينما لا يزال خيط النواة ينفذ على الاستبدال.
أخيرًا، يتم وضع علامة على التعيين المشترك كقابل للتنفيذ ويتم تجميع فرع فوري غير مشروط (B). يوجه التنفيذ إلى بداية tramp، وهو ما يحل محل التعليمات الأولى لدالة النواة التي تم ربطها الآن. لسوء الحظ، يحدنا هذا من التفرع إلى هياكل الخطاف على بعد أكثر من 128 ميغابايت من دالة نواة معينة. يقوم xnuspy بالتحقق من هذا السيناريو قبل الإقلاع ويعود إلى الكود غير المستخدم الموجود بالفعل في kernelcache لتستقر عليه هياكل الخطاف بدلاً من ذلك إذا وجد أن هذا قد يحدث.
أبذل قصارى جهدي للتأكد من أن أدوات البحث عن التصحيحات تعمل، لذا إذا كان هناك شيء لا يعمل، يرجى فتح مشكلة.