rodata
段的妙用
本项目不依赖任何第三方库,实现本地读取可执行程序的二进制文件的rodata
段中的数据,并根据特定的偏移量获取实际的字符串,适用于C/S
架构模型中控制端获取服务端状态,服务端以字符串形式返回状态时,可只用发送字符串起始地址,控制端在二进制文件中直接根据地址偏移找到字符串,避免明文发送字符串导致泄露信息或者字符串过长引起发送负载过大的问题
项目地址: https://gitcode.com/m0_52370851/read__rodata.git
注意:使用项目函数load_elf_file
结束之后一定要调用free_elf_resource
,否则会造成内存泄露
1.ELF文件格式
想要实现读取rodata
段的数据,必须先了解对应的文件的格式,即ELF文件格式。
详细内容见:ELF文件格式的详解_.elf-优快云博客
2.读取ELF文件代码
了解完对应二进制结构之后,来看看对应在项目代码中怎么实现的
下载项目对应代码,解析ELF文件头的代码在myelf.c
中,具体对应如下代码,功能入口在load_elf_file
函数中的parser_elf_file_enrty
函数
其中read_elf_ident_header
函数对读取的文件数据做了简单校验,并将数据交给外层函数使用。
int open_elf_file(const char *filename) //打开对应的ELF文件,获取fd
{
if (NULL == filename)
{
printf("error: filename is NULL\n");
return FAILE;
}
int fd = open(filename, O_RDONLY);
if (fd < 0)
{
printf("open file : %s error\n", filename);
perror("open()");
return FAILE;
}
return fd;
}
int read_elf_ident_header(int fd, elf_ident_t *ident) //读取前面16个字节,并校验部分字段
{
read_bytes_from_file(fd, ident, sizeof(elf_ident_t));
if (ident->elfMagic[0] != 0x7f ||
(ident->elfMagic[1] != 'E') ||
(ident->elfMagic[2] != 'L') ||
(ident->elfMagic[3] != 'F'))
{
printf("wrong file type\n");
return FAILE;
}
return SUCCESS;
}
在parser_elf_file_enrty
中对读取的ident_heade
的内容中的字节序和系统架构进行了判断,随后调用对应系统架构的解析函数,需要注意的是大小端转换的问题,如果不注意进行大小端转换,会导致后续读取ELF文件头中的偏移量和数据量大小的时候读取出一些异常值,从而导致解析失败。
以64位系统解析为例:
rodata_info64_t *parser_elf_class64_ehdr(int fd, Elf64_Ehdr *elf64_ehdr, int change_order_flag)
{
Elf64_Shdr elf64_section_header = {0};
lseek_file_offset(fd, -16, SEEK_CUR);//注意开始读取了16个字节,这里需要将文件指针偏移回去,才能一次完整的读取Elf64_Ehdr头部
read_bytes_from_file(fd, elf64_ehdr, sizeof(Elf64_Ehdr));
if (change_order_flag)
{
elf64_ehdr->e_shoff = swap64(elf64_ehdr->e_shoff);
elf64_ehdr->e_shentsize = swap16(elf64_ehdr->e_shentsize);
elf64_ehdr->e_shnum = swap16(elf64_ehdr->e_shnum);
elf64_ehdr->e_shstrndx = swap16(elf64_ehdr->e_shstrndx);
}
lseek_file_offset(fd, elf64_ehdr->e_shoff + elf64_ehdr->e_shstrndx * elf64_ehdr->e_shentsize, SEEK_SET);
read_bytes_from_file(fd, &elf64_section_header, sizeof(Elf64_Shdr));
if (change_order_flag)
{
elf64_section_header.sh_size = swap64(elf64_section_header.sh_size);
elf64_section_header.sh_offset = swap64(elf64_section_header.sh_offset);
}
// shstrtab_need_free是所有段的名称的表
//所有的段的名称都用一个index表示,这样方便统一头部的大小,因此需要读取每一个段的头部,找到对应的index
char *shstrtab_need_free = parser_elf_class64_shdr(fd, &elf64_section_header);
// 读取每一段,比对名称,找到.rodata段
lseek_file_offset(fd, elf64_ehdr->e_shoff, SEEK_SET);
Elf64_Shdr *shdr = malloc_warpper(elf64_ehdr->e_shnum * elf64_ehdr->e_shentsize);
read_bytes_from_file(fd, shdr, elf64_ehdr->e_shnum * elf64_ehdr->e_shentsize);
// 找到 .rodata 节
Elf64_Shdr *rodata_shdr = NULL;
for (int i = 0; i < elf64_ehdr->e_shnum; ++i)
{
if (shdr[i].sh_name < elf64_section_header.sh_size && strncmp(&shstrtab_need_free[shdr[i].sh_name], ".rodata", 8) == 0)
{
rodata_shdr = &shdr[i];
break;
}
}
if (!rodata_shdr)
{
printf(".rodata section not found\n");
free(shstrtab_need_free);
free(shdr);
close(fd);
return NULL;
}
// 读取 .rodata 节的内容
if (change_order_flag)
{
rodata_shdr->sh_size = swap64(rodata_shdr->sh_size);
rodata_shdr->sh_offset = swap64(rodata_shdr->sh_offset);
rodata_shdr->sh_addr = swap64(rodata_shdr->sh_addr);
}
char *rodata = malloc_warpper(rodata_shdr->sh_size + 1);
lseek_file_offset(fd, rodata_shdr->sh_offset, SEEK_SET);
read_bytes_from_file(fd, rodata, rodata_shdr->sh_size);
rodata[rodata_shdr->sh_size] = '\0'; // 确保字符串以NULL结尾
rodata_info64_t *rodata_info_p = (rodata_info64_t *)malloc_warpper(sizeof(rodata_info64_t));
rodata_info_p->buf = rodata;
rodata_info_p->size = rodata_shdr->sh_size;
rodata_info_p->offset = rodata_shdr->sh_offset;
rodata_info_p->addr = rodata_shdr->sh_addr;
free(shstrtab_need_free);
free(shdr);
close(fd);
return rodata_info_p;
}
3.rodata
段的使用
通过前面的函数,能够定位并找到rodata
段,其中返回的指针的buf
对应的就是rodata
段中的数据,后续只需要拿到字符串在rodata
段中的起始地址,就可以进行偏移得到对应数据。
3.1 已知rodatat
地址转换
比如buf2str_addr64
函数,只需要接收一个对应的地址,就能将地址值通过强转计算得到对应的偏移,随后将对应位置的buf
数据进行返回,其中的str_max_len
参数用来控制字符串最大长度,如果设置为0,返回默认遇到\0
就结束的字符串。
使用实例见test2.c
文件
char *buf2str_addr64(rodata_info_t *rodata, uint64_t addr, uint32_t str_max_len)
{
if (64 == rodata->class_type)
{
if ((uint64_t)addr < (uint64_t)(rodata->rodata_64->addr) ||
((uint64_t)addr > ((uint64_t)(rodata->rodata_64->addr) + rodata->rodata_64->size)))
{
printf("error offset\n");
return NULL;
}
else
{
if (0 == str_max_len)
{
return &(rodata->rodata_64->buf[(uint64_t)addr - (uint64_t)(rodata->rodata_64->addr)]);
}
else
{
rodata->rodata_64->buf[(uint64_t)addr - (uint64_t)(rodata->rodata_64->addr) + str_max_len] = '\0';
return &(rodata->rodata_64->buf[(uint64_t)addr - (uint64_t)(rodata->rodata_64->addr)]);
}
}
}
else
{
printf("error rodata info use,no class64 system\n");
return NULL;
}
}
3.2 内存地址转换
如果不知道在rodata
中的地址,只知道运行时刻内存中的地址,用在C/S
网络架构中,C
端向S
端发送数据,这样只需要发送字符串地址,就能进行后续字符串解析了。
原理在于服务器端会将定位字符串进行在rodata
端中搜索,并接收客户机发来的定位字符串在内存中的地址,一旦搜索成功,就会将后续收到的内存地址与定位字符串内存地址做偏移,得到实际的字符串
使用实例见test1.c
char *location_string_relocatio_offset(rodata_info_t *rodata, const char *pattern)
{
const char *buf = NULL;
size_t len = 0;
if (64 == rodata->class_type)
{
buf = rodata->rodata_64->buf;
len = rodata->rodata_64->size;
}
else
{
buf = rodata->rodata_32->buf;
len = rodata->rodata_32->size;
}
const char *p = buf;
const char *end = buf + len;
size_t pattern_len = strlen(pattern);
while (p <= end - pattern_len)
{
if (strncmp(p, pattern, pattern_len) == 0)
{
if (p + pattern_len == end || strncmp(p + pattern_len, pattern, 1) != 0)
{
return p;
}
}
p++;
}
return NULL;
}
//test1.c
const char *test_string_0 = "test_string_0";
const char *location_string = "location_string";
const char *test_string_1 = "test_string_1";
/*
演示通过定位字符串重定位功能,用在常量字符串在内存地址与在rodata段地址不一致的场景
*/
int main(int argc, char **argv)
{
if (argc != 2)
{
printf("Usage: ./test1 test1 \n");
return 0;
}
rodata_info_t *info_p = load_elf_file(argv[1]);
if (NULL != info_p)
{
if (64 == info_p->class_type)
{
char *ptr = location_string_relocatio_offset(info_p, "location_string");
if (NULL != ptr)
{
//通过定位字符串做内存偏移,直接从rodata段找
printf("get string frome rodata:%s\n", ptr + (int64_t)(test_string_0) - (int64_t)(location_string));
printf("get string frome rodata:%s\n", ptr + (int64_t)(test_string_1) - (int64_t)(location_string));
}
}
}
else
{
printf("error when load_elf_file\n");
}
free_elf_resource(info_p);
return 0;
}