可执行文件中全局const变量的偏移量
希望有一个可执行文件通过修改自己的全局常量来保存其状态。 只是为了拥有一个完全独立的可执行文件。
想到的一些解决方案/黑客:
- 使用libelf并让程序解析自己以找到偏移量。
- 添加特定标记,然后在可执行文件中搜索它。 我想这甚至可能有点跨平台?
- 使用对象转储工具来确定可执行文件中的地址。 这可能需要始终作为项目构建的后期处理来完成。
让链接器提供此信息是很好的。
是否可以让链接器提供可执行文件中只读部分的偏移量?
谢谢
你本质上是在谈论二进制重写。 在不摆弄编译过程的情况下实现此目的的一种方法是将虚拟地址映射到物理地址然后对其进行修补。 有趣的是,这是我在硕士论文中所涉及的内容 。 从该文档中提取以下图像和文本:
请注意,我原始项目背后的概念是在其他二进制文件中重写代码,假设编译过程无法修改。 如果您的要求和假设不同,这可能不是最简单和最好的方法。
这里最重要的想法是磁盘表示中的一个部分在映射到内存时被保留(不分割)。 这意味着在加载到内存中之后,磁盘表示中某个部分偏移的数据将偏移相同的量。
在libelf
,与libbfd
类似,可执行文件包含一组代码和数据都可以驻留的部分。 当操作系统将可执行文件加载到内存中时,每个部分都基于某个基址。 我们可以将其反转以将虚拟内存地址映射到物理文件偏移量。 如果可以找到物理文件偏移量,则可以将字节修补为常规文件。
- 首先,使用
libelf
解析可执行文件的节头。 这允许我们获得一组部分,最重要的是,对于每个部分,libelf
可以告诉我们三件事:- 截面尺寸截面的尺寸。
- 节基本地址当磁盘上的可执行文件加载到内存时该节将基于的地址。
- 截面磁盘偏移节的磁盘偏移量。
- 通过迭代在前一步骤中提取的部分信息,可以找出包含任意虚拟存储器地址的部分。在修补期间,我们感兴趣的存储器地址是绕行的代码的地址。要写。 可以通过
(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; }
更详细的信息可以在报告中找到, 这里可以公开获得。
你想要做的是棘手和不可移植。
但是你可以在src / unexelf.c中学习GNU emacs unexec
函数(对于Linux;其他操作系统有类似的文件)。
您也可以使用自己的ld
脚本播放链接器技巧。
请注意,这些技巧可能是特定于处理器,编译器,内核和libc的版本。
也许你想要应用程序检查点 。 特别是, BLCR可能对您有用。
这是不可能的,因为如果可能的话,编译器通常将全局和静态常量替换为机器代码中的immediates值。 例如:
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