34


27

私はここでファイルの類似性を決定することに関していくつかの質問を見ました、しかしそれらはすべて特定のドメイン(画像、音、テキストなど)にリンクされています。 解決策として提供される技術は、比較されるファイルの基礎となるファイルフォーマットの知識を必要とします。 私が探しているのは、この要件を持たない方法で、そこに含まれるデータの種類を理解する必要なしに任意のバイナリファイルを比較することができます。 つまり、私は、2つのファイルのバイナリデータの類似率を求めています。

あなたが取り組むためのもう少し詳細を与えるために、これは多くのことに潜在的に適用可能ですが、私は私が取り組んでいるという特定の問題を抱えています。 私は現在実用的な解決策も持っていますが、それが理想的だとは思いません。 比較方法と結果の保存に関しては、おそらく多くの最適化があります。 うまくいけば、ここの何人かの人々は私にいくつかの新しいアイデアを与えることができるでしょう。 2、3日後に私の現在の方法に関するいくつかの情報を編集するつもりですが、私はすでにそれをやっている方法をあなたに伝えることによって問題についての人々の考えを偏らせたくありません。

私が取り組んでいる問題は、ビデオゲームのROMイメージのクローン検出です。 エミュレーションの経験がない人にとって、ROMはゲームカートリッジのデータのダンプです。 ROMの「クローン」は通常、同じゲームの修正版で、最も一般的なタイプは翻訳版です。 たとえば、NESのオリジナルの「ファイナルファンタジー」の日本語版と英語版はクローンです。 ゲームはほとんどすべての資産(スプライト、音楽など)を共有していますが、テキストは翻訳されています。

現在、さまざまなシステムのクローンのリストを管理するためのグループがいくつかありますが、私の知る限りでは、これはすべて手動で行われます。 私がやろうとしているのは、「これらは同じゲームのように見える」のではなく、データの類似性に基づいて、類似のROMイメージを自動的かつ客観的に検出する方法を見つけることです。 クローンを検出する理由はいくつかありますが、主な動機の1つは Solid compressionで使用することです。 これにより、すべてのゲームクローンをまとめて同じアーカイブに圧縮することができます。圧縮されたクローンセット全体では、個々のROMの1つよりわずかに多くのスペースしか占有しないことがよくあります。

潜在的なアプローチを考え出す際に考慮すべきいくつかの懸念:

  • ROMはシステムによってサイズが大きく異なります。 小規模なものもありますが、最近のシステムでは256MB以上の大規模なものがあります。 いくつかの(すべて?)システムは可能なサイズとして2のべき乗しか持っていません、これらのシステムのうちの1つの上の130MBのゲームは256MBのROMを持っているでしょう、大部分は空です。 このため、ゲームバージョンがしきい値を超え、サイズの2倍のカートリッジを使用する必要がある場合、クローンによってはサイズが大きく異なることがあります。

  • 現在多くのシステムには数千の既知のROMがありますが、ほとんどのシステムではまだ新しいROMが絶えずリリースされています。 古いシステムでさえも、頻繁に変更されたROMを生成する主要なROMハッキングコミュニティがあります。

  • 考えられるROMのすべてのペアについて類似性データを格納すると、一般的なシステムでは何百万行ものデータが生成されます。 5000個のROMを持つシステムでは、2500万行の類似性データが必要になり、1つの新しいゲームでさらに5000行が追加されます。

  • 処理の状態は回復可能でなければならないので、中断された場合は中断したところから再開できます。 どのような方法でも、多くの処理が必要になり、全体が1つのバッチで実行されると想定するのは安全ではありません。

  • 新しいROMはいつでも追加できます。そのため、このメソッドはすでに「完全」なセットを持っていると想定しないでください。 つまり、既存のすべてのROMの類似性を把握した後でも、新しいROMが追加された場合(これは以前の処理が完全に終了する前にも発生する可能性があります)。 (もしあれば)それはのクローンです。

  • より高い処理速度は(ある程度)正確さより優先されるべきです。 2つのROMが94%または96%類似しているかどうかを知ることは特に重要ではありませんが、新しいROMを以前のROMと比較するのに1日の処理時間がかかる場合、プログラムはおそらく本当に完成しません。

取り組むのは興味深い問題でした。他の人々が思いつくことができるのを楽しみにしています。 あなたがこれ以上の詳細が欲しいなら私にコメントで知らせてください、そして私はそれらを供給しようとします。

10 Answer


20


バイナリデルタ、またはバイナリデルタのアプリケーションから派生したインデックス(サイズなど)が必要なようです。 その後、このインデックスを実験的に決定したベースラインと比較して、それが「クローン」かどうかを判断できます。

圧縮とデルタ作成の間には多くの類似点があるので、現在の実装にそれほど遠くないと思います。

そうは言っても、データベース内のすべてのバイナリファイルのペアワイズ比較はおそらく法外に高価です(O(n ^ 2 ^)、私は思います)。 私は比較のために可能な候補を識別するための単純なハッシュを見つけようとします。 概念的にはspdenneとEduardが提案しているものと似ているものがあります。 つまり、すべての項目に1回適用できるハッシュを見つけ、そのリストをソートしてから、リスト内でハッシュが近い項目に対してより詳細な比較を使用します。

一般的な場合に有用なハッシュの構築は、数年間CSで積極的に追求されている研究トピックです。 LSHKitソフトウェアライブラリは、この種のアルゴリズムをいくつか実装しています。 インターネットでアクセス可能な論文 大規模ファイルシステムでの類似ファイルの検索テキストファイルの比較を目的としている可能性がありますが、可能性があります。あなたに役立つでしょう。 最新の論文http://www.sciencedirect.com/science?_ob=ArticleURL ただし、購読なしではアクセスできないようです。 他のリソースを閲覧するときには、おそらく Locality Sensitive Hashingにあるウィキペディアの記事を便利に保管してください。 それらはすべてかなり技術的になっており、ウィキペディアのエントリ自体はかなり数学的なものです。 よりユーザーフレンドリーな代替手段として、あなたは Acoustic Fingerprintingの分野からいくつかのアイデア(あるいは実行可能ファイルさえ)を適用することができるかもしれません。

一般的なケースを放棄しても構わないと思っているのであれば、あなたのROMだけに使える、はるかに単純な(そしてより速い)ドメイン固有のハッシュ関数を見つけることができるでしょう。 標準的な、または一般的なバイトシーケンスの配置とその近くの選択ビットの値を含む可能性があります。 私はあなたのバイナリフォーマットについてはあまり知りませんが、サウンド、イメージ、テキストなどの領域のようにファイル内のセクションの始まりを知らせるものを想像しています。 バイナリフォーマットは、ファイルの先頭近くにこの種のセクションのアドレスを格納することがよくあります。 また、最初のセクションのアドレスを既知の場所にそのサイズと共に格納する連鎖メカニズムも使用されています。 これにより、サイズなども含まれている次のセクションに移動できます。 ちょっと調べれば、あなたがまだそれに気付いていないのであれば、おそらく関連するフォーマットを見つけることができるでしょう。

ハッシュ関数がうまくいかない場合(またはメトリック/距離を定義するために何らかの入力を必要とする場合)、Web上で利用可能なバイナリデルタアルゴリズムと実装がいくつかあります。 私が一番よく知っているのは、Subversionのバージョン管理システムです。 バイナリファイルのリビジョンを効率的に保存するためにxdeltaと呼ばれるバイナリデルタアルゴリズムを使用します。 これを実装しているリポジトリ内のファイルへの直接リンクは次のとおりです。 xdelta.c。 おそらくこれをよりアクセスしやすくするツールがWeb上にあります。


11


あなたは bsdiffを見たいと思うかもしれません。これはバイナリ差分/パッチシステムです。 理論がたくさんある論文もあります。


7


http://en.wikipedia.org/wiki/Plagiarism_detection[Plagiarism Detection]アルゴリズムからいくつかのアイデアを使用してください。

私の考え:

ROMごとに同等の「シグネチャ」を作成するには、小さい部分が変わるとわずかに変化して単語頻度グラフのようなものを作成しますが、単語の頻度を記録する代わりにROMの非常に短いセクションをハッシュして記録します。ハッシュ値の頻度

1つのセクションをハッシュしてから最初のセクションの最後から始まる次のセクションをハッシュするのではなく、スライディングウィンドウを使用して、バイト1から始まるセクションをハッシュし、次にバイト2から同じサイズのセクションをハッシュします。 3など それはあなたのROM内の可変サイズの可変部分の効果を打ち消すでしょう。

各8ビットバイトのxorのような単純なハッシュ関数を使用した場合は、現在のハッシュを発信8ビットとxor、着信8ビットをxorすることで、次のウィンドウ位置のハッシュを簡単に計算できます。 他の代替ハッシュ関数は単に命令コード語長を使用することであり得る。 機械命令を表すコードの静的パターンを作成するのに十分である可能性があります。 重要なことは、命令コード内で共通の短いシーケンスが結果として同じハッシュ値になるようなハッシュ関数が欲しいということです。

おそらく、それぞれの頻度が高いほど少ないハッシュ値が必要ですが、行き過ぎたり、グラフが平坦になりすぎたりするため、比較しにくくなります。 同様に、幅を広げすぎないでください。さもないと、非常に小さい周波数が多くなり、比較が困難になります。

ROMごとにこのグラフを保存してください。 各ハッシュ値の頻度の差の二乗和を計算して、2つの異なるROMの頻度グラフを比較します。 それがゼロになるならば、ROMは同一である可能性があります。 ゼロから離れるほど、ROMは類似しなくなります。


6


それは「数日」以上のものでしたが、私はおそらくここに私の現在の解決策を追加すべきだと考えました。

Nils Pipenbrinckは私の現在の方法と同じ方向に進んでいました。 クローンを見つけた主な結果の1つは、堅固なアーカイブからの大幅な節約であるため、2つのROMをまとめて圧縮してどれだけのスペースが節約されているかを見ることができます。 これには 7zipのLZMAアルゴリズムを使用しています。

最初のステップは、すべてのROMを個別に圧縮し、その圧縮サイズを書き留めてから、任意の2つのROMを一緒にアーカイブして、結果のサイズが個々の圧縮サイズとどれほど異なるかを確認することです。 組み合わせたサイズが個々のサイズの合計と同じである場合、それらは0%類似しており、サイズがそれらのうちの1つと同じ(最大のもの)である場合、それらは同一です。

さて、これは膨大な数の圧縮試行が必要なので、これまでにいくつか最適化を行っています(そして、もっと詳しく知りたいと思います)。

  1. 圧縮サイズの類似度に基づいて比較の優先順位を付けます。 ROM Aの圧縮サイズが10MBで、ROM Bの圧縮サイズが2MBの場合、それらが20%を超えて類似することは不可能であるため、実際の結果を得るためにそれらを比較することは後まで可能です。 非常に類似したファイルに対して同じ圧縮アルゴリズムを実行すると、サイズが類似した結果になる傾向があるため、多くのクローンが非常に迅速に見つかります。

  2. 上記と組み合わせることで、ROMのペア間の類似性の上限と下限の両方を維持できます。 これにより、さらなる優先順位付けが可能になります。 ROM AとBが95%類似し、ROM BとCが2%類似しているだけであれば、AとCは0%から7%の間であることがすでにわかります。 これはクローンにするには低すぎるので、この比較は安全に延期することも、完全に無視することもできます。ただし、すべての厳密な類似点を本当に知りたいのでなければ。


3


データ圧縮から借用したいくつかの手法は、ここでは興味深いものになると思います。

AとBの2つのファイルがあるとします。

各ファイルを個別に圧縮し、圧縮サイズをまとめて追加します。 次に、2つのファイルを1つの大きなファイルに連結して、同様に圧縮します。

サイズの違いは、ファイルがどれほど似ているかをおおよその目安にします。

圧縮するためにBurrow Wheeler変換(bzip2)を試してみることをお勧めします。 他のほとんどの圧縮アルゴリズムは限られた歴史しかありません。 BWTアルゴリズムotohは、非常に大きなチャンクのデータに対しても機能します。 アルゴリズムは両方のファイルを同時に「認識」し、類似性があると圧縮率が高くなります。


2


XDeltaは、まともなバイナリ差分を取得するのに非常に便利です。http://xdelta.org


1


あなたは ハッシュツリーのようなものを保存することから始めることができます。 各ROMに対してそのようなハッシュのセットを1つ格納するだけでよく、必要な記憶スペースは、一定のブロックサイズを仮定すると、ROMのサイズに比例するだけです(ただし、それよりはるかに小さい)。 選択されたブロックサイズは正確さを保証するのに十分な粒度を与えなければなりません、例えば:128MiBの最小サイズ、1%の正確さの制約と Tiger-128 hash( DirectConnect経由で転送されたファイルのチェックに使用されるものと同様に、1MiBのブロックサイズで問題はなく、すべてのハッシュを128 * 128/8に格納できます。 = 2048バイト!したがって、10,000個のROMに対してそれを実行するには、約20MiBしか必要としません。 スペースの。 さらに、安全性は低くなりますが、高速または小さいハッシュを選択することができます。 新しいROMを追加/類似性をチェックするには、次のようなものが必要になります。

  1. 新しいROMをブロックに分割し、それぞれをハッシュします。

  2. データベースにすでに存在するすべてのROMについて、そのハッシュと新しいROMのハッシュを比較します(下記参照)。

比較関数は類似性をチェックします。 しかしそれは各ハッシュを不可分な値として扱うべきです。 2つのハッシュ間の論理的に有意な差関数を見つけようとしなくても大丈夫です。 ブロックサイズが十分に小さく、ハッシュ衝突が十分にまれである限り、正確さは単純な等しい比較によって保証されます。

見ての通り、この問題はパフォーマンス的に単純なものに縮小されました。類似性を調べるためにもっと小さいデータセットをチェックします。


1


二つの考え:

  • ファイルをデータフローグラフとして整理し、その表現を正規化することを検討してください。 あなたは命令セットを知っているので、これは可能かもしれません、逆アセンブラを片付けてテキスト処理をするだけかもしれません。

  • CRM114のような学習可能な分類器は、バイナリに多くの共通点があるかどうかを知るための簡潔な表現を提供するのに役立ちます。


1


Waylon Flinnが言ったように、バイナリデルタアルゴリズムが必要かもしれません。 rsync algorithmは良いものです。 高速で信頼性があります。 ユーティリティのドキュメントも参照してください。


1


ここでの難点は、実行可能コードを扱っているので、単純な変更がROM全体に広がる可能性があることです。 ALL値のアドレスとオフセットは、単一の変数またはノーオペレーション命令を追加することで変更できます。 それはブロックベースのハッシュでさえも価値がありません。

手っ取り早い解決策は difflib(またはあなたの好きな言語と同等のもの)を使って解決策を導き出すことです。データの追加または削除を処理できる比較。 ROMを実行可能ファイルとデータセクションに分割します(可能な場合)。 データセクションを直接比較して 類似率を計算することもできますが、それでもアドレスやオフセットのある問題があるでしょう。

実行可能セクションはもっとおもしろいです。 マシンのasmフォーマットを読み、実行ファイルを取り出し、それを一連のオペコードに分割します。 オペコードとレジスタ部分は残しますが、「ペイロード」/「イミディエイト」部分(可変アドレスをロードする部分)はマスクします。 得られた情報を類似度計算機にも渡します。

残念なことに、これは追跡するROMの数に対するO(n ^ 2)の操作ですが、(インクリメンタル)クラスタリングまたは頻度ベースの比較順序を使用して比較を減らすことができます。