
يستغل صلاحيات مصحح الأخطاء في macOS وDYLD_INSERT_LIBRARIES لتفريغ ذاكرة عملية قيد التشغيل أو البحث فيها، مع تحويل إسناد EDR إلى ملف ثنائي مساعد موقّع.
# memdumper memdumper يقرأ ذاكرة عملية macOS قيد التشغيل باستخدام ترخيص المصحح (debugger entitlement) الخاص بثنائي موقّع آخر. الهدف هو أن يتم تشغيل `task_for_pid` و`ptrace` بصفة ذلك الثنائي المصرَّح له، بحيث يسجّله نظام كشف الاستجابة للنقاط الطرفية (EDR) بدلاً من memdumper. بقلم cenobyte <[email protected]> 2026 https://github.com/cenobyte-vincit/memdumper ## ملخص يُشغّل memdumper ثنائيًا مصرَّحًا له يحمل `com.apple.security.cs.debugger` بالإضافة إلى الترخيصين اللذين يسمحان بحقن `DYLD_INSERT_LIBRARIES`. استخدمه للبحث عن ذاكرة عملية أخرى وتفريغها بصلاحيات الجذر (root). يُعد `jspawnhelper` من OpenJDK ثنائيًا شائع الوجود، خاصة على أجهزة المطوّرين. أعطِ `attach.sh` معرّف العملية المستهدفة (PID) والثنائي المصرَّح له. يعمل مُنشئ (constructor) الـ dylib قبل `main` الخاص بذلك الثنائي، فيقرأ الهدف ثم يستدعي `_exit`. لا يصل الثنائي المصرَّح له أبدًا إلى `main`، لذلك لا يطبع مساعد مثل `jspawnhelper` رسالة الاستخدام الخاصة به. تبقى الوسائط الإضافية بعد الثنائي المصرَّح له في سطر الأوامر الذي يسجّله EDR. بدون `-s`، يقوم الـ dylib بطباعة تمثيل سداسي عشري (hex-dump) لبداية كل منطقة قابلة للقراءة. مع `-s` يبحث في تلك المناطق عن السلسلة النصية. لا تتم كتابة مساحة العنوان المستهدفة. قد يؤدي الإرفاق عبر `ptrace` إلى إيقاف الهدف مؤقتًا. حالة التفريغ هي سطر `[RESULT]` في المخرجات المدمجة. يؤدي الإدراج الناجح إلى الخروج بالكود 0 بعد انتهاء المُنشئ. ## المتطلبات ### المضيف وقت التشغيل - macOS (Darwin) - صلاحيات الجذر (root) - ثنائي مصرَّح له بالتراخيص الثلاثة جميعها: `com.apple.security.cs.debugger` و`com.apple.security.cs.allow-dyld-environment-variables` و`com.apple.security.cs.disable-library-validation` (على سبيل المثال `jspawnhelper` من OpenJDK) ### مضيف البناء - macOS (Darwin) مع Xcode Command Line Tools أو Xcode - `clang` - `make` - **shellcheck** لـ `attach.sh` وسكربتات الاختبار (`brew install shellcheck`) - **cppcheck** (`brew install cppcheck`) ## البناء على مضيف البناء: ```bash make ``` يُنتج ذلك `memdumper.dylib`. انسخ `attach.sh` و`memdumper.dylib` إلى الهدف إذا لم يكن الجهازان متطابقين. ## الاستخدام شغّل `attach.sh` بصلاحيات الجذر من الدليل الذي يحتوي على `memdumper.dylib`. يُرفض أي مستخدم غير جذر قبل بدء تشغيل الثنائي المصرَّح له (`root required`). يتجنّب ذلك نافذة كلمة مرور Developer Tool Access (`taskgated` / Authorization Services). تلك النافذة ليست من نظام الشفافية والموافقة والتحكم (TCC). ```bash sudo ./attach.sh [options] -p <pid> <entitled-binary> [args...] ``` | الخيار | المعنى | |--------|---------| | `-p`, `--pid PID` | معرّف العملية المستهدفة (مطلوب) | | `-s`, `--search STRING` | البحث عن STRING في المناطق القابلة للقراءة | | `-n`, `--max-regions N` | الحد الأقصى للمناطق القابلة للقراءة للطباعة السداسية العشرية (الافتراضي 100). مسار التفريغ فقط | | `-f`, `--force` | المتابعة عندما يُبلغ `codesign` عن Hardened Runtime | | `-h`, `--help` | طباعة الاستخدام (الخروج بالكود 1) | تحقّق من أن الثنائي المصرَّح له المرشّح يحمل التراخيص الثلاثة. يختلف بادئة Cellar وإصدار OpenJDK؛ يستخدم `attach.sh` هذا التخطيط في ملف المساعدة الخاص به: ```bash codesign -d --entitlements - \ /opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper ``` افرغ المناطق القابلة للقراءة للعملية 4543: ```bash sudo ./attach.sh -p 4543 \ /opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper ``` ابحث في تلك العملية عن `HELLO`: ```bash sudo ./attach.sh -s HELLO -p 4543 \ /opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper ``` اقصر التفريغ على 50 منطقة قابلة للقراءة، باستخدام `java` كالثنائي المصرَّح له. يبقى `TestSpawn` في سطر الأوامر؛ ولا يتم تشغيل `main` الخاص بـ `java`: ```bash sudo ./attach.sh -n 50 -p 4543 \ /opt/homebrew/Cellar/openjdk/25.0.2/bin/java TestSpawn ``` محرّر نصوص بدون Hardened Runtime يُعد هدفًا جيدًا لاختبار سريع (smoke test). ابدأ تشغيله باسم المستخدم الذي يملك الملف، ثم ارفق بصلاحيات الجذر. في طرفية واحدة: ```bash nano foo ``` ضع `HELLO` في المخزن المؤقت بحيث تكون السلسلة موجودة في العملية (احفظ الملف أو اتركه دون حفظ). في طرفية أخرى، من الدليل الذي يحوي `memdumper.dylib`: ```bash sudo ./attach.sh -s HELLO -p "$(pgrep nano)" \ /opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper ``` تبدو النتيجة الناجحة هكذا. بعدها تخرج العملية بالكود 0: ```text Target: pid 9810 (nano) Target has no hardened runtime [MEMDUMPER] Injected into PID 9852 (UID 0) [MEMDUMPER] Entitled binary: /opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper [MEMDUMPER] Target: PID 9810 (nano) [METHOD1] task_for_pid() on PID 9810 (nano) [*] Got task port 0x1013 [*] Task info: virt=425098.0MB res=8.3MB [*] Threads: 1 [MEMORY SEARCH] Looking for "HELLO" in PID 9810 MATCH at 0xa70c60200 (region: 0xa70c00000-0xa71000000, prot: rw-) Context: 0x0000000a70c601c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0x0000000a70c601d0: 4f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |O...............| 0x0000000a70c601e0: 23 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |#...............| 0x0000000a70c601f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0x0000000a70c60200: 48 45 4c 4c 4f 00 00 00 00 00 00 00 00 00 00 00 |HELLO...........| 0x0000000a70c60210: 66 6f 6f 00 00 00 00 00 00 00 00 00 00 00 00 00 |foo.............| 0x0000000a70c60220: 6f d9 b9 83 27 dc 6a ec 00 04 00 00 00 00 00 00 |o...'.j.........| 0x0000000a70c60230: 48 45 4c 4c 4f 00 00 00 00 00 00 00 00 00 00 00 |HELLO...........| 0x0000000a70c60240: 2e 2f 00 00 00 |./...| MATCH at 0xa70c60230 (region: 0xa70c00000-0xa71000000, prot: rw-) Context: 0x0000000a70c601f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0x0000000a70c60200: 48 45 4c 4c 4f 00 00 00 00 00 00 00 00 00 00 00 |HELLO...........| 0x0000000a70c60210: 66 6f 6f 00 00 00 00 00 00 00 00 00 00 00 00 00 |foo.............| 0x0000000a70c60220: 6f d9 b9 83 27 dc 6a ec 00 04 00 00 00 00 00 00 |o...'.j.........| 0x0000000a70c60230: 48 45 4c 4c 4f 00 00 00 00 00 00 00 00 00 00 00 |HELLO...........| 0x0000000a70c60240: 2e 2f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |./..............| 0x0000000a70c60250: 66 6f 6f 00 00 00 00 00 00 00 00 00 00 00 00 00 |foo.............| 0x0000000a70c60260: 2f d9 b9 83 27 dc 6a ec 22 90 00 00 00 00 00 00 |/...'.j.".......| 0x0000000a70c60270: 00 00 00 00 00 |.....| Search complete: 2 matches found in 70 regions [METHOD2] ptrace(PT_ATTACHEXC) on PID 9810 (nano) [*] Attached with ptrace [*] Detached successfully [METHOD3] proc_pidinfo() on PID 9810 (nano) [*] Process info: virt=425098MB res=8MB threads=1 [RESULT] 3/3 methods successful - FULL ACCESS [MEMDUMPER] Done ``` لا يتحقق `attach.sh` من تراخيص الثنائي المصرَّح له. أي ثنائي ينقصه أي من التراخيص الثلاثة يفشل عند الإدراج أو عند `task_for_pid`. ## أكواد الخروج | الكود | المعنى | |------|---------| | 0 | انتهى المُنشئ بعد إدراج ناجح | | 1 | خطأ في الاستخدام، أو عدم وجود صلاحيات الجذر، أو غياب `memdumper.dylib` في الدليل الحالي، أو غياب `-p`، أو عدم العثور على معرّف العملية المستهدف، أو Hardened Runtime بدون `--force`، أو لم يحدث الإدراج (عندها يعمل الثنائي المصرَّح له ويخرج بكود غير صفري) | اقرأ `[RESULT]` لمعرفة نتيجة التفريغ. الخروج بالكود 0 يعني فقط أن المُنشئ عمل واستدعى `_exit`. ## الإعداد يقرأ الـ dylib ثلاثة متغيرات بيئة. يضبطها `attach.sh` من الخيارات. | المتغير | يُضبط بواسطة | المعنى | |----------|--------|---------| | `MEMDUMPER_TARGET_PID` | `-p` | معرّف العملية المستهدفة. إذا كان غير مضبوط أو ليس عددًا صحيحًا موجبًا، يطبع المُنشئ رسالة تشخيص ويعود | | `MEMDUMPER_SEARCH` | `-s` | إذا لم يكن فارغًا، يبحث بدلاً من التفريغ | | `MEMDUMPER_MAX_REGIONS` | `-n` | المناطق القابلة للقراءة للطباعة السداسية العشرية (الافتراضي 100). يُتجاهل في مسار البحث | يمكن لأي مُشغِّل يضبط `DYLD_INSERT_LIBRARIES` على `memdumper.dylib` وهذه المتغيرات أن يحقن الـ dylib. `attach.sh` هو المُشغِّل المضمّن في المستودع. ## التحقق على مضيف البناء، يبني `make` الـ dylib ويشغّل **cppcheck** و**shellcheck**. يشغّل `make test` اختبارات الوحدة ومجموعة الاختبارات الوظيفية. هذه الفحوصات ليست دليلًا على بيئة تشغيل نظيفة. تتطلب اختبارات البحث والتفريغ المباشرة Homebrew OpenJDK وuid 0. تستخدم `find` للعثور على `jspawnhelper` تحت `/opt/homebrew/Cellar` و`/usr/local/Cellar` (بدون تثبيت إصدار). لا تستدعي `sudo` بنفسها. بدون Homebrew OpenJDK، أو إذا لم تكن المجموعة تعمل بالفعل بصلاحيات uid 0، تُتخطى هاتان الحالتان (الخروج بالكود 77)، لذلك لا يعرض `make test` بصلاحيات غير جذر نافذة Developer Tool Access. استخدم `sudo make test` عندما تريد تشغيل تلك الحقن: ```bash make make test sudo make test make test-unit make test-functional ``` ## القيود - يمنع Hardened Runtime كلاً من `task_for_pid` و`ptrace` وقراءة الذاكرة (1Password, Chrome). يحاول `--force` مع ذلك ويكاد يفشل دائمًا. - يتطلب مضيف التشغيل صلاحيات الجذر. يعرض الإرفاق بصلاحيات غير جذر لنفس المستخدم نافذة كلمة مرور Developer Tool Access: يقيّم `taskgated` حق Authorization Services `system.privilege.taskport`. تلك النافذة ليست من TCC. لا يلغي وضع المطوّر (`DevToolsSecurity -enable`) مع العضوية في `admin` أو `_developer` هذه النافذة بالنسبة إلى Homebrew `jspawnhelper` (adhoc، ترخيص مصحح عام). إنه يتخطى كلمة المرور فقط للأدوات الموقّعة من Apple مثل `lldb`. تُبقي حماية سلامة النظام (SIP) عمليات النظام المحمية بعيدة عن المتناول حتى بصلاحيات الجذر. - ترفض بعض العمليات (بما في ذلك `bash`) الإرفاق عبر فحوصات أخرى. - يبحث `attach.sh` عن `memdumper.dylib` في دليل العمل الحالي، وليس بجانب السكربت. لتسليح هذا بالكامل، نفّذ ذلك المُشغِّل في المرحلة 1 أو المرحلة 2 بدلاً من شحن `attach.sh`. - يقرأ البحث 10 MiB على الأكثر من كل منطقة قابلة للقراءة ويتنقل عبر 1000 منطقة على الأكثر. يتوقف مسار التفريغ بعد 10000 منطقة ويطبع فقط أول 256 بايت من كل منطقة معروضة (`-n` / `MEMDUMPER_MAX_REGIONS`، الافتراضي 100). - `ptrace` هو إرفاق وفصل فقط. لا يفرّغ الذاكرة. `[RESULT] 3/3` يعني أن الاستدعاءات الثلاثة نجحت جميعها، وليس أن ثلاثة مسارات تفريغ عملت. - يسجّل CrowdStrike Falcon لنظام macOS متغيرات بيئة العمليات، بما في ذلك `DYLD_INSERT_LIBRARIES`. يستخدم صائدو التهديدات هذا الحقل لرصد هذا الحقن. - معرّف عملية واحد مستهدف لكل استدعاء. ## انظر أيضًا - ARCHITECTURE.md: وراثة التراخيص، taskgated و`system.privilege.taskport`، الطرق الثلاث، ما يسجّله EDR - AGENTS.md: مضيف البناء مقابل الهدف