
يشغّل برامج macOS عبر واجهة XPC الخاصة بـ launchd دون استخدام استدعاء exec لها، مما يجعل EDR يسجّل launchd كعملية أصل. يدعم المهام لمرة واحدة، وKeepAlive، والمهام القائمة على plist.
يبدأ xspawn برنامجًا على macOS عبر launchd ولا ينفّذ (exec) ذلك البرنامج بنفسه أبدًا. الهدف هو أن يسجّل نظام EDR أداة launchd كعملية أصل (parent)، وليس هذه الأداة أو الصدفة المستدعية.
بقلم cenobyte [email protected] 2026
https://github.com/cenobyte-vincit/xspawn
يفتح xspawn الدالة xpc_pipe_create_from_port(bootstrap_port) ويُقلع تنفيذ برنامج عبر _xpc_pipe_interface_routine، وهو نفس أنبوب XPC الخاص الذي يستخدمه launchctl، ولا ينفّذ البرنامج بنفسه أبدًا.
/bin/launchctl أبدًا.gui/<uid>cc)makebrew install cppcheck)make
xspawn oneshot -l <label> [-o <stdout>] [-e <stderr>] [--] <program> [args...]
xspawn submit -l <label> [-o <stdout>] [-e <stderr>] [--] <program> [args...]
xspawn remove -l <label>
xspawn load -p <plist>
لمرة واحدة (RunAtLoad + LaunchOnlyOnce؛ 0 تعني بلا نوم):
./xspawn oneshot -l com.example.once -- /tmp/helloworld 0
الوسائط بعد -- هي ProgramArguments. ويشمل ذلك الكود المضمّن (python3 -c, perl -e). يسجّل CrowdStrike Falcon لنظام macOS سطر الأوامر الكامل CommandLine، لذا استخدم الكود المضمّن مع المفسرات (interpreters) بحذر.
./xspawn oneshot -l com.example.py -o /tmp/py.out -- \
/usr/bin/python3 -c "print('hello world')"
مهمة KeepAlive، بنفس دورة حياة launchctl submit. اجعل البرنامج ينام 60 ثانية حتى يظل CrowdStrike Falcon لنظام macOS وlaunchctl print يريان العملية:
./xspawn submit -l com.example.svc \
-o /tmp/out.log -e /tmp/err.log -- /tmp/helloworld 60
افحص باستخدام launchctl print (مرجع فقط؛ هذا العميل لا يستدعيها):
launchctl print gui/$(id -u)/com.example.svc
يُظهر النجاح type = LaunchAgent (وليس Submitted)، وprogram كمسار مطلق، وstate = running أو xpcproxy لفترة وجيزة. Submitted تعني أن المهمة لم تسلك مسار الإقلاع (bootstrap).
نظّف مهمة اختبار:
./xspawn remove -l com.example.svc
حمّل plist يملكه المستدعي (لا يُحذف بعد الرد):
./xspawn load -p /tmp/job.plist
يجب أن يكون <program> مسارًا مطلقًا. لا يبحث launchd في $PATH.
يتطلب load -p مسارًا مطلقًا ينتهي بـ.plist.
قد يكون -o / -e نسبيًا. يتم حلهما نسبةً إلى دليل العمل الحالي قبل كتابتهما في plist. -o و-e المحذوفان يكونان /dev/null.
يختبر oneshot وsubmit المعرّف في gui وuser (الوصف 708) قبل كتابة ملف plist المؤقت. إذا كان المعرّف مستخدمًا يُنهي البرنامج برسالة label already loaded دون أي مخرجات قياسية. هذا الفحص موجود حتى لا يكتب طلب 800 المحكوم بالفشل $TMPDIR/XXXXXX/XXXXXX.plist (أثر جنائي رقمي DFIR؛ يحتفظ CrowdStrike Falcon بالمسار في ASEPFilePath) أو يطبع نسخة XML من قاموس المهمة. إذا كان المعرّف حرًا يطبع المسار المؤقت، ثم نسخة XML، ثم يرسل 800. يجري load -p نفس فحص الإشغال على Label الخاص بالملف، ثم يطبع مسار المستدعي وXML. تتم إزالة الدليل المؤقت عند كل خروج. remove يتم بالمعرّف.
| الكود | المعنى |
|---|---|
| 0 | نجح إقلاع XPC أو إخراجه (bootout) |
| 1 | خطأ في الاستخدام، أو معرّف غير صالح، أو الجذر، أو رفض من launchd/XPC |
مضيف البناء (make وشجرة الاختبار؛ غالبًا ما يتعايش مع جلسة gui). هذه الفحوص ليست دليلًا على بيئة تشغيل نظيفة:
make
make test
make test-unit
make test-functional
./xspawn oneshot -l com.example.once -- /tmp/helloworld 0
gui/<uid> لنفس المستخدم. الجذر مرفوض. لا استهداف لمعرّف مستخدم آخر.sw_vers -buildVersion (انظر ARCHITECTURE.md).$TMPDIR/XXXXXX/XXXXXX.plist ($TMPDIR يجب أن يكون مطلقًا، وإلا فـ/tmp). تتم إزالة الدليل عند كل خروج. المعرّف المستخدم لا ينشئ هذا الملف أبدًا.ASEPFilePath) في حدث ProcessRollup2. تبقى العملية الأصل launchd.xspawn المستقل مرئي كعميل: محفوظات الصدفة، وحدث عملية EDR لهذا الثنائي. يسجّل CrowdStrike Falcon لنظام macOS سطر الأوامر الكامل CommandLine، والذي يتضمن مسار البرنامج ووسائطه. ادمج العميل في أدوات أخرى عندما تكون تلك الصورة وargv مميزة. لا يزيل الدمج ASEPFilePath أو سطر إقلاع launchd.log (انظر ARCHITECTURE.md، الأصل Parentage).launchd هو خادم إقلاع (bootstrap) من نوع Mach. لا يستخدم هذا العميل XPC العام (xpc_connection_create). يفتح أنبوب libxpc خاصًا على منفذ bootstrap_port الموروث عبر xpc_pipe_create_from_port(bootstrap_port, 4)، ثم يرسل _xpc_pipe_interface_routine. هذه الرموز موجودة في libxpc وليست في ترويسات SDK.
معرّف الروتين هو وسيط الوصف (descriptor)، وليس مفتاحًا في قاموس الطلب. على macOS 26.6.1 build 25G76، يكون التحميل هو الوصف 800 والإخراج هو 801. أعلام الواجهة هي 6. مطلوب جلسة gui/<uid>: المنفذ الموروث هو نطاق launchd لواجهة gui فقط داخل جلسة تسجيل دخول Aqua، وهذا العميل يرسل فقط type 8 مع handle = uid.
التحميل (800) هو قاموس XPC. تعريف المهمة ليس في نص الرسالة.
handle uid (uint64)
type 8 (gui)
paths [absolute .plist]
by-cli true
ينفذ launchd stat على المسار، ويحلل plist، ثم يستدعي posix_spawn على xpcproxy. ينفذ xpcproxy البرنامج عبر exec في نفس PID. النجاح هو إرجاع الأنبوب 0، دون xpc-fault، وerror 0، وbootstrap-error 0.
الإخراج (801) يتم بالمعرّف: handle وtype 8 وname وno-einprogress وwait. لا يوجد plist.
هذه القناة هي فن سابق (prior art). أظهر جوناثان ليفين (launjctl، 2015؛ Mac OS X and iOS Internals المجلد 1) أن launchctl يتواصل مع launchd عبر أنبوب XPC خاص، ووثّق xpc_pipe_create_from_port / xpc_pipe_routine بمفاتيح القاموس type وhandle وsubsystem وroutine وname. وثّق باتريك واردل (The Art of Mac Malware المجلد 2) _xpc_pipe_interface_routine كمدخل الإرسال الأحدث. رسم كسابا فيتزل وبراندون دالتون (OBTS) خريطة نفس عائلة القواميس وأكواد نوع النطاق (gui هو 8). استخدمت مقتطفات عامة بالفعل xpc_pipe_create_from_port(bootstrap_port, 4).
تلك المقالات تصف فئة البروتوكول. لا تتضمن ثوابت التحميل الفعلية لـ25G76. التقاط ليفين عام 2015 وضع subsystem وroutine داخل القاموس واستخدم xpc_pipe_routine. على 25G76 هذه المفاتيح غائبة. يصل launchctl bootstrap إلى _xpc_pipe_interface_routine مع معرّف الروتين كوسيط الوصف. لا يزال التحليل الثابت arm64e لـlaunchctl يبدو وكأنه مسار القاموس القديم ويقترح 703 كمعرّف تحميل. يستخدم كل من lldb الحي x86_64 وتشغيل عميل arm64 الرقمين 800 / 801 مع غياب هذه المفاتيح. يوفّر هذا العميل هذا الشكل الواحد على كلتا الشريحتين.
تفريغات المسجلات، ووصفة إعادة التثبيت عبر lldb، وملاحظات حقل الأصل موجودة في ARCHITECTURE.md.
| الأمر الفرعي | دورة الحياة |
|---|
oneshot | لمرة واحدة (RunAtLoad + LaunchOnlyOnce) |
submit | KeepAlive |
load | plist يملكه المستدعي كما هو مكتوب |
remove | إلغاء التحميل بالمعرّف (label) |
/private/var/log/com.apple.xpc.launchd/launchd.log (انظر ARCHITECTURE.md، الأصل Parentage).XPCService في plist مكتوب يدويًا: عندها يتفرع xpcproxy ويسجّل CrowdStrike Falcon لنظام macOS الأصل xpcproxy.