在网络安全领域,分析恶意软件的行为、理解其感染机制是防御工作的重要环节。针对Linux系统中常见的ELF(可执行与可链接格式)二进制文件,有一类工具专门用于静态分析、动态监控以及模拟感染过程,帮助安全人员摸清恶意软件的"套路"。这类工具的设计思路融合了文件格式解析、系统监控、二进制操作等多种技术,今天我们就来聊聊它的核心原理。
一、工具能做什么?核心功能拆解
简单来说,这类工具主要围绕ELF文件做三件事:分析它、生成恶意代码片段、感染合法文件。具体拆解开有这些核心功能:
-
静态分析ELF文件
就像拆机器看内部结构,工具会读取ELF文件的"说明书"------各种头部信息(比如文件头、程序头、节区头),检查是否有可疑的节区(比如权限异常的段)、异常的入口点(程序开始执行的位置不对),以此判断文件是否被篡改或本身就是恶意文件。
-
动态监控程序行为
光看静态结构不够,还得让程序跑起来观察。工具会通过调试技术跟踪程序运行,记录它调用了哪些系统调用(比如读文件、创建进程、联网等),一旦发现危险操作(比如偷偷删除系统文件),就会提醒用户并让用户选择是否允许。
-
生成可植入的恶意代码片段(Shellcode)
恶意软件要感染其他文件,需要一段能被执行的二进制代码(Shellcode)。工具可以把原始的二进制代码转换成不同格式(比如C语言字符串、纯二进制),方便嵌入到其他ELF文件中。
-
模拟感染ELF文件
为了研究感染原理,工具能模拟恶意软件的行为:修改合法ELF文件的结构,比如添加一个新的节区存放恶意代码,再修改入口点让程序先执行恶意代码,最后跳回原程序继续运行,以此演示感染的全过程。
-
捕获程序崩溃信息
恶意代码有时会因为编写不规范导致程序崩溃,工具能捕获崩溃时的信号(比如段错误、非法指令)、寄存器状态、调用栈等信息,帮助分析恶意代码的漏洞或触发条件。
二、核心功能的实现原理:大白话解释
1. 静态分析:靠"读说明书"找破绽
ELF文件就像一个按固定规则打包的文件夹,里面有描述文件属性的"封面"(文件头)、记录程序如何加载到内存的"加载指南"(程序头)、存放代码和数据的"子文件夹"(节区)。静态分析的核心就是按规则解析这些结构:
- 解析头部信息:通过读取文件头(ELF Header)确定文件是32位还是64位、CPU架构等;通过程序头(Program Header)知道哪些部分会被加载到内存,权限是可读可写可执行还是仅可读;通过节区头(Section Header)定位代码段、数据段、符号表等。
- 找可疑点:比如正常程序的代码段(.text)通常是"只读可执行",如果发现它有"可写"权限,就可能被篡改过;入口点地址如果指向一个不在代码段的位置,也可能藏了猫腻。
代码上会用结构体对应ELF的各种头部(比如Elf32_Ehdr、Elf64_Phdr),通过文件读写操作把二进制数据映射到这些结构体中,再逐个字段检查。比如下面这段伪代码示意:
c
// 打开ELF文件
FILE *elf_file = fopen("target.elf", "rb");
// 读取文件头
Elf64_Ehdr ehdr;
fread(&ehdr, sizeof(ehdr), 1, elf_file);
// 检查入口点是否在代码段
if (ehdr.e_entry < code_segment_start || ehdr.e_entry > code_segment_end) {
printf("可疑入口点!");
}
2. 动态分析:像"监控摄像头"盯紧每一步
动态分析的关键是"跟踪程序运行",Linux下最常用的技术是ptrace系统调用------它能让一个进程(监控进程)控制另一个进程(被分析进程)的执行,比如暂停、查看寄存器、修改内存等。
工具的工作流程大概是这样:
- 启动被分析程序时,用
fork创建子进程,子进程通过ptrace(PTRACE_TRACEME)让父进程监控自己; - 父进程用
ptrace拦截子进程的系统调用(比如每次子进程要调用open打开文件时,都会被暂停); - 父进程检查系统调用的类型和参数(比如打开的文件名、操作权限),判断是否危险;
- 让用户选择允许、拒绝或记录该操作,再让子进程继续执行。
比如监控到unlink(删除文件)系统调用时,工具会弹出提示,让用户决定是否允许:
c
// 拦截系统调用后,获取系统调用号
int syscall_num = ptrace(PTRACE_PEEKUSER, child_pid, ORIG_RAX * 8, 0);
if (syscall_num == SYS_UNLINK) { // SYS_UNLINK是删除文件的系统调用号
char *filename = get_filename_from_child(child_pid); // 获取要删除的文件名
printf("发现删除文件操作:%s,是否允许?", filename);
// 根据用户输入决定是否让子进程继续
}
3. Shellcode生成:把"恶意代码"打包成可植入格式
Shellcode是一段能直接被CPU执行的二进制指令(比如打开终端、下载文件的代码)。但直接用二进制不方便嵌入其他文件,工具会把它转换成更易处理的格式:
- 纯二进制格式:直接保存原始指令,适合直接写入ELF文件的节区;
- C字符串格式 :把二进制转换成
\x01\x02\x03这样的C语言转义字符串,方便在C代码中嵌入。
代码上就是读取原始二进制文件,逐个字节处理:
c
// 生成C字符串格式的Shellcode
FILE *input = fopen("shellcode.bin", "rb");
FILE *output = fopen("shellcode_c.txt", "w");
unsigned char byte;
while (fread(&byte, 1, 1, input)) {
fprintf(output, "\\x%02x", byte); // 每个字节转成\xXX格式
}
4. ELF感染:给合法文件"插一脚"
感染ELF文件的核心是修改文件结构,让恶意代码先执行。常见思路有两种:
- 修改入口点:找到ELF文件的入口点(程序开始执行的地址),先改成恶意代码的地址,等恶意代码执行完,再跳回原入口点;
- 添加新节区:在ELF文件末尾加一个新的节区,放入恶意代码,再修改程序头让这个节区被加载到内存,最后调整入口点指向新节区。
工具会先解析ELF的程序头和节区头,计算新节区的位置和大小,再用文件操作写入恶意代码,最后更新头部信息确保系统能正确加载。
5. 崩溃捕获:记录"翻车现场"
恶意代码经常写得很粗糙,容易导致程序崩溃(比如访问不存在的内存地址)。工具通过捕获崩溃信号(如SIGSEGV段错误),收集当时的关键信息:
- 崩溃时的信号类型和代码(比如是内存访问错误还是非法指令);
- 寄存器状态(比如程序计数器PC的值,能定位到崩溃的代码位置);
- 调用栈(能看出崩溃前执行了哪些函数)。
实现上用signal函数注册信号处理函数,一旦收到崩溃信号,就通过ptrace读取子进程的寄存器和内存信息,保存到日志文件中。
三、设计思路:怎么把这些功能串起来?
这类工具的设计遵循"模块化"和"分层处理"的思路,就像搭积木:
-
按功能拆模块:静态分析、动态分析、Shellcode处理、感染操作各自作为独立模块,模块间通过简单的接口传递数据(比如ELF文件路径、配置参数)。这样改一个功能不会影响其他模块,也方便扩展新功能(比如以后加一个"反编译"模块)。
-
依赖底层技术封装 :直接操作ELF文件、系统调用、
ptrace这些底层功能很复杂,工具会封装一层通用函数(比如读取ELF头部的函数、解析系统调用的函数),上层模块直接调用,不用重复造轮子。 -
用户交互简单直观 :通过命令行参数控制功能(比如
tool analyse -i target.elf分析文件,tool infect -i good.elf -m malware.bin感染文件),动态分析时用简单的菜单让用户选择操作(允许/拒绝/记录),降低使用门槛。 -
兼容多种场景 :考虑到ELF有32位和64位之分,工具会通过条件编译(比如
#ifdef ELF64)处理不同格式;同时支持多种Shellcode格式、多种感染方式,适应不同的分析需求。
cpp
...
#define DISSECT "dissect"
#define SHELLCODE "shellcode"
#define INFECT "infect"
#define DYNAMIC_ANALYSIS "dynanalyse"
#define DISAS "disas"
#define DATABASE "database"
#define ANALYSE "analyse"
static void _malelf_help()
{
HELP("\n");
HELP("Tool to infect and/or analyse ELF binary.\n");
HELP("Usage: malelf <command> [-h] <options> \n");
HELP("Commands:\n");
HELP(" dissect \tShow ELF binary info. \n");
HELP(" disas \tDisassembly binary ELF in NASM compatible format.\n");
HELP(" infect \tInfect the binary with a malware.\n");
HELP(" shellcode \tcreate the virus shellcode in the proper format\n\
\tto use with the infect command.\n");
HELP(" dynanalyse \tDinamically analyse the ELF binary for malwares.\n");
HELP(" database \tStores ELF binary info in a database.\n");
HELP(" analyse \tAnalyse ELF binary info in a database.\n");
HELP("\n");
exit(MALELF_SUCCESS);
}
void show_version(char *prog_name)
{
printf("%s version %s (%s)\n", prog_name, VERSION, MALELFICUS_URL);
}
int main(int argc, char **argv)
{
...
if (argc == 1) {
_malelf_help();
return -1;
}
if (strncmp(argv[1], DISSECT, sizeof(DISSECT)) == 0) {
error = malelf_dissect_init(&dissect, argc, argv);
malelf_dissect_finish(&dissect);
} else if (strncmp(argv[1],
SHELLCODE,
sizeof (SHELLCODE)) == 0) {
malelf_shellcode_init(argc, argv);
malelf_shellcode_finish();
} else if (strncmp(argv[1], INFECT, sizeof(INFECT)) == 0) {
malelf_infect_init(&infect, argc, argv);
malelf_infect_finish(&infect);
} else if (strncmp(argv[1], DYNAMIC_ANALYSIS, sizeof(DYNAMIC_ANALYSIS)) == 0) {
malelf_dynanalyse_init(argc, argv);
malelf_dynanalyse_finish();
} else if (strncmp(argv[1], DISAS, sizeof(DISAS)) == 0) {
disas_init(&disas, argc, argv);
disas_finish(&disas);
} else if (strncmp(argv[1], DATABASE, sizeof(DATABASE)) == 0) {
database_init(&database, argc, argv);
database_finish(&database);
} else if (strncmp(argv[1], ANALYSE, sizeof(ANALYSE)) == 0) {
error = analyse_init(&analyse, argc, argv);
analyse_finish(&analyse);
} else if (strncmp(argv[1], "-v", 2) ||
strncmp(argv[1], "--version", 9)) {
show_version(*argv);
} else {
_malelf_help();
}
if (MALELF_SUCCESS != error) {
if (MALELF_ERROR != error) {
MALELF_PERROR(error);
}
return error;
}
return 0;
}
If you need the complete source code, please add the WeChat number (c17865354792)
分析系统自带的 ELF 文件
解析/bin/bash的ELF头文件:
bash
$ malelf dissect -h
This command display information about the ELF binary.
Usage: malelf dissect <options>
-h, --help Dissect Help
-i, --input Binary File
-e, --ehdr Display ELF Header
-s, --shdr Display Section Header Table
-p, --phdr Display Program Header Table
-S, --stable Display Symbol Table
-f, --format Output Format (XML or Stdout). Default is Stdout.
-o, --output Output File.
Example: malelf dissect -i /bin/ls -f xml -o /tmp/binary.xml
$ malelf dissect -i /bin/bash --ehdr
+-----------------------------------------------------------------------------+
| ELF Header |
+------------------------+------------------------------+---------------------+
| Structure Member | Description | Value |
+------------------------+------------------------------+---------------------+
| e_type | Object Type | Executable file |
| e_version | Version | 1 |
| e_entry | Entry Point | 0x08064678 |
| e_phoff | PHT Offset | 0x00000034 |
| e_shoff | SHT Offset | 0x000e5864 |
| e_ehsize | ELF Header Size | 52 |
| e_phentsize | Size of PHT entries | 32 |
| e_phnum | Number of PHT entries | 9 |
| e_shentsize | Size of one entry in SHT | 40 |
| e_shnum | Number of sections | 28 |
| e_shstrndx | SHT symbol index | 27 |
+------------------------+------------------------------+---------------------+
要将XML格式的程序头表报告保存到文件bash_phdr.xml中,请使用以下命令:
bash
$ malelf dissect -i /bin/bash --phdr -f xml -o ./bash_phdr.xml
infect 这个功能并非魔法,任何对ELF(可执行和链接格式)内部机制有基本了解的人都能自己实现。虽然有许多技术可以用来感染ELF二进制文件,但malelficus目前只实现了一种,即silvio cesare文本填充。
使用这种技术感染ELF二进制文件并生成恶意ELF文件非常简单,但首先我们需要恶意软件载荷(或外壳代码)。由于法律问题,我们无法共享我们的Unix恶意软件样本数据集,但该项目中有一个包含基本汇编文件的示例目录,
bash
nasm -f bin message32.asm -o message32.bin
目前,你可以使用以下命令来感染你的/bin/ls:
bash
$ malelf infect -h
This command is used to assist in the process of binary infection.
Usage: malelf infect <options>
-h, --help Infect Help
-i, --input Input host file
-o, --output Output infected file
-m, --malware FLAT binary malware.
-f, --offset-return Offset in shellcode to patch the host entrypoint
-a, --auto-shellcode Automatically patch shellcode with host entrypoint
-t, --technique Technique to infect.
-l, --list List techniques.
Example: malelf infect -i /bin/ls -o myls -m evil.bin -t 'silvio-text-padding'
$ malelf infect -i /bin/ls -o ./myls -m ./message32.bin -t silvio-text-padding -a
[+] Infecting by silvio cesare technique (text-padding)
[+] binary input: '/bin/ls', size: 112700 bytes
[+] binary output: './myls'
[+] malware payload: './message32.bin', size: 48 bytes
[+] Payload shellcode automatically created, magic bytes at '0x0031'
[+] Successfully infected.
成功感染 =) 要测试,请运行本地已感染的ls:
bash
$ ./myls
OWNED BY I4K
backdoor.asm backdoor.bin daniel-ls message32.asm message32.bin message64.asm
myls syscall.inc.asm util.inc.asm
要获取所有可用的感染技术,请使用:
bash
$ malelf infect -l
List of infect techniques supported:
0 - silvio-text-padding
Shellcode 生成功能
生成一段简单的 Shellcode(如执行/bin/sh)
bash
# 生成C字符串格式的Shellcode
./malelf shellcode -f c -o shellcode.c
生成的shellcode.c内容示例:
c
// Generated Shellcode (x86_64)
unsigned char shellcode[] =
"\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\xb8\x2f\x62"
"\x69\x6e\x2f\x73\x68\x00\x50\x48\x89\xe7\x48\x31\xc0\xb0\x3b\x0f"
"\x05";
unsigned int shellcode_len = 28;
将二进制 Shellcode 转换为 C 语言可嵌入的字符串格式;
动态分析功能(监控程序行为)
监控测试程序的系统调用
bash
# 动态分析test_crash程序(即使崩溃,也会先监控系统调用)
./malelf dynanalyse ./test_crash
还有一些这里就不举例了。
四、相关领域知识点:这些技术支撑了工具的实现
要理解这类工具,需要了解几个关键领域的知识:
- ELF文件格式:这是基础,必须知道文件头、程序头、节区头的结构和作用,以及程序如何被加载到内存。
- 系统调用 :Linux程序通过系统调用与内核交互(比如读写文件、创建进程),了解常见系统调用的功能(如
execve执行程序、connect联网)是动态分析的关键。 - 调试技术 :
ptrace系统调用是动态监控的核心,需要知道如何用它跟踪进程、拦截系统调用、读写寄存器和内存。 - 二进制操作 :包括文件读写、内存映射(
mmap)、字节序转换等,用于解析ELF文件和生成Shellcode。 - 信号处理:Linux用信号通知进程事件(如崩溃、中断),捕获和解析信号是崩溃处理的基础。
总结
这类工具本质上是ELF文件格式、系统底层技术、安全分析思路的结合体。通过静态解析找结构异常,通过动态跟踪看行为可疑,通过生成和植入Shellcode模拟攻击,最终帮助安全人员理解恶意软件的工作方式。对于学习网络安全的人来说,理解它的原理不仅能掌握分析工具的用法,更能深入了解Linux系统的运行机制和恶意软件的攻击思路------毕竟,知己知彼才能更好地防御。
Welcome to follow WeChat official account【程序猿编码】