内存间接调用和寄存器间接调用有什么区别?
我正在尝试学习有关linux rootkit检测的一些知识,如何在反汇编内存中识别出这样的调用?在编译之前,他们如何看待C语言?
答案 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 Manual,call
接受寄存器,内存或指针操作数。启动时链接器正在处理此类调用,并且该函数的名称将替换为实际的内存地址。因此,调用共享库会导致内存间接调用(最终)。
除了这些容易复制的案例外,您还可以遇到具有直接地址的操作,例如call dword ptr ds:[00923030h]
(描述为here)或类似jmp
。我无法提供合理的解释来自哪里。
区分寄存器和内存调用非常简单:只需查看操作数即可。
如果您对rootkit检测感兴趣,那么最好只需要使用一些现有的rootkit并查看它的源代码并并行生成程序集。
我对编译器和架构不是很有经验,所以我可以把事情弄错了。
希望它有所帮助。