checksec是一个用于检测文件、进程、内核安全特性的开源项目,开源在使用shell脚本编写,主要应用在Linux平台。
根据项目主页描述,checksec项目最早由漏洞挖掘专家Tobias Klein编写,目前该项目主要由slimm609、teoberi等人维护。
根据项目主页描述,checksecc是checksec的C语言重写版,并给出了详细的安装和使用教程。项目全部由C语言和Makefile编写,适合C语言的初学者学习。
本章节将简要分析checksec检测文件的原理,重点分析使用C语言重写该项目的逻辑。下文以checksec.sh和checksecc来区分二者。
checksec.sh文件检测有以下五个用法。
file、dir、listfile选项的逻辑都是检测文件、文件列表的基础安全特性,最新版本支持以下11种。
fortify-file选项是对危险函数加固情况的具体说明,为了性能考虑,编译时默认不启用加固。该选项包含以下信息。
extended扩展选项在基础特性上增加了3个特性。
checksec.sh检测文件的核心函数在源码 src/functions/filecheck.sh 中。基本逻辑是使用readelf工具对文件的程序头表、动态节、节头表、函数名进行字符匹配,来判断该文件是否开启了某一安全特性。
以检测RELRO为例,使用readelf检测文件的程序头表,是否有GNU_RELRO。( GNU_RELRO仅在加载文件时进行解析,而不映射在段中 )如果编译器配置了GNU_RELRO,则说明启用了RELRO保护策略,需要进一步判断是Partial还是Full。接着通过匹配动态节的输出信息,如果有BIND_NOW标志,则说明文件需要在加载时绑定,而非延迟绑定,即RELRO Full策略。
checksecc是checksec.sh项目的C重写,将fortify特性合并到了extended选项中。并且考虑到GCC、Clang一些安全特性都是通过插桩实现的,将CFI、SafeStack等统一合并到Sanitized,同时添加了Asan、Tsan等检测。
项目主页的例子,使用clang -fsanitize=address test.c -o test编译出test,检测test可得到如下输出。
使用C语言重写checksec.sh的重点在于,如何编程替代readelf工具。一种方案是使用原生库libbfd,libbfd是GNU Binutils的原生库,广泛应用于GCC、GDB,包括readelf等涉及二进制文件解析的工具中。
而checksecc通过编写一个适用于检测安全特性的最简加载器,来替代readelf/libbfd完成工作。加载器的核心实现在 srcs/loader.c 。每个文件通过loader解析到结构体Binary中,Binary中保存着解析时的映射地址、文件类型、架构、名称、大小、入口点。还有符号链表头、节链表头,最后是各种表头。
还是以检测RELRO为例,上文提到RELRO的检测需要程序头表和动态节,所以我们重点关注loader解析程序头表、节的实现。
loader对程序头表的解析实现在函数load_elf_programhs中,参数传递Binary指针和程序头信息。该函数根据elf是32位还是64位,分别生成链表保存程序头表的信息。
查看调用load_elf_programhs函数的load_elf函数,ph_info数组是根据elf文件头解析出来的三个信息:程序头表文件偏移e_phoff、每个程序头的大小e_phentsize和数量e_phnum。
elf文件头有很多信息,参考图如下(图源 《Practical Binary Analysis》)。
loader解析节的逻辑类似,但是有两点注意。
loader完成解析任务后,交给chk_file执行检测任务。checksecc检测文件的核心逻辑在 srcs/chk_file.c 。
检测一个elf文件的逻辑在函数chk_file_one_elf中,这里使用函数指针的形式管理所有检测函数,在没有指定extended选项的情况下,chk_file_one_elf会检测以下的安全特性。
回到检测RELRO上,chk_elf_relro函数的执行过程可以分为三步。这里逻辑和checksec.sh相同,都是先寻找GNU_RELRO,再寻找BIND_NOW,不过C语言实现需要找到具体的字段来匹配。
DT_BIND_NOW
表示在将控制权返回给程序之前,必须处理此目标文件的所有重定位项。通过环境或 dlopen(3C) 指定时,提供的此项优先于使用延迟绑定的指令。此元素的用途已被 DF_BIND_NOW 标志取代。请参见执行重定位的时间。
--
file
={
file
}
--
dir
={directory}
--listfile={text
file
with one
file
per line}
--fortify-
file
={executable-
file
}
--extended
--
file
={
file
}
--
dir
={directory}
--listfile={text
file
with one
file
per line}
--fortify-
file
={executable-
file
}
--extended
* FORTIFY_SOURCE support available (libc)
* Binary compiled with FORTIFY_SOURCE support
------ EXECUTABLE-FILE ------- . -------- LIBC --------
FORTIFY-able library functions | Checked
function
names
-------------------------------------------------------
fdelt_chk | __fdelt_chk
read
| __read_chk
syslog_chk | __syslog_chk
fprintf_chk | __fprintf_chk
vsnprintf_chk | __vsnprintf_chk
fgets | __fgets_chk
strncpy | __strncpy_chk
snprintf_chk | __snprintf_chk
memset | __memset_chk
strncat_chk | __strncat_chk
memcpy | __memcpy_chk
fread | __fread_chk
sprintf_chk | __sprintf_chk
SUMMARY:
* Number of checked functions
in
libc
* Total number of library functions
in
the executable
* Number of FORTIFY-able functions
in
the executable
* Number of checked functions
in
the executable
* Number of unchecked functions
in
the executable
* FORTIFY_SOURCE support available (libc)
* Binary compiled with FORTIFY_SOURCE support
------ EXECUTABLE-FILE ------- . -------- LIBC --------
FORTIFY-able library functions | Checked
function
names
-------------------------------------------------------
fdelt_chk | __fdelt_chk
read
| __read_chk
syslog_chk | __syslog_chk
fprintf_chk | __fprintf_chk
vsnprintf_chk | __vsnprintf_chk
fgets | __fgets_chk
strncpy | __strncpy_chk
snprintf_chk | __snprintf_chk
memset | __memset_chk
strncat_chk | __strncat_chk
memcpy | __memcpy_chk
fread | __fread_chk
sprintf_chk | __sprintf_chk
SUMMARY:
* Number of checked functions
in
libc
* Total number of library functions
in
the executable
* Number of FORTIFY-able functions
in
the executable
* Number of checked functions
in
the executable
* Number of unchecked functions
in
the executable
filecheck() {
if
[[ $(${readelf} -l
"${1}"
2>
/dev/null
) =~
"no program headers"
]];
then
echo_message
'\033[32mN/A \033[m '
'N/A,'
'<file relro="n/a"'
" \"${1}\": { \"relro\":\"n/a\","
elif
${readelf} -l
"${1}"
2>
/dev/null
|
grep
-q
'GNU_RELRO'
;
then
if
${readelf} -d
"${1}"
2>
/dev/null
|
grep
-q
'BIND_NOW'
|| ! ${readelf} -l
"${1}"
2>
/dev/null
|
grep
-q
'\.got\.plt'
;
then
echo_message
'\033[32mFull RELRO \033[m '
'Full RELRO,'
'<file relro="full"'
" \"${1}\": { \"relro\":\"full\","
else
echo_message
'\033[33mPartial RELRO\033[m '
'Partial RELRO,'
'<file relro="partial"'
" \"${1}\": { \"relro\":\"partial\","
fi
else
echo_message
'\033[31mNo RELRO \033[m '
'No RELRO,'
'<file relro="no"'
" \"${1}\": { \"relro\":\"no\","
fi
...
}
filecheck() {
if
[[ $(${readelf} -l
"${1}"
2>
/dev/null
) =~
"no program headers"
]];
then
echo_message
'\033[32mN/A \033[m '
'N/A,'
'<file relro="n/a"'
" \"${1}\": { \"relro\":\"n/a\","
elif
${readelf} -l
"${1}"
2>
/dev/null
|
grep
-q
'GNU_RELRO'
;
then
if
${readelf} -d
"${1}"
2>
/dev/null
|
grep
-q
'BIND_NOW'
|| ! ${readelf} -l
"${1}"
2>
/dev/null
|
grep
-q
'\.got\.plt'
;
then
echo_message
'\033[32mFull RELRO \033[m '
'Full RELRO,'
'<file relro="full"'
" \"${1}\": { \"relro\":\"full\","
else
echo_message
'\033[33mPartial RELRO\033[m '
'Partial RELRO,'
'<file relro="partial"'
" \"${1}\": { \"relro\":\"partial\","
fi
else
echo_message
'\033[31mNo RELRO \033[m '
'No RELRO,'
'<file relro="no"'
" \"${1}\": { \"relro\":\"no\","
fi
...
}
> checkc --
file
=.
/test
--extended
File .
/test
RELRO Partial RELRO
STACK CANARY Canary found
NX NX enabled
PIE PIE enabled
RPATH NO RPATH
RUNPATH NO RUNPATH
Stripped Not Stripped
Sanitized asan Yes
Sanitized tsan NO
Sanitized msan NO
Sanitized lsan Yes
Sanitized ubsan Yes
Sanitized dfsan NO
Sanitized safestack NO
Sanitized cet-ibt NO
Sanitized cet-shadow-stack NO
Fortified FORTIFY SOURCE support available (
/lib/x86_64-linux-gnu/libc
.so.6) : Yes
Fortified Binary compiled with FORTIFY SOURCE support (.
/test
) : Yes
Fortified __sprintf_chk Fortified
Fortified __longjmp_chk Fortified
Fortified __fprintf_chk Fortified
Fortified __vsprintf_chk Fortified
Fortified __snprintf_chk Fortified
Fortified __vsnprintf_chk Fortified
> checkc --
file
=.
/test
--extended
File .
/test
RELRO Partial RELRO
STACK CANARY Canary found
NX NX enabled
PIE PIE enabled
RPATH NO RPATH
RUNPATH NO RUNPATH
Stripped Not Stripped
Sanitized asan Yes
Sanitized tsan NO
Sanitized msan NO
Sanitized lsan Yes
Sanitized ubsan Yes
Sanitized dfsan NO
Sanitized safestack NO
Sanitized cet-ibt NO
Sanitized cet-shadow-stack NO
Fortified FORTIFY SOURCE support available (
/lib/x86_64-linux-gnu/libc
.so.6) : Yes
Fortified Binary compiled with FORTIFY SOURCE support (.
/test
) : Yes
Fortified __sprintf_chk Fortified
Fortified __longjmp_chk Fortified
Fortified __fprintf_chk Fortified
Fortified __vsprintf_chk Fortified
Fortified __snprintf_chk Fortified
Fortified __vsnprintf_chk Fortified
typedef
struct
Binary{
void
*mem;
bin_type bin_type;
bin_arch bin_arch;
const
char
*bin_name;
uint64_t bin_size;
uint64_t entry;
Symbol *sym;
Section *sect;
Header *hd;
}Binary;
typedef
struct
Binary{
void
*mem;
bin_type bin_type;
bin_arch bin_arch;
const
char
*bin_name;
uint64_t bin_size;
uint64_t entry;
Symbol *sym;
Section *sect;
Header *hd;
}Binary;
void
load_elf_programhs(Binary *elf,uint64_t *ph_info){
void
*mem=elf->mem;
uintptr_t
ph_addr=(
uintptr_t
)mem+ph_info[0];
Programh *ph=MALLOC(1,Programh);
elf->hd->Pxheader.ph=ph;
switch
(elf->bin_type){
case
BIN_TYPE_ELF32:
for
(uint16_t ph_num=0;ph_num < ph_info[2];ph_num++){
uintptr_t
ph32_addr=ph_addr+ph_num*ph_info[1];
E32_ph *ph32=(E32_ph*)ph32_addr;
Programh *
new
=MALLOC(1,Programh);
new
->sgm_type=load_elf_programh_types(ph32->p_type);
new
->sgm_vma=ph32->p_vaddr;
new
->sgm_flag=ph32->p_flags;
ph->ph_next=
new
;
ph=
new
;
}
break
;
case
BIN_TYPE_ELF64:
for
(uint16_t ph_num=0;ph_num < ph_info[2];ph_num++){
uintptr_t
ph64_addr=ph_addr+ph_num*ph_info[1];
E64_ph *ph64=(E64_ph*)ph64_addr;
Programh *
new
=MALLOC(1,Programh);
new
->sgm_type=load_elf_programh_types(ph64->p_type);
new
->sgm_vma=ph64->p_vaddr;
new
->sgm_flag=ph64->p_flags;
ph->ph_next=
new
;
ph=
new
;
}
}
ph->ph_next=NULL;
}
void
load_elf_programhs(Binary *elf,uint64_t *ph_info){
void
*mem=elf->mem;
uintptr_t
ph_addr=(
uintptr_t
)mem+ph_info[0];
Programh *ph=MALLOC(1,Programh);
elf->hd->Pxheader.ph=ph;
switch
(elf->bin_type){
case
BIN_TYPE_ELF32:
for
(uint16_t ph_num=0;ph_num < ph_info[2];ph_num++){
uintptr_t
ph32_addr=ph_addr+ph_num*ph_info[1];
E32_ph *ph32=(E32_ph*)ph32_addr;
Programh *
new
=MALLOC(1,Programh);
new
->sgm_type=load_elf_programh_types(ph32->p_type);
new
->sgm_vma=ph32->p_vaddr;
new
->sgm_flag=ph32->p_flags;
ph->ph_next=
new
;
ph=
new
;
}
break
;
case
BIN_TYPE_ELF64:
for
(uint16_t ph_num=0;ph_num < ph_info[2];ph_num++){
uintptr_t
ph64_addr=ph_addr+ph_num*ph_info[1];
E64_ph *ph64=(E64_ph*)ph64_addr;
Programh *
new
=MALLOC(1,Programh);
new
->sgm_type=load_elf_programh_types(ph64->p_type);
new
->sgm_vma=ph64->p_vaddr;
new
->sgm_flag=ph64->p_flags;
ph->ph_next=
new
;
ph=
new
;
}
}
ph->ph_next=NULL;
}
void
load_elf(Binary *elf,
void
*mem){
...
switch
(elf->bin_type)
{
case
BIN_TYPE_ELF32:
...
ph_info[0]=elf32_fh->e_phoff;
ph_info[1]=elf32_fh->e_phentsize;
ph_info[2]=elf32_fh->e_phnum;
break
;
case
BIN_TYPE_ELF64:
...
ph_info[0]=elf64_fh->e_phoff;
ph_info[1]=elf64_fh->e_phentsize;
ph_info[2]=elf64_fh->e_phnum;
break
;
}
load_elf_programhs(elf,mem,ph_info);
...
}
void
load_elf(Binary *elf,
void
*mem){
...
switch
(elf->bin_type)
{
case
BIN_TYPE_ELF32:
...
ph_info[0]=elf32_fh->e_phoff;
ph_info[1]=elf32_fh->e_phentsize;
ph_info[2]=elf32_fh->e_phnum;
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课