妙用rodata

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

工大小汪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值