简体   繁体   English

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

[英]Offset of global const variable in executable

Would like to have an executable save its state by modifying its own global constants. 希望有一个可执行文件通过修改自己的全局常量来保存其状态。 Just for the kicks of having a totally self-contained executable. 只是为了拥有一个完全独立的可执行文件。

A few solutions/hacks that come to mind: 我想到了一些解决方案/技巧:

  1. Use libelf and have the program parse itself to find the offset. 使用libelf并使程序自行解析以查找偏移量。
  2. Add a specific marker and just search for it in the executable file. 添加一个特定的标记,然后在可执行文件中搜索它。 I guess this might even be somewhat cross-platform? 我想这甚至可能是跨平台的?
  3. Use object dumping utils to determine the address in the executable file. 使用对象转储实用程序确定可执行文件中的地址。 This probably needs to be always done as a post-process to project build.. 这可能需要始终作为项目构建的后期处理来完成。

It would be neat to have the linker provide this info. 链接器提供此信息会很方便。

Is it possible to have the linker provide the offset of a read-only section in the executable file? 链接器是否可以在可执行文件中提供只读节的偏移量?

Thanks 谢谢

What you want to do is tricky and unportable. 您想要做的是棘手且难以携带的。

You could however study GNU emacs unexec function eg in src/unexelf.c (for Linux; other OSes have a similar file). 但是,您可以研究src / unexelf.c中的 GNU emacs unexec函数(对于Linux;其他OS具有类似的文件)。

You could also play linker tricks, eg with your own ld script. 您也可以玩链接器技巧,例如使用自己的ld脚本。

Beware that these tricks may be processor-, compiler-, kernel-, and libc- version specific. 请注意,这些技巧可能是特定于处理器,编译器,内核和libc版本的。

Perhaps you want application checkpointing . 也许您想要应用程序检查点 In particular, BLCR might be useful to you. 特别地, BLCR对您可能有用。

You are essentially talking about binary rewriting. 您实质上是在谈论二进制重写。 One method to achieve this without fiddling with the compile process is to map a virtual address to a physical one and then patch it. 实现此目标而又不影响编译过程的一种方法是将虚拟地址映射到物理地址,然后对其进行修补。 Interestingly, this is something I covered in my master's thesis . 有趣的是,这是我在硕士论文中介绍的内容 The following images and text are pulled from that document: 以下图像和文本是从该文档中提取的:

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

Note that the concept behind my original project was to rewrite code in other binaries assuming that the compile process could not be modified. 请注意,我的原始项目背后的概念是在假定无法修改编译过程的情况下,用其他二进制代码重写代码。 If your requirements and assumptions are different, this may well not be the easiest and best approach. 如果您的要求和假设不同,那么这可能不是最简单,最好的方法。

The most important idea here is that a section in the disk representation is preserved (not split) when it is mapped into memory. 这里最重要的想法是,磁盘表示中的一部分映射到内存时会保留(而不是拆分)。 This means data that is at a certain offset into the section in the disk representation will be offset by the same amount after loaded into memory. 这意味着在磁盘表示形式的部分中具有一定偏移量的数据在加载到内存后将偏移相同的数量。

In libelf , similarly to libbfd , an executable contains a set of sections in which both code and data can reside. libelf ,类似于libbfd ,可执行文件包含一组节,代码和数据都可以驻留在其中。 When the operating system loads the executable into memory, each section is based at some base address. 当操作系统将可执行文件加载到内存中时,每个部分都基于某个基址。 We can reverse this to map a virtual memory address to a physical file offset. 我们可以将其反转以将虚拟内存地址映射到物理文件偏移。 If a physical file offset can be found, the bytes can be patched as a regular file. 如果可以找到物理文件偏移,则可以将字节修补为常规文件。

  • First, the section header of the executable is parsed with libelf . 首先,使用libelf解析可执行文件的节头。 This allows us to obtain a set of sections and most importantly, for each section libelf can tell us three things: 这使我们能够获得一组节,最重要的是,对于libelf每个节,它可以告诉我们三件事:
    1. Section size The size of the section. 区段大小区段的大小。
    2. Section base address The address that the section will be based at when the executable on disk is loaded into memory. 节基地址当磁盘上的可执行文件加载到内存中时,节将基于的地址。
    3. Section disk offset The disk offset of the section. 节磁盘偏移量节的磁盘偏移量。
  • By iterating through the section information extracted in the previous step, it is possible to find out what section an arbitrary virtual memory address is contained in. During patching, the memory address we are interested in is the address of the code at which a detour is to be written. 通过遍历上一步中提取的段信息,可以找出任意虚拟内存地址包含在哪个段中。在修补过程中,我们感兴趣的内存地址是绕行代码所在的地址被写。 The offset of the virtual memory address into the section can be calculated by (virtual_memory_address - section_base_address) . 虚拟内存地址到段中的偏移量可以通过(virtual_memory_address - section_base_address)计算。
  • Therefore, the disk offset of the virtual memory address can be calculated by (section_disk_offset + (virtual_memory_address - section_base_address)) . 因此,可以通过(section_disk_offset + (virtual_memory_address - section_base_address))计算虚拟内存地址的磁盘偏移量。

This process allows an arbitrary virtual memory address to be mapped to its corresponding disk file offset. 此过程允许将任意虚拟内存地址映射到其相应的磁盘文件偏移量。 This offset can then be patched with regular C file IO functions such as fopen / fseek / fwrite / fclose . 然后可以使用常规的C文件IO功能(例如fopen / fseek / fwrite / fclose修补此偏移量。

This is my code for mapping a virtual address to a physical file offset using the above steps: 这是我使用以上步骤将虚拟地址映射到物理文件偏移量的代码:

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

This is the code to patch an ELF executable given an 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;
}

More detailed information can be found in the report itself which is publicly available here . 更详细的信息可以在报告本身中找到,该报告可在此处公开获得。

This won't be possible, because the compiler usually replaces global and static constants as immediates values in machine code if possible. 这是不可能的,因为如果可能的话,编译器通常会替换全局和静态常量作为机器代码中的立即数值。 For example: 例如:

const int x=3;

int main()
{
 return x;
}

produces this code for main() (OSX/gcc -O3): 为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