
أبحاث استغلال نواة تحقق صلاحيات root مؤقتة على Amazon Fire 7 (Fire OS 7.3.3.1) عبر ثغرة use-after-free في Mali kbase JIT بالمعرّف CVE-2022-38181، مع سلسلة استغلال تعتمد على الكتابة فوق modprobe_path.
مشروع بمساعدة الذكاء الاصطناعي. تم إنتاج هذا البحث وتطوير الاستغلال والتوثيق بمساعدة الذكاء الاصطناعي باستخدام النموذجين GLM-5.3 و DeepSeek V4.1 Flash.
بحث استغلال الجذر (root exploit) لجهاز Amazon Fire 7 الجيل التاسع (mustang, MT8163, Mali-T720) على البرنامج الثابت النهائي — Fire OS 7.3.3.1, PS7331.4463N, kernel 4.9.117 (built 2025-05-03, SPL 2024-08-01).
الهدف: LineageOS. مسار الـ bootloader ميت على هذه الوحدة (bootrom مُرقّع — preloader فقط عبر CMD short)، لذا المسار الوحيد المتبقي هو استغلال برمجي للنواة (kernel exploit).
nix-shell -p android-tools --run './run.sh' # add -p zig too if no zig
عند النجاح:```
/data/metrics/su id # run a command as root
/data/metrics/su # interactive root shell
يفوز reclaim بنحو إقلاع واحد من كل 3، والخسارة تؤدي إلى panic/إعادة تشغيل الجهاز اللوحي؛
run.sh ينتظر فقط إعادة التشغيل ويعيد المحاولة. يتم فرض SELinux إلى Permissive كجزء
من الاستغلال، لذا فإن root يعمل وقت التشغيل فقط — إعادة التشغيل تستعيد الوضع الأصلي
وتعيد تشغيل run.sh.
يتم تضمين st3 و su المبنيين مسبقًا (armv7 static)، لذا لا حاجة إلى toolchain
للتشغيل. ./run.sh --build يعيد بناءهما من poc/*.c إذا كان لديك zig.
GhostLock (أدناه) متوقف مؤقتًا: متغير rtmutex BUG_ON الخاص بـ MTK + عدم الكشف عن عنوان kernel من shell = طريق مسدود معماري على هذا البناء (الجلسات 2-4). تمت إعادة تشخيص kbase JIT UAF (كان "panic غير المشروط" في destroy-worker هو JIT_FREE deref، وفُقد السجل بسبب موت adbd في منتصف الـ panic) والمرحلة 2 الآن مُثبتة بالأوراكل — انظر قسم الجلسة 5.
rtmutex remove_waiter() futex-PI stack-UAF (إفصاح NebuSec 2026-07، الإصلاح 3bfdc63936dd
وصل 2026-04). النطاق المعرّض 2.6.39–7.1 → إصدارنا 4.9.117 (مايو 2025) متأثر.
تم التحقق على بنائنا الدقيق:
CONFIG_FUTEX=y، rtmutex مُجمّع، الخلل موجود حرفيًا:
rtmutex.c:1108-1111 يستخدم current->pi_lock/current->pi_blocked_on (يجب أن يكون
waiter->task)؛ موقع الاستدعاء المعطوب rtmutex.c:1723 (مسار خطأ rt_mutex_start_proxy_lock)WAIT_REQUEUE_PI/CMP_REQUEUE_PI)، لا device node،
لا شيء محمي بـ SELinux — العوائق القاتلة في مسار kbase غير موجودة هناsched_setattr → __sched_setscheduler → rt_mutex_adjust_pi(p) عند
sched/core.c:4706 — يعمل deref لـ القديم ✓exp32/main.crt_waiter مقابل منطقة do_sys_select fd_set — فك تجميع
vmlinux الخاص بنا (do_sys_select stack_fds مقابل إطار futex_wait_requeue_pi)، كشف
STAMP_NFDS/STAMP_WAITER_OFF كمعاملات قابلة للضبطFailed critical init step 3/dev/mali0 world-RW + SELinux gpu_device، kbase r26p0-01rel0mustang، Fire OS 7.3.3.1 PS7331.4463N/00315758630404.9.117-g08fe75b-dirty، بُني السبت 3 مايو 01:25:15 UTC 2025 (Linaro GCC 6.3-2017.05)/dev/kb، /dev/dkb (أقسام النسخ الاحتياطي لنواة Amazon) root:drmrpc 0660 — مقفلةmali_kbase r26p0-01rel0 (Midgard، Mali-T720)، داخل نطاق NVD المتأثر r4p0–r31p0mali_kbase_mem.c:2721 kbase_jit_destroy_worker يحرر المنطقة، لا يمسح kctx->jit_alloc[id] أبدًاmali_kbase_softjobs.c:1270 kbase_jit_free_finish يعمل deref لـ jit_alloc[ids[j]] القديمmali_kbase_mem.c:3138 kbase_jit_backing_lost → مسار destroy (يُفعّل أثناء reclaim)0xc0008000 VA ثابت / 0x40080000 PA)ARM_SW_DOMAIN_PAN → ret2usr قابل للتطبيق؛ CONFIG_PANIC_ON_OOPS=y (المحاولات الفاشلة = إعادة تشغيل)SLAB_FREELIST_RANDOM/HARDENED، لا CONFIG_USER_NS/USERFAULTFD/NF_TABLESCONFIG_MODULES=y، لا STATIC_USERMODEHELPER → الكتابة فوق modprobe_path = root_IOC_TYPE 0x80)MEM_ALLOC بحجم 32 بايت (in يحتوي على 4 × u64 بما في ذلك extent)BASE_MEM_PROT_GPU_RD|WR (بتات 2|3)، وليس R|W القديمةmmap(fd, offset=3<<12, PROT_NONE)JOB_SUBMIT قيمة sizeof(base_jd_atom_v2) = 48 (base_jd_prio/base_jd_dep_type هي typedefs من نوع u8)MEM_JIT_INIT (nr 14، بنية v2)، alloc/free هي soft jobs عبر JOB_SUBMIT
(BASE_JD_REQ_SOFT_JIT_ALLOC=0x209، ...FREE=0x20a؛ =مؤشر مستخدم، =العدد)يحدث panic أثناء الإخلاء نفسه (evictable_reclaim_scan_objects → backing_lost →
destroy worker) — يتم اجتياز المراجع المعلقة (jit_alloc[]، قائمة evict) قبل أن نرسل
JIT_FREE أبدًا. يجب أن تفوز المرحلة 2 بالسباق: إعادة تخصيص kbase_va_region المحرر
بـ spray MEM_ALLOC الخاص بنا بينما الضغط لا يزال جاريًا.
poc/stage2.c — استغلال المرحلة 2 (الأوضاع: step/uaf/spstep/spfree/spray/keys)
— spray 700 = تشغيل أوراكل كامل؛ ينجو ويتوقف مؤقتًا (اقتل للتنظيف)poc/mustang_jit_uaf.c — PoC المرحلة 1 (الأوضاع: jit N / control N / pressure N)poc/build.sh — بناء متقاطع بـ zig (static musl armv7)kernel/vmlinux — الرموز المستعادة من بناء OTA الدقيق (vmlinux-to-elf)nix-shell -p zig --run 'zig cc -target arm-linux-musleabihf -static -O2 -o juaf poc/mustang_jit_uaf.c' adb push juaf /data/local/tmp/juaf && adb shell chmod 755 /data/local/tmp/juaf adb shell /data/local/tmp/juaf jit 700 # full trigger (~reboots device) adb shell /data/local/tmp/juaf control 700 # no-JIT control adb shell /data/local/tmp/juaf pressure 700 # raw memory-pressure control
## المراجع
- إرشاد GHSL-2022-054: https://securitylab.github.com/advisories/GHSL-2022-054_Arm_Mali/
- تدوينة Mo عن استغلال Pixel 6: https://github.blog/2023-01-23-pwning-the-all-google-phone-with-a-non-google-bug/
- سابقة Fire HD 10 (trona)، نفس عائلة العلّة: ericpardee.github.io/fire-hd-ownership
- بوابة Amazon للمصادر المفتوحة: amazon.com gp/help/customer/display.html nodeId=200203720
- خيط فتح XDA (ميت لهذه المراجعة العتادية): xdaforums.com/t/fire-7-2019-mustang-unbrick-downgrade-unlock-root.3944365/
## ملحق الجلسة 3 (مسح معمّق لطوابع نداءات النظام)
أعماق النسخ المصدرية المقيسة (مطلقة مقابل sp0 عند مدخل نداء النظام؛ يمتد المُنتظِر من -0x1d8 إلى -0x1a8):
- sendto sockaddr @ -0xc4 | process_vm iov @ -0x104 | recvmsg iov @ -0x11c
- sendmsg iov @ -0x12c | recvmmsg iov @ -0x154 | select fds @ -0x174
- pselect6 fds @ -0x19c | **sendmmsg iov @ -0x1bc (الأفضل — أقصر بـ 0x1c من waiter+0x00)**
- poll entries @ -0x3e0 (بالكامل أدناه؛ الجانب الخاطئ)
استُبعدت في هذه الجلسة:
- سلسلة io_submit ضحلة جدًا (~-0x130) | semtimedop: CONFIG_SYSVIPC=n (stub)
- configfs مُثبّت لكن بصفر أنظمة فرعية مسجّلة (لا أهداف mkdir)
- /sys/kernel/debug، /config: مرفوضة من SELinux للصدفة
- /proc/sys/kernel: getdents يعمل (29 مدخلًا مُدرجًا)، فقط pid_max قابل للفتح؛
قراءات kptr_restrict/hotplug/hostname/domainname كلها مرفوضة
- قيمة الكتابة دائمًا waiter+0 (عنوان مكدّس النواة، قابل للتنفيذ، shellcode عند
+0x1c): rb_link_node *link = node، insert_color يكتب parent-color — لا
يوجد متغيّر بقيمة متحكَّم بها دون ختم حقول الشجرة (فجوة -0x1d8..-0x1bc)
- حقول الإرسال ذات الإشارة المزدوجة تقرأ *(waiter+0)=1، *(waiter+4/+8)=0 — BLX 1/0
(nf_hooks، net_families، inet[6]_protos، seq_file->op كلها ميتة)
- timer_list.function@+0xc و work_struct.func@+0xc ستقرأ *(waiter+0xc) =
pi_tree self-ptr = waiter+0xc قابل للتنفيذ — لكن لا مسار يضع waiter+0 كـ
timer/work (تلف الروابط / لا مصادر طابور غير مباشرة)
- جذر الشجرة غير صفري (فتحة معالج sysctl) = تتبّع مؤشر حتمي عبر
.text كشجرة rb؛ ينتهي عند كلمة صفرية — قابل للمحاكاة دون اتصال، لكن الهبوط
في فتحة قابلة للكتابة مفيدة غير معقول
خيوط متبقية للجلسة 4:
1. مسارات ioctl العميقة: نسخ dev_ioctl ifreq (40B بيانات مستخدم) — قِس
عمق سلسلة SyS_ioctl→sock_ioctl→dev_ioctl مقابل -0x1d8
2. أي نسخ آخر أعمق بـ 0x1c من sendmmsg (لم يُعثر على شيء بعد)
3. إذا فشل البحث عن سطح الختم: أعد النظر في البنى المتسلسلة بالمشي أو
ابحث عن أصناف فتحات قابلة للكتابة-الصفرية-المُستدعاة لم تُعدّد بعد
## الجلسة 4 — الاختراقان
### 1. ملف rtmutex_common.h من MTK هو اللغز بأكمله
استبدلت MTK النسخة الأصلية الآمنة ضد NULL rt_mutex_top_waiter بـ:```c
w = rb_entry(lock->waiters_leftmost, struct rt_mutex_waiter, tree_entry);
BUG_ON(w->lock != lock); // compiled to: ldr sb,[lock+8]; ldr r3,[sb+0x1c]; cmp; bne→udf#0x12
NO NULL CHECK + BUG_ON. كل مرساة صفرية بالكامل تموت عند *(NULL+0x1c)؛ المراسي العشوائية تموت عند udf. THE WALK REQUIRES: lock->waiters_leftmost (lock+8) يجب أن يشير إلى waiter وهمي W (قابل للكتابة) مع W->lock (+0x1c) == lock.
تم رسم تدفق المشي بالكامل (rt_mutex_adjust_prio_chain @ 0xc0189a58):
/proc/self/task//stat الحقل 28 (kstkesp) يُرجع SP حقيقي للنواة للخيوط المحجوبة بـ syscall من سياق SHELL (تم التحقق: قيم غير صفرية ملاحظة).
المشي يُطلق بشكل حتمي (dead-lock probe: crash في كل مرة، كود نظيف). الكتابة لا يمكن أن تهبط بسبب مصادفة تصلب النواة ثلاثية الاتجاه:
نافذة الطابع (waiter+0x1c..0x5b) هي الذاكرة الوحيدة المتحكم بها ومعروفة المحتوى، لكن عنوانها هو المجهول الذي نحتاجه. البنى المرجعية الذاتية كلها تتطلب ختم عنوان نواة كثابت — دائري بدون تسريب.
نواة 5.4 الخاصة بهم لديها UPSTREAM rtmutex_top_waiter (آمن من NULL: if (!leftmost) return NULL) — مراسي الشجرة الفارغة تنجو، كتابتهم هبطت على null_fops
(قابل للكتابة على نواتهم). حتى هم عالقون عند الإرسال ("ioctl reboot").
شجرة Mustang 4.9.117 MTK لديها متغير BUG_ON — أجهزة Fire OS 8 اللوحية
(GhostLock-5.10) نجحت لأن نوى 5.10 الخاصة بهم بأسلوب upstream.
تم اختباره وميت من نطاق shell:
الخلاصة: GhostLock على mustang يتطلب إفصاحًا عن عنوان النواة لا تكشفه هذه النواة لنطاق shell. القفل الوهمي المرجعي الذاتي لا يمكن بناؤه بدونه.
(a) طحن حتمي عند الإقلاع: إعادة تشغيل → معايرة عنوان المكدس عبر crash oracle (~20-30 إعادة تشغيل)، التحقق من قابلية التكرار. فرصة بعيدة — تخصيص مكدس الخيط في الإقلاع المتأخر غير مرجح أن يكون مستقرًا. (b) PIVOT مرة أخرى إلى kbase CVE-2022-38181 مع الأصول المتراكمة: vmlinux للبناء الدقيق + المصدر الكامل + toolchain + انضباط تتبع O_SYNC + معرفة عميقة بـ 4.9. العائق الأصلي (panic destroy-worker أثناء إخلاء JIT) هو مشكلة توقيت spray، مفهومة الآن بشكل أفضل. (c) التوقف عند ~45% الصادقة: trigger مُثبت، المشي مُرسم حتى التعليمة، الكتابة محجوبة بواسطة MTK BUG_ON + لا تسريب.
موصى به: (b) — خطأ kbase مُتحقق من وجوده في هذا المصدر الدقيق، كان لديه trigger عامل، وعائقه ميكانيكي، وليس معماريًا.
وضع step (alloc id=1 → DONT_NEED → ضغط 700MB → MEM_QUERY، بدون free)
ينجو: query=-1 (المنطقة حُررت بواسطة destroy worker، rbtree-clean).
مسار worker مطابق بايت ببايت لتدفق JIT_FREE-تحت-الضغط القانوني.
انهيار الجلسة 1 كان دائمًا deref المتدلي JIT_FREE؛ سطر السجل الخاص به
ضاع لأن panic يقتل adbd أثناء flush. تم التحقق مرتين إضافيتين مع وضع uaf
(free عاري → panic، نفس قطع السجل). تدفق GHSL-2022-054 حي بالكامل على هذا البناء.
kbase_jit_free(kctx, reg) @ 0xc058495c مع reg وهمي متحكم به بالكامل:
reg->cpu_alloc NULL → حجم مدعوم 0 → كتلة trim متخطاة (0xc0584978)kctx+0x147dd (بايت) + kctx+0x147de+bin_id (بايت)mark_reclaim(reg->gpu_alloc) @ 0xc059b158: سلسلة
K=*(gpu_alloc+0x38) → *(K+0x1429c)==0 يتخطى mm-atomics →
atomic_sub nents@K+0x141c8، D=*(K+4) → atomic_sub nents@D+0x538.
مع nents=0 جميع الكتابات هي مخازن no-op (strex لنفس القيمة).reg->flags |= 0x100000 (كتابة في الوهمي، حميدة)list_add(gpu_alloc->evict_node, &kctx->evict_list): رأس evict_list
@ kctx+0x1427c؛ كتابات في gpu_alloc+0x18/0x1c (يجب أن تكون قابلة للكتابة)r3=*(reg+0x3c) prev, r2=*(reg+0x38) next
→ *(next+4)=prev; *(prev+0)=next — كتابتان تعسفيتان write-what-where،
ثم إعادة ربط reg+0x38 في jit_pool_head @ kctx+0x148e89 مرشحين؛ S=0xc118b7ec (بيانات xfrm، خاملة على هذا الجهاز):
*(S+8)=0 (nents)، *(S+0x18)=S+0x18 (evict_node فارغ → لا WARN)،
K=*(S+0x38)=0xc118b820 → *(K+0x1429c)=0، K/D+0x141c8/+0x538 كلها في
بيانات قابلة للكتابة. أهداف Oracle مُجهزة: init_uts_ns.name.nodename=0xc110d561
("(none)"، قابلة للقراءة عبر uname)، scratch P=0xc118bd58 (أصفار xfrm).
تجنب S=0xc111cba4 (ملاصق لـ tracepoint). CONFIG_DEBUG_RODATA=y → جميع أهداف
الكتابة يجب أن تكون في .data/.bss (bss 0xc11d9000-0xc12d9000).
add_key (CONFIG_KEYS=y): مرفوض من SELinux لـ shell. ميت.setxattr: kvmalloc(96)+copy_from_user يحدث قبل
فحص SELinux → رقصة alloc محصنة من SELinux حتى عندما يفشل الاستدعاء؛
عابر (يُحرر عند نهاية syscall)، البايتات تستمر عند +4..95 (مؤشر freelist
يطمس +0..3 = rblink، غير مستخدم بواسطة kbase_jit_free)تشغيل spray 700 2026-09-11: 5895 منطقة مرشوشة أثناء الضغط، JIT_FREE
على id=1 المتدلي اكتمل على منطقة مُسترجعَة، ثم
JIT_ALLOC(0x40, bin 0) مشى jit_pool_head وأرجع VA المنطقة المرشوشة
#4251 (0x142701000) — المنطقة الدقيقة التي استهلكها المؤشر المتدلي.
قتل العملية بعد: تفكيك kctx نظيف، لا crash. Stage 2 مكتمل:
إعادة توجيه UAF حتمية مع نوع كائن ومحتويات متحكم بها.
استرجاع نوع المنطقة يعطي نجاة الرقصة القانونية لكن jit_node مُهيأ ذاتيًا → لا بدائية unlink. نحتاج بايتات خام عند +0x38/+0x3c:
*(N+4)=P مع P=صفحة shellcode في userland (لا PAN!) —
المرشحون: const fops هي .rodata (DEBUG_RODATA) → استهدف fn ptr غير const
في .data، أو رأس قائمة binfmt formats، أو sysctl proc_handler
(تحقق من قابلية كتابة الجدول). احتياطي: modprobe_path عبر كتابات
مسلسلة بالبايت (القيم يجب أن تكون عناوين قابلة للكتابة — استخدم أهدافًا بشكل مؤشر)poc/stage3.c):
kern_table[pid_max].proc_handler @ 0xc1113f40 (قابل للكتابة
.data، تم التحقق عبر مسح مؤشر السلسلة + handler == proc_dointvec_minmax)b +8؛ W2 يكتب N عند P = حقل handler)read /proc/sys/kernel/pid_max
(قابل للقراءة من shell)؛ يعمل في سياق مهمته الخاصة → creds تنطبق عليناinotify_handle_event يخصص kmalloc name_len+0x1d،
بايتات الاسم (متحكم بها بالكامل، قيد خالٍ من NUL/slash) عند event+0x1c؛
name_len=60 → kmalloc-96؛ في قائمة الانتظار → محتفظ بها؛ 4 نسخ → 4 أحداث لكل
rename؛ SELinux-OK من shell. الوهمي أُعيد تصميمه خاليًا من NUL: cpu_alloc
يشير إلى S (nents@S+8 = 0 → نفس دلالات NULL)| variant | result |
|---|---|
| rename sprayers متزامنة أثناء العاصفة |
الفرضية العاملة: سباق storm-junk — بين تحرير destroy worker للفتحة (في منتصف العاصفة) وقتل الطفل/الهدوء، نشاط استرجاع متبقٍ يأخذ الفتحة الحرة الوحيدة في slab الضحية ببايتات غير حمولة. spray المنطقة (stage 2) يفوز لأنه يخصص باستمرار أثناء العاصفة؛ renames لا تستطيع.
iso): توقيت مطابق، ذيل مع مناطق commit-0
current->mm هو لخيط نواة → التصميم الأنيق
"وجّه مؤشرات الوهمي إلى mmap الخاص بـ userland" (لا PAN!)
يُخطئ بشكل غير حتمي. 5/5 انهيارات مع وهمي مثالي بخلاف ذلك.K=*(S+0x38) قابلة للقراءة، *(K+0x1429c)==0 في وقت التشغيل
(يتخطى mm-atomics)، K+0x141c8 قابلة للكتابة، D=*(K+4) → D+0x538
قابلة للكتابة، nents *(S+8) يفضل أن تكون 0. المرشحون التسعة S دون اتصال تم
التحقق منهم مقابل بايتات الملف — الانحراف في وقت التشغيل (تهيئة xfrm/tracepoint)
يجعلهم غير مُتحققين. سلسلة خاطئة = crash = إعادة تشغيل (~3 دقائق دورة).A. physmap-spray fake (ret2dir، arm32 الكلاسيكي بدون PAN): رش ~450MB من صفحات المستخدم كل منها يحتوي على النمط الوهمي المخبوز لعنوان واحد مُخمَّن G (G&0xfff = 0x141 لبايتات اسم خالية من NUL؛ S=G؛ K=G-0x141b4 بحيث K+4 يهبط في الصفحة؛ K+0x141c8/+0x1429c → G+0x10/+0xd4 في الصفحة؛ D=G+0x300؛ مخازن sub-0 الشاردة تصطدم بـ RAM مُعيّن عشوائيًا - غير ضارة مع nents=0). spray يضاعف كضغط الإخلاء (anon قذر = غير قابل للإخلاء → فقط ~100-200MB إضافية مطلوبة). الاحتمالات ≈ 45% (إصابة الصفحة) × ~50% (كلمة K+0x1429c الغريبة صفرية... إذا بقي K+0x1429c في الصفحة وفقًا للتخطيط أعلاه، الاحتمالات = إصابة الصفحة فقط). خطأ = crash = إعادة تشغيل، أعد المحاولة. B. القوة الغاشمة على المرشحين التسعة S الثابتين (xfrm 0xc118b7ec أولًا، الملاصق لـ tracepoint 0xc111cba4 ثانيًا...): إعادة تشغيل واحدة لكل مرشح، حمولة oracle الحميدة أولًا، السلاح عند الإصابة. C. G دقيق قائم على pagemap (ميت: PFNs مُقنَّعة) — لا تعد إليه.
/tmp/opencode/ksrc2/kernel/mediatek/mt8163/4.9/ (arch/arm بما في ذلك
mustang.dtsi، mm/، fs/eventpoll.c، fs/notify) من ksrc/platform.tar.
النتائج:
physmap_retouch() بعد القتل (يعيد إدخال جميع صفحات الرشّ
قبل trailing/deref)استرجاع المناطق: 3/3. الأحداث: 0/~12 محاولة بما في ذلك 28K تخصيص حصري مع أسبقية و fake صحيح بالبناء. إذا استرجعت الأحداث مع p_hit(G)≈0.35، فإن خمس إخفاقات pmap ≈ 11.6% — ممكن لكنه الآن غير مرجّح (~10-15%). إما أن الأحداث بنيويًا لا يمكنها أخذ هذه الخانة (السبب غير معروف — نفس الـ cache، نفس السياق، نفس التوقيت) أو أن تخميناتنا لـ G تفشل بشكل منهجي (انحراف حدود highmem، توزيع الـ allocator).
vmalloc=496M slub_max_order=0 slub_debug=O loglevel=8 initcall_debug
المناطق تأخذ خانة الضحية 3/3؛ الأحداث 0/13. نفس الـ cache (مُثبت على مستوى المصدر + disasm)، نفس سياق العملية، نفس cpus المثبّتة، نفس توقيت ما بعد القتل، آلاف التخصيصات مع أسبقية حصرية. الآلية غير معروفة. المشتبه بهم المتبقّون: ارتباط معدل/تكرار التخصيص مع دوران القائمة الجزئية (region ioctls بفاصل ~1ms مقابل event renames بفاصل ~100µs — اتجاهان متعاكسان؟)، أو تفاصيل ترتيب freelist في SLUB تحت slub_max_order=0 التي تفضّل... غير واضح.
[+] uname.nodename="X?lhost" (was "localhost") <<< ORACLE HIT
**سلسلة البايت الخام الكاملة تعمل على الجهاز الحيّ**: الحدث يستعيد
فتحة المنطقة المحرَّرة → kbase_jit_free يُشير إلى المزيّف الخاص بنا (S=صفحة physmap من
رشّ kbm، G=0xc154b2a4) → يقوم unlink بتنفيذ الكتابتين لدينا.
تم التحقق منها مرارًا باستخدام الحمولة الحميدة (كتابة nodename).
### سلسلة الاكتشافات خلال الجلسة
1. كانت صفحات kbm من نوع GFP_HIGHUSER → HIGHMEM، غير مرئية لـ physmap
(mali_kbase_mem_pool.c:164!) — تم إصلاحها عبر zonelist spill: رشّ 260 × 2MB
> highmem-free → الفائض يهبط في ZONE_NORMAL (physmap)
2. محاولات السلاح الأولى انهارت: هدف W1 عند N+4 كان صفحة USER —
يعمل unlink في سياق kworker (بدون mm) → خطأ. تم الإصلاح بدمج
shellcode ذي الحلقة-0 داخل نمط physmap عند page+0x600 (خريطة مباشرة RWX
على arm32 غير LPAE) — كود مقيم في النواة، دون حاجة إلى ret2usr
3. خطأ في إزاحة ctl_table: proc_handler يقع عند entry+0x14، وليس +0x18 (المسح
الأصلي كان صحيحًا؛ تعريفـي كان خاطئًا) — كان يكتب extra1
4. تم اكتشاف تراجع الضغط المفرط وإرجاعه: ميزانية kid 20×100MB +
ذيل 10s أفسد الاستعادة؛ الإعداد العامل هو 2 kids/200MB +
5s/+3200 ذيل (إصابة حميدة 1/1 بعد الإرجاع)
5. **diag2: W2 → &pid_max العام (0xc1114d7c) → القراءة تُعيد قيمتنا
(-1055861411 = 0xc110d55d كـ int32) — الكتابة + القراءة العكسية مُثبَتة**
### اللغز المتبقي (تجربة واحدة تفصلنا عن الإغلاق)
diag1 مع عنوان المعالج الصحيح (0xc1113f3c): W1 يُطلق (nodename
يتغير)، W2 لا بد أنه نُفِّذ (التعليمة التالية) — ومع ذلك لا تزال قراءات pid_max
تُعيد قيمًا نظيفة → حقل المعالج الذي نكتبه ليس الذي
يوزّع عبره الـ inode. diag3 (في الانتظار؛ يحتاج إقلاع إصابة): W2 →
حقل entry->data (0xc1113f2c) مشيرًا إلى nodename — إذا أظهرت القراءة حينها
بايتات nodename كعدد صحيح، فإن entry لدينا حيّ وأن إزاحة المعالج فقط
خاطئة بطريقة ما؛ وإذا لم تتأثر، فإن الـ inode يستخدم نسخة جدول ظلّية
ونبحث عن الحيّة.
### واقع معدل الإصابة
رمية عملة لكل إقلاع (~25-40%)، متجمّعة؛ عدة إقلاعات آمنة-تفويت/انهيار
متتالية أمر طبيعي. تقريبًا 1 من كل 3-4 إقلاعات تكون إصابة. أبقِ الإعداد
العامل بالضبط (2 kids، ذيل 5s، kbm بـ 260 منطقة، G=0xc154b2a4).
### مهام الجلسة-9
1. أكمل diag3 على إقلاع إصابة (أعد المحاولة حتى "W1 FIRED")
2. إذا كان entry حيًّا: أعد فحص إزاحة المعالج تجريبيًا (اكتب
عنوان proc_dostring كمعالج عبر... يجب أن يساوي N قيمة
مفيدة — استخدم unlink لكتابة entry->data بدلًا من ذلك وانتقل: مثلًا،
data=selinux_enforcing-المجاور...)
3. إذا كان جدول ظلّي: حدّد الحي — kallsyms لا يحتوي على رموز بيانات؛
المرشحون: امسح سلوك /proc/sys، أو اعثر على منطقة ctl_table ثانية
عبر نمط قائمة الترويسة في .data (مدخلات بخطوة 0x20
مع handler=proc_dointvec_minmax و maxlen=4 — عدّد الكل و
اكتب تشخيصيًا في كل منها)
4. فئة هدف بديلة تتجنب التوزيع كليًا: مؤشرات دوال .data
تُستدعى من مسارات يمكن الوصول إليها من shell (يلزم تدقيق)
5. الكتابة البدائية نفسها منتهية — أي هدف موثوق لعنوان نواة
يكفي الآن للحصول على root
## الجلسة 9 — سلاح-nf: reclaim+unlink+إصابة-G مُثبَتة داخل السلاح؛ لم يتبقَّ سوى مسح الخطاف
### تصميم الزناد الجديد (يستبدل مسار معالج sysctl بالكامل)
فكرة المستخدم مترجمة إلى ذاكرة النواة: لا ملف SUID (النظام
dm-verity للقراءة فقط؛ البدائي يكتب في RAM النواة). بدلًا من ذلك: **خطاف netfilter
مزيّف**. هذه النواة لديها backport الشائع في Android لواجهة
nf_hook_entries الجديدة، لكنها منفَّذة كقائمة مترابطة (تم التحقق عبر
تفكيك nf_hook_slow + المساعد 0xc09897d4):
- `__ip_local_out(net, sk, skb)` يحمّل خلية entries من
**[net+0x58c]**، يخزّنها في state+0x1c، يستدعي nf_hook_slow
- المسح: `entry = *cell`; while(entry){ if (state->[4] <= entry->[0x20])
call entry->[0xc](entry->[0x14], skb, state); entry = entry->[0]; }
— أي **fn@entry+0x0c، priv@entry+0x14، priority@entry+0x20،
next@entry+0x00**؛ state+4 = عتبة INT_MIN (تمرّ دائمًا)
- init_net = **0xc1104548** (CONFIG_NET_NS=n → sock_net() تُضمّن
الثابت؛ 6678 مرجع movw/movt، بطل المدرّج التكراري؛ مؤكَّد متقاطعًا بواسطة
القيمة الحرفية لـ nf_hook_slow نفسها). الهدف: **[init_net+0x58c] = 0xc1104ad4**
- خطاف LOCAL_OUT يعمل في سياق عملية المُرسِل → hookfn لدينا
commit_creds(prepare_kernel_cred(0)) يجذّر العملية التي أرسلت
الحزمة. الزناد = sendto(127.0.0.1:9) UDP.
### تخطيط وضع nf (poc/stage3.c، الوضع `nf 200`)
- صفحات نمط kbm (نسبةً إلى الصفحة، مصدر حقيقة واحد — السلاح
القديم كان به ثلاثة أخطاء أُصلحت الآن: proc_handler@+0x14 وليس +0x18؛ هدف W1
يجب أن يكون ذاكرة نواة (سياق kworker، بدون mm)؛ الكود المدمج عند
page+0x600 مقابل عدم تطابق entry G+0x600=page+0x8a4):
- +0x2ac/+0x2bc/+0x2c0/+0x2dc/+0x3a8: سلسلة phy-alloc المزيّفة (دون تغيير)
- +0x600: hookfn الخاص بـ nf_code (تخزين علامة + prepare_kernel_cred +
commit_creds + إرجاع NF_ACCEPT(1))
- +0x700: entry مزيّف {next=0, fn=PM_PAGE+0x600, priv=0, prio=0x100}
- +0x740: cell → PM_PAGE+0x700
- الحمولة: N = PM_PAGE+0x740 (0xc154b740)، P = 0xc1104ad4
→ W1: *(cell+4)=P (تهبط في صفحتنا)، W2: *(init_net+0x58c)=cell
### أدوات تشخيص ذاتي (kbm_scan_for)
يتم الاحتفاظ بتخطيطات kbm لوحدة المعالجة المركزية؛ بعد التحرير نمسح كل صفحة
مرشوشة بحثًا عن كلمة معروفة:
- scan(HOOKS_PTR_ADDR) عند page+0x744 → يثبت reclaim + unlink ويكشف
أي صفحة فيزيائية تسند تخمين G
- scan(0x600d600d) عند page+0x7f0 → يثبت أن hookfn نُفِّذ
(nf_code يكتب هذه العلامة كإجرائه الثاني)
### التشغيل المهم (2026-09-11، أواخر الجلسة 9)```
[+] W1 CONFIRMED: region 135 page+0x185000 <- 0xc1104ad4 (reclaim + G hit!)
[+] nf trigger done, uid=2000 euid=2000
سلسلة السلاح الكاملة أُطلقت على إقلاع حيّ: استعاد الحدث الفتحة، وعمل المزيّف، ونُفّذ إلغاء الربط، وكانت صفحة تخمين G (0xc154b000) ملكنا فعلاً (المنطقة 135 الصفحة 389). W2 = *(0xc1104ad4)=cell هي التعليمة المجاورة — لا بد أنها نُفّذت. ومع ذلك لم يمنحنا إرسال UDP صلاحيات الجذر → الفشل داخل مسار الخطاف: دلالات المشي، توصيلات [state+0x1c]، مقارنة الأولوية، أو حقول الإدخال. (أُضيفت تجربة العلامة للتمييز بين تنفيذ hookfn وعدمه؛ لا يوجد سوى إقلاعات الانهيار قبل نهاية الجلسة — لا بيانات نظيفة بعد.)
pmap 200) هو فحص سلامة البيئة (4/4 إصابات عند
السخونة؛ فقدان-انهيار عند البرودة)nf 200 على إقلاعات متأخرة الاستقرار حتى يظهر سطر W1-CONFIRMED،
ثم اقرأ سطر MARKER:
a. العلامة موجودة، uid!=0 → فشلت بيانات اعتماد shellcode (تحقق
من عناوين prepare_kernel_cred/commit_creds؛ ترميزات blx)
b. العلامة غائبة → المشي لم يستدعنا أبداً: تحقق بعلامة ثانية
يكتبها... التشخيصات التالية: hookfn يكتب العلامة فقط ويعيد 1
(بدون بيانات اعتماد) — إن بقيت غائبة:
ls -la; اترك ملفات العلامةpoc/stage3.c: pin/root/drain{,2,3,4,5}/iso — السلاح الكامل +
oracle + حاضنة العزل، تحقيقات جنائية لنقطة انهيار O_SYNCinit_net خاطئ: 0xc1104548 هو __stack_chk_guard (مدرّج
movw/movt تلوّث بأحمال stack-canary — 2025 بنت خطة nf بأكملها عليه).
init_net الحقيقي = 0xc1185040 (مؤكد: ip_send_skb(net,...) يُستدعى
بهذا الحرف؛ ~994 مرجعاً كلها في مكدس الشبكة). خلية IPv4 LOCAL_OUT =
init_net+0x58c = 0xc11855cc.nf_iterate يعامل [init_net+0x58c]
كمؤشر nf_hook_ops نفسه — يقرأ fn@+0xc, priv@+0x14, prio@+0x20
مباشرة من تلك القيمة. إدخال الجلسة-9 المزيّف كان يقع عند
PM_PAGE+0x700 مع الخلية تشير إليه (حقول next/ غير المستخدمة
عند الخلية → → انهيار). الإدخال المزيّف يجب أن يقع (0xc154b740). مع إصلاح هذا، ثبت استدعاء
الخطاف (وضع = SAFE_FN يعالج كل 200 إرسال بنظافة).commit_creds(prepare_kernel_cred(0)) / override_creds(&init_cred) يمنح
uid 0 لكنه يهبط في SID النواة kernel، الذي لا تسمح له سياسة Fire OS هذه
بالكتابة في /data أو /sys/fs/selinux/enforce (مؤكد: EACCES).
SID init الحقيقي (7) مرفوض أيضاً (اختبار struct cred المزيّف).
enforcing_setup هو __init (محرَّر → انهيار). atomic_sub في
mark_reclaim يحتاج nents=1 مما يكسر الخروج المبكر لـ shrink_cpu_mapping.
الانتصار: عملية الاستغلال تحتفظ بتعيينات kbm CPU، لذا يمكن إعادة كتابة إدخال nf المزيّف في مكانه بين الحزم:
selroot: الإدخال = {fn=0xc01d503c (mov r3,#0;str r3,[r0];bx lr), priv=0xc1213ea8 (selinux_state.enforcing)}.*(enforcing)=0 → SELinux متساهل.kbm_cpu[reg]+off إلى {fn=commit_creds, priv=&init_cred}.commit_creds(&init_cred) في مهمة المرسل → uid 0 مع
SELinux متساهل → جذر قابل للاستخدام، كل ذلك في استعادة واحدة، دون حاجة
إلى سلسلة.[+] W1 CONFIRMED: region 67 page+0xad000 <- 0xc11855cc (reclaim + G hit!) [*] sendto 0 -> -1 errno=1 uid=0 euid=0 [+] nf trigger done, uid=0 euid=0 <<< HOOK RAN - commit_creds OK [+] ROOT: uid=0 euid=0 [+] setenforce write=1 [+] su copied bytes=236220
- `getenforce` → **Permissive**؛ st3 المتوقف هو `Uid: 0 0 0 0`،
`CapEff: 3fffffffff`.
- `/data` مُثبَّت **nosuid** لذا لا يمكن لـ `su` ذي setuid أن يعمل. أداة
`rootshell` صغيرة (إرسال UDP → `commit_creds` على الذات → `execl sh`) تمنح
صدفة root تفاعلية: `uid=0(root) context=u:r:kernel:s0`.
- يمكن لـ root القراءة/الكتابة على `/dev/block/by-name/*` (`dd if=boot ...` يعمل).
### حالة كود Stage-3 (`poc/stage3.c`)
- الأوضاع: `nf <mb> [probe|uprobe|oc|chain|fc <sid>|notrig|selroot]`
- `selroot` هو السلاح العامل. الثوابت الأساسية: `init_net=0xc1185040`،
`HOOKS_PTR_ADDR=0xc11855cc`، `ENFORCING_ADDR=0xc1213ea8`،
`ZERO_GADGET=0xc01d503c`، `commit_creds=0xc014993c`،
`init_cred=0xc1114f54`.
- ما بعد الاستغلال هو استدعاءات نظام مباشرة (بدون `system()`)؛ أبقِ kctx حيًّا
(`pause()`) لتجنب انهيار التفكيك.
### المتبقي (Stage 4/5)
- الاستمرارية عبر إعادة التشغيل (verity / صورة boot / recovery)، لأن اختطاف
الخلية + SELinux المتساهل يعملان في وقت التشغيل فقط وإعادة تشغيل الاستغلال
تتطلب رمي العملة العشوائي لإعادة الاستحواذ بنسبة ~1/3.
- سيحتاج `su` إلى home غير nosuid (`/system`) أو مشغّل يعيد التشغيل.
## SESSION 11 — PERSISTENCE RECON (Track B + Track A) and the RE handoff
الهدف كان root دائمًا. تم تحديد مسارين:
- **Track B**: تعطيل verified boot (dm-verity / SELinux) حتى يمكن ترقيع `/system`.
- **Track A**: إعادة تشغيل الاستغلال عند الإقلاع.
كلاهما يختزل إلى نفس العائق: **جعل LK يعامل الجهاز كـ `eng`/`unlocked`.**
### حقائق verified-boot (البناء الدقيق)
- Bootloader مقفل، AVB `green`، `ro.boot.unlocked_kernel=false`، `ro.boot.secure_cpu=1`،
`rpmb_state=1`. Bootrom مُرقَّع (لا BROM)؛ preloader فقط عبر CMD short.
- `/system` مُثبَّت بواسطة **Android dm-verity من سطر أوامر kernel المبني بواسطة lk**:
`root=/dev/dm-0 dm="system none ro,0 1 android-verity PARTUUID=b6404ef3-… "`,
`veritykeyid=id:f3530e18f64d11fc25eb2dd762979f078de990bf`، `androidboot.veritymode=eio`،
`skip_initramfs` (system-as-root). `dm-0` = جهاز verity المسمى `system`؛ `dm-1` = `/vendor`.
- LK: Amazon **UFBL**، `ro.boot.lk_version=0x0006`، بناء `0db73c9-20231025_030009`؛
preloader `pl_version=0x000a`، بناء `80c6fcb-20230523_065640`. `/dev/block/by-name/lk` = mmcblk0p5 (1 MB).
- GPT الكامل (16 قسمًا، بدون `persist`/`seccfg`/`nvram`/`protect`/`para`):
`proinfo` p0، `PMT` p1، `kb` p2، `dkb` p3، `lk` p4، `tee1` p5، `tee2` p6، `metadata` p7،
`MISC` p8، `reserved` p9، `boot` p10، `recovery` p11، `system` p12، `vendor` p13،
`cache` p14، `userdata` p15. eMMC boot0 (1 MB) = preloader (سحر `EMMC_BOOT`)؛
boot1 (4 MB) = مخزن IDME.
### نتائج LK (UFBL) الساكنة
ترويسة `lk.img`: `88 16 88 58 | 00052974 | "LK"`؛ جدول متجهات ARM عند 0x200، الباقي Thumb-2،
مستقل عن الموضع/مُعاد توطينه (تجمعات الحرفيات تستخدم `ldr+add pc`، لذا يفشل التفكيك الساذج نسبةً إلى القاعدة).
السلاسل ذات الصلة (إزاحات الملف): `amzn_image_verify`، `amzn_verify_unlock`، `amzn_verify_code_internal`،
`unlock_code`، `unlock code error`، `unlock failed`، `$Common Kernel Signing Engineering CA0`،
`seccfg`، `para`، `ENV_v1`، `LK_ENV`، `Kfos_flags`/`Kdev_flags`/`Kusr_flags`/`Kunlock_code`/`Kunlock_version`،
`FOS_FLAGS_{NONE,ADB_ON,ADB_ROOT,CONSOLE_ON,RAMDUMP_ON,VERBOSITY_ON,ADB_AUTH_DISABLE,FORCE_DM_VERITY,DM_VERITY_OFF,BOOT_DEXOPT}`،
`[DM-VERITY] verify for system(root) is enabled`، `[DM-VERITY] verify off by fos_flags`،
`[DM-VERITY] disabled by fos_flags on eng devices or unlocked device`،
`[SELINUX] set to permissive mode by dev_flags`، `androidboot.prod=1|0`، `androidboot.unlocked_kernel=%s`.
**الخلاصة: LK يربط تأثيرات الأمان لـ `fos_flags`/`dev_flags` على eng/unlocked.**
### مخزن IDME (eMMC **boot1**) — قابل للكتابة، دائم، يُقرأ بواسطة LK وAndroid
- السحر `beefdeed` + `"2.1\0"` + count(0x19=25) عند 0x0؛ العناصر من 0x10.
- صيغة العنصر: `char name[16]; u32 size; u32 type(=1); u32 magic(=0x124); u8 data[size] (pad4)`.
- إزاحات العناصر (الأصلية): `board_id@0x10 serial@0x3c mac_addr@0x68 mac_sec@0x94 bt_mac_addr@0xd0
bt_mfg@0xfc product_name@0x198 productid@0x1d4 productid2@0x210 region@0x24c bootmode@0x26c
postmode@0x28c bootcount@0x2ac manufacturing@0x2d0 unlock_code@0x4ec sensorcal@0x908 alscal@0x9c4
KB@0xa00 DKB@0x1e1c device_type_id@0x2238 dev_flags@0x2274 fos_flags@0x2298 usr_flags@0x22bc
wifi_mfg@0x22e0 unlock_version@0x26fc`. القيم ASCII (الأعلام **سلاسل hex**).
- القراءة وقت التشغيل: `/proc/idme/<name>` (للقراءة فقط). قيم آخر إقلاع مخزنة مؤقتًا؛ الكتابة إلى boot1
تسري في الإقلاع التالي. مسار الكتابة يتطلب مسح `/sys/block/mmcblk0boot1/force_ro` (root).
- **تأكد أن LK يقرأ boot1**: تغيير `serial` غيّر `ro.boot.serialno` في الإقلاع التالي.
لكن LK **يقتطع serial إلى 16 بايت** وتجاهل `fos_flags=0x80`، `dev_flags=0xff`،
all-ones، إلخ — verity/selinux/`prod` دون تغيير. لذا فشل حقن cmdline عبر serial.
### مستهلكو أعلام IDME من جهة Android
- `/init.fosflags.sh` (خدمة `fosflags`، `u:r:fosflags:s0`): `FOS_FLAGS_ADB_ON=0x1`،
`CONSOLE_ON=0x4`، `RAMDUMP_ON=0x8`، `VERBOSITY_ON=0x10`، `ADB_AUTH_DISABLE=0x20`،
`BOOT_DEXOPT=0x100`. تم التحقق: ضبط الأعلام يسري (`sys.usb=adb`، `noadbauth=1`).
- **adbd** (ARM ET_EXEC غير مجرد؛ `.text` VA 0x8160 / ملف 0x160؛ fileoff = VA-0x8000):
- `amzn_is_root_allowed` @0x2d5b8 = `amzn_is_dev_unlocked() && (fos_flags & 0x2)`
- `amzn_is_adb_auth_disable_allowed` @0x2d5e8 = `fos_flags & 0x20` (غير مُقيَّد)
- `amzn_is_dev_unlocked` @0x2d5fc = `/proc/cmdline` يحتوي على `androidboot.prod=0` **أو**
`androidboot.unlocked_kernel=true`
- `fos_read_debug_flags` @0x2d724 يقرأ `/proc/idme/<name>` ويحلل **hex**
- `restart_root_service` @0xcb74 / `restart_unroot_service` @0xcc64
- السلاسل: `amzn_fos: ADB: Auto-root succeeded`، `… eng_device=%d`، `… unlocked_kernel=%d`،
`adbd cannot run as root in production builds`، `ro.debuggable`
- `adb root` → "cannot run as root in production builds" (`ro.debuggable=0`) — لذا حتى مع
تحقق بوابة auto-root، يقيّد فحص AOSP prod مسار الأمر.
### لماذا لا يستمر root من Session-10
- SELinux المتساهل + اختطاف الخلية + root تعمل في وقت التشغيل فقط.
- `/data/metrics` هو **vpartition**: `/system/bin/vpartition.sh` يثبّت `/data/vp/metrics.img`
(ext4، غير nosuid/noexec) عند `/data/metrics` في كل إقلاع؛ `su` المكتوب هناك **لا**
ينجو من إعادة التشغيل. (أيضًا لماذا أعطى setuid `su` uid 0 لكن **بدون caps**.)
### Track A (إعادة الاستغلال عند الإقلاع) — محظور
- لا يوجد مشغّل `.rc` في init ينفذ كودًا قابلًا للتحكم (جميع الاستيرادات تم التحقق منها؛ مشغّلات `persist.*` فقط
`start` لخدمات ثابتة؛ سكربتات في `/system`/`/vendor`).
- خدمات root تقرأ إعدادات `/data` لكنها لا تنفذ منها أبدًا (`perfmonitord`، `amazonfiled`،
`vpartition.sh`، `kisd`، …).
- المنفذ الوحيد للإقلاع = **تطبيق**، لكن بصمة الاستغلال المتوقفة هي **VmRSS 534 MB**
(رشّ `kbm`) → lmkd يقتله؛ بالإضافة إلى panic عند فقدان إعادة الاستحواذ (`PANIC_ON_OOPS`) → bootloop.
- **adbd auto-root** موجود لكنه مقيّد بسطر أوامر kernel المبني بواسطة LK (`prod=0`/`unlocked_kernel=true`).
### الخلاصة / الهدف التالي (المختار: Track B RE)
كل شيء يتوقف على جعل LK يبلّغ `eng`/`unlocked`. في المتناول:
`androidboot.prod=1|0` و`androidboot.unlocked_kernel=false` يتم ضبطهما بواسطة LK. اعكس LK لتجد:
1. أين يقرأ `fos_flags`/`dev_flags`/`usr_flags` (عناصر `K*`) والبوابة الدقيقة؛
2. تحديد `prod`/`unlocked` (عنصر IDME؟ buildvariant؟ نتيجة `amzn_verify_unlock`؟)؛
3. `amzn_verify_unlock` (تحقق RSA من libtomcrypt) لتجاوز أو مسار unlock_code/version ضعيف؛
4. تخزين `seccfg`/`para`/`ENV_v1`(LK_ENV) (ليس في أي قسم مُفرَّغ — ربما محمي بـ tee)؛
5. preloader (`boot0`، `EMMC_BOOT`) بحثًا عن ثغرة.
إذا سمح أي من هذه بضبط eng/unlocked (بشكل دائم، عبر boot1 أو كتابة قسم خام)، فإن
`FOS_FLAGS_DM_VERITY_OFF` يعطّل verity لـ system(root) ويمكن ترقيع `/system` بشكل دائم.
### المخرجات (من هذه الجلسة)
`/tmp/opencode/mustang-dumps/` (قد تُمسح عند إعادة تشغيل المضيف): `lk.img`، `boot1.img` (أصلي)،
`boot.img`، `MISC.img`، `metadata*.img`، `pmt.img`، `mbr.img`، `kb.img`، `dkb.img`، `reserved.img`،
`cache.img`، `boot0.img`، `boot1.img`، `adbd.bin`، `perfmonitord.bin`، `amazonfiled.bin`.
المساعدات: `tools/findinitnet.py`، `findgadget*.py`، `findstores.py`، `adbd_sym.py` (في /tmp)؛
المستودع يحتوي على `run.sh`، `poc/stage3.c` (`selroot`)، `poc/su.c`، `rootcmd.sh`.
### أوامر مفيدة```
# IDME read
/data/metrics/su sh -p -c 'for f in fos_flags dev_flags usr_flags serial region device_type_id unlock_version; do echo -n "$f="; cat /proc/idme/$f; echo; done'
# write boot1 (root; su lives only until reboot -> re-run run.sh first)
/data/metrics/su sh -p -c 'echo 0 > /sys/block/mmcblk0boot1/force_ro; dd if=/data/local/tmp/boot1.img of=/dev/block/mmcblk0boot1 bs=4096 count=4; sync; echo 1 > /sys/block/mmcblk0boot1/force_ro'
# dump a partition to host
adb exec-out '/data/metrics/su dd if=/dev/block/by-name/lk bs=4096 2>/dev/null' > lk.img
الهدف: جعل LK يتعامل مع الجهاز كـ eng/unlocked، أو إيجاد ثغرة في preloader/LK،
حتى يمكن تعطيل verity/SELinux بشكل دائم. النتيجة: تم عكس مسار كود LK ذي الصلة
من البداية إلى النهاية؛ القلب غير قابل للوصول عبر المخازن المتاحة.
لم يتم إتلاف أي جهاز؛ تمت إعادة تجربة boot1 الوحيدة إلى حالتها الأصلية.
يبدأ lk.img بجزء ARM صغير (الملف 0x200). الناسخ المُعيد للتمركز عند 0x224
ينسخ من 0x200 إلى وجهة حرفية ويتفرع إلى نقطة دخول حرفية:
إذن عنوان وقت التشغيل = 0xFF400000 + إزاحة الملف للإزاحات >= 0x200.
كل ما بعد الجزء الصغير هو Thumb-2، مستقل عن الموضع. تُبنى السلاسل النصية
بـ ldr rT,[pc,#imm] (إزاحة T1 = imm8*4؛ إزاحة ldr.w = imm12) متبوعة بـ
add rT, pc؛ الهدف هو (add+4) + *pool. تمت إضافة ماسح قوي يصمد
أمام جزء ARM ومجمعات الحرفيات كـ tools/lk_xref.py (يتعامل مع الصيغ
16- و32-بت، ويفحص كل بايتين). جميع الإزاحات أدناه هي إزاحات الملف؛
أضف 0xFF400000 لعناوين وقت التشغيل.
يقرأ 0x20b4 عنصر unlock_code في IDME (0x400 بايت، كلها أصفار على هذه
الوحدة) ويشغّل amzn_verify_unlock (0x222c -> 0x20f0). تلك الدالة تقود
libtomcrypt (عشرات المسارات /features/libtomcrypt/src/pk/asn1/der/... و
التحقق من RSA)، والصورة تتضمن مادة الشهادة:
Sunnyvale / Amazon Lab126 / "$Common Kernel Signing Engineering CA0" عند
0x317d9+، بالإضافة إلى رسائل التشخيص
Image FAILED AUTHENTICATION on PRODUCTION device (0x3166e),
Authentication failed on engineering device with production certificate (0x316a0),
Image FAILED AUTHENTICATION on ENGINEERING device (0x31703),
Image AUTHENTICATED with PRODUCTION certificate (0x31736).
لا يوجد اختصار للرمز الفارغ / الطول / الإصدار: verify(zeros) != 0، وبالتالي
(مؤكَّد في ). قلب
أو يتطلب إما
صالحًا موقّعًا من Amazon (المفتاح الخاص غير متاح) أو ثغرة تنفيذ كود في
المُتحقِّق. لم يُعثر على أي شيء قابل للاستغلال (حدود/حجم) بشكل ساكن في
0x20b4/0x222c/0x20f0. =>
تُقرأ أعلام الأمان عبر الجالب عند 0x57c. اختبار تجريبي:```
dd if=/dev/block/mmcblk0boot1 ... ; reboot /proc/idme/fos_flags -> 00000080 (persisted, Android sees it) ro.boot.veritymode -> eio (unchanged!) root=/dev/dm-0 dm="system none ro,0 1 android-verity PARTUUID=..." (unchanged) androidboot.prod=1 / secure_cpu=1 / buildvariant=user (unchanged)
`fos_flags=0x80` هو `FOS_FLAGS_DM_VERITY_OFF`؛ كانت البوابة المُفكَّكة ستُعطِّل
verity **إذا** كان الـ getter قد أعادها. لم يفعل. البوابة فعّالة، وليست
كودًا ميتًا: قيمة الإرسال المخزَّنة مؤقتًا لمرة واحدة هي `-1` في الصورة
(`*(u32*)0x50c74 == 0xffffffff`)، لذا نفّذت الدالة فعلاً مسار
`check_flag("fos_flags",0x80)` وحصلت على 0. لذلك فإن الـ getter (على الأقل
عند وقت حماية verity) **لا** يقرأ عناصر boot1 IDME.
المخزن المرشح الآخر هو **LK env**، المحمَّل من قسم يُسمَّى حرفيًا
`"para"` (loader 0x12fd4، magic `ENV_v1`، checksum @0x3ffc). يسرد جدول
الأقسام الخاص بـ LK (0x4fcc0..0x50340) preloader/proinfo/nvram/protect1/
protect2/persist/seccfg/secro/**para**/logo/custom/expdb/tee1/tee2/metadata/
system/cache/userdata — لكن GPT الفعلي للجهاز اللوحي يحتوي على **16 مدخلاً فقط**، جميعها
من النوع `af3dc60f838472478e793d69d8477de4`:```
#0 proinfo 0x400 #1 PMT 0x1c00 #2 kb 0x4000 #3 dkb 0x4800
#4 lk 0x5000 #5 tee1 0x5800 #6 tee2 0x8000 #7 metadata 0xa800
#8 MISC 0x1e400 #9 reserved 0x1e800 #10 boot 0x22800 #11 recovery 0x2a800
#12 system 0x34800 #13 vendor 0x644000 #14 cache 0x6b4800 #15 userdata 0x7ae800
لا توجد أقسام para أو seccfg أو nvram أو protect أو persist على
هذا المنتج (وجميع تفريغات PMT/pmt.img أصفار). لذا فإن بيئة LK فارغة،
ومفاتيح Kfos_flags/Kdev_flags غير موجودة أبدًا، وجميع فحوصات
fos_flags/dev_flags تُحل إلى 0 — بغض النظر عما تحتويه عناصر IDME. عناصر
boot1 IDME يستهلكها Android (/init.fosflags.sh، adbd،
/proc/idme/*) ولكن ليس بوابات الأمان في LK.
unlocked_kernel وجود unlock_code موقّع من Amazon (RSA/libtomcrypt،
CA مضمّن). غير قابل للتزوير دون اتصال؛ لم يُعثر على خطأ في المدقّق. حظر صارم.DM_VERITY_OFF / selinux=permissive من بيئة LK
(para/ENV_v1)، وهي غير موجودة في جدول GPT هذا. يتم تجاهل IDME fos_flags
تجريبيًا من قِبل LK (استمر 0x80، وبقي verity على eio). حظر صارم
ما لم يُعدَّل جدول الأقسام.fos_flags=0x80 لن يضبط سوى androidboot.veritymode= disabled وroot= غير dm-0؛ لن يفتح القفل، وسيظل SELinux بحاجة إلى
dev_flags من نفس البيئة الغائبة ليصبح permissive.para/ENV_v1: أضف مدخل GPT باسم para (يجب تحديث
GPT الأساسي + الاحتياطي معًا) في المساحة الحرة بعد userdata
(ينتهي userdata عند LBA 0x3a3dfde؛ القرص = 30535680 قطاعًا)، ثم اصنع بيئة
تحتوي على fos_flags=0x80 وdev_flags=0x40 (المجموع الاختباري عند +0x3ffc = مجموع البايتات على
0x3ffc). هذا هو المسار الوحيد المتبقي لإيقاف verity. المخاطر: إتلاف
GPT الأساسي/الاحتياطي قد يُعطّل الجهاز؛ ولم يُثبَت أن بوابة verity
تقرأ فعليًا para (فقط أنها ليست boot1 IDME).boot0/EMMC_BOOT): لم يُعكس هندسيًا في هذه الجلسة. كتابة
boot0 محظورة حتى يتوفر نسخة أصلية ومسار استرداد.tools/lk_xref.py — محلّل مراجع سلاسل LK مستقل عن القاعدة./tmp/opencode/mustang-dumps/lk.img، boot1.img (أصلي)،
boot0.img، mbr.img (GPT)، pmt.img (أصفار)./tmp/opencode/s12/boot1_f80.img؛ أُعيد الجهاز إلى boot1 الأصلي
(تم التحقق من /proc/idme/fos_flags -> 0)../run.sh)```./run.sh --no-build
/data/metrics/su /system/bin/sh -p -c 'cat /proc/cmdline'
for f in fos_flags dev_flags usr_flags unlock_version serial; do cat /proc/idme/$f; echo; done
## الجلسة 13 — الـ preloader قابل للوصول في النهاية (`1949:20ff` = MTK preloader، ناقل HID)
أثناء إيقاف التشغيل وتوصيله بمنفذ USB، يُعدّد الجهاز اللوحي نفسه كـ **`1949:20ff`**
(`Lab126`) — *ليس* Android و*ليس* `0e8d:0003` bootrom. الوصف:```
bInterfaceClass 3 (HID), iConfiguration "HID", iInterface "HID Interface"
HID report descriptor = 05 01 09 00 a1 01 c0 (empty collection!)
EP 0x81 IN interrupt 4 bytes, bInterval 4
EP 0x01 OUT interrupt 4 bytes, bInterval 4
iSerial = GCC0X90805310009 (the IDME serial)
التعريف. 0x20FF مُدرج باسم "MTK Preloader" في ملف mtkclient
config/usb_ids.py (تحت MediaTek VID 0x0e8d: 0xe8d:{0x0003 Brom, 0x2000/0x2001/0x20ff/0x3000 Preloader}). أبقَت Amazon على معرّف preloader PID وغيّرت VID إلى 0x1949، وتُقدّمه كزوج من نقاط نهاية HID مع واصف تقرير وهمي. لذا فهذا هو وضع MediaTek preloader / USBDL، وهي مرحلة أدنى من LK — يُوصَل إليها هنا عبر إيقاف التشغيل + التوصيل، وليس عبر قصر CMD.
سلاسل الواصفات "HID"/"HID Interface" غير موجودة في lk.img، أو boot0.img، أو boot.img أو غيرها من الملفات المستخرجة، أي أن الوضع ينتجه مكوّن لم نستخرجه بعد (bootrom/TEE) أو يُجمَّع في وقت التشغيل.
لماذا يهم هذا. إن preloader الخاص بـ Amazon المستخدم بواسطة aftv2-tools يكشف عن أوامر مدمجة، بدون Download-Agent، عبر هذا التدفق البايتي تحديدًا:```
handshake : host A0 0A 50 05 -> dev 5F F5 AF FA
0xD1 read32 (addr, n_words) : echo cmd/addr/n, 00 00, nu32, 00 00
0xD4 write32(addr, words[]) : echo cmd/addr/n, 00 00, nu32, 00 00
`aftv2-tools/read_mmc.py` يستخدم `read32`/`write32` للوصول إلى متحكم MSDC
(القاعدة `0x11230000` على MT8173؛ تحقق من ذلك بالنسبة لـ MT8163) وقراءة/كتابة **كتل eMMC
الخام** بدون DA وبالتالي بدون AVB/verity في الطريق. إذا كان preloader الخاص بـ mustang
يقبل 0xD1/0xD4، فهذا مسار مباشر لفتح دائم (patch `boot` /
`lk`)، مستقل عن رمز فتح RSA وبيئة LK الغائبة.
### الأدوات المضافة (تحتاج root؛ قم بعمل chmod لعقدة USB أولاً)```
lsusb -d 1949:20ff # note Bus/Dev, e.g. Bus 001 Device 003
sudo chmod 666 /dev/bus/usb/001/003
# 1) does it answer the MTK handshake? (no DA, no flash access)
nix-shell -p python3Packages.pyusb --run \
'python3 tools/mtk_preloader_hid.py handshake'
# 2) read-only arbitrary memory read
nix-shell -p python3Packages.pyusb --run \
'python3 tools/mtk_preloader_hid.py read32 0x00100000 4'
tools/probe_preloader.py هو مسبار المصافحة فقط (handshake-only) الأدنى؛
tools/mtk_preloader_hid.py هو النقل الكامل (handshake/read32/
write32؛ write32 محمي ولا ينبغي استخدامه حتى يتم تأكيد خريطة سجلات
eMMC).
read_mmc)، ثم ترقيع boot.img/lk من الـ preloader وإعادة التشغيل.amzn_verify_unlock أو بوابة بيئة LK.pi_blocked_onfutex.c:1975 يمرر this->rt_waiter،
المُعرّف في futex_wait_requeue_pi عند futex.c:2880) → waiter يطبع إطاره المحرر
عبر arm32 select (nr 142) fd_setsDEBUG_RT_MUTEXES معطّل
→ rt_mutex_waiter مضغوط بحجم 48 بايت (tree_entry@0، pi_tree_entry@0xc، task@0x18،
lock@0x1c، prio@0x20، deadline@0x28)modprobe_path @ 0xc111488c (السلسلة موجودة ذاتيًا في
vmlinux؛ KALLSYMS_ALL معطّل لذا رموز البيانات تحتاج هذه الحيلة) → exec binfmt غير معروف → root
script (setenforce 0، تعطيل OTA، su)refs/: NebuSec/CyberMeowfia (الأصلي)، GhostLock-5.10 (منفذ Fire OS 8،
trigger كامل لـ 32-bit ARM في src/exp32/)، ghostlock-...-4.19-k40 (منفذ Qualcomm 4.19 Android)selrootselinux_state.enforcingcommit_creds(&init_cred)uid=0jcnr_extresinfo->gpu_alloc_addr (GPU VA يجب أن
تخصصه مسبقًا وتمرره)| كائن قابل للإخلاء | الضغط | النتيجة |
|---|
| لا شيء | 900 MB | نجا |
| لا شيء | 1300 MB | panic (خلل lowmem في النظام — غير ذي صلة) |
| منطقة عادية + DONT_NEED | 700 MB | نجا |
| منطقة JIT + DONT_NEED | 700 MB | panic في مسار reclaim |
kernel/config-*/proc/config.gzksrc/ — مصدر Amazon OSS (platform.tar + شجرة midgard-r26p0 المستخرجة)/tmp/opencode/mustang_ota.bin (sha256 6068515a… يطابق fireos-archive)
و tarball مصدر kernel بحجم 2.2 GB محفوظ في ~/Desktop/amazon-mustang/ksrc/kernel/mediatek/mt8163/4.9/drivers/misc/mediatek/gpu/gpu_mali/mali_midgard/midgard-r26p0/| renames تتوقف (journal/GFP_NOFS) → 128 إجمالي → deref عشوائي |
| pre-drain 12K أحداث + ضغط + ذيل صغير | crash عند deref |
| + kill-child-at-eviction (استطلاع 10ms) | crash عند deref |
| + دورة حياة مثبتة على cpu0 (drain3) | crash عند deref |
| ضغط متدرج (drain4 v1) | الأطفال حرروا الذاكرة عند الخروج → لا إخلاء (مسار قانوني مُتحقق) |
| run | result |
|---|
| pmap G=c2a412a4 400MB | crash at deref |
| pmap G=c2f4b2a4 480MB | crash; +0 post-kill renames (480MB suffocates fs) |
| iso2 (spray + regions + oracle) | REGION HIT — spray doesn't break reclaim |
| mix v1 (concurrent events+regions) | crash; confounded (spinning event threads) |
| pmap G=c2a7d2a4 350MB + retouch | crash; +4452 renames OK |
| mix2 (sequential: 2s events THEN regions) | crash; +7126 renames (28K event allocs), 2715 regions |
fnfn=0PM_PAGE+NF_CELL_OFFprobekernel_x_end: arch/arm/mm/mmu.c
map_lowmem() يعيّن lowram تحت نص النواة MT_MEMORY_RWX، لكن كل شيء
فوق kernel_x_end MT_MEMORY_RW → PMD_SECT_XN (السطر 509). shellcode
المضمّن عند 0xc154b600 يسبب prefetch-abort. الحمولة يجب أن تكون
مؤشر دالة نواة حقيقي، لا كوداً في physmap.| الحرفي (إزاحة الملف) | القيمة | المعنى |
|---|
| 0x270 | 0xFF4002F8 | str r4,[r6] مساحة مؤقتة |
| 0x274 | 0xFF40027C | الوجهة (base+0x27C) |
| 0x278 | 0xFF54A440 | نهاية النسخ (بما في ذلك BSS) |
| 0x27C | 0xFF400484 | نقطة الدخول |
| الإزاحة | الدالة |
|---|
0xdf7c | is_secure_or_prod() -> byte[ [[g]+0 ] + 0x163 ]; g = global @0x52838. 1 على هذه الوحدة. |
0x20b4 | verify_stored_unlock() = memset(buf,0,0x100); read IDME/env unlock_code (0x100) via 0x57c; bl 0x222c; return (verify==0). |
0x222c / 0x20f0 | amzn_verify_unlock(code,len) — libtomcrypt RSA/PKCS#1 verify (انظر أدناه). |
0xda3e | is_unlocked() = is_secure_or_prod() && verify_stored_unlock(). |
0x29a28 | is_verity_disabled() = (fos_flags & 0x80) && !(is_secure_or_prod() && verify_stored_unlock()); مخزّن مؤقتًا في global @0x50c74. |
0x29974 | باني سطر أوامر SELinux: dev_flags & 0x20 -> androidboot.selinux=enforce, dev_flags & 0x40 -> ...=permissive (كل منهما مُقيَّد بـ byte[+0x162]). |
0x118xx/0x11bxx | باني سطر أوامر النواة (unlocked_kernel, prod=1/0, verifiedbootstate, rpmb_state, secure_cpu, الإصدارات, root=). |
0x27af8 | بوابة UART: fos_flags & 0x4 -> printk.disable_uart=0, وإلا =1. |
0x12fd4 | مُحمِّل بيئة LK: القسم "para"، 0x4000 بايت، السحر ENV_v1، المجموع الاختباري = مجموع البايتات على 0x3ffc مقارنةً بالكلمة @0x3ffc. |
0x1efd0 | البحث عن القسم بالاسم (يُستخدم لـ "para", "boot", ...). |
0x57c | موزّع الجالب عبر خانة الاستدعاء @0x58218؛ الخانات @0x58200..0x5821c مسجّلة من جدول عند 0x5a8-0x734. |
0x2a19c | fastboot oem unlock: bl 0x222c(code,len); عند النجاح يكتب unlock_code (0x100) عبر 0x408. |
unlocked_kernel=false/proc/cmdlineandroidboot.unlocked_kernelandroidboot.produnlock_code