Linuxコマンドラインからバイナリファイルの内部を覗く方法

公開: 2022-01-29
ラップトップ上に緑色のテキストの行がある定型化されたLinux端末。
fatmawati achmad zaenuri / Shutterstock

ミステリーファイルをお持ちですか? Linuxのfileコマンドは、それがどのタイプのファイルであるかをすばやく教えてくれます。 ただし、バイナリファイルの場合は、さらに詳しく知ることができます。 fileには、分析に役立つ安定した仲間がたくさんいます。 これらのツールのいくつかの使用方法を紹介します。

ファイルタイプの識別

ファイルには通常、ソフトウェアパッケージがファイルの種類と、その中のデータが何を表しているかを識別できる特性があります。 MP3音楽プレーヤーでPNGファイルを開こうとしても意味がないので、ファイルに何らかの形式のIDが含まれていると便利で実用的です。

これは、ファイルの先頭にある数バイトの署名バイトである可能性があります。 これにより、ファイルの形式と内容を明示的にすることができます。 ファイルの種類は、ファイルアーキテクチャと呼ばれる、データ自体の内部構成の特徴的な側面から推測される場合があります。

Windowsなどの一部のオペレーティングシステムは、ファイルの拡張子によって完全にガイドされます。 騙されやすい、または信頼できると呼ぶことができますが、Windowsは、DOCX拡張子を持つファイルが実際にはDOCXワードプロセッシングファイルであると想定しています。 すぐにわかるように、Linuxはそのようなものではありません。 証拠が必要で、ファイル内を調べて見つけます。

ここで説明するツールは、この記事の調査に使用したManjaro 20、Fedora 21、およびUbuntu20.04ディストリビューションにすでにインストールされています。 fileコマンドを使用して調査を開始しましょう。

ファイルコマンドの使用

現在のディレクトリには、さまざまな種類のファイルのコレクションがあります。 これらは、ドキュメント、ソースコード、実行可能ファイル、およびテキストファイルの混合物です。

広告

lsコマンドはディレクトリの内容を表示し、 -hl (人間が読めるサイズ、長いリスト)オプションは各ファイルのサイズを表示します。

 ls -hl 

これらのいくつかをfileしてみて、何が得られるか見てみましょう。

 ファイルbuild_instructions.odt
 ファイルbuild_instructions.pdf
 ファイルCOBOL_Report_Apr60.djvu 

3つのファイル形式が正しく識別されます。 可能な場合、 fileはもう少し情報を提供します。 PDFファイルはバージョン1.5形式であると報告されています。

ODTファイルの名前をXYZの任意の値の拡張子を持つように変更しても、ファイルは、 Filesファイルブラウザー内とファイルを使用するコマンドラインの両方で正しく識別されfile

拡張子がXYZであっても、ファイルファイルブラウザ内で正しく識別されたOpenDocumentファイル。

Filesファイルブラウザ内で、正しいアイコンが表示されます。 コマンドラインで、 fileは拡張子を無視し、ファイル内を調べてそのタイプを判別します。

 ファイルbuild_instructions.xyz 

広告

画像や音楽ファイルなどのメディア上のfileを使用すると、通常、フォーマット、エンコーディング、解像度などに関する情報が得られます。

 ファイルscreenshot.png
 ファイルscreenshot.jpg
 ファイルPachelbel_Canon_In_D.mp3 

興味深いことに、プレーンテキストファイルであっても、 fileはファイルの拡張子でファイルを判断しません。 たとえば、拡張子が「.c」で、標準のプレーンテキストを含み、ソースコードを含まないファイルがある場合、 fileはそれを本物のCソースコードファイルと間違えません。

 ファイルfunction + headers.h
 ファイルmakefile
 ファイルhello.c 

fileは、ヘッダーファイル(「.h」)をファイルのCソースコードコレクションの一部として正しく識別し、makefileがスクリプトであることを認識しています。

バイナリファイルでのファイルの使用

バイナリファイルは、他のファイルよりも「ブラックボックス」です。 適切なソフトウェアパッケージを使用して、画像ファイルを表示したり、音声ファイルを再生したり、ドキュメントファイルを開いたりすることができます。 ただし、バイナリファイルはもっと難しいものです。

たとえば、ファイル「hello」と「wd」はバイナリ実行可能ファイルです。 それらはプログラムです。 「wd.o」というファイルはオブジェクトファイルです。 ソースコードがコンパイラによってコンパイルされると、1つ以上のオブジェクトファイルが作成されます。 これらには、完成したプログラムの実行時にコンピューターが最終的に実行するマシンコードと、リンカーの情報が含まれています。 リンカは、ライブラリへの関数呼び出しについて各オブジェクトファイルをチェックします。 プログラムが使用するライブラリにリンクします。 このプロセスの結果は、実行可能ファイルです。

ファイル「watch.exe」は、Windowsで実行するためにクロスコンパイルされたバイナリ実行可能ファイルです。

 ファイルwd
 ファイルwd.o
 ファイルこんにちは
ファイルwatch.exe 

広告

最後の1つを最初に取り上げると、 fileは、「watch.exe」ファイルがMicrosoftWindows上のx86プロセッサフ​​ァミリ用のPE32 +実行可能コンソールプログラムであることを示しています。 PEは、32ビットバージョンと64ビットバージョンのポータブル実行可能形式の略です。 PE32は32ビットバージョンであり、PE32 +は64ビットバージョンです。

他の3つのファイルはすべて、Executable and Linkable Format(ELF)ファイルとして識別されます。 これは、実行可能ファイルおよびライブラリなどの共有オブジェクトファイルの標準です。 ELFヘッダー形式については後ほど説明します。

目を引くのは、2つの実行可能ファイル(「wd」と「hello」)がLinux Standard Base(LSB)共有オブジェクトとして識別され、オブジェクトファイル「wd.o」がLSB再配置可能として識別されることです。 実行可能ファイルという言葉は、それがないことから明らかです。

オブジェクトファイルは再配置可能です。つまり、オブジェクトファイル内のコードはメモリの任意の場所にロードできます。 実行可能ファイルは、この機能を継承するようにオブジェクトファイルからリンカによって作成されているため、共有オブジェクトとしてリストされています。

これにより、アドレス空間配置のランダム化(ASMR)システムは、選択したアドレスのメモリに実行可能ファイルをロードできます。 標準の実行可能ファイルのヘッダーにはロードアドレスがコード化されており、メモリのどこにロードされるかが決まります。

ASMRはセキュリティ技術です。 実行可能ファイルをメモリの予測可能なアドレスにロードすると、攻撃を受けやすくなります。 これは、それらのエントリポイントとその機能の場所が、攻撃者に常に知られているためです。 ランダムなアドレスに配置されたPositionIndependent Executable(PIE)は、この感受性を克服します。

広告

プログラムをgccコンパイラでコンパイルし、 -no-pieオプションを指定すると、従来の実行可能ファイルが生成されます。

-o (出力ファイル)オプションを使用すると、実行可能ファイルの名前を指定できます。

 gcc -o hello -no-pie hello.c

新しい実行可能fileのファイルを使用して、何が変更されたかを確認します。

 ファイルこんにちは

実行可能ファイルのサイズは以前と同じ(17 KB)です。

 ls -hl hello 

これで、バイナリが標準の実行可能ファイルとして識別されます。 これはデモンストレーション目的でのみ行っています。 この方法でアプリケーションをコンパイルすると、ASMRのすべての利点が失われます。

実行可能ファイルが非常に大きいのはなぜですか?

私たちの例のhelloプログラムは17KBなので、大きなとは言えませんが、すべてが相対的です。 ソースコードは120バイトです。

 猫hello.c
広告

ターミナルウィンドウに1つの文字列を出力するだけの場合、バイナリをバルクアウトするのは何ですか? ELFヘッダーがあることはわかっていますが、64ビットバイナリの場合は64バイトしかありません。 明らかに、それは何か他のものでなければなりません:

 ls -hl hello 

バイナリをstringsコマンドでスキャンして、その中身を見つけるための簡単な最初のステップとして見てみましょう。 それをlessものにパイプします:

 文字列こんにちは| 以下

「こんにちは、オタクの世界!」以外にも、バイナリ内には多くの文字列があります。 ソースコードから。 それらのほとんどは、バイナリ内の領域のラベル、および共有オブジェクトの名前とリンク情報です。 これらには、ライブラリ、およびバイナリが依存するそれらのライブラリ内の関数が含まれます。

lddコマンドは、バイナリの共有オブジェクトの依存関係を示しています。

 lddこんにちは

出力には3つのエントリがあり、そのうちの2つにはディレクトリパスが含まれています(最初のエントリには含まれていません)。

  • linux-vdso.so:Virtual Dynamic Shared Object(VDSO)は、カーネル空間ルーチンのセットにユーザー空間バイナリからアクセスできるようにするカーネルメカニズムです。 これにより、ユーザーカーネルモードからのコンテキストスイッチのオーバーヘッドが回避されます。 VDSO共有オブジェクトは、Executable and Linkable Format(ELF)形式に準拠しているため、実行時にバイナリに動的にリンクできます。 VDSOは動的に割り当てられ、ASMRを利用します。 カーネルがASMRスキームをサポートしている場合、VDSO機能は標準のGNUCライブラリによって提供されます。
  • libc.so.6:GNUCライブラリ共有オブジェクト。
  • /lib64/ld-linux-x86-64.so.2:これはバイナリが使用したいダイナミックリンカーです。 ダイナミックリンカはバイナリに問い合わせて、バイナリがどのような依存関係を持っているかを検出します。 それらの共有オブジェクトをメモリに起動します。 実行するバイナリを準備し、メモリ内の依存関係を見つけてアクセスできるようにします。 次に、プログラムを起動します。

ELFヘッダー

readelfユーティリティと-h (ファイルヘッダー)オプションを使用して、ELFヘッダーを調べてデコードできます。

 readelf -h hello 

ヘッダーは私たちのために解釈されます。

広告

すべてのELFバイナリの最初のバイトは、16進値0x7Fに設定されます。 次の3バイトは、0x45、0x4C、および0x46に設定されます。 最初のバイトは、ファイルをELFバイナリとして識別するフラグです。 このクリスタルを明確にするために、次の3バイトはASCIIで「ELF」を綴ります。

  • クラス:バイナリが32ビットまたは64ビットの実行可能ファイル(1 = 32、2 = 64)であるかどうかを示します。
  • データ:使用中のエンディアンを示します。 エンディアンエンコーディングは、マルチバイト数を格納する方法を定義します。 ビッグエンディアンエンコーディングでは、数値は最上位ビットを最初に格納されます。 リトルエンディアンエンコーディングでは、数値は最下位ビットを最初に格納されます。
  • バージョン: ELFのバージョン(現在は1)。
  • OS / ABI:使用中のアプリケーションバイナリインターフェイスのタイプを表します。 これは、プログラムと共有ライブラリなどの2つのバイナリモジュール間のインターフェイスを定義します。
  • ABIバージョン:ABIのバージョン。
  • タイプ: ELFバイナリのタイプ。 一般的な値は、再配置可能リソース(オブジェクトファイルなど)の場合はET_REL-no-pieフラグを使用してコンパイルされた実行可能ファイルの場合はET_EXEC 、ASMR対応の実行可能ファイルの場合はET_DYNです。
  • マシン:命令セットアーキテクチャ。 これは、バイナリが作成されたターゲットプラットフォームを示します。
  • バージョン:このバージョンのELFでは、常に1に設定されます。
  • エントリポイントアドレス:実行が開始されるバイナリ内のメモリアドレス。

他のエントリは、バイナリ内の領域とセクションのサイズと数であるため、それらの場所を計算できます。

hexdumpを使用してバイナリの最初の8バイトをざっと見ると、ファイルの最初の4バイトに署名バイトと「ELF」文字列が表示されます。 -C (正規)オプションを使用すると、16進値とともにバイトのASCII表現が得られ、 -n (数値)オプションを使用すると、表示するバイト数を指定できます。

 hexdump -C -n 8 hello 

objdumpとグラニュラービュー

本質的な詳細を確認したい場合は、 -d (逆アセンブル)オプションを指定してobjdumpコマンドを使用できます。

 objdump -d hello | 以下

これにより、実行可能マシンコードが逆アセンブルされ、同等のアセンブリ言語と一緒に16進バイトで表示されます。 各行の最初のバイのアドレス位置は、左端に表示されます。

これは、アセンブリ言語を読むことができる場合、またはカーテンの後ろで何が起こっているのか知りたい場合にのみ役立ちます。 出力が多いので、 lessものにパイプしました。

コンパイルとリンク

バイナリをコンパイルする方法はたくさんあります。 たとえば、開発者はデバッグ情報を含めるかどうかを選択します。 バイナリのリンク方法も、その内容とサイズに影響します。 バイナリ参照がオブジェクトを外部依存関係として共有する場合、依存関係が静的にリンクするものよりも小さくなります。

広告

ほとんどの開発者は、ここで説明したコマンドをすでに知っています。 ただし、他の人にとっては、バイナリブラックボックスの中に何があるかを簡単に調べて確認する方法がいくつかあります。