**macOS でのコード注入 ― 楽しむためだけ、収益は一切ない(2024)**

2026/03/05 2:05

**macOS でのコード注入 ― 楽しむためだけ、収益は一切ない(2024)**

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

記事では、Mach API を用いて macOS の実行中プロセスにコードを注入する方法を示し、関数の置き換えとグローバルデータの変更を行う最小限で機能的なプロトタイプを提示しています。まず、

data
というグローバル変数を出力し
foo()
を呼び出す単純な C++ テストプログラムから始まります。インジェクタはテストプログラムが書き込んだ
data.txt
ファイルを読み取り、対象プロセスの PID と
foo()
および
data
のアドレスを取得し、
task_for_pid
でアタッチします。

プロセスは

task_suspend
で停止し、後で
task_resume
で再開されます。メモリは
vm_read_overwrite
で読み取り、
vm_write
で書き込みを行います。例では
data
123 から 456 に変更し、パッチ適用後にプログラムが 857 を出力することで、注入された関数
bar()
foo()
の代わりに実行されていることを確認しています。

コードを注入するには、インジェクタ自身の

bar()
をコンパイルし、ダミーの
barEnd()
マーカーでサイズを計算します。次に
vm_allocate
でターゲットプロセスに可実行メモリを確保し、
writeBytes
で機械語を書き込み、別途保護呼び出しで読み取り/実行権限へ変更します。トランプル(ジャンプ)コードは
foo()
の先頭に書かれます(x86_64 では 14 バイト、arm64 では 16 バイト)。記事ではトランプルを書き込む際に
VM_PROT_COPY
を使用しています。

テストプログラムの

foo()
は、ARM トランプル用に十分なスペースを確保するために
-fpatchable-function-entry=4,0
でコンパイルし、最初に 4 バイト分の NOP を挿入する必要があります。著者はこれは最小限のプロトタイプであり、本番環境向けではないことを指摘し、トランプル上書き中のスレッド停止やデバッガ統合の欠如など潜在的な問題点も挙げています。

コードは GitHub(

https://github.com/badlogic/macinject
)でホストされており、macOS 10.14+ で CMake を使ってビルドできます。この手法はライブパッチングやデバッグツールの基盤として利用できるほか、同様の仕組みがマルウェアに悪用される可能性も示しています。

本文

2024‑07‑20


概要

本ガイドでは、Mach API(

task_for_pid
vm_write
など)を使って macOS 上で実行中のプロセスにコードを注入する方法 を解説します。内容は以下の通りです。

  1. CMake の設定とエンタイトルメント
  2. 実行中プロセスへのアタッチ
  3. スレッドの停止/再開
  4. リモートメモリの読み書き
  5. コード注入とトランスペイリング(trampoline

1. CMake の設定とエンタイトルメント

cmake_minimum_required(VERSION 3.10)
project(macinject)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")

# ---------- テストプログラム ----------
file(GLOB_RECURSE TEST_SOURCES "test/*.cpp")
add_executable(test ${TEST_SOURCES})
set_target_properties(
    test PROPERTIES COMPILE_FLAGS "-O0 -g -fpatchable-function-entry=4,0"
)

# ---------- 注入プログラム ----------
file(GLOB_RECURSE SOURCES "src/*.cpp")
add_executable(macinject ${SOURCES})

# デバッガ権限でインジェクタを署名
add_custom_command(
    TARGET macinject POST_BUILD
    COMMAND codesign --entitlements "${CMAKE_SOURCE_DIR}/entitlements.plist" \
                      -s "Apple Development" $<TARGET_FILE:macinject>
)

entitlements.plist
:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
        "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.cs.debugger</key>
    <true/>
</dict>
</plist>

ビルド手順:

git clone https://github.com/badlogic/macinject
cd macinject
mkdir build && cd build
cmake ..
make          # ソースを変更したら再ビルド

2. 実行中プロセスへのアタッチ

テストプログラムは

data.txt
に自分の PID、
foo
のアドレス、
data
のアドレスを書き込みます。

#include <cstdio>
#include <cstdlib>
#include <mach-o/arch.h>
#include <mach/mach.h>
#include <unistd.h>

struct RemoteProcess {
    pid_t   pid;
    void*   fooAddr;
    void*   dataAddr;
    mach_port_t task;
};

bool attach(RemoteProcess &proc) {
    FILE *file = fopen("data.txt", "r");
    if (!file) { perror("open data.txt"); return false; }

    fscanf(file, "%d", &proc.pid);
    fscanf(file, "%p", &proc.fooAddr);
    fscanf(file, "%p", &proc.dataAddr);
    fclose(file);

    kern_return_t kr = task_for_pid(mach_task_self(), proc.pid, &proc.task);
    if (kr != KERN_SUCCESS) {
        fprintf(stderr, "task_for_pid failed: %s\n",
                mach_error_string(kr));
        return false;
    }
    printf("Attached to pid %d\n", proc.pid);
    return true;
}

3. スレッドの停止/再開

bool suspend(RemoteProcess &proc) {
    if (task_suspend(proc.task) != KERN_SUCCESS) {
        perror("task_suspend");
        return false;
    }
    printf("Task suspended\n");
    return true;
}

bool resume(RemoteProcess &proc) {
    if (task_resume(proc.task) != KERN_SUCCESS) {
        perror("task_resume");
        return false;
    }
    printf("Task resumed\n");
    return true;
}

4. リモートメモリの読み書き

bool writeBytes(RemoteProcess &proc, void *remoteAddr,
                const void *src, size_t n) {
    if (vm_write(proc.task, reinterpret_cast<vm_address_t>(remoteAddr),
                 reinterpret_cast<vm_offset_t>(const_cast<void*>(src)), n)
        != KERN_SUCCESS) {
        perror("vm_write");
        return false;
    }
    printf("Wrote %zu bytes to %p\n", n, remoteAddr);
    return true;
}

bool readBytes(RemoteProcess &proc, void *remoteAddr,
               void *dst, size_t n) {
    vm_size_t outSize;
    if (vm_read_overwrite(proc.task, reinterpret_cast<vm_address_t>(remoteAddr),
                          n, reinterpret_cast<vm_address_t>(dst), &outSize)
        != KERN_SUCCESS || outSize != n) {
        perror("vm_read_overwrite");
        return false;
    }
    printf("Read %zu bytes from %p\n", n, remoteAddr);
    return true;
}

5. コード注入とトランスペイリング

5.1 メモリ確保と実行権限付与

bool allocate(RemoteProcess &proc, size_t sz, void **addr) {
    vm_address_t a = 0;
    if (vm_allocate(proc.task, &a, sz, VM_FLAGS_ANYWHERE)
        != KERN_SUCCESS) {
        perror("vm_allocate");
        return false;
    }
    *addr = reinterpret_cast<void*>(a);
    printf("Allocated %zu bytes at %p\n", sz, *addr);
    return true;
}

bool changeProtection(RemoteProcess &proc, void *addr,
                      size_t sz, vm_prot_t prot) {
    if (vm_protect(proc.task, reinterpret_cast<vm_address_t>(addr),
                   sz, false, prot) != KERN_SUCCESS) {
        perror("vm_protect");
        return false;
    }
    printf("Changed protection of %p to %x\n",
           addr, static_cast<unsigned>(prot));
    return true;
}

5.2 注入する関数のビルド

int bar() { return 857; }
void barEnd() {}          // サイズ測定用マーカー

int main(int argc,char**argv) {
    RemoteProcess proc;
    attach(proc); suspend(proc);

    /* --- Part 1: data を変更する --- */
    int old, newv = 456;
    readBytes(proc, proc.dataAddr, &old, sizeof(old));
    printf("Old value: %d\n", old);
    writeBytes(proc, proc.dataAddr, &newv, sizeof(newv));
    resume(proc);

    /* --- Part 2: bar() を注入する --- */
    sleep(4);                     // テストが新しいデータを出力できるように待つ
    suspend(proc);

    int barSize = reinterpret_cast<char*>(&barEnd)
                 - reinterpret_cast<char*>(&bar);
    void *remoteBar;
    allocate(proc, barSize, &remoteBar);
    writeBytes(proc, remoteBar, (void*)&bar, barSize);
    changeProtection(proc, remoteBar, barSize,
                     VM_PROT_READ | VM_PROT_EXECUTE);

    /* --- Part 3: foo() を bar() に差し替えるトランスペイリング --- */
    trampoline(proc, proc.fooAddr, remoteBar);

    resume(proc);
}

5.3 トランスペイリング実装

bool trampoline(RemoteProcess &proc, void *oldFunc, void *newFunc) {
    const NXArchInfo *arch = NXGetLocalArchInfo();
    if (!arch) { perror("NXGetLocalArchInfo"); return false; }

    unsigned char instr[16];
    size_t sz;

    if (strcmp(arch->name,"x86_64")==0) {
        // 14 バイトの絶対間接ジャンプ
        instr[0] = 0xFF; instr[1] = 0x25;
        *(uint32_t*)(instr+2)=0;            // 未使用領域
        *(uint64_t*)(instr+6)=reinterpret_cast<uint64_t>(newFunc);
        sz = sizeof(instr);

    } else if (strcmp(arch->name,"arm64")==0 ||
               strcmp(arch->name,"arm64e")==0) {
        // LDR X16, #8  ; BR X16
        instr[0] = 0x50; instr[1] = 0x00;
        instr[2] = 0x00; instr[3] = 0x58;
        instr[4] = 0x00; instr[5] = 0x02;
        instr[6] = 0x1F; instr[7] = 0xD6;
        *(uint64_t*)(instr+8)=reinterpret_cast<uint64_t>(newFunc);
        sz = sizeof(instr);

    } else {
        fprintf(stderr,"Unsupported arch %s\n",arch->name);
        return false;
    }

    // 書き込み可能にする
    changeProtection(proc, oldFunc, sz,
                     VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);
    writeBytes(proc, oldFunc, instr, sz);
    // もう一度実行権限を付与
    changeProtection(proc, oldFunc, sz,
                     VM_PROT_READ | VM_PROT_EXECUTE);

    printf("Patched %p → %p\n", oldFunc, newFunc);
    return true;
}

6. デモの実行

# ターミナル A
./build/test

# ターミナル B(テストが起動した後)
./build/macinject

テストプログラムは最初に

123
を表示し、注入後は
456
、最後にトランスペイリングで
foo()
bar()
に差し替わったため
857
を出力します。


注意点

  • 本コードは 教育目的 であり、実運用を想定したものではありません。
  • ターゲットプロセスに他のスレッドが存在する場合、全スレッド停止が妨げになる可能性があります。
  • macOS では適切なエンタイトルメント(上記
    entitlements.plist
    )を持ち、必要に応じて root 権限または「デバッグ」を許可しておく必要があります。

安全かつ責任あるハッキング・学習を楽しんでください!

同じ日のほかのニュース

一覧に戻る →

2026/03/08 5:43

CAS番号(化学物質登録番号)

## Japanese Translation: CasNumは、コンパスと定規を用いた幾何学的構成により任意精度算術を実装するPythonライブラリです。数値は平面上の点としてエンコードされ、加算・乗算・除算・論理ゲートなどの演算は、線/点、円、直線と円の交点などの5つの基本的な幾何学プリミティブから構築されます。最適化には、2倍による特殊ケース乗算や剰余計算で最高位ビット(2のべき乗)を除去する手法が含まれています。 このライブラリはGame BoyエミュレータのALUに組み込むことを想定しています。CasNumを統合するには、PyBoy の `opcodes_gen.py` を編集するだけで、他のエミュレータコードは変更されません。使用例としては、単純なRSA実装(`python3 -m examples.rsa`)や、幾何学ベースの算術のみで動作させるポケットモンスター 赤版(`python3 -m examples.basic`)があります。初回起動に約15分かかりますが、その後はPython の `lru_cache` によりほぼ 0.5–1 FPS で再起動できます。 ビュアースクリプト (`casnum/cas/viewer.py`) は幾何学的構成を可視化し自動ズームします。RSA例では手動ズームが必要になる場合があります。依存関係は、sympy(コア)、可視化用のオプション pyglet、テスト用 pytest‑lazy‑fixtures、RSA 用 pycryptodome、および任意で Euclid Postulate V です。インストールは `git clone --recursive` の後に `pip install -r requirements.txt` を実行します。使用している ROM(`2048.gb`)は zlib ライセンス、CasNum コアコードは MIT ライセンス、PyBoy は LGPL v3.0 でライセンスされており、このプロジェクトはオープンソースや教育プロジェクトに適しています。

2026/03/08 6:56

3T ブラインドスポット:米国の非営利団体

## Japanese Translation: **概要** 米国の非営利セクターは年間 **3兆ドル** を管理しており、これはイギリスのGDPを上回る金額ですが、そのうち実際にプログラム費用に充てられるのは **約36%(1,800億ドル)** に過ぎません。残りはオーバーヘッド、スタッフ給与、資金調達に使われています。登録済み非営利団体は **180万人以上** であり、その多くは収益が5万ドル未満の場合 IRS Form 990 の提出義務から免除されているため、セクター全体の大部分が公衆の監視から隠れています。 寄付者の信頼感は低下しています。**米国の寄付者の32%が5年以上前よりも慈善団体に不信感を抱いています**(BBB Wise Giving Alliance)、世界的にも三分の一が非営利団体への信頼を失っています(Gallup)。財務的負担は顕著で、**調査対象の非営利団体の36%が2024年末に営業赤字を報告し、10年間で最高水準となりました**。また **41%しか全職員に生活賃金を支払えません**。資金提供者は通常オーバーヘッドを約15 % に抑えるよう指示しますが、多くの非営利団体は管理費に **31 % 近くを使っており、過小報告やコーナーカットが頻発する** ― これは「非営利組織の飢餓サイクル」と呼ばれる現象です。 企業会計との大きな違いは顕著です。IRS Form 990 は年間一度提出され、公開までに **12–18か月** を要し、監査済み財務諸表や詳細なプログラム内訳が欠如しています。一方で公的企業は **10-K(年次)、10-Q(四半期)、8-K(重要事象)** を提出し、60日以内に監査済みの声明を求められます。このコンプライアンス中心の枠組みが可視性の問題を生み出し、寄付者の信頼を侵食しています。 国際的には、英国で実施された研究で **ウガンダの井戸の45%が非営利団体によって資金提供されましたが、機能していませんでした**。これにより 2億1,500万〜3億6,000万ドル相当のリソースが無駄になっています—非効率性の重大さを示しています。既存技術(カメラ・センサー・衛星画像)はリアルタイムで成果を追跡できる可能性がありますが、現在の報告規則ではそのような機能は義務付けられていません。 非営利セクターの将来は、コンプライアンス重視から真の透明性と説明責任への転換にかかっています。この変革なしには、非営利団体は営業赤字と寄付者の懐疑心を続けるでしょう。変革が実現すれば、信頼を回復し持続可能な資金調達を確保できる可能性があります。

2026/03/06 16:17

既存のブリックからLEGO NXTファームウェアをダンプする(2025)

## Japanese Translation: > 著者はPybricksプロジェクトで作業している際、オリジナルのファームウェアバージョン 1.01を動作させていた中古Lego NXTを入手し、このファームウェアの保存コピーが存在しないこと(利用可能なのは新しい 1.03のみ)に気づきました。 > > NXTのAT91SAM7S256 MCU上では、SAM‑BA PEEK/POKE を呼び出すことはできますが、それを行うとファームウェアを書き換えてしまい、古いMCUにはモダンなデバッグインターフェースが欠けているためJTAGも実用的ではありません。ロボットのプログラムは制限付きメモリ内で動作するバイトコードVMで走るので、著者は低レベル機能に焦点を当てました。 > > PyUSB を介して USB 「Read IO Map」コマンドを送信し、`0x100d3d`(フラッシュの約 3 KiB)に位置するVMの書き込み可能な関数ポインタ `pRCHandler` を読み取りました。32 KiB の書き込み可能 MemoryPool は NOP とカスタム ARM コードで埋めることができ、`pRCHandler` をこのプール内のアドレスにリダイレクトすることで任意の直接コマンドをそのコードとして実行させることができます。 > > 著者は、受信パケットから4バイトのアドレスを読み取り、そのアドレス上のワードを返す組み込みアセンブリを挿入し、元のハンドラを置き換えました。この乗っ取られたハンドラを利用して、USB経由で「direct」コマンドをバイト単位で送信し、フラッシュ領域全体(`0x00100000–0x00200000`)を読み取り、完全なファームウェアとユーザーデータを `nxtpwn-dump.bin` にダンプしました。 > > この脆弱性は、ストックファームウェアを実行している任意のNXTで機能し、未改変デバイス上でもベアメタルコードが動作できることを示しています。これにより、保存ツールや自己複製型マルウェアなどの可能性が開かれ、NXTファームウェアの整合性チェックにおける脆弱性も浮き彫りになっています。