【问题标题】:Dynamically executed ARM ASM syscall with relocations from C++使用 C++ 重定位动态执行的 ARM ASM 系统调用
【发布时间】:2019-07-05 14:25:08
【问题描述】:

我想从 C++ 调用 ARM/ARM64 ASM 代码。 ASM 代码包含系统调用和对外部函数的重定位。 这里的ARM架构不是那么重要,我只是想从概念上了解如何解决我的问题。

我有以下在共享库中调用的 ASM 系统调用(来自objdump -d 的输出):

 198:   d28009e8    mov x8, #0x4f                   // #79
 19c:   d4000001    svc #0x0
 1a0:   b140041f    cmn x0, #0x1, lsl #12
 1a4:   da809400    cneg    x0, x0, hi
 1a8:   54000008    b.hi    0 <__set_errno_internal>
 1ac:   d65f03c0    ret

这段代码调用fstatat64 syscall 并通过外部__set_errno_internal 函数设置errnoreadelf -r 显示 __set_errno_internal 函数的以下重定位:

00000000000001a8 R_AARCH64_CONDBR19  __set_errno_internal

我想从 C++ 中调用这段代码,所以我将它转换为缓冲区:

  unsigned char machine_code[] __attribute__((section(".text"))) =
        "\xe8\x09\x80\xd2"
        "\x01\x00\x00\xd4"
        "\x1f\x04\x40\xb1"
        "\x00\x94\x80\xda"
        "\x08\x00\x00\x54"   // Here we have mentioned relocation
        "\xc0\x03\x5f\xd6";

编辑:重要细节 - 我选择使用缓冲区(不是内联汇编等),因为我想在此缓冲区上运行额外的处理(例如字符串文字上的解密函数作为软件保护机制,但这在这里并不重要)被评估为机器码。

之后,可以将缓冲区强制转换为函数并直接调用以执行机器代码。显然搬迁有问题,它不是自动修复的,我必须手动修复它。但是在运行时我不能这样做,因为.text 部分是只读和可执行的。

虽然我几乎可以完全控制源代码,但我不能关闭堆栈保护和其他功能以使该部分可写(不要问为什么)。因此,似乎应该在链接阶段以某种方式执行重定位修复。据我所知,共享库在链接器修复重定位后包含相对偏移量(用于类似的外部函数调用),二进制 *.so 文件应包含正确的偏移量(无需运行时重定位工作),因此修复 machine_code链接期间的缓冲区应该是可能的。

我正在使用手动构建的 Clang 7 编译器,并且我可以完全控制 LLVM 通行证,所以我认为也许可以编写某种在链接时执行的 LLVM 通行证。虽然看起来 ld 最终被调用了,所以 LLVM 通行证在这里可能无济于事(这里不是专家)。

不同的想法也将不胜感激。 如您所见,问题非常复杂。也许你有一些方向/想法如何解决这个问题?谢谢!

【问题讨论】:

  • 为什么不能在函数返回后在 C 端完成 __set_errno_internal?或者,您可以将函数地址作为参数传递。
  • 好的,我得考虑一下。目前我有很多用 ASM 编写的系统调用包装器,所以我认为如果不需要对它们进行任何更改,那就太好了。但是我必须先测试一下,看看它是否有效。
  • 为什么要把它写成缓冲区而不使用内联汇编?而不是采用操作码(十六进制数字),而是采用汇编器文本并将其转换为内联汇编器宏。您的正常构建过程将按照正常方式链接它。请参阅:GCC pre-process as assembler
  • @Jester 根据您的想法,我通过删除 cmncnegb.hi 指令来删除重定位。有什么好处 - 这是对 ASM 系统调用包装器的一个非常简单的修改。这种方式通常返回 0(或在其他返回句柄的系统调用的情况下为正值)或负 errno 值(在错误的情况下)返回。之后,如果从汇编代码返回负值,我可以围绕它编写一个 C++ 包装器来手动设置errno。谢谢!
  • 您可以对任何代码运行加密。只需使用链接器或属性并放入输入部分(.text.encrypt)。您可以在本节的开头/结尾定义变量并运行在线/离线加密/解密。来自storing CRC in elf 的一些想法可以用于加密。不过,我看不到您将在哪里获得可以阻止攻击者的解密密钥;但它相当于数组,对重定位没有任何限制。

标签: c++ assembly linker arm llvm


【解决方案1】:

已经有一个有效的、打包的机制来处理重定位。它被称为dlsym()。虽然它没有直接为您提供函数指针,但所有主要的 C++ 编译器都支持 reinterpret_castdlsym 的结果指向任何普通函数指针。 (会员功能完全是另一个问题,但这里不相关)

【讨论】:

  • 这是个好主意,虽然我使用-fvisibility=hidden,所以默认情况下它不会工作。我可能会做一个例外并使该功能可见。
  • 另一个问题是我提到的:“很明显,重定位有问题,它不是自动修复的,我必须手动修复它。但是在运行时我不能这样做,因为.text 部分是只读和可执行的。”。我假设您打算在运行时和补丁重定位期间调用dlsym()
  • @jozols:显然您在运行时调用了dlsym,但不需要手动重定位处理。这已经由 ELF 加载程序(操作系统)处理。 dlsym 返回指向重定位函数或蹦床的指针。
  • 是的,但我必须将其作为我的程序集/机器代码的一部分来调用 ("\x08\x00\x00\x54" // Here we have mentioned relocation)。
  • @jozols:我们似乎没有交流。那是尝试进行手动重定位,我建议不要这样做。 b.hi 条件分支是到__set_errno_internal相对跳转。由于您既不知道__set_errno_internal 的重定位位置,也不知道分支指令本身的位置,因此甚至无法保证它们之间的差异适合相关分支允许的 24 位 (!)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-04-29
  • 2014-05-02
  • 2016-09-18
  • 1970-01-01
  • 2020-05-20
  • 2015-03-28
  • 1970-01-01
相关资源
最近更新 更多