【ITニュース解説】How a crypto library got my Tauri app flagged as a trojan (and how I found it by bisection)
2026年10月01日に「Dev.to」が公開したITニュース「How a crypto library got my Tauri app flagged as a trojan (and how I found it by bisection)」について初心者にもわかりやすく解説しています。
ITニュース概要
開発したデスクトップアプリがセキュリティソフトにウイルスと誤検知された。データ暗号化ライブラリが原因と判明。変更履歴をたどり効率的に特定し、純粋Rust製の代替ライブラリに交換して問題を解決した。開発では、予期せぬ誤検知対策として、ライブラリの選定と問題箇所の効率的な特定が重要だ。
ITニュース解説
ある開発者が作成したデスクトップアプリケーションが、突然、ウイルス対策ソフトウェアによって「トロイの木馬」だと誤って判定されてしまった事例について解説する。このアプリは「RAM (Roblox Account Manager)」というオープンソースのWindows用アプリケーションで、RustとTauriという技術を使って作られている。Robloxアカウントの管理や、ゲーム内での操作の自動化といった機能を持っている。
このアプリの機能は、そもそもウイルス対策ソフトウェアのヒューリスティックエンジン(怪しい挙動を検知する仕組み)にとって、少し疑わしいと判断されやすい性質を持っていた。例えば、ユーザーのログイン情報(セッションクッキーなど)を保存したり、他のプログラムを起動したり、ウィンドウを操作したり、キーボードやマウスの入力をシミュレートしたりといった挙動は、マルウェアが行うことと似ているため、常に誤検知のリスクを抱えていたのだ。そのため、少しでも「怪しい」と判断される要素が加わると、すぐに検知されてしまう可能性があった。
問題が発生したのは、新しいバージョンをリリースした後だった。Windowsのセキュリティ警告が表示されるようになり、オンラインのウイルス検査サービスであるVirusTotalで確認すると、Microsoftのエンジンが「Trojan:Win32/Wacatac.B!ml」という名前でアプリをトロイの木馬と判定していた。この「!ml」という接尾辞が重要で、これは特定のマルウェアのパターンと一致したのではなく、機械学習モデルが「このプログラムはマルウェアに似ている」と判断したことを意味する。つまり、見たことのない新しい種類の脅威だと判断された可能性があったのだ。
開発者はまず、正常だった以前のビルドと問題のビルドを比較して原因を探ろうとした。プログラムが外部の関数を呼び出すための「インポートライブラリ」や、プログラムの構造を示す「セクションエントロピー」などを調べたが、大きな違いは見られなかった。いくつかの新しいインポートはあったものの、それらはファイル操作やメッセージボックス表示など、ごく一般的なものばかりだった。このため、開発者は当初、「プログラムの動作プロファイルは同じで、単にウイルス対策モデルの判断基準が変わっただけだろう。特に修正する点はない」と考えた。しかし、ウイルス対策モデルはインポートテーブルだけを見ているわけではなかったため、この推測は間違っていた。
そこで開発者は、推測に頼るのをやめ、より体系的な方法で原因を特定することにした。それが「二分探索(Git Bisect)」という手法だった。これは、ソフトウェアのバージョン管理に使われるGitというツールが提供する機能で、問題が導入された特定のコミット(変更履歴の単位)を効率的に見つけ出すことができる。開発者は、過去のコミット履歴をさかのぼり、途中の時点のコードをビルドし、それが「正常」か「問題あり」かを判定するという作業を繰り返した。この判定には、VirusTotal APIというサービスを利用した。これはプログラムファイルをアップロードしてウイルス検査を自動で行い、その結果をプログラムで取得できるものだ。各ステップでプログラムをビルドし、VirusTotalでスキャンし、問題があれば「bad」、なければ「good」とマークすることで、問題の原因となったコミットを絞り込んでいった。
この二分探索の結果、原因が判明した。それは、アカウントファイルを暗号化するために「sodiumoxide」という暗号化ライブラリを追加したコミットだった。sodiumoxideは、C言語で書かれた高性能な暗号ライブラリである「libsodium」の機能をRust言語で利用するためのバインディングだ。
なぜlibsodiumが問題だったのか。libsodiumは、暗号化処理を高速かつ安全に行うために高度に最適化されたC言語のコードの塊であり、これがアプリの実行ファイルに静的に組み込まれる。プログラムがユーザーの認証情報を保存し、他のプロセスを起動し、ユーザーの入力をエミュレートするといった機能を持っている状況で、その中に「最適化されたネイティブの暗号化コード」という塊が存在する組み合わせが、ウイルス対策ソフトウェアの機械学習モデルにとって「ランサムウェアや情報窃取型のマルウェアがよく使う手口に似ている」と判断されたのだ。libsodium自体は安全で、世界中で広く使われている正当なライブラリだが、特定の文脈で組み合わせると、その特性がマルウェアのそれと酷似してしまうことがあった。
原因が特定されたため、開発者はlibsodiumを別のライブラリに置き換える必要があった。ただし、重要な課題があった。すでにアプリを使っているユーザーは、libsodiumを使って暗号化されたファイルをディスクに保存しており、一部のファイルにはパスワードも設定されていた。そのため、新しいライブラリに置き換えても、既存の暗号化されたファイルを問題なく読み取れるようにしなければ、ユーザーは自分のデータにアクセスできなくなってしまう。
そこで開発者は、完全にRust言語で書かれた「RustCrypto」という暗号化クレート群(argon2、crypto_secretbox、sha2など)を採用した。libsodiumが使っていた暗号化のアルゴリズムやパラメータ(たとえば、鍵導出の際に使うArgon2のバージョン、メモリ使用量、MACと呼ばれる認証タグの位置など)を、新しいRustCryptoのライブラリで完全に再現することで、後方互換性を確保した。この互換性が確保できていることを確実に検証するため、開発者は特別なテストコードを作成した。それは、古いlibsodiumの実装で実際に暗号化された小さなデータをテストケースとして用意し、新しいRustCryptoの実装でそれを正確に復号できることを検証するテストだった。もし暗号化パラメータが一つでもずれていれば、このテストは失敗し、ユーザーのデータが失われる事態を防ぐことができた。
ライブラリの置き換えには副作用もあった。純粋なRust実装のArgon2は、デバッグビルド(開発中に使う、最適化されていないビルド)では非常に処理が遅く、テストスイート全体の実行時間が大幅に伸びてしまった。libsodiumはC言語で事前にコンパイルされ最適化されていたため、このような問題はなかった。このパフォーマンス低下を解決するため、開発者はRustのビルド設定ファイル(Cargo.toml)を修正し、開発ビルド時でも、アプリ自身のコードはデバッグ用に最適化しない一方で、依存しているライブラリ(RustCryptoなど)だけは最適化された状態でコンパイルされるように設定した。この変更により、テスト実行時間はlibsodiumを使っていた頃よりもさらに高速になり、パフォーマンスの問題は解消された。
最終的な結果として、アプリの実行ファイルはVirusTotalで75個のエンジンすべてでクリーンと判定され、Microsoft Defenderによる誤検知もなくなった。
この経験から、開発者はいくつかの重要な教訓を得た。 第一に、「!ml」と表示される機械学習によるウイルス判定は常に変化する動的な標的であるということだ。一度クリーンになったからといって安心できるわけではなく、ビルド設定や細かなコード変更でも判定が変わる可能性があるため、新しいリリースごとに必ず検査を行う必要がある。 第二に、VirusTotalのようなオンラインサービスと、自分のパソコンにインストールされているMicrosoft Defenderのようなローカルのウイルス対策ソフトは、必ずしも同じ判定をしないことがあるため、ユーザーは両方を目にする可能性があることを踏まえ、両方で問題がないかを確認すべきだ。 第三に、原因究明においては、頭で理論を組み立てるよりも、Gitの二分探索のような具体的な手法を使って実際に検証することの方がはるかに効果的だということ。開発者の最初のインポートテーブル分析は間違った結論を導いたが、二分探索は正確な原因を特定した。 第四に、ウイルススキャンを行うスクリプトは、エラーが発生した際に必ず明確に警告するように設計するべきだということも学んだ。以前は、スキャンが途中で失敗しても「問題なし」と報告してしまう不備があったため、この点も修正された。 最後に、C言語などのネイティブなコードに依存するライブラリは、ヒューリスティックな誤検知のリスクを高める可能性があるということだ。もし、純粋なRust言語で同等の機能を持つライブラリが存在し、既存のデータ形式との互換性をテストで証明できるのであれば、そちらへの置き換えを検討する価値がある。
長期的な解決策としては、デジタル署名(コードサイニング)の導入が挙げられる。これは、プログラムが正規の開発者によって作成され、改ざんされていないことを証明するもので、ウイルス対策ソフトウェアからの信頼性を高める効果がある。オープンソースプロジェクト向けに無料で利用できるコードサイニングのオプションも存在するため、今後の検討材料となるだろう。それまでは、リリースごとにアプリの安全性を測定し、ユーザーにクリーンなダウンロードを提供し続けることが、開発者にとってできる最善の策となる。