Linux中國

在 Linux 上使用 Checksec 識別二進位文件的安全屬性

編譯源代碼會生成一個二進位文件(LCTT 譯註:即 .o 文件)。在編譯期間,你可以向 gcc 編譯器提供 標誌 flags ,以啟用或禁用二進位文件的某些屬性,這些屬性與安全性相關。

Checksec 是一個漂亮的小工具,同時它也是一個 shell 腳本。Checksec 可以識別編譯時構建到二進位文件中的安全屬性。編譯器可能會默認啟用一些安全屬性,你也可以提供特定的標誌,來啟用其他的安全屬性。

本文將介紹如何使用 Checksec ,來識別二進位文件的安全屬性,包括:

  1. Checksec 在查找有關安全屬性的信息時,使用了什麼底層的命令
  2. 在將源代碼編譯成二進位文件時,如何使用 GNU 編譯器套件 GNU Compiler Collection (即 GCC)來啟用安全屬性

安裝 checksec

要在 Fedora 和其他基於 RPM 的 Linux 系統上,安裝 Checksec,請使用以下命令:

$ sudo dnf install checksec

對於基於 Debian 的 Linux 發行版,使用對應的 apt 命令,來安裝 Checksec。

$ sudo apt install checksec

shell 腳本

在安裝完 Checksec 後,能夠發現 Checksec 是一個單文件的 shell 腳本,它位於 /usr/bin/checksec,並且這個文件挺大的。Checksec 的一個優點是你可以通過快速通讀這個 shell 腳本,從而了解 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 命令的格式是:checksec --file=,後面再跟上二進位文件的絕對路徑:

$ checksec --file=/usr/bin/ls
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols        Yes   5       17              /usr/bin/ls

當你在終端中對某個二進位文件運行 Checksec 時,你會看到安全屬性有顏色上的區分,顯示什麼是好的安全屬性(綠色),什麼可能不是好的安全屬性(紅色)。我在這裡說 「可能」 是因為即使有些安全屬性是紅色的,也不一定意味著這個二進位文件很糟糕,它可能只是表明發行版供應商在編譯二進位文件時做了一些權衡,從而捨棄了部分安全屬性。

Checksec 輸出的第一行提供了二進位文件的各種安全屬性,例如 RELROSTACK CANARYNX 等(我將在後文進行詳細解釋)。第二行列印出給定二進位文件(本例中為 ls)在這些安全屬性的狀態(例如,NX enabled 表示為堆棧中的數據沒有執行許可權)。

示例二進位文件

在本文中,我將使用以下的 「hello world」 程序作為示例二進位文件。

#include <stdio.h>

int main()
{
        printf("Hello Worldn");
        return 0;
}

請注意,在編譯源文件 hello.c 的時候,我沒有給 gcc 提供任何額外的標誌:

$ 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 運行二進位文件 hello,列印的某些安全屬性的狀態,與上面的 ls 二進位文件的結果不同(在你的屏幕上,某些屬性可能顯示為紅色):

$ checksec --file=./hello
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   85) Symbols       No    0       0./hello
$

(LCTT 譯註:在我的 Ubuntu 22.04 虛擬機,使用 11.3.0 版本的 gcc,結果與上述不太相同,利用默認參數進行編譯,會得到 RELRO、PIE、NX 保護是全開的情況。)

更改 Checksec 的輸出格式

Checksec 允許自定義各種輸出格式,你可以使用 --output 來自定義輸出格式。我將選擇的輸出格式是 JSON 格式,並將輸出結果通過管道傳輸到 jq 實用程序,來得到漂亮的列印。

接下來,確保你已安裝好了 jq,因為本教程會使用 jq 從 Checksec 的輸出結果中,用 grep 來快速得到某一特定的安全屬性狀態,並報告該安全屬性是否啟動(啟動為 yes,未啟動為 no):

$ 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"
  }
}

看一看所有的安全屬性

上面的二進位文件 hello 包括幾個安全屬性。我將該二進位文件與 ls 的二進位文件進行比較,以檢查啟用的安全屬性有何不同,並解釋 Checksec 是如何找到此信息。

1、符號(Symbol)

我先從簡單的講起。在編譯期間,某些 符號 symbols 包含在二進位文件中,這些符號主要用作於調試。開發軟體時,需要用到這些符號,來調試和修復錯誤。

這些符號通常會從供用戶普遍使用的最終二進位文件中刪除。刪除這些符號不會影響到二進位文件的執行。刪除符號通常是為了節省空間,因為一旦符號被刪除了,二進位文件就會稍微小一些。在閉源或專有軟體中,符號通常都會被刪除,因為把這些符號放在二進位文件中,可以很容易地推斷出軟體的內部工作原理。

根據 Checksec 的結果,在二進位文件 hello 中有符號,但在 ls 的二進位文件中不會有符號。同樣地,你還可以用 file 命令,來找到符號的信息,在二進位文件 hello 的輸出結果的最後,看到 not stripped,表明二進位文件 hello 有符號:

$ checksec --file=/bin/ls --output=json | jq | grep symbols
    "symbols": "no",

$ checksec --file=./hello --output=json | jq | grep symbols
    "symbols": "yes",

$ 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

Checksec 是如何找到符號的信息呢?Checksec 提供了一個方便的 --debug 選項,來顯示運行了哪些函數。因此,運行以下的命令,會顯示在 shell 腳本中運行了哪些函數:

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

在本教程中,我試圖尋找 Checksec 查找安全屬性信息時,使用了什麼底層命令。由於 Checksec 是一個 shell 腳本,因此你始終可以使用 Bash 功能。以下的命令將輸出從 shell 腳本中運行的每個命令:

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

如果你滾動瀏覽上述的輸出結果的話,你會看到 echo_message 後面有各個安全屬性的類別。以下顯示了 Checksec 檢測二進位文件是否包含符號時,運行的底層命令:

+ readelf -W --symbols ./hello
+ grep -q &apos;\.symtab&apos;
+ echo_message &apos;33[31m96) Symbolst33[m  &apos; Symbols, &apos; symbols="yes"&apos; &apos;"symbols":"yes",&apos;

上面的輸出顯示,Checksec 利用 readelf,來讀取二進位文件,並提供一個特殊 --symbols 標誌,來列出二進位文件中的所有符號。然後它會查找一個特殊值:.symtab,它提供了所能找到的條目的計數(即符號的個數)。你可以在上面編譯的測試二進位文件 hello 上,嘗試以下命令,得到與 Checksec 查看二進位文件類似的符號信息:

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

(LCTT 譯註:也可以通過直接查看 /usr/bin/checksec 下的 Checksec 源文件。)

如何刪除符號

你可以在編譯後或編譯時刪除符號。

  • 編譯後: 在編譯後,你可以使用 strip,手動地來刪除二進位文件的符號。刪除後,使用 file 命令,來檢驗是否還有符號,現在顯示 stripped,表明二進位文件 hello 無符號了:
$ 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, not stripped
$
$ 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, stripped
$ 
  • 編譯時: 你也可以在編譯時,用 -s 參數讓 gcc 編譯器幫你自動地刪除符號:
$ 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, stripped
$

重新運行 Checksec,你可以看到現在二進位文件 hellosymbols 這一屬性的值是no

$ checksec --file=./hello --output=json | jq | grep symbols
    "symbols": "no",
$

2、Canary(堆棧溢出哨兵)

Canary 是放置在緩衝區和 stack 上的控制數據之間的已知值,它用於監視緩衝區是否溢出。當應用程序執行時,會為其分配兩種內存,其中之一就是 。棧是一個具有兩個操作的數據結構:第一個操作 push,將數據壓入堆棧;第二個操作 pop,以後進先出的順序從棧中彈出數據。惡意的輸入可能會導致棧溢出,或使用特製的輸入破壞棧,並導致程序崩潰:

$ checksec --file=/bin/ls --output=json | jq | grep canary
    "canary": "yes",
$
$ checksec --file=./hello --output=json | jq | grep canary
    "canary": "no",
$

Checksec 是如何確定二進位文件是否啟用了 Canary 的呢?使用上述同樣的方法,得到 Checksec 在檢測二進位文件是否啟用 Canary 時,運行的底層命令:

$ readelf -W -s ./hello | grep -E &apos;__stack_chk_fail|__intel_security_cookie&apos;
啟用 Canary

為了防止棧溢出等情況,編譯器提供了 -stack-protector-all 標誌,它向二進位文件添加了額外的代碼,來檢查緩衝區是否溢出:

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

$ checksec --file=./hello --output=json | jq | grep canary
    "canary": "yes",

Checksec 顯示 Canary 屬性現已啟用。你還可以通過以下方式,來驗證這一點:

$ readelf -W -s ./hello | grep -E &apos;__stack_chk_fail|__intel_security_cookie&apos;
     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)

位置無關可執行文件 Position-Independent Executable (PIE),顧名思義,它指的是放置在內存中某處執行的代碼,不管其絕對地址的位置,即代碼段、數據段地址隨機化(ASLR):

$ checksec --file=/bin/ls --output=json | jq | grep pie
    "pie": "yes",

$ checksec --file=./hello --output=json | jq | grep pie
    "pie": "no",

通常,PIE 僅對 libraries 啟用,並不對獨立命令行程序啟用 PIE。在下面的輸出中,hello 顯示為 LSB executable,而 libc 標準庫(.so) 文件被標記為 LSB shared object

$ 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

$ file /lib64/libc-2.32.so
/lib64/libc-2.32.so: ELF 64-bit LSB shared object, 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 查找是否啟用 PIE 的底層命令如下:

$ readelf -W -h ./hello | grep EXEC
  Type:                              EXEC (Executable file)

如果你在共享庫上嘗試相同的命令,你將看到 DYN,而不是 EXEC

$ readelf -W -h /lib64/libc-2.32.so | grep DYN
  Type:                              DYN (Shared object file)
啟用 PIE

要在測試程序 hello.c 上啟用 PIE,請在編譯時,使用以下命令:

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

你可以使用 Checksec,來驗證 PIE 是否已啟用:

$ checksec --file=./hello --output=json | jq | grep pie
    "pie": "yes",
$

現在,應該會顯示為 「 PIE 可執行 pie executable 」,其類型從 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 代表 不可執行 non-executable 。它通常在 CPU 層面上啟用,因此啟用 NX 的操作系統可以將某些內存區域標記為不可執行。通常,緩衝區溢出漏洞將惡意代碼放在堆棧上,然後嘗試執行它。但是,讓堆棧這些可寫區域變得不可執行,可以防止這種攻擊。在使用 gcc 對源程序進行編譯時,默認啟用此安全屬性:

$ checksec --file=/bin/ls --output=json | jq | grep nx
    "nx": "yes",

$ checksec --file=./hello --output=json | jq | grep nx
    "nx": "yes",

Checksec 使用以下底層命令,來確定是否啟用了 NX。在尾部的 RW 表示堆棧是可讀可寫的;因為沒有 E,所以堆棧是不可執行的:

$ readelf -W -l ./hello | grep GNU_STACK
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
演示如何禁用 NX

我們不建議禁用 NX,但你可以在編譯程序時,使用 -z execstack 參數,來禁用 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 防寫)

RELRO 代表 「 重定位只讀 Relocation Read-Only 」。可執行鏈接格式(ELF)二進位文件使用全局偏移表(GOT)來動態地解析函數。啟用 RELRO 後,會設置二進位文件中的 GOT 表為只讀,從而防止重定位攻擊:

$ checksec --file=/bin/ls --output=json | jq | grep relro
    "relro": "full",

$ checksec --file=./hello --output=json | jq | grep relro
    "relro": "partial",

Checksec 使用以下底層命令,來查找是否啟用 RELRO。在二進位文件 hello 僅啟用了 RELRO 屬性中的一個屬性,因此,在 Checksec 驗證時,顯示 partial

$ readelf -W -l ./hello | grep GNU_RELRO
  GNU_RELRO      0x002e10 0x0000000000403e10 0x0000000000403e10 0x0001f0 0x0001f0 R   0x1

$ readelf -W -d ./hello | grep BIND_NOW
啟用全 RELRO

要啟用全 RELRO,請在 gcc 編譯時,使用以下命令行參數:

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

$ checksec --file=./hello --output=json | jq | grep relro
    "relro": "full",

現在, RELRO 中的第二個屬性也被啟用,使程序變成全 RELRO:

$ 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)       

6、Fortify

Fortify 是另一個安全屬性,但它超出了本文的範圍。Checksec 是如何在二進位文件中驗證 Fortify,以及如何在 gcc 編譯時啟用 Fortify,作為你需要解決的課後練習。

$ checksec --file=/bin/ls --output=json | jq  | grep -i forti
    "fortify_source": "yes",
    "fortified": "5",
    "fortify-able": "17"

$ checksec --file=./hello --output=json | jq  | grep -i forti
    "fortify_source": "no",
    "fortified": "0",
    "fortify-able": "0"

其他的 Checksec 功能

關於安全性的話題是永無止境的,不可能在本文涵蓋所有關於安全性的內容,但我還想提一下 Checksec 命令的一些其他功能,這些功能也很好用。

對多個二進位文件運行 Checksec

你不必對每個二進位文件都進行一次 Checksec。相反,你可以提供多個二進位文件所在的目錄路徑,Checksec 將一次性為你驗證所有文件:

$ checksec --dir=/usr

對進程運行 Checksec

Checksec 除了能檢查二進位文件的安全屬性,Checksec 還能對程序起作用。以下的命令用於查找你系統上所有正在運行的程序的安全屬性。如果你希望 Checksec 檢查所有正在運行的進程,可以使用 --proc-all,或者你也可以使用進程名稱,選擇特定的進程進行檢查:

$ checksec --proc-all

$ checksec --proc=bash

對內核運行 Checksec

除了本文介紹的用 Checksec 檢查用戶態應用程序的安全屬性之外,你還可以使用它來檢查系統內置的 內核屬性 kernel properties

$ checksec --kernel

快來試一試 Checksec 吧

Checksec 是一個能了解哪些用戶空間和內核的安全屬性被啟用的好方法。現在,你就可以開始使用 Checksec,來了解每個安全屬性是什麼,並明白啟用每個安全屬性的原因,以及它能阻止的攻擊類型。

via: https://opensource.com/article/21/6/linux-checksec

作者:Gaurav Kamathe 選題:lujun9972 譯者:chai001125 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
1
不錯
1
愛死了
1
不太好
1
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國