在 Linux 上使用 Checksec 識別二進位文件的安全屬性
編譯源代碼會生成一個二進位文件(LCTT 譯註:即 .o
文件)。在編譯期間,你可以向 gcc
編譯器提供 標誌 ,以啟用或禁用二進位文件的某些屬性,這些屬性與安全性相關。
Checksec 是一個漂亮的小工具,同時它也是一個 shell 腳本。Checksec 可以識別編譯時構建到二進位文件中的安全屬性。編譯器可能會默認啟用一些安全屬性,你也可以提供特定的標誌,來啟用其他的安全屬性。
本文將介紹如何使用 Checksec ,來識別二進位文件的安全屬性,包括:
- Checksec 在查找有關安全屬性的信息時,使用了什麼底層的命令
- 在將源代碼編譯成二進位文件時,如何使用 GNU 編譯器套件 (即 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 輸出的第一行提供了二進位文件的各種安全屬性,例如 RELRO
、STACK CANARY
、NX
等(我將在後文進行詳細解釋)。第二行列印出給定二進位文件(本例中為 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)
我先從簡單的講起。在編譯期間,某些 符號 包含在二進位文件中,這些符號主要用作於調試。開發軟體時,需要用到這些符號,來調試和修復錯誤。
這些符號通常會從供用戶普遍使用的最終二進位文件中刪除。刪除這些符號不會影響到二進位文件的執行。刪除符號通常是為了節省空間,因為一旦符號被刪除了,二進位文件就會稍微小一些。在閉源或專有軟體中,符號通常都會被刪除,因為把這些符號放在二進位文件中,可以很容易地推斷出軟體的內部工作原理。
根據 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 '\.symtab'
+ echo_message ' 33[31m96) Symbolst 33[m ' Symbols, ' symbols="yes"' '"symbols":"yes",'
上面的輸出顯示,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,你可以看到現在二進位文件 hello
的 symbols
這一屬性的值是no
:
$ checksec --file=./hello --output=json | jq | grep symbols
"symbols": "no",
$
2、Canary(堆棧溢出哨兵)
Canary 是放置在緩衝區和 棧 上的控制數據之間的已知值,它用於監視緩衝區是否溢出。當應用程序執行時,會為其分配兩種內存,其中之一就是 棧。棧是一個具有兩個操作的數據結構:第一個操作 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 '__stack_chk_fail|__intel_security_cookie'
啟用 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 '__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),顧名思義,它指的是放置在內存中某處執行的代碼,不管其絕對地址的位置,即代碼段、數據段地址隨機化(ASLR):
$ checksec --file=/bin/ls --output=json | jq | grep pie
"pie": "yes",
$ checksec --file=./hello --output=json | jq | grep pie
"pie": "no",
通常,PIE 僅對 庫 啟用,並不對獨立命令行程序啟用 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 可執行 」,其類型從 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 代表 不可執行 。它通常在 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 代表 「 重定位只讀 」。可執行鏈接格式(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 檢查用戶態應用程序的安全屬性之外,你還可以使用它來檢查系統內置的 內核屬性 :
$ checksec --kernel
快來試一試 Checksec 吧
Checksec 是一個能了解哪些用戶空間和內核的安全屬性被啟用的好方法。現在,你就可以開始使用 Checksec,來了解每個安全屬性是什麼,並明白啟用每個安全屬性的原因,以及它能阻止的攻擊類型。
via: https://opensource.com/article/21/6/linux-checksec
作者:Gaurav Kamathe 選題:lujun9972 譯者:chai001125 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive