C语言可执行文件检测程序优化及代码问题排查咨询
检测可执行文件类型:Shell脚本与二进制文件的准确区分及代码问题修正
现有代码存在的问题
- 竞态条件漏洞:使用
access()检查文件存在性和可执行权限存在TOCTOU(检查时间与使用时间不一致)问题。在access()调用后到后续操作文件的间隙,文件的权限或存在状态可能被篡改,导致判断结果失效。应该用stat()直接获取文件元数据,避免这个问题。 - 函数命名与功能不符:
checkIfFileExists函数实际不仅检查文件存在,还判断可执行权限和计划检测文件类型,命名改为checkExecutableType更准确。 - 冗余的返回语句:
err_quit函数(来自《UNIX环境高级编程》)会直接终止程序,所以在err_quit之后的return 0完全多余,不会被执行。 - 字符串格式错误:代码中
err_quit的参数字符串直接换行,这会导致编译错误,应该用\n来表示换行。 - 缺少核心类型判断逻辑:仅保留了注释,没有实现区分Shell脚本和二进制文件的代码。
准确区分Shell脚本与二进制文件的高效实现
判断逻辑
- Shell脚本:几乎所有可执行脚本都以shebang(
#!)开头,这是脚本的标识,读取文件前2字节即可判断。 - 二进制可执行文件:主流的Linux/Unix二进制文件是ELF格式,其开头4字节为固定魔数:
0x7f 0x45 0x4c 0x46(对应ASCII的\x7fELF),读取前4字节即可识别。 - 前置检查:先用
stat()判断文件是否为普通文件(S_ISREG),排除目录、设备文件等有执行权限但不是常规可执行文件的情况。
实现步骤
- 通过
stat()获取文件状态,验证文件存在且为普通文件。 - 检查文件是否具有可执行权限(通过
stat()返回的st_mode判断,无需access())。 - 打开文件,读取前几个字节进行类型判断:
- 先读取2字节,判断是否为
#!,若是则判定为Shell/解释型脚本。 - 若不是,读取前4字节判断是否为ELF魔数,若是则判定为二进制可执行文件。
- 其余情况标记为未知类型的可执行文件。
- 先读取2字节,判断是否为
修正后的完整代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <apue.h> #include <sys/stat.h> #include <fcntl.h> void checkExecutableType(const char *fileName); int main(int argc, char *argv[]) { if (argc < 2) { err_quit("File to check not specified\n"); } if (argc > 2) { err_quit("Too many arguments\n"); } checkExecutableType(argv[1]); return 0; } void checkExecutableType(const char *fileName) { struct stat st; // 用stat获取文件信息,同时检查文件存在性 if (stat(fileName, &st) == -1) { err_sys("Failed to access file %s", fileName); } // 检查是否为普通文件 if (!S_ISREG(st.st_mode)) { printf("The file %s is not a regular file\n", fileName); return; } // 检查当前用户是否有执行权限 if ((st.st_mode & S_IXUSR) == 0) { printf("The file %s is not an executable (no user execute permission)\n", fileName); return; } printf("The file %s is an executable\n", fileName); // 打开文件检测类型 int fd = open(fileName, O_RDONLY); if (fd == -1) { err_sys("Failed to open file %s", fileName); } unsigned char buf[4]; ssize_t read_len = read(fd, buf, sizeof(buf)); if (read_len >= 2) { if (buf[0] == '#' && buf[1] == '!') { printf("Type: Shell/Interpreted Script\n"); close(fd); return; } } if (read_len >= 4) { if (buf[0] == 0x7f && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F') { printf("Type: ELF Binary Executable\n"); close(fd); return; } } printf("Type: Unknown executable type\n"); close(fd); }
补充说明
- 使用
stat()替代access()避免了竞态条件,同时一次性获取文件的类型和权限信息,效率更高。 - 读取文件头部字节时,只需读取必要的几个字节,无需加载整个文件,保证了检测的高效性。
- 加入了普通文件的判断,避免误判目录、设备等特殊文件。
内容的提问来源于stack exchange,提问作者Mirel Vlaseanu
相关产品推荐
相关产品推荐

