Linux内核有一个alternative() macro,它允许开发人员为一系列代码指定多种实现,并在运行时选择使用特定的替代方案(对于内核alternative()宏,这是在启动过程的早期)。

是否有合理的方法来实现用户模式应用程序的相似功能?特别是,记录替代位置和运行时修补代码。

它对于半静态检测可能很有用:可以在运行时启用或禁用但仍仅“编译”到特定位置的检测。

最佳答案

如果您使用的是Linux的Intel icc编译器,则可以使用__notify_intrinsic功能来允许附加补丁。也就是说,它是一种允许您安全地挂钩现有函数并添加其他行为的方法-与alternative()有所不同,后者专注于在两个序列之间进行选择。
__notify_intrinsic确保在放置内在函数的代码中的位置处有探针准备就绪的指令序列。这使您可以安全地在该点注入(inject)“探针”(实际上可以是任意代码),即使代码正在执行。

从原则上讲,这可能会产生零开销:探测就绪序列只是6个指令字节的序列1,您可以安全地将其重定位到其他地方:这主要意味着6个字节在指令边界上均匀对齐,没有位置相关的代码2,并且没有任何跳入范围。

这个想法是,您可以使用准备就绪的探针序列在运行时修改行为,方法是将字节复制到其他位置,再复制新代码,然后使用jmp指令将探针序列修补到离线“探针”中(但实际上可以是任何东西)。

实际上,icc版本13至17似乎只是插入6字节的NOP,而不是实际尝试使用现有指令。作为example,以下C代码:

int add(int a, int b) {
  if (a < b) {
    __notify_intrinsic("a lt b", 0);
    a *= b;
    return a * b;
  } else {
    __notify_intrinsic("a gt b", 0);
    return a << b;
  }
}

生成该程序集:
add:
        cmp       edi, esi                                      #2.11
        jge       ..B1.4        # Prob 50%                      #2.11
        xor       edx, edx                                      #3.5
        .byte     102                                           #
        .byte     15                                            #
        .byte     31                                            #
        .byte     68                                            #
        .byte     0                                             #
        .byte     0                                             #
        imul      edi, esi                                      #4.5
        imul      esi, edi                                      #5.16
        mov       eax, esi                                      #5.16
        ret                                                     #5.16
..B1.4:                         # Preds ..B1.1
        xor       edx, edx                                      #7.5
        .byte     102                                           #
        .byte     15                                            #
        .byte     31                                            #
        .byte     68                                            #
        .byte     0                                             #
        .byte     0                                             #
        mov       ecx, esi                                      #8.17
        shl       edi, cl                                       #8.17
        mov       eax, edi                                      #8.17
        ret

这一系列的6个.byte指令构成了一个六字节长的nop指令。

编译器在ELF二进制文件的特殊__notify_intrinsic节中记录每个.itt_not_tab的位置和其他信息,例如注释的值,您可以在运行时检查它们。

您可以找到有关此方法in the documentation的更多详细信息,但不幸的是,目前看来,它仅限于英特尔专有的icc编译器。

1在32位x86上为5字节。

2例如,相对跳转或rip相对寻址。

09-15 18:54