2012-01-21 12 views
3

メモリ間接呼び出しとレジスタ間接呼び出しの違いは何ですか?メモリ間接呼び出しとレジスタ間接呼び出しの相違点

私はLinuxのルートキット検出について何か勉強しようとしていますが、そのような呼び出しを逆アセンブルされたメモリでどのように認識できますか?彼らはコンパイルする前にC言語でどのように見えるのですか?

答えて

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 W/O任意の最適化)である:

.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 Manualを参照すると、callは、レジスタ、メモリまたはポインタのどちらかのオペランドを受け付けます。このような呼び出しは起動時にリンカによって処理され、関数の名前は実際のメモリアドレスに置き換えられています。したがって、共有ライブラリを呼び出すと(最終的に)メモリ間接呼び出しが行われます。

call dword ptr ds:[00923030h]hereと記載されています)またはjmpのような即時のアドレスの操作が発生することはありません。彼らはどこから来たのか合理的な説明はできません。

レジスタとメモリの呼び出しを区別するのはかなり簡単です:単にオペランドを見てください。

ルートキットの検出に興味がある場合は、既存のルートキットを使用して、ソースと生成されたアセンブリを並べて表示することをお勧めします。

私はコンパイラやアーキテクチャに慣れていないので、少し間違いがあります。

希望します。

5

間接分岐は、レジスタまたはメモリの場所に格納されているアドレスに分岐される分岐です。分岐命令のオペランドは、分岐するアドレスを格納するレジスタまたはメモリ位置です。

詳細については、ウィキペディアのページを参照してください: http://en.wikipedia.org/wiki/Indirect_branch

Cで実装(およびCPU)に依存する関数は関数ポインタを通じて呼び出されたときに、間接分岐がしばしば行われています。また、switchステートメントのヒューリスティックには、ジャンプポインタを使用して関数ポインタが使用されるため、switchステートメントに間接分岐もあります。

+0

ありがとうございますが、レジスタを使用しているときとメモリを使用していますか?私はメモリで間接呼び出しがレジスタ間接呼び出しよりも呼び出し側のアドレスを見つける方がはるかに簡単であることを読んだことがあります。どうして? –

+0

であるため、メモリの内容は後で容易に読み取ることができるが、レジスタの内容はほぼ即時にゴミ箱に入る。 –

+3

まず、いくつかのMCUはレジスタからの間接分岐のみをサポートするため、プラットフォームに依存します。両方をサポートする人の場合、メモリからの間接呼び出しはより多くのCPUサイクルを要する可能性があります。そして、@ noah1989は、いくつかのプラットフォームのように長すぎるレジスタを保持したくないと言っているので、メモリが安く、プログラムの存続期間中にアドレスを格納するために使用できるのに対し、あまり多くはありません。 – ouah

関連する問題