状態:保守中 · 担当:OhMyMeme ベースのカスタマイズ(単独開発) · コード:公開 · 最終更新:2026-11

最初に正直なところ:これは fork です

MemeCollector は私がゼロから書いたものではありません。OhMyMeme(デスクトップ、Python + Vue + pywebview)と OhMyMeme-Android(Kotlin)をベースにしています。アップストリームはどちらも GPL-3.0 で、私も GPL-3.0 で配布し、元の著作権表示とライセンスはソースに同梱しています。アップストリームは 2 つのコミットに固定していて(リポジトリの upstream-lock.json 参照)、最新ブランチを追いかけているわけではありません。

アップストリームにもともとあったもの:全体のフレームワーク、クラウド同期、プラグインシステム、GIF ステガノグラフィの一式。私が追加したもの:

  • Windows:候補の選択、遅延ダウンロード(プレビューまたは保存時に初めて原ファイルを取得)、インクリメンタルな索引、厳密な形式検証、失敗時のクリーンアップ
  • Android:フィルタと動画プレビュー、結果の分類(成功 / 重複 / 非対応 / 上限超過 / 失敗)、権限ガイド、上限付き読み込み、独立したパッケージ名と署名
  • 両端:デフォルトはローカル、アップストリームの更新を自動インストールしない;ビルドスクリプトとテスト
  • そして以下で述べるセキュリティ監査と 10 件の修正

この境界を最初に書くのは、この後の「私がやった」はすべて上のリストだけを指し、アップストリームにもともとあったものを指さないからです。

何を解決したかったか

QQ や微信で気に入ったスタンプを見つけて保存しようとすると、たいていこうなります:動画スタンプが静止画で保存される、スクリーンショットでアニメーションが消える、あるいはそもそも保存できない。私が欲しかったのは原ファイルです——GIF はその GIF のまま、動画 WebP はその動画 WebP のまま、フレーム抽出も再エンコードもしない。

プライバシー面のトレードオフもあります:このツールは能動的に選んだものだけ保存します。チェックしたものだけ保存し、チャット全体を継続的に読み取ることはありません。微信側では、まずデスクトップでスタンプパネルを開いて初めてマニフェストを読みに行きます。

バイト単位という話

簡単そうに聞こえますが、罠だらけです。

内容で判定し、拡張子を信じない。 キャッシュファイルの拡張子はよく間違っています。vivo X100 Ultra(Android 16)で実測したところ、元の名前が .jpg のファイルが、内容で判定すると実際は 240×240・28 フレームの GIF で、保存後にバイト単位と SHA-256 が完全に一致しました。拡張子を信じていたら、この動画は静止画として扱われていたところです。

非対応は非対応と言う。 微信ストアの独自形式、QQ スーパースタンプ、デコードできないファイルは「非対応」と明示的に報告し、最初のフレームだけ保存して成功を装いません。これは数形式多く対応するより大事だと思います——偽の成功は失敗よりたちが悪い。

重複排除とインクリメンタル。 SHA-256 で重複排除;失敗は「収集済み」と記録しないので再スキャンで回復できる;1 ファイルの失敗は他に影響しない。実測でその GIF を 1 枚保存した後、未保存候補は 755 から 754 に減りました。

デフォルトの取り込み上限は 20 MiB・長辺 2560px(Android はメモリ抑制のためこの上限を維持、デスクトップは調整可)。サムネイルと原ファイルは分けて保存します。

微信の鍵はどこから来るか

微信のスタンプ庫は暗号化された SQLite です。鍵はファイルの中ではなく、微信プロセスのメモリの中にあります。そこでデスクトップ側はプロセスメモリフォレンジクスを行う小さな C++ ヘルパーを同梱しています(読み取り専用、ネットワークなし、微信には一切書き込まない)。

鍵取得の主な経路は mask recovery です:庫ファイルの先頭 16 バイトは既知の salt で、メモリには 32 バイトの XOR mask で隠された 99 バイトの鍵バッファがあり、salt から mask を逆算できます。利点は RVA オフセットに依存しないことで、微信のバージョン変更でオフセットを探し直す必要がありません(古い RVA スキャンはフォールバックとして残しています)。SHA-512 などのプリミティブは OpenSSL を持ち込まずツリー内に自前で実装しました。

私が気にしているいくつかの細部:

  • ヘルパーの SHA-256 はビルド時に固定します——MSVC ビルドは再現不可能(タイムスタンプを埋め込む)なので、スクリプトがコンパイル後に実際の成果物のハッシュを計算し、ソースに書き戻してからパッケージします。手でハッシュをコミットするのではありません。検証は fail-closed:実際のハッシュが設定されていなければ実行を拒否します。
  • テスト用の --key 引数(鍵を直接注入してフォレンジクスをスキップ)は release では完全にコンパイルアウトされ、CMake オプションを明示的に有効にしたときだけ存在します。
  • 鍵は収集索引に書き込まれず、クラウドサービスにも送られません。Python 側は 90 秒のタイムアウトで全体を制限します。

(ここでは設計だけを述べ、手順は書きません——これは私自身のスタンプをバックアップするためのもので、チュートリアルではありません。)

Android 側:Shizuku と一括 stat

Android でスマホの QQ キャッシュを直接読むのは Shizuku 頼みで、root ではありません。具体的な罠が 2 つ:

  • ファイルごとに shell を起こすのは遅すぎる。 一括 stat に変えた後、ADB 実測で 756 ファイルのサイズ/mtime/パス読み取りが約 0.22 秒;最終署名 APK は起動から 754 件の未保存候補を表示するまで約 3 秒。
  • リモートプロセス待機の互換性。 Shizuku のリモートプロセスを Java のデフォルトタイムアウトでポーリングすると process hasn't exited が発生するので、再発防止の回帰ケースを追加しました。

Shizuku がなくても使えます:ファイル/アルバム取り込み、システムの画像共有の受け取り、デスクトップで収集してスマホへ転送、デスクトップ ADB プルは、いずれもスマホの Shizuku に依存しない入口です。Shizuku は「QQ キャッシュを直接読む」経路だけの必須依存で、アプリ全体の前提ではありません。

自分の fork をセキュリティ監査する

これが一番書く価値のある部分だと思います。fork はアップストリームのコードを引き継ぎ、アップストリームの前提も引き継ぎます。リリースして他人に使ってもらうなら、その前提を自分で一通り検証しなければなりません。主要なデータ経路(QQ/Shizuku、取り込みと原ファイル保存、ストレージ移行、バックアップ復元、クラウド同期、両端 LAN)を監査し、P1 が 4 件、P2 が 6 件見つかりました。いくつか挙げます:

  • デバイスを拒否した後も LAN データ接口が開いたまま。 デバイス確認のコードは、ユーザーが承認・拒否・タイムアウトのいずれでも最後に confirmed.set() を呼び、後続のリクエストはそのイベントだけを見て approved を見ていませんでした。公式 Android クライアントは拒否されると切断しますが、別のクライアントはデータを引き続きプルできました。明示的な承認のときだけ認可し、拒否/タイムアウトは即切断するよう修正。
  • 空の鍵はオールゼロの AES 鍵で、リプレイフレームも受け付ける。 空パスワードは 32 個のゼロバイトに派生され、同一サブネットの誰でも復号できる;パスワードを設定してもセッション鍵はパスワードと固定 salt からのみ派生され、今回のハンドシェイクの nonce に紐づかず、フレームに序号がない——同じ暗号化フレームを 2 回送ったら両方受け付けられました。LAN v2 は非空の鍵を必須とし、双方の nonce から方向鍵を派生し、認証付き序号を使って重複/順序不正フレームを拒否します。
  • バックアップ復元は削除してから書き込み、ロールバックなし。 元は ZIP に memes.db という名前のメンバーがあれば既存庫を上書きし、まず対象を全削除してから 1 つずつコピー、途中失敗で両方失う。バックアップ形式/データベース完全性/参照メディアを先に検証し、新位置に完全書き込み+ロールバック可能なコミット、成功まで旧データは触らないよう変更。
  • クラウドマニフェストのパストラバーサル。 一括プルはマニフェストのファイル名をそのまま createFile に渡すので、../memes.db のようなものでキャッシュ外に書き込めた。マニフェスト入口とファイル書き込み層の両方に制約を追加。

10 件すべて修正しました。修正後:デスクトップ pytest 552 passed・1 skipped、Android JVM 84 件全通過、エミュレータの取り込みと復元安全 10 件全通過、独立した両端 LAN 動画往復 1 件通過。

ただし検証記録では線を引いています:エミュレータは実機ではなく、USB TCP フォワーディングは Wi-Fi ディスカバリではない。 実際の Shizuku 起動/停止、ベンダー SAF の失敗挙動、実 Wi-Fi での UDP 自動発見とファイアウォールは、いずれも実機で未検証です。そしてこの 10 件の修正は現在 codex/audit-hardening ブランチにあり、インストーラーはまだリリースしていません。v0.1.0 がリリース済みバージョンで、監査修正は次バージョンの話です。

制限

  • Android 11 実機、Wi-Fi 自動発見、ファイアウォール環境、スマホ微信で新規追加したスタンプがデスクトップ経由で戻ってくる完全な往復は、いずれも未検証
  • 微信ストア独自スタンプ、QQ スーパースタンプは非対応で、今後のクライアントバージョンも保証しない
  • 微信の鍵取得は特定クライアントのメモリ配置に依存し、微信の大型アップデートで使えなくなる可能性がある
  • fork の維持コストは現実的:アップストリームの更新は上書きではなく再マージが必要
  • 個人利用のみ;QQ/微信および各プラットフォームの規約に従ってください
ソースを見る