9

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

想到的一些解决方案/黑客:

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

让链接器提供此信息会很好。

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

谢谢

4

3 回答 3

6

您想要做的事情是棘手且不可移植的。

但是,您可以在src/unexelf.c中学习 GNU emacsunexec函数(对于 Linux;其他操作系统也有类似的文件)。

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

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

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

于 2012-09-23T10:55:21.953 回答
5

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

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;
}

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

于 2012-09-23T11:37:49.943 回答
4

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

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
于 2012-09-23T11:21:28.547 回答