Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
zipdefrag — Presented at Recon Montreal 2018 | Kitploit
ツール/GitHubGitHub/nccgroup/zipdefrag
Embedded Systems SecurityMemory ForensicsReverse EngineeringData RecoveryDigital ForensicsFirmware Analysis
GitHubnccgroup/zipdefrag

zipdefrag

Presented at Recon Montreal 2018

リポジトリを見る
748年前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

このダンプはパズルである

あるいは、愚か者と反逆者のための高度なショットガンパーシング

むかしむかし

むかしむかし、親愛なる読者よ、暗く嵐の夜のこと、あなたの忠実な著者は、実に当惑させられる神秘的な状況に出くわした――チップオフメモリ解析とリバースエンジニアリングを阻む唯一のものが、未知の断片化されたプロプライエタリファイルシステムと、組み込みJavaの使用による圧縮の組み合わせであり、それが既存のファイルカービングツールの効果を低下させていたのである。

当時は、手動でくっつきそうなチャンクをつなぎ合わせ、少しばかりの厄介なPythonコンソール作業と醜いアドホックなbashスクリプトで、その場しのぎの解決策が間に合わせで作られたかもしれない。それは「十分に良い」ものだったが、時間がかかった。

チップオフ解析において、平文の非圧縮データを抽出することは、ハードウェアハッカーにとっては日常茶飯事の仕事だが、圧縮データの断片が非合理的で不愉快な方法であちこちに散らばっている場合、抽出に対する他の実際の保護が何もなくても、圧縮は重大な問題を引き起こす。

しかし、きっと何かもっと良い方法があるはずだ?

特にZipファイルにはいくつか興味深い点がある(JARファイルに使われる基本フォーマットである)。長い間International Journal of PoC||GTFOの真面目な読者であり、特にAnge Albertiniによるファイルフォーマットの変形技法の研究を追ってきた者として、zipファイルの中にはzipファイル自身についての十分なデータが含まれており、それをすべて再びつなぎ合わせるのにかなり良い仕事ができるのではないかと思った。

さて、前置きはこのくらいにして、技術的な詳細に入ろう。

まず最初に、ファイルシステムの詳細については私たちは知らないかもしれない(そして私の研究の観点から、問題に遭遇したシステムから独立して、私は単に気にしない方が良いと判断した)。しかし、ほとんどのファイルシステムが実装される方法について、私たちはいくつか知っている。特に、それらはチャンク単位で書き込まれる傾向があることを知っている。チャンクにはページと呼ばれる何らかの最小サイズがあり、ダンプを閲覧して書き込まれる最小ブロックサイズを特定することで、そのページサイズを識別できる。

これらの一部は連続しているかもしれないが、一部はそうではなく、ブロックが連続するタイミングに明確なパターンはない。

つまり、私たちが抱える問題は、抽出したいファイルの有効な(あるいは十分に近い)イメージが得られるように、データページをどのように並べ替えるかということだ。

Zipファイルは一種の逆階層を実装するように書かれている。最初に圧縮されたファイルデータ(それを説明するローカルファイルヘッダーで包まれている)。次にセントラルディレクトリ(ローカルファイルヘッダーのオフセットをリストする)、そしてエンドオブセントラルディレクトリレコード(とりわけzipに格納されているファイル数、セントラルディレクトリが始まるオフセット、セントラルディレクトリのサイズを記述する)が続く。

それを逆から見て、もう少し詳しく掘り下げよう:

  • エンドオブセントラルディレクトリは以下を教えてくれる:

    • Zipファイル内におけるEOCDレコードの正確な位置(CDのオフセットと CDの長さの和。EOCDの直前に位置する)
    • 探すべきファイル数(つまりCDレコード数)
    • Zipファイル内における最初のCDレコードの正確な位置
  • 各セントラルディレクトリレコードは以下を教えてくれる:

    • 圧縮されたファイルデータのCRC32
    • タイムスタンプ
    • その他多数のメタデータ(圧縮方法、フラグ、使用/必要とされる OSバージョン...)
    • 対応するLFチャンクのファイル内インデックス
    • 重要な点:対応するLFチャンクのイメージを構築するのに十分なデータ
  • 各ローカルファイルレコードは以下を教えてくれる:

    • ダンプ内におけるファイルの開始位置
    • ファイルが十分に小さければ、同じページ内にファイル全体が収まるか、 あるいはzipファイルの次のページ化されたチャンクに次のファイルヘッダーが現れるおかげで収まる!
    • 十分な数の小さなファイルが十分な数のページに詰め込まれていれば、ページの位置と既知の ディレクトリ値を利用してページの順序付け(既知のギャップ付き!)を作成できる

以上のすべてにより、ファイルの大部分について再構築が達成される。

どんでん返し - 現実には複数のJARファームウェアを扱わなければならない!

まず第一に、異なるファームウェアからのデータを区別するためのステップが必要だ。その理由は、すべてのオフセットがそれぞれのzipファイル内でのみ有効だからである――競合が発生するとzipストリームの不一致と破損を招き、私たちは破損していないデータをできるだけ多く取り出したいのである。また、例えば対象ファームウェアで診断した脆弱性が、普段動作しているのを見るファームウェアに影響し、単に置き去りにされた他のファイルには影響しないという確かな保証も欲しい。

ここで必要な解決策はkmeansアルゴリズム(別名「ロイドのアルゴリズム」)だ。仕組みを説明する素晴らしいビデオはこちら。SciPyには使える優れたバージョンが既にあったが、Rust実装でこれを動作させるには、このアルゴリズムを実装している唯一のクラスタリング/解析クレートを特定/パッチする必要があった。幸運なことに、ゼロから書く必要はなかった。

その後はもうお手の物だ。

これを行うにはいくつかの特徴量を使用できる。Flags、Method、Versionの各フィールドは、ファイルの圧縮に使用されたZipスタックによって異なる。さらに、ヘッダーにはタイムスタンプがあり、すべてのファームウェアがまったく同じ時刻にコンパイルおよび圧縮されたとは通常考えにくい。

ちなみに、ZipファイルはMS-DOS形式のタイムスタンプを使用していることに言及しておく価値がある。 これは年-月-日と時-分-2秒を表すビット詰めのショート整数である。 これを分類データとして使用する前に絶対スカラー値に変換しなければ、 1年の差と1秒の差に同じ重みを与えてしまうことになりかねず、それはまったく良くない!

これらをユークリッドベクトル(ℝ上の値のn次元配列、つまり浮動小数点座標を指す格好いい言葉だが、上でリンクしたビデオがおそらく最もわかりやすい説明だろう)に変換すると、クラスタリングアルゴリズムが残りのほとんどすべてをやってくれ、解析されたすべてのヘッダーを期待する数のバケットに集めてくれる。

パースに関する簡単な補足

この手法のプロトタイピングとしてこれをかなり粗雑なPythonスクリプトで書いたが、PoCでおおよそ70〜80%のJARコンテンツ復元率に達したため、そこで止めてRustで高速版を実装することに決めた。

Rustにはnomというクレートがあり、パーサー検証器を書くのに絶対的に素晴らしい。これがRustで書き直す主な魅力の一つだった(参考までに)。明確で非常に厳格なパーサーを書ける能力は、これをすべてPythonで処理しようとするよりも、ある意味ではるかに簡単にする(Pythonははるかに寛容な傾向があり、その寛容さゆえに、エッジケースを捕まえる際に誤った失敗を見逃してしまっていないと確信するのが、時には少し難しいほどである)。

素晴らしく高速で読みやすいパーサーに興味があれば、こちらをチェックしてほしい。

  • まるで2017年であるかのようにパーサーを書く
  • Nomベンチマーク - そこでは誰かがRustでhttpパーサーをゼロから書き、非常に高速なC実装よりわずかに速く、しかもバッファオーバーフローなしという結果を出している。

何はともあれ、この種の解析をPythonで実行することは本質的に遅い部類であり、そもそもこの手法の実現可能性を探るためのPoCへの道筋以上のものとして意図されたことはなかった。

とにかく、その話はこれくらいにして...

先へ進む

残りのチャンクを再構築するアプローチには、まず残りのページを高エントロピー候補でフィルタリングすること、まず小さなギャップを埋めること(検索リストからできるだけ多くのページを排除する。これらへの順列テストは最悪の場合指数時間のタスクであるため、簡単なケースを素早く解くことが優先であり、進めるにつれて私たちの問題を指数関数的に単純化する!)が含まれる。

また、ページ境界のためにローカルファイルヘッダーがパースできないインスタンスを見つけることで、迅速な成果を得ることもできる(同様のアラインメントが一意であり、他の破損アーティファクトと衝突しないケースでは、同じアラインメントの対応物とマッチさせることができるはずだ)。

欠落ページの候補をどのようにチェックするのか? 私たちのセントラルディレクトリにはファイルのCRC32チェックサムがすぐそこにあるではないか! ファイル全体に対してCRC32を計算するよりも、おそらくこの問題に取り組む最善の方法は、既知のチャンクに対してCRC32を計算することだ(ギャップが始まる前のページの末尾にあるデータから前方へ、そしてデータ(またはdeflateストリームの後のDataDescriptorチャンク)から後方へ)そしてこれらから、欠落ページの各ブロックについて期待すべき中間CRC32を割り出す。

基本的に、私たちが最も単純/最速の問題を選んで解くたびに、無駄を排除することで、より困難な問題を大幅に単純化する。これがシャノンエントロピーを使用して空のページやほぼ空のページを完全に捨て去る理由だ――高エントロピーのzipページだけが存在するとは限らないが、外れ値があったとしても、最初にその複雑さに対処するのを避けることは大幅な高速化になる。

俺はこのクソッタレな物をビルドしたいだけなんだが、どうなってる?

これで遊んでみたいなら、rustをインストールしよう(素晴らしいrustup nightlyを使用することを推奨する)。そして:

root@kitploit:~
$ git clone [repo]
...
$ cd zipdefrag
...
$ cargo build --release

デバッグを有効にするにはreleaseフラグを付けずにビルドできる。

ビルド成果物は/target/{debug,release}に置かれる。

cargo docでドキュメントをビルドできる(このクレートは非常に充実したドキュメントが付いている。私は書くことが好きなのだ)。

現在、Rust版にはデフォルトのターミナル出力がない。cliハーネスを実行したい場合は、環境変数RUST_LOG=zipdefragを設定する必要がある。これにより、これまでの解析を示す詳細なターミナルログが有効になる。

今後の予定:

  • 未知のファイルシステムからのパズル解きzipダンプ用の、高速でポータブルなネイティブ実行可能ファイル(pythonフック付き)。

  • デモ用のダンプ

既知の問題

  • 現在、Rust実装のパフォーマンスは、対応するLFHチャンクの検索まわりの無駄な挙動のために壊れている。これを修正し、教訓とするつもりだ。

  • この手法は、JAR内の多くのファイルがページサイズよりも大幅に大きい場合にはうまく機能しない。zipファイルに本来備わっている構造に大きく依存しているため、データ量の多いファイルはあまりうまくいかない。

幸いなことに、J2MEミドレットのクラスファイルは一般にかなり小さい傾向があるが、内部にパッケージされた大きなバイナリはおそらく復元不可能だろう。

また、python PoCには多くの算術バグが含まれている。

ツールをダウンロード