内存间接调用和寄存器间接调用之间的区别

时间:2012-01-21 17:53:30

标签: c linux assembly

内存间接调用和寄存器间接调用有什么区别?

我正在尝试学习有关linux rootkit检测的一些知识,如何在反汇编内存中识别出这样的调用?在编译之前,他们如何看待C语言?

2 个答案:

答案 0 :(得分:5)

间接分支是一个分支,其分支是存储在寄存器或存储器位置的地址。分支指令的操作数是寄存器或存储分支地址的存储单元。

有关更多信息,请参阅维基百科页面: http://en.wikipedia.org/wiki/Indirect_branch

在C中,取决于实现(和CPU),通常在通过函数指针调用函数时进行间接分支。正如switch语句的一些启发式方法使用函数指针(通过跳转表),也可以在switch语句中找到间接分支。

答案 1 :(得分:3)

内存间接调用是一个调用,它从内存中获取被调用者的地址,寄存器间接调用从寄存器中获取地址。

调用的实现非常依赖于所使用的体系结构和编译器。在x86上,内存和寄存器调用都是可能的,因此编译器可以选择最佳选择。

看看一个简单的测试:

#include "stdlib.h"
#include "stdio.h"
#include "time.h"

void example_fun(int param)
{
    printf("%d\n", param);
}

void fn1(void)
{
    printf("fn1\n");
}

void fn2(void)
{
    printf("fn2\n");
}

typedef void (*fn)(void);

int main(void)
{
    void (*fp) (int) = &example_fun;
    int c;
    fn fn_arr[] = {&fn1, &fn2};

    (*fp)(2);
    srand(time(NULL));
    c = rand() / (RAND_MAX / 2);
    switch (c)
    {
        case 0: (*(fn_arr[0]))(); break;
        case 1: (*(fn_arr[1]))(); break;
        default: (*fp)(1);
    }
}

生成的程序集是(gcc 4.6.1没有任何优化):

    .file   "indirect.c"
    .section    .rodata
.LC0:
    .string "%d\n"
    .text
    .globl  example_fun
    .type   example_fun, @function
example_fun:
.LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $24, %esp
    movl    $.LC0, %eax
    movl    8(%ebp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE0:
    .size   example_fun, .-example_fun
    .section    .rodata
.LC1:
    .string "fn1"
    .text
    .globl  fn1
    .type   fn1, @function
fn1:
.LFB1:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $24, %esp
    movl    $.LC1, (%esp)
    call    puts
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE1:
    .size   fn1, .-fn1
    .section    .rodata
.LC2:
    .string "fn2"
    .text
    .globl  fn2
    .type   fn2, @function
fn2:
.LFB2:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $24, %esp
    movl    $.LC2, (%esp)
    call    puts
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE2:
    .size   fn2, .-fn2
    .globl  main
    .type   main, @function
main:
.LFB3:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $32, %esp
    movl    $example_fun, 24(%esp)
    movl    $fn1, 16(%esp)
    movl    $fn2, 20(%esp)
    movl    $2, (%esp)
    movl    24(%esp), %eax
    call    *%eax
    movl    $0, (%esp)
    call    time
    movl    %eax, (%esp)
    call    srand
    call    rand
    movl    %eax, %ecx
    movl    $-2147483645, %edx
    movl    %ecx, %eax
    imull   %edx
    leal    (%edx,%ecx), %eax
    movl    %eax, %edx
    sarl    $29, %edx
    movl    %ecx, %eax
    sarl    $31, %eax
    movl    %edx, %ecx
    subl    %eax, %ecx
    movl    %ecx, %eax
    movl    %eax, 28(%esp)
    movl    28(%esp), %eax
    testl   %eax, %eax
    je  .L6
    cmpl    $1, %eax
    je  .L7
    jmp .L9
.L6:
    movl    16(%esp), %eax
    call    *%eax
    jmp .L10
.L7:
    movl    20(%esp), %eax
    call    *%eax
    jmp .L10
.L9:
    movl    $1, (%esp)
    movl    24(%esp), %eax
    call    *%eax
.L10:
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE3:
    .size   main, .-main
    .ident  "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
    .section    .note.GNU-stack,"",@progbits

在所有情况下,我们的调用都被转换为call *%eax,这是一个内存间接调用,执行位于%eax指向的地址的函数。添加优化会混淆结构,但是机制仍然是注册调用。

调用共享动态库(请参阅示例here)只会产生call <function_name>。咨询x86 Developer Manualcall接受寄存器,内存或指针操作数。启动时链接器正在处理此类调用,并且该函数的名称将替换为实际的内存地址。因此,调用共享库会导致内存间接调用(最终)。

除了这些容易复制的案例外,您还可以遇到具有直接地址的操作,例如call dword ptr ds:[00923030h](描述为here)或类似jmp。我无法提供合理的解释来自哪里。

区分寄存器和内存调用非常简单:只需查看操作数即可。

如果您对rootkit检测感兴趣,那么最好只需要使用一些现有的rootkit并查看它的源代码并并行生成程序集。

我对编译器和架构不是很有经验,所以我可以把事情弄错了。

希望它有所帮助。