如何在已构建的ELF可执行文件中追加键值表并在运行时读取?
解决方案:在ELF可执行文件中追加键值表并运行时读取
一、核心结论
你的需求完全可以实现,核心思路是在构建后的ELF文件中添加自定义段存储秘密键值对,或直接追加数据到文件末尾,再通过程序自身逻辑读取对应内容,同时可以轻松处理段/数据不存在的情况。
二、具体实现方案
1. 部署时注入自定义ELF段(推荐)
使用objcopy工具即可给已编译好的ELF文件添加自定义段,无需修改原程序的构建流程:
- 先准备存储键值对的文本文件
secrets.txt:DB_PASSWORD=mysecretpass API_KEY=abc123xyz - 执行命令注入段:
参数说明:objcopy --add-section .secrets=secrets.txt --set-section-flags .secrets=noload,readonly your_executable--add-section:创建名为.secrets的段,内容来自secrets.txt--set-section-flags:设置段属性为noload(不自动加载到内存,需手动读取文件)或load(自动加载到内存,可直接通过地址访问),按需选择。
2. 程序运行时读取自定义段
方案A:直接访问内存中的段(适用于load属性)
如果注入时设置段为load,可以通过链接器生成的边界符号直接获取段的起始和结束地址:
#include <stdio.h> // 声明链接器自动生成的段边界符号 extern char __start__.secrets[]; extern char __stop__.secrets[]; int main() { // 判断段是否存在 if (__start__.secrets == __stop__.secrets) { printf("No secrets section found\n"); return 1; } // 读取并打印段内容 printf("Secrets:\n"); for (char *p = __start__.secrets; p < __stop__.secrets; p++) { putchar(*p); } putchar('\n'); // 自行实现键值对解析逻辑(如按行分割、字符串截取) return 0; }
编译时无需额外参数,链接器会自动处理这些符号。若段不存在,两个符号会指向同一地址,以此判断即可。
方案B:解析自身ELF文件读取段(适用于noload属性)
如果不想让秘密被加载到进程内存(更安全),可以让程序打开自身二进制文件,解析ELF结构找到目标段:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <elf.h> #include <string.h> int main() { int fd = open("/proc/self/exe", O_RDONLY); if (fd == -1) { perror("Failed to open self"); return 1; } Elf64_Ehdr ehdr; if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr)) { perror("Failed to read ELF header"); close(fd); return 1; } // 读取段表 Elf64_Shdr *shdr = malloc(ehdr.e_shentsize * ehdr.e_shnum); lseek(fd, ehdr.e_shoff, SEEK_SET); read(fd, shdr, ehdr.e_shentsize * ehdr.e_shnum); // 读取段名字符串表 char *strtab = malloc(shdr[ehdr.e_shstrndx].sh_size); lseek(fd, shdr[ehdr.e_shstrndx].sh_offset, SEEK_SET); read(fd, strtab, shdr[ehdr.e_shstrndx].sh_size); // 查找.secrets段 Elf64_Shdr *secret_shdr = NULL; for (int i = 0; i < ehdr.e_shnum; i++) { if (strcmp(strtab + shdr[i].sh_name, ".secrets") == 0) { secret_shdr = &shdr[i]; break; } } if (!secret_shdr) { printf("No secrets section found\n"); free(strtab); free(shdr); close(fd); return 1; } // 读取段内容 char *secrets = malloc(secret_shdr->sh_size + 1); lseek(fd, secret_shdr->sh_offset, SEEK_SET); read(fd, secrets, secret_shdr->sh_size); secrets[secret_shdr->sh_size] = '\0'; printf("Secrets:\n%s\n", secrets); // 清理资源 free(secrets); free(strtab); free(shdr); close(fd); return 0; }
注:以上是64位ELF代码,32位需替换Elf64_*为Elf32_*。
3. 更简便的替代:直接追加数据到文件末尾
如果不想修改ELF结构,可直接把键值对追加到文件末尾:
# 部署时追加秘密(用特殊分隔符标记起始,方便程序定位) echo -n "===SECRET_START===" >> your_executable echo "DB_PASSWORD=mysecretpass" >> your_executable echo "API_KEY=abc123xyz" >> your_executable
程序读取时,从文件末尾反向查找分隔符,读取后续内容:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <string.h> #define SECRET_MARK "===SECRET_START===" int main() { int fd = open("/proc/self/exe", O_RDONLY); if (fd == -1) { perror("Failed to open self"); return 1; } struct stat st; fstat(fd, &st); off_t file_size = st.st_size; size_t mark_len = strlen(SECRET_MARK); // 从文件末尾反向查找标记 char *buf = malloc(file_size); lseek(fd, 0, SEEK_SET); read(fd, buf, file_size); char *secret_start = strstr(buf, SECRET_MARK); if (!secret_start) { printf("No secret found\n"); free(buf); close(fd); return 1; } secret_start += mark_len; printf("Secrets:\n%s", secret_start); free(buf); close(fd); return 0; }
这种方法无需修改ELF结构,但要注意后续更新程序时需重新追加秘密。
三、安全提示
- 注入秘密时确保部署环境安全,避免秘密泄露
- 读取秘密后,建议立即用
memset清除内存中的敏感数据 noload段或文件末尾追加的方式,秘密不会被自动加载到进程内存,比load段更难被内存dump工具获取
内容的提问来源于stack exchange,提问作者Alex Flint
相关产品推荐
相关产品推荐

