Linuxのバイナリファイルの脆弱性を見つけて修正する-checksecユーティリティとgccコンパイラを使用



画像:インターネットアーカイブブックの画像。Opensource.comによって変更されました。CC BY-SA 4.0



同じソースコードをコンパイルした後、異なるバイナリになってしまう可能性があります。これは、コンパイラーの手に渡すフラグによって異なります。これらのフラグの一部を使用すると、バイナリのセキュリティ関連のプロパティの数を有効または無効にできます。



それらのいくつかは、デフォルトでコンパイラーによって有効または無効にされます。これが、私たちが気付いていないバイナリファイルに脆弱性が発生する可能性がある方法です。



Checksecは、コンパイル時に含まれていたプロパティを判別するための単純なユーティリティです。この記事で私はあなたに言うでしょう:



  • checksecユーティリティを使用して脆弱性を見つける方法。
  • 見つかった脆弱性を修正するためにgccコンパイラを使用する方法


checksecのインストール



Fedora OSおよびその他のRPMベースのシステムの場合:



$ sudo dnf install checksec
      
      





Debianベースのシステムの場合はaptを使用します。



checksecによるクイックスタート



checksecユーティリティは、単一のスクリプトファイルで構成されていますが、これは非常に大きくなります。この透過性のおかげで、バイナリの脆弱性を検索するためのどのシステムコマンドが内部で実行されているかを知ることができます。



$ file /usr/bin/checksec

/usr/bin/checksec: Bourne-Again shell script, ASCII text executable, with very long lines

$ wc -l /usr/bin/checksec

2111 /usr/bin/checksec
      
      





ディレクトリブラウジングユーティリティ(ls)でchecksecを実行してみましょう。



$ checksec --file=/usr/bin/ls

<strong>RELRO           STACK CANARY      NX            PIE             RPATH     RUNPATH      Symbols       FORTIFY Fortified       Fortifiable    FILE</strong>

Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols        Yes   5       17              /usr/bin/ls

      
      





ターミナルでコマンドを実行すると、このバイナリが持つ有用なプロパティと持たないプロパティに関するレポートを受け取ります。  



最初の行はテーブルの先頭で、RELRO、STACK CANARY、NXなどのさまざまなセキュリティプロパティが一覧表示されます。2行目は、lsユーティリティバイナリのこれらのプロパティの値を示しています。



こんにちはバイナリ!



最も単純なCコードからバイナリをコンパイルします。



#include <stdio.h>

int main()

{

        printf(«Hello World\n»);

        return 0;

}

      
      





これまでのところ、-oを除いて、コンパイラに単一のフラグを渡していないことに注意してください(これは要点の横にありますが、コンパイル結果を出力する場所を示しているだけです)。



$ gcc hello.c -o hello

$ file hello

hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped

$ ./hello

Hello World
      
      





次に、バイナリに対してchecksecユーティリティを実行します。一部のプロパティはプロパティとは異なります



ls (     ):

$ checksec --file=./hello

<strong>RELRO           STACK CANARY      NX            PIE             RPATH     RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE</strong>

Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   85) Symbols       No    0       0./hello

      
      





Checksecを使用すると、さまざまな出力形式を使用できます。これは、-outputオプションで指定できます。JSON形式を選択し、jqユーティリティを使用して出力をよりわかりやすくします



$ checksec --file=./hello --output=json | jq

{

  «./hello»: {

    «relro»: «partial»,

    «canary»: «no»,

    «nx»: «yes»,

    «pie»: «no»,

    «rpath»: «no»,

    «runpath»: «no»,

    «symbols»: «yes»,

    «fortify_source»: «no»,

    «fortified»: «0»,

    «fortify-able»: «0»

  }

}

      
      





脆弱性の分析(checksec)と排除(gcc)



上で作成されたバイナリファイルには、たとえば、その脆弱性の程度を決定するいくつかのプロパティがあります。このファイルのプロパティをlsバイナリ(上記にもリストされています)のプロパティと比較し、checksecユーティリティを使用してこれを行う方法を説明します。 



さらに、アイテムごとに、見つかった脆弱性を排除する方法を示します。



1.デバッグシンボル



簡単に始めましょう。特定のシンボルは、コンパイル時にバイナリに含まれます。これらのシンボルはソフトウェア開発で使用されます。デバッグとバグ修正に必要です。



デバッグシンボルは通常、開発者が一般的な使用のためにリリースするバイナリのバージョンから削除されます。これは、プログラムの動作にはまったく影響しません。このクリーンアップ(単語ストリップで示される )は、文字が削除された後にファイルが軽くなるため、スペースを節約するために行われることがよくあります。また、プロプライエタリソフトウェアでは、攻撃者がバイナリ形式で文字を読み取って独自の目的に使用できるため、これらの文字は削除されることがよくあります。



Checksecは、デバッグシンボルがバイナリに存在することを示していますが、lsファイルには存在しません。 



$ checksec --file=/bin/ls --output=json | jq | grep symbols

    «symbols»: «no»,

$ checksec --file=./hello --output=json | jq | grep symbols

    «symbols»: «yes»,
      
      







fileコマンドを実行すると、同じことが表示されます。文字は削除されません。



$ file hello

hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, <strong>not stripped</strong>
      
      





checksecのしくみ



--debugオプションを指定してこのコマンドを実行してみましょう。



$ checksec --debug --file=./hello

      
      





checksecユーティリティは1つの長いスクリプトであるため、Bash関数を使用して調べることができます。スクリプトがhelloファイルに対して実行するコマンドを表示してみましょう。



$ bash -x /usr/bin/checksec --file=./hello
      
      





echo_messageに特に注意してください-バイナリにデバッグシンボルが含まれているかどうかに関するメッセージの出力:



+ readelf -W --symbols ./hello

+ grep -q '\.symtab'

+ echo_message '\033[31m96) Symbols\t\033[m  ' Symbols, ' symbols=«yes»' '«symbols»:«yes»,'
      
      





checksecユーティリティは、特別なフラグ--symbolsを指定したreadelfコマンドを使用して、バイナリファイルを読み取ります。バイナリ内のすべてのデバッグシンボルを出力します。 



$ readelf -W --symbols ./hello
      
      





.symtabセクションの内容から、見つかったシンボルの数を確認できます。



$ readelf -W --symbols ./hello | grep -i symtab
      
      





コンパイル後にデバッグシンボルを削除する方法



ストリップユーティリティはこれを支援します。



$ gcc hello.c -o hello

$
 

$ file hello

hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, for GNU/Linux 3.2.0, <strong>not stripped</strong>

$
 

$ strip hello

$
 

$ file hello

hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, for GNU/Linux 3.2.0, <strong>stripped</strong>
      
      





コンパイル時にデバッグシンボルを削除する方法



コンパイルするときは、-sフラグを使用します。



$ gcc -s hello.c -o hello

$

$ file hello

hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=247de82a8ad84e7d8f20751ce79ea9e0cf4bd263, for GNU/Linux 3.2.0, <strong>stripped</strong>

      
      





checksecユーティリティを使用して、シンボルが削除されたことを確認することもできます。



$ checksec --file=./hello --output=json | jq | grep symbols

    «symbols»: «no»,

      
      





2.カナリア



カナリア(情報提供者)は、バッファーと制御データの間のスタックに格納される「秘密の」値です。これらは、バッファオーバーフロー攻撃から保護するために使用されます:これらの値が変更された場合は、アラームを鳴らす価値があります。アプリケーションが起動されると、そのアプリケーション用に独自のスタックが作成されます。この場合、それはプッシュ操作とポップ操作を備えた単なるデータ構造です。攻撃者は悪意のあるデータを準備してスタックに書き込む可能性があります。この場合、バッファがオーバーフローし、スタックが損傷する可能性があります。将来的には、これはプログラムのクラッシュにつながるでしょう。カナリア値の分析により、ハッキングが発生したことをすばやく理解してアクションを実行できます。



$ checksec --file=/bin/ls --output=json | jq | grep canary

    «canary»: «yes»,

$

$ checksec --file=./hello --output=json | jq | grep canary

    «canary»: «no»,

$

 ,    canary,  checksec   :

$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie'
      
      





カナリアをオンにする



これを行うには、コンパイル時に-stack-protector-allフラグを使用します。



$ gcc -fstack-protector-all hello.c -o hello

$ checksec --file=./hello --output=json | jq | grep canary

    «canary»: «yes»,

      
      





これで、checksecは、カナリアメカニズムがオンになっていることを明確な良心で教えてくれます。



$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie'

     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@GLIBC_2.4 (3)

    83: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@@GLIBC_2.4

$

      
      





3.PIE



有効なPIEプロパティを使用すると、絶対アドレスに関係なく、実行可能コードをメモリに任意に配置できます



。PIE(Position Independent Executable)-位置に依存しない実行可能コード。プロセスのアドレス空間のどこにどの領域のメモリがあるかを予測する機能は、攻撃者の手に渡ります。ユーザープログラムは、PIEオプションでコンパイルされていない限り、事前定義されたプロセス仮想メモリアドレスからロードおよび実行されます。 PIEを使用すると、オペレーティングシステムは実行可能コードのセクションをメモリの任意のチャンクにロードできるため、解読がはるかに困難になります。



$ checksec --file=/bin/ls --output=json | jq | grep pie

    «pie»: «yes»,

$ checksec --file=./hello --output=json | jq | grep pie

    «pie»: «no»,

      
      





多くの場合、PIEプロパティは、ライブラリをコンパイルするときにのみ含まれます。以下の出力では、helloはLSB実行可能ファイルとしてマークされ、標準ライブラリlibc(.so)ファイルはLSB共有オブジェクトとしてマークされています。



$ file hello

hello: ELF 64-bit <strong>LSB executable</strong>, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped

$ file /lib64/libc-2.32.so

/lib64/libc-2.32.so: ELF 64-bit <strong>LSB shared object</strong>, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4a7fb374097fb927fb93d35ef98ba89262d0c4a4, for GNU/Linux 3.2.0, not stripped

      
      





Checksecは、次のようにこの情報を取得します。



$ readelf -W -h ./hello | grep EXEC

  Type:                              EXEC (Executable file)

      
      





ライブラリに対して同じコマンドを実行すると、EXECの代わりにDYNが表示されます。



$ readelf -W -h /lib64/libc-2.32.so | grep DYN

  Type:                              DYN (Shared object file)

      
      





PIEをオンにします



プログラムをコンパイルするときは、次のフラグを指定する必要があります。



$ gcc -pie -fpie hello.c -o hello
      
      





PIEプロパティが有効になっていることを確認するには、次のコマンドを実行します。



$ checksec --file=./hello --output=json | jq | grep pie

    «pie»: «yes»,

$
      
      





これで、バイナリファイル(hello)のタイプがEXECからDYNに変更されます。



$ file hello

hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bb039adf2530d97e02f534a94f0f668cd540f940, for GNU/Linux 3.2.0, not stripped

$ readelf -W -h ./hello | grep DYN

  Type:                              DYN (Shared object file)

      
      





4.NX



オペレーティングシステムとプロセッサツールを使用すると、仮想メモリページへのアクセス権を柔軟に構成できます。NX(No Execute)プロパティを有効にすることで、データがプロセッサ命令として解釈されるのを防ぐことができます。多くの場合、バッファオーバーフロー攻撃では、攻撃者はコードをスタックにプッシュしてから実行しようとします。ただし、これらのメモリセグメントでコードが実行されないようにすることで、このような攻撃を防ぐことができます。gccを使用した通常のコンパイルでは、このプロパティはデフォルトで有効になっています。



$ checksec --file=/bin/ls --output=json | jq | grep nx

    «nx»: «yes»,

$ checksec --file=./hello --output=json | jq | grep nx

    «nx»: «yes»,

      
      





Checksecは、再度readelfコマンドを使用して、NXプロパティに関する情報を取得します。この場合、RWはスタックが読み取り/書き込みであることを意味します。ただし、この組み合わせにはE文字が含まれていないため、このスタックからコードを実行することは禁止されています。



$ readelf -W -l ./hello | grep GNU_STACK

  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10

      
      





NXを無効にする 



NXプロパティを無効にすることはお勧めしませんが、次のように行うことができます。



$ gcc -z execstack hello.c -o hello

$ checksec --file=./hello --output=json | jq | grep nx

    «nx»: «no»,

      
      





コンパイル後、スタックのアクセス許可がRWEに変更されたことがわかります。



$ readelf -W -l ./hello | grep GNU_STACK

  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10
      
      





5. RELRO



動的にリンクされたバイナリでは、特別なGOT(グローバルオフセットテーブル)を使用して、ライブラリから関数を呼び出します。このテーブルは、ELF(Executable Linkable Format)バイナリによって参照されます。RELRO(Relocation Read-Only)保護が有効になっている場合、GOTは読み取り専用になります。これにより、テーブルレコードを変更するいくつかのタイプの攻撃から保護できます。



$ checksec --file=/bin/ls --output=json | jq | grep relro

    «relro»: «full»,

$ checksec --file=./hello --output=json | jq | grep relro

    «relro»: «partial»,

      
      





この場合、RELROプロパティの1つだけが有効になっているため、checksecは値「partial」を出力します。Checksecは、readelfコマンドを使用して設定を表示します。 



$ readelf -W -l ./hello | grep GNU_RELRO

  GNU_RELRO      0x002e10 0x0000000000403e10 0x0000000000403e10 0x0001f0 0x0001f0 R   0x1

$ readelf -W -d ./hello | grep BIND_NOW
      
      





フルプロテクションをオンにする(FULL RELRO)



これを行うには、コンパイル時に適切なフラグを使用する必要があります。



$ gcc -Wl,-z,relro,-z,now hello.c -o hello

$ checksec --file=./hello --output=json | jq | grep relro

    «relro»: «full»,

      
      





これで、バイナリはFULLRELROの名誉称号を取得しました。



$ readelf -W -l ./hello | grep GNU_RELRO

  GNU_RELRO      0x002dd0 0x0000000000403dd0 0x0000000000403dd0 0x000230 0x000230 R   0x1

$ readelf -W -d ./hello | grep BIND_NOW

 0x0000000000000018 (BIND_NOW)    
      
      



      

その他のchecksec機能



セキュリティのトピックは際限なく研究することができます。この記事で簡単なchecksecユーティリティについて話していても、すべてを網羅することはできません。ただし、さらにいくつかの興味深い可能性について説明します。



複数のファイルをチェックする



ファイルごとに個別のコマンドを実行する必要はありません。複数のバイナリに対して1つのコマンドを一度に実行できます。



$ checksec --dir=/usr/bin
      
      





プロセスの確認



checksecユーティリティを使用すると、プロセスのセキュリティを分析することもできます。次のコマンドは、システムで実行中のすべてのプログラムのプロパティを表示します(これを行うには、-proc-allオプションを使用する必要があります)。 



$ checksec --proc-all
      
      





名前を指定して、チェックするプロセスを1つ選択することもできます。



$ checksec --proc=bash
      
      





カーネルチェック



同様に、システムのカーネルの脆弱性を分析できます。



$ checksec --kernel
      
      





事前に警告されています



セキュリティプロパティを詳細に調査し、それぞれが正確に何に影響し、どのような種類の攻撃を防ぐことができるかを理解してください。あなたを助けるためにChecksec!






Macleodのクラウドサーバー 高速で安全です。



上記のリンクを使用するか、バナーをクリックして登録すると、任意の構成のサーバーをレンタルした最初の月が10%割引になります。






All Articles