繁体   English   中英

可执行文件中全局const变量的偏移量

[英]Offset of global const variable in executable

希望有一个可执行文件通过修改自己的全局常量来保存其状态。 只是为了拥有一个完全独立的可执行文件。

我想到了一些解决方案/技巧:

  1. 使用libelf并使程序自行解析以查找偏移量。
  2. 添加一个特定的标记,然后在可执行文件中搜索它。 我想这甚至可能是跨平台的?
  3. 使用对象转储实用程序确定可执行文件中的地址。 这可能需要始终作为项目构建的后期处理来完成。

链接器提供此信息会很方便。

链接器是否可以在可执行文件中提供只读节的偏移量?

谢谢

您想要做的是棘手且难以携带的。

但是,您可以研究src / unexelf.c中的 GNU emacs unexec函数(对于Linux;其他OS具有类似的文件)。

您也可以玩链接器技巧,例如使用自己的ld脚本。

请注意,这些技巧可能是特定于处理器,编译器,内核和libc版本的。

也许您想要应用程序检查点 特别地, BLCR对您可能有用。

您实质上是在谈论二进制重写。 实现此目标而又不影响编译过程的一种方法是将虚拟地址映射到物理地址,然后对其进行修补。 有趣的是,这是我在硕士论文中介绍的内容 以下图像和文本是从该文档中提取的:

http://localhostr.com/file/hyB1iFuiL0nV/Loading_Binary.jpg

请注意,我的原始项目背后的概念是在假定无法修改编译过程的情况下,用其他二进制代码重写代码。 如果您的要求和假设不同,那么这可能不是最简单,最好的方法。

这里最重要的想法是,磁盘表示中的一部分映射到内存时会保留(而不是拆分)。 这意味着在磁盘表示形式的部分中具有一定偏移量的数据在加载到内存后将偏移相同的数量。

libelf ,类似于libbfd ,可执行文件包含一组节,代码和数据都可以驻留在其中。 当操作系统将可执行文件加载到内存中时,每个部分都基于某个基址。 我们可以将其反转以将虚拟内存地址映射到物理文件偏移。 如果可以找到物理文件偏移,则可以将字节修补为常规文件。

  • 首先,使用libelf解析可执行文件的节头。 这使我们能够获得一组节,最重要的是,对于libelf每个节,它可以告诉我们三件事:
    1. 区段大小区段的大小。
    2. 节基地址当磁盘上的可执行文件加载到内存中时,节将基于的地址。
    3. 节磁盘偏移量节的磁盘偏移量。
  • 通过遍历上一步中提取的段信息,可以找出任意虚拟内存地址包含在哪个段中。在修补过程中,我们感兴趣的内存地址是绕行代码所在的地址被写。 虚拟内存地址到段中的偏移量可以通过(virtual_memory_address - section_base_address)计算。
  • 因此,可以通过(section_disk_offset + (virtual_memory_address - section_base_address))计算虚拟内存地址的磁盘偏移量。

此过程允许将任意虚拟内存地址映射到其相应的磁盘文件偏移量。 然后可以使用常规的C文件IO功能(例如fopen / fseek / fwrite / fclose修补此偏移量。

这是我使用以上步骤将虚拟地址映射到物理文件偏移量的代码:

/*
 * Returns the corresponding 32 bit executable file offset of a virtual memory
 * address.
 */
uint32_t vaddr32_to_file_offset(char * filepath, uint32_t vaddr)
{
    int      fd     = open(filepath, O_RDONLY);
    Elf *    e      = elf_begin(fd, ELF_C_READ, NULL);
    uint32_t offset = 0;

    Elf_Scn * scn = NULL;
    while((scn = elf_nextscn(e, scn)) != NULL) {
        Elf32_Shdr * shdr = elf32_getshdr(scn);
        if(vaddr >= shdr->sh_addr &&
                (vaddr <= (shdr->sh_addr + shdr->sh_size))) {
            offset = shdr->sh_offset + (vaddr - shdr->sh_addr);
            break;
        }
    }

    elf_end(e);
    close(fd);
    return offset;
}

/*
 * Returns the corresponding 64 bit executable file offset of a virtual memory
 * address.
 */
uint64_t vaddr64_to_file_offset(char * filepath, uint64_t vaddr)
{
    int      fd     = open(filepath, O_RDONLY);
    Elf *    e      = elf_begin(fd, ELF_C_READ, NULL);
    uint64_t offset = 0;

    Elf_Scn * scn = NULL;
    while((scn = elf_nextscn(e, scn)) != NULL) {
        Elf64_Shdr * shdr = elf64_getshdr(scn);
        if(vaddr >= shdr->sh_addr &&
                (vaddr <= (shdr->sh_addr + shdr->sh_size))) {
            offset = shdr->sh_offset + (vaddr - shdr->sh_addr);
            break;
        }
    }

    elf_end(e);
    close(fd);
    return offset;
}

这是在给定偏移量的情况下修补ELF可执行文件的代码:

/*
 * Sets the bytes at an arbitrary offset of a file to the contents of buffer.
 */
static bool patch_file(char * filepath, uint64_t offset, void * buffer,
        size_t size)
{
    FILE * pFile = fopen(filepath, "r+");

    if(pFile == NULL) {
        return FALSE;
    }

    fseek(pFile, offset, SEEK_SET);
    fwrite(buffer, 1, size, pFile);
    fclose(pFile);
    return TRUE;
}

更详细的信息可以在报告本身中找到,该报告可在此处公开获得。

这是不可能的,因为如果可能的话,编译器通常会替换全局和静态常量作为机器代码中的立即数值。 例如:

const int x=3;

int main()
{
 return x;
}

为main()(OSX / gcc -O3)生成以下代码:

_main:
Leh_func_begin1:
    pushq   %rbp
Ltmp0:
    movq    %rsp, %rbp
Ltmp1:
    movl    $3, %eax // <= immediate constant!
    popq    %rbp
    ret

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM