メモリ間接呼び出しとレジスタ間接呼び出しの違いは何ですか?メモリ間接呼び出しとレジスタ間接呼び出しの相違点
私はLinuxのルートキット検出について何か勉強しようとしていますが、そのような呼び出しを逆アセンブルされたメモリでどのように認識できますか?彼らはコンパイルする前にC言語でどのように見えるのですか?
メモリ間接呼び出しとレジスタ間接呼び出しの違いは何ですか?メモリ間接呼び出しとレジスタ間接呼び出しの相違点
私はLinuxのルートキット検出について何か勉強しようとしていますが、そのような呼び出しを逆アセンブルされたメモリでどのように認識できますか?彼らはコンパイルする前にC言語でどのように見えるのですか?
メモリ間接呼び出しは、メモリから呼び出し先のアドレスを受け取り、レジスタ間接呼び出しがそれに応じてレジスタからアドレスを取得する呼び出しです。
呼び出しの実装は、使用されるアーキテクチャとコンパイラによって大きく異なります。 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
のような即時のアドレスの操作が発生することはありません。彼らはどこから来たのか合理的な説明はできません。
レジスタとメモリの呼び出しを区別するのはかなり簡単です:単にオペランドを見てください。
ルートキットの検出に興味がある場合は、既存のルートキットを使用して、ソースと生成されたアセンブリを並べて表示することをお勧めします。
私はコンパイラやアーキテクチャに慣れていないので、少し間違いがあります。
希望します。
間接分岐は、レジスタまたはメモリの場所に格納されているアドレスに分岐される分岐です。分岐命令のオペランドは、分岐するアドレスを格納するレジスタまたはメモリ位置です。
詳細については、ウィキペディアのページを参照してください: http://en.wikipedia.org/wiki/Indirect_branch
Cで実装(およびCPU)に依存する関数は関数ポインタを通じて呼び出されたときに、間接分岐がしばしば行われています。また、switch
ステートメントのヒューリスティックには、ジャンプポインタを使用して関数ポインタが使用されるため、switch
ステートメントに間接分岐もあります。
ありがとうございますが、レジスタを使用しているときとメモリを使用していますか?私はメモリで間接呼び出しがレジスタ間接呼び出しよりも呼び出し側のアドレスを見つける方がはるかに簡単であることを読んだことがあります。どうして? –
であるため、メモリの内容は後で容易に読み取ることができるが、レジスタの内容はほぼ即時にゴミ箱に入る。 –
まず、いくつかのMCUはレジスタからの間接分岐のみをサポートするため、プラットフォームに依存します。両方をサポートする人の場合、メモリからの間接呼び出しはより多くのCPUサイクルを要する可能性があります。そして、@ noah1989は、いくつかのプラットフォームのように長すぎるレジスタを保持したくないと言っているので、メモリが安く、プログラムの存続期間中にアドレスを格納するために使用できるのに対し、あまり多くはありません。 – ouah