~/field-notes — leeguoo@misonote — zsh EN 中文 ● 日本語
❯ field-notes v3.4.1
leeguoo@misonote:/ja/posts/macos-binary-rev-methodology/ $ 記事

# ソフトな手法からハードなパッチへ — macOS Mach-O リバースエンジニアリング方法論の振り返り

8時間にわたる macOS バイナリリバースエンジニアリングの方法論メモ — ソフトな手法はなぜ失敗するのか、いつ hex patch に切り替えるべきか、Ghidra/lldb/llvm-objdump をどう連携させるか、そして「キラーパッチ」という考え方を攻防演習でどう使えるか。具体的な target は公開せず、方法・ツール・トラブルシューティングのコツだけを扱う。

2026年5月27日 · 記事 · 公開 · 記事

このページの目次
1.{起点|きてん}2.{最初|さいしょ}の{教訓|きょうくん}:まずソフトな{方法|ほうほう}を{尽|つ}くす。ただし、いつ{諦|あきら}めるかを{知|し}る3.{二|ふた}つ{目|め}の{教訓|きょうくん}:ツールチェーンは{積|つ}み{重|かさ}ねて{使|つか}うもので、{代替|だいたい}{関係|かんけい}ではない4.3つ{目|め}の{教訓|きょうくん}:「キラー{補丁|パッチ}」を{探|さが}せ、すべての callsite を{巡回|じゅんかい}するな5.caller-side の{安全|あんぜん}{準則|じゅんそく}を{補足|ほそく}6.{第|だい}{四|よん}の{教訓|きょうくん}:{再現可能性|さいげんかのうせい}は{最低|さいてい}{条件|じょうけん}7.{五|いつ}つ{目|め}の{教訓|きょうくん}:{検証|けんしょう}は patch {自体|じたい}より{時間|じかん}がかかる8.チーム share で{何|なに}を{話|はな}せるか9.{結|むす}び

起点きてん

ある現実げんじつ問題もんだい:手元に macOS Universal Mach-O App があり、ソースコードも、開発者かいはつしゃ証明書しょうめいしょも、サーバーへのアクセス権限けんげんもない前提ぜんていで、それをうごかす必要ひつようがあった。こういう場面ばめんは、実際じっさい攻防こうぼうではそれほどめずらしくない — ひとつのバイナリをれ、その内部ないぶ判断はんだん経路けいろ理解りかいし、意味いみえない前提ぜんていで、ある分岐ぶんき自分じぶんのぞ方向ほうこうすすませたい、という状況じょうきょうだ。

以下いかでは、変更へんこうされた App がなにだったのかはあつかわず、方法ほうほうだけをあつかう。すべての trick は、日常にちじょうのセキュリティ監査かんさ、debug、グレーボックステストにそのまま応用おうようできる。

前提ぜんてい本稿ほんこうでは、あなたがそのバイナリにたいして**合法ごうほうなリバースけん**をっていることを想定そうていしている — 自分じぶん製品せいひん、CTF の課題かだい許可きょかたセキュリティ監査かんさ対象たいしょう、またはすでに公開こうかい終了しゅうりょうしているが購入こうにゅうしたことのあるふるいソフトウェアなどだ。この技術ぎじゅつ他人たにん商用しょうよう製品せいひんけることは、この記事の想定そうていする場面ばめんではない。

ネタバレ:この実戦じっせんの 8 時間じかんのうち、6 時間じかんはソフトな方法ほうほうでつまずき、2 時間じかん最終的さいしゅうてきな 18 のパッチの 90% を仕上しあげた。以下いかの 5 つの教訓きょうくんは、この 8 時間じかんかえりだ。Ghidra をすくなくとも一度いちど使つかったことがあるが、patch を実際じっさい動作どうさする app へ反映はんえいしたことはまだない、というひといている。

最初さいしょ教訓きょうくん:まずソフトな方法ほうほうくす。ただし、いつあきらめるかを

正式せいしきにバイナリをいじりはじめるまえに、私は「ソフトな方法ほうほう」にまるまる数時間すうじかんついやした — ユーザー空間くうかん設定せっていえる、Keychain をえる、hosts をえる、外向そとむきファイアウォールをる。これらのこころみはどれも失敗しっぱいしたが、失敗しっぱい仕方しかたはそれぞれちがい、そのひとつひとつがひとつのことをおしえてくれた:

  • まず crash log を:binary にれるまえ最初さいしょ動作どうさは、ぎゃくコンパイルではなく ~/Library/Logs/DiagnosticReports/<app>-*.ips だ。Dyld の初期しょきエラー(Library not loaded: @rpath/...Symbol not foundunknown library ordinal -8bundleIdentifierVerification)は、たいてい termination reasonsexception フィールドにかなり率直そっちょくかれていて、つぎにフレームワークの整合性せいごうせい、シンボル輸出ゆしゅつテーブル、receipt 検証けんしょうパスのどれをるべきかを直接ちょくせつおしえてくれる。この一手いってしたのどのソフトな方法ほうほうよりも「ソフト」で、ソフトな方法ほうほう系譜けいふにおけるだい 0 であるべきだ。
  • plist preferences をえる:feature flag にえる key のおおくは、実際じっさいには i18n localization key にすぎない。i18n key と本物ほんもの状態じょうたい key を区別くべつするための識別しきべつ信号しんごうひとつは、app がこの文字列もじれつったあと、直接ちょくせつ表示ひょうじするのか、それとも判定はんていブランチに使つかうのか、というてんだ。reflection metadata のなか命名めいめい規約きやくれば、たいていすぐに見分みわけられる。
  • Keychain entries を偽造ぎぞうする現代げんだいの Swift app は、永続化えいぞくかデータに Ed25519 署名しょめいけるのが一般的いっぱんてきだ。秘密鍵ひみつかぎがなければ正当せいとう署名しょめい生成せいせいできない。entry をんだとしても、ロード段階だんかいぎたところで署名しょめい検証けんしょうとされ、hasCachedXxx=falseかたちでログに記録きろくされる — それでもあなたは、認識にんしきされたとおもってしまう。
  • /etc/hosts でブロックする:Cloudflare WARP / iCloud Private Relay / 何らかのユーザー空間くうかん DNS リゾルバを使つかっている機械きかいでは、/etc/hosts はそもそも参照さんしょうされない。これはとくおぼえておく価値かちがある。なぜなら、hosts ファイルをつめながら、なぜ nslookup が本物ほんものの IP をかえすのかと疑問ぎもんおもうことになるからだ — かけと事実じじつ完全かんぜんはなされている。
  • 外向そとむきファイアウォール(LuLu):work はするが、移植性いしょくせいがない。べつの Mac にえるとやりなおしになる。

教訓きょうくんは「ソフトな方法ほうほうやくたない」ではない。教訓きょうくんは — ソフトな方法ほうほうにはそれぞれ解決かいけつできる問題もんだい領域りょういきがあり、目標もくひょう検証けんしょうチェーン全体ぜんたいがこちらでえられる範囲はんいそとにあるとき、ソフトな方法ほうほうをいくらかさねてもノイズでしかない、ということだ。このときはまよわずバイナリそうえるべきだ。

この臨界点りんかいてん判断はんだんするおおまかな信号しんごうがある:目標もくひょう状態じょうたい機械きかいがどううごくかはもう把握はあくしているが、どの状態じょうたいへの入口いりぐち署名しょめい / サーバー / キャッシュで交差こうさ的にまもられている、という状況じょうきょうだ。この段階だんかいでまだ外側そとがわからパッチをつづけるのは、検証けんしょうチェーンと競走きょうそうしているようなものだ。

ソフトな方法ほうほうくしたあとの判断点はんだんてんえたら、つぎはツールチェーンの選択せんたくはいる。

ふた教訓きょうくん:ツールチェーンはかさねて使つかうもので、代替だいたい関係かんけいではない

実際じっさいにバイナリそうはいったあとに使つかったツールぐん

  • Ghidra 11.3.2 headless mode主力しゅりょくのデコンパイラ。Swift バイナリは Hopper/IDA にとっても簡単かんたんではないが、Ghidra 内蔵ないぞうの Swift demangler + decompiler なら、10MB 規模きぼの Mach-O にたいする完全かんぜんな AutoAnalysis はだいたい 7〜8 ふんわる。一度いちどはしらせてから、自分じぶんの Java スクリプトで解析済かいせきずみの .gpr project を何度なんども query するのがよい。毎回まいかい Analysis をはしらせなおすべきではない。
  • xcrun llvm-objdump --disassemble --start-address=X --stop-address=Y — Ghidra が擬似ぎじ C が信用しんようできないときは、asm にもどってる。具体的ぐたいてきには、ある cbnz/tbnz の immediate を spot-check したり、movz/movk でてられたインライン Swift 文字列もじれつ確認かくにんしたりする。
  • xcrun swift-demanglenm出力しゅつりょくを pipe すれば、_$s10AppName14SomeManagerC... がどの class のどの method かすぐかる。Swift は構造こうぞう情報じょうほうのあまりにもおおくを mangling のなかかくしているので、demangle できないと作業さぎょうにならない。
  • rabin2 -zz (radare2) — すべての cstring / objc methname / Swift reflstr を vaddr きで一括いっかつ dump する。strings -a よりずっとつよい。
  • lldb -b -s commands.txt — batch mode で patch が本当ほんとう実行中じっこうちゅうの image にはいっているかを検証けんしょうする。Attach + memory read --size 4 --count 1 --format x <addr> で、期待きたいする byte と照合しょうごうする。これは「ファイルをえた」ことと「実行中じっこうちゅうのプロセスが本当ほんとうあたらしいコードを実行じっこうしている」ことを区別くべつできる唯一ゆいいつ方法ほうほうだ — 後者こうしゃでは ASLR slide も考慮こうりょしなければならない。
  • otool -l / lipo -detailed_info — Universal binary の fat header から arm64 slice の offset をる。Universal binary の universal-offset と arm64-slice-offset には固定こていの base があり、このあたいは app ごとにちがうため、毎回まいかい lipo -detailed_info <bin> | grep -A4 arm64現物げんぶつ確認かくにんする必要ひつようがある(出力しゅつりょくoffset フィールドがその base)。自分じぶん最初さいしょに base を計算けいさん間違まちがえていたた。patch はけたようにえるのに、byte がまったく無関係むかんけい場所ばしょちていたのだ。これは新人しんじんがもっともみやすいわな:スクリプトをくときは universal offset と slice offset を別々べつべつ変数名へんすうめい厳密げんみつ区別くべつする。
  • dyld_info -fixups <bin> / dyld_info -exports <bin> — macOS 12+ に標準ひょうじゅんはいっている。nm よりつよいのは、dyld が実際じっさいている chained fixups と export trie の視点してんおしえてくれることだ。nmえる「export された記号きごう」と、dyld が実際じっさいに bind に使つかう export trie は一致いっちしないことがある(build script が class を半端はんぱに rename したときによくある)。これは nm だけでは絶対ぜったい見抜みぬけないわなだ。
  • codesign --force --deep --sign - — byte をえたらかなら再署名さいしょめいする。macOS の sealed-resource 検証けんしょうは deep mode だと Contents/ ないちいさなファイルまでく。見分みわかたcodesign --verify --verbose /Applications/<app>.appsealed resource is missing or invalid報告ほうこくしたら、エラー情報じょうほうなか具体的ぐたいてきな path がしめされる(たいていは内蔵ないぞう framework / bundle 資源しげんのどれか)。そのファイルを .app のそと移動いどうしてから sign し、署名後にもどす。app ごとに「違反いはんファイル」はちがうが、特定とくていのパターンは固定こていだ。
  • hdiutil create -format UDZO変更後へんこうごの .app を DMG にもどし、「べつ機械きかい」に配布はいふできる媒体ばいたいにする。

3つ教訓きょうくん:「キラー補丁パッチ」をさがせ、すべての callsite を巡回じゅんかいするな

逆向ぎゃくこう 8 時間じかんなかでいちばん価値かちがあった認識にんしき変化へんか36 の callsite にそれぞれ補丁パッチてるより、そこから共通きょうつうしてばれる関数かんすうさがしたほうがいい

具体的ぐたいてきには:badge 描画びょうが、modal 表示ひょうじ、feature gate、メニュー項目こうもくクリック応答おうとう表面上ひょうめんじょうは 36 独立どくりつした UI / action callsite だ。だがいくつかぎゃくアセンブルすると、どれもおなじ pattern をとおることがかる:

bl  <some_fn>       ; bitmask を返す
mvn wN, w0          ; 反転
and wN, wN, MASK    ; 注目する数 bit を取り出す
cbnz xN, deny_path  ; 全部 0 でなければ拒否

ちがいは MASK だけ。すべての callsite がおな<some_fn> 判断はんだん関数かんすう共有きょうゆうしている。このときは、その関数かんすうの prologue をそのまま movn x0, #0; ret0xFFFFFFFFFFFFFFFF を返す)にえれば、すべての callsite が同時どうじに「MASK が完全かんぜん一致いっち通過つうか」となす。1 箇所かしょ変更へんこうが 36 箇所かしょぶんになる。

このかんがかた防御側ぼうぎょがわでもつ:コードを監査かんさするときは、こういう「全局ぜんきょく判断点はんだんてん関数かんすうつける。それらは攻撃者こうげきしゃにとって最高価値さいこうかち標的ひょうてきであると同時どうじに、防御ぼうぎょ重点的じゅうてんてきまもるべき対象たいしょうでもある(多重たじゅう検証けんしょう、runtime 完全性かんぜんせい名前なまえ難読化なんどくか)。

caller-side の安全あんぜん準則じゅんそく補足ほそく

「callee の prologue を movn x0, #0; retえる」この定石じょうせきは、基本型きほんがた / 整数せいすう bitmask を返す関数かんすうにしか安全あんぜんではない。callee が Swift 関数かんすうで retain-counted な class instance / Optional を返すなら、caller はほぼ確実かくじつ swift_retain(x0)bl _objc_release、x0 を log 引数ひきすうとして stack に保存ほぞん、さらに後続こうぞくswift_release_n(x0, #2) などをおこなう。このとき callee に 0 / nil を返させると、caller はすぐ nil を object として dereference して SIGSEGV になる。

実践じっせん準則じゅんそく

  • callee をいじるまえに、caller がもどをどう使つかうかる。mov x28, x0直後ちょくごbl _swift_retainつづくなら危険きけん信号しんごうだ。
  • もど再利用さいりようされるなら、caller がわから call 全体ぜんたい迂回うかいするほうが安全あんぜんtbnz / cbz を 1 ぽんえてつねに「すでに条件じょうけんたした」分岐ぶんきかせ、callee がそもそもばれないようにする。あるいはばれても caller が「もど消費しょうひしない」path にすすませる。
  • callee 入口いりぐち直接ちょくせつ ret にえる前提条件ぜんていじょうけんは:callee がもともと void-ish なもどりであること、または caller が x0 に対して retain / release / dereference をしないこと。

これはたりまえこえるが、自分じぶん初回しょかい試行しこうでそのまま SIGSEGV したので、このとしあな明確めいかくいておく。

だいよん教訓きょうくん再現可能性さいげんかのうせい最低さいてい条件じょうけん

どんなに野心的やしんてきな patch プロジェクトでも、初日しょにちから reapply スクリプトをかならえるべきだ。理由りゆうは:

  1. システムアップグレード / app 更新こうしん / 自分じぶんすべりで一度いちど上書うわがきした — こういう場合ばあいに 30 びょう以内いないで patched 状態じょうたいもど必要ひつようがある。
  2. 作業さぎょう二台目にだいめの Mac に複製ふくせいするとき、「自分じぶん手動しゅどうでどのバイトをえたか」だけが唯一ゆいいつのドキュメントだと破滅的はめつてき
  3. 復盤ふくばんやチーム共有きょうゆうのとき、スクリプトは最良さいりょうの「なにをしたか」の叙述じょじゅつになる — 各行かくぎょう明確めいかくひとつの動作どうさだからだ。

スクリプト形式けいしき推奨すいしょう

  • テーブル駆動くどうにする。hex blob ではない。かく entry は (univ_offset, expected_old_hex, new_hex, description)
  • かく patch はまえに、もとのバイトが期待通きたいどおりか check する。不一致ふいっちなら skip + ログ記録きろくし、スクリプト全体ぜんたいを abort しない — そうすればスクリプトは idempotent で、かえ実行じっこうしても無害むがい
  • patch codesign --verify一度いちどんで、再署名さいしょめいされた状態じょうたい確認かくにんする。
  • 最後さいごに、環境側かんきょうがわ強化策きょうかさく(たとえば自動更新じどうこうしん無効化むこうか)も再度さいど reassert する。この部分ぶぶんは per-user defaults なので、移行時いこうじうしなわれやすい。

いつ教訓きょうくん検証けんしょうは patch 自体じたいより時間じかんがかかる

patch ファイルをき、署名しょめいし、プロセスを起動きどうする — これはまだ第一歩だいいっぽにすぎない。本当ほんとう有効ゆうこうになったことを確認かくにんするには、つぎのステップが必要ひつよう

  1. 静的確認せいてきかくにんxxd でディスクじょうのバイトがたしかにわったことをる。
  2. 再署名確認さいしょめいかくにんcodesign -dv で signature があたらしくなっていることを確認かくにんする(codesign は、ほんのすうバイトえただけだからといって文句もんくわないが、再署名さいしょめいわすれると文句もんくう)。
  3. 読込確認よみこみかくにん:プロセスががったら vmmap <pid> で Load Address を取得しゅとくし、ASLR slide を計算けいさんしてから、lldb attach で実行中じっこうちゅうメモリないの patch 位置いちむ。もとの stp ではなく movn の encoding がえれば、patch が本当ほんとうに CPU の実行経路じっこうけいろっていることになる。
  4. 挙動確認きょどうかくにん:patch 経路けいろとおる UI / API を発火はっかさせ、目視もくしまたはログで挙動変化きょどうへんか確認かくにんする。
  5. 回帰確認かいきかくにん:app を十分じゅうぶん時間じかんはしらせ、patch したのが一回限いっかいかぎりの初期化経路しょきかけいろではなく、安定あんていして箇所かしょであることを確認かくにんする。

このいつつはどれもかせない。何度なんどか、step 1 + 2 は問題もんだいなさそうにえたのに、step 3 で ASLR slide の計算けいさん間違まちがっているとかったことがある — プロセスないのバイトはまだふるいままだった。理由りゆう

  • ファイル不一致ふいっち改変かいへんしたのが /Applications/... ない実際じっさい起動きどうされているコピーではなかった。
  • 署名しょめいキャッシュ:codesign 、macOS カーネルには cache があり、ふるい app インスタンスの Code Signing Identifierあたらしく署名しょめいしたものと一致いっちしなくても、カーネルがすぐには認識にんしきしない。
  • ASLR slide 計算けいさんミス:Mach-O 64-bit executable の __TEXT vmaddr base は 0x100000000(これは link まる。otool -l <bin> | grep -A4 'segname __TEXT' の vmaddr フィールドで確認かくにんする);dylib / framework の base は通常つうじょう 0x0slide = LoadAddress - static_base であり、LoadAddress 自体じたいではない。executable の slide 計算けいさんには 0x100000000 を、framework には 0x0使つかう — 混同こんどうしないこと。
  • TCC 権限けんげんが CDHash で失効しっこうするcodesign再署名さいしょめいで binary の CDHash がわり、macOS の TCC(Privacy & Security)は CDHash を権限けんげんしゅキーとしてあつかう。以前いぜん付与ふよした Accessibility / Input Monitoring / Screen Recording / Full Disk Access はすべてだまって失効しっこうする — UI じょうの toggle はオンにえるが、実際じっさいには権限けんげんがない。症状しょうじょう:⌥d / グローバルショートカットなどが突然とつぜん反応はんのうしなくなる。修正法しゅうせいほう:System Settings → Privacy & Security → Accessibility(または対応項目たいおうこうもく)で、app を**削除さくじょしてから再追加さいついか**する。toggle off/on ではない。

どれも一度いちどは 20〜30 ぷんうしなわせてくれた。

チーム share でなにはなせるか

この作業さぎょう社内しゃない share にとしむなら、こういう構成こうせいすすめる:

  1. 「ソフトな方法ほうほう vs ハードな方法ほうほう転換点てんかんてん見極みきわめ」 — これは経験けいけんであって、知識ちしきではない。5 つの具体的ぐたいてきなソフト方法ほうほう失敗しっぱいと、それぞれの失敗しっぱいなに露呈ろていしたかをはなす。新人しんじんはたいてい、あるしゅのソフト方法ほうほう丸一日まるいちにちらいつくので、他人たにん失敗しっぱいパターンをるだけで時間じかん節約せつやくできる。
  2. 「ツールチェーンは並列へいれつ代替だいたいではなく、スタックしきかさね」 — Ghidra のデコンパイルで大筋おおすじて、llvm-objdump で局所的きょくしょてきな asm をり、lldb で runtime を検証けんしょうする。IDA だけ、あるいは r2 だけを使つか同僚どうりょうたことがあるが、あるしゅ場面ばめんまる。
  3. 「キラーパッチをさがかんがかた — callgraph ツールで入次数にゅうじすうもっとたか関数かんすう列挙れっきょする。静的解析せいてきかいせきわせて判断点はんだんてん特定とくていする。このパターンは防御側ぼうぎょがわで「重要じゅうよう保護対象ほごたいしょう」を識別しきべつするのにもおなじように有効ゆうこう
  4. 「reapply スクリプトと再現性さいげんせい規律きりつ — これはもっと過小評価かしょうひょうかされやすい。demo とわせる:練習用れんしゅうよう app を使つかって一連いちれんながれを最後さいごのスクリプトまでとおし、チームメンバーにそので「うわ patch がえた — スクリプトをはしらせる — もどった」を体感たいかんしてもらう。
  5. 検証けんしょうの 5 階層かいそううえの 5 ステップそれぞれに、実際じっさいんだとしあな事例じれいを 1 つずつえる。

テーマの着地点ちゃくちてんかならずしも「どう crack するか」ではなく、「バイナリそう観測可能性かんそくかのうせい再現性さいげんせい」。このスキルセットは、本番ほんばん crash の調査ちょうさ、セキュリティ監査かんさ、third-party SDK の sanity check にも使つかえる。

むす

この 8 時間じかんのうち、6 時間じかんはソフトな方法ほうほうためすことについやし、2 時間じかん最終的さいしゅうてきな 18 の patch の 90% をつくった。効率こうりつわるこえるかもしれない — でも、その 6 時間じかん無駄むだではなかった。ソフトな方法ほうほう失敗しっぱいするたびに、ターゲットの内部構造ないぶこうぞうについて 1 つまなびがあり、それらの情報じょうほうのちにすべて、最終的さいしゅうてきな patch 箇所かしょ判断はんだん沈殿ちんでんしていった。もし最初さいしょからいきなり Ghidra で黙々もくもくとデコンパイルしていたら、事前仮説じぜんかせつがないまま、むしろ間違まちがった関数かんすうにもっと時間じかん使つかっていただろう。

リバースエンジニアリングの本質ほんしつは、「情報密度じょうほうみつど最適化さいてきか」という仕事しごとだ — 手元てもとにあるバイナリはこうエントロピーであり、かぎられた時間じかんのなかでツールを使つかってそのエントロピーを圧縮あっしゅくし、「どの数行すうぎょうのコードが、どのユーザー可視かし挙動きょどうめているのか」という問いいにこたえる。この意味いみで、攻防演習こうぼうえんしゅう訓練価値くんれんかち最終的さいしゅうてきな patch にあるのではなく、「制約せいやくされた情報じょうほうのもとで構造こうぞう推論すいろんする」筋肉記憶きんにくきおくにある。

この筋肉記憶きんにくきおくは、コードをいてプロダクトをつくひとたちにも役立やくだつ。

← 前の記事
ステータスバーのあの cache 4m23s は、結局正確なのか?
次の記事 →
AI監査に向き合う:リバースエンジニアリングプロジェクトをどのように「合法化」できるか?

コメント

コメントは即時公開されますが、ポリシー違反時は非表示になる場合があります。

最大 1000 文字。

    ⎇ main ● 0 errors · 0 warnings UTF-8 Markdown /ja/posts/macos-binary-rev-methodology/ © 2026 leeguoo