You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

借助LD_PRELOAD实现ELF程序跳转至共享库指定指令的相关问题

解答:ARM32下LD_PRELOAD实现指令替换跳转 + 相关问题解析

首先直接回应你的核心问题:LD_PRELOAD本身不能直接完成指令替换,但可以结合动态内存修改的方式间接达成目标。

LD_PRELOAD的核心能力是预加载共享库并覆盖符号,但你要的是把test程序里的某条4字节ARM指令,直接替换成分支指令跳转到f.so的指定指令(而非发起函数调用)。要实现这个,你可以在f.so里写一个构造函数(用__attribute__((constructor))标记),这个函数会在test主程序执行前被自动调用。在构造函数里,你需要做这几件事:

  • 找到test程序中目标指令的内存地址:如果test是非PIE可执行文件,可以直接用符号表或硬编码偏移;如果是PIE,需要先获取程序的加载基址,再加上指令的相对偏移。
  • 修改目标地址所在内存页的权限:用mprotect()系统调用把页权限改成可写+可执行(默认代码段是只读的)。
  • 替换指令:把目标位置的原指令改成ARM分支指令(B或BL),注意ARM分支指令用的是相对地址编码,需要按规则计算偏移量。

接下来解答你的两个疑问:

1. test与f.so的内存加载地址为何存在差异?

这主要是**ASLR(地址空间布局随机化)**的作用——现代操作系统默认开启这个安全机制,每次程序启动时,会随机分配主程序(test)和共享库(f.so)的加载地址,防止攻击者通过固定地址发起缓冲区溢出等攻击。

除此之外:

  • 如果test是PIE(位置无关可执行文件),它本身被编译成可以加载到任意地址的代码,配合ASLR,每次启动的基址都不一样;
  • f.so作为共享库,本身就是PIC(位置无关代码),必须支持加载到任意地址,所以它的加载地址必然是随机的(除非手动关闭ASLR)。

即使关闭ASLR,test和f.so的加载地址也不会相同:test的基址由其ELF文件的程序头表决定,而f.so的基址是链接时指定的默认值(可以用readelf -l f.so查看),两者本身就不会重叠。

2. 如何准确计算分支指令的目标地址,以静态patch test二进制文件实现跳转?

这里要先明确一个关键限制:静态patch无法直接实现跳转到f.so的指定指令,因为f.so的加载地址是动态随机的(ASLR),静态时你根本无法预知它会被加载到哪里。

如果一定要尝试静态patch,只能在关闭ASLR的前提下操作,步骤如下:

  1. 确定f.so的加载基址:用readelf -l f.so查看LOAD段的VirtAddr,这就是共享库关闭ASLR时的默认加载基址;
  2. 计算f.so中指定指令的绝对地址:基址 + 该指令在f.so中的偏移(可以用objdump -d f.so找到指令的偏移值);
  3. 计算ARM分支指令的相对偏移:ARM的B/BL指令偏移量是「目标地址 - (当前指令地址 + 4)」(因为ARM指令是4字节,当前指令执行完后PC会指向当前地址+4);
  4. 编码分支指令:ARM32的B指令格式是0b1010xxxxxxxxxxxxxxxxxxxxxxxx,其中的x部分是偏移量右移2位后的补码(因为偏移量是以4字节为单位的);
  5. 替换指令:用二进制编辑器把test中目标位置的4字节指令替换成你计算好的分支指令。

但这种方法局限性极大:一旦开启ASLR,f.so的加载地址变化,静态patch的分支指令就会跳错地址。更可靠的方案还是前面说的动态修改方式——在f.so的构造函数里运行时计算目标地址并修改指令。


内容的提问来源于stack exchange,提问作者husin alhaj ahmade

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 18:50:29