C代码编程
(编译工具gcc (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609平台ubuntu i386 32位)ubuntu
int bar(int c , int d){ int e = c +d; return e; } int foo (int a , int b){ return bar(a,b); } int main (void){ foo(2,3); return 0; }
gcc -S -masm=intel function_asm.c 生成一个intel风格的汇编代码文件ide
.file "function_asm.c" .intel_syntax noprefix .text .globl bar .type bar, @function bar: .LFB0: .cfi_startproc push ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 mov ebp, esp .cfi_def_cfa_register 5 sub esp, 16 mov edx, DWORD PTR [ebp+8] mov eax, DWORD PTR [ebp+12] add eax, edx mov DWORD PTR [ebp-4], eax mov eax, DWORD PTR [ebp-4] leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size bar, .-bar .globl foo .type foo, @function foo: .LFB1: .cfi_startproc push ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 mov ebp, esp .cfi_def_cfa_register 5 push DWORD PTR [ebp+12] push DWORD PTR [ebp+8] call bar add esp, 8 leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE1: .size foo, .-foo .globl main .type main, @function main: .LFB2: .cfi_startproc push ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 mov ebp, esp .cfi_def_cfa_register 5 push 3 push 2 call foo add esp, 8 mov eax, 0 leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609" .section .note.GNU-stack,"",@progbits
objdump -M intel -S -d a.out 将生成的二进制文件用intel格式反汇编,咱们关心的以下:函数
080483db <bar>: 80483db: 55 push ebp 80483dc: 89 e5 mov ebp,esp 80483de: 83 ec 10 sub esp,0x10 80483e1: 8b 55 08 mov edx,DWORD PTR [ebp+0x8] 80483e4: 8b 45 0c mov eax,DWORD PTR [ebp+0xc] 80483e7: 01 d0 add eax,edx 80483e9: 89 45 fc mov DWORD PTR [ebp-0x4],eax 80483ec: 8b 45 fc mov eax,DWORD PTR [ebp-0x4] 80483ef: c9 leave 80483f0: c3 ret 080483f1 <foo>: 80483f1: 55 push ebp 80483f2: 89 e5 mov ebp,esp 80483f4: ff 75 0c push DWORD PTR [ebp+0xc] 80483f7: ff 75 08 push DWORD PTR [ebp+0x8] 80483fa: e8 dc ff ff ff call 80483db <bar> 80483ff: 83 c4 08 add esp,0x8 8048402: c9 leave 8048403: c3 ret 08048404 <main>: 8048404: 55 push ebp 8048405: 89 e5 mov ebp,esp 8048407: 6a 03 push 0x3 8048409: 6a 02 push 0x2 804840b: e8 e1 ff ff ff call 80483f1 <foo> 8048410: 83 c4 08 add esp,0x8 8048413: b8 00 00 00 00 mov eax,0x0 8048418: c9 leave 8048419: c3 ret 804841a: 66 90 xchg ax,ax 804841c: 66 90 xchg ax,ax 804841e: 66 90 xchg ax,ax
函数有几个关键点调用函数、参数传递、返回值、调用后会返回计息执行下面的指令工具
其中参数和返回地址都是经过栈来实现的。平衡栈的动做根据不一样的调用约定来实现的。C编译器符合3d
参数从右到左入栈rest
int main (void){ foo(2,3); return 0; } main函数调用foo函数(2,3)做为参数被传递 8048407: 6a 03 push 0x3 8048409: 6a 02 push 0x2 804840b: e8 e1 ff ff ff call 80483f1 <foo> 8048410: 83 c4 08 add esp,0x8 先将3入栈,再将2入栈 call foo 函数(跳转到80483f1 <foo>执行) call指令 call首先将它的下一条指令入栈(也就是8048410地址)将它调用的函数地址(foo函数地址复制到EIP寄存器当中) 下条指令CPU会执行EIP中地址,如今进入foo函数当中 080483f1 <foo>: 80483f1: 55 push ebp 80483f2: 89 e5 mov ebp,esp 80483f4: ff 75 0c push DWORD PTR [ebp+0xc] 80483f7: ff 75 08 push DWORD PTR [ebp+0x8] 80483fa: e8 dc ff ff ff call 80483db <bar> 80483ff: 83 c4 08 add esp,0x8 8048402: c9 leave 8048403: c3 ret foo调用了bar进入bar int bar(int c , int d){ int e = c +d; return e; } int foo (int a , int b){ return bar(a,b); } ================================================================================ 080483db <bar>: 80483db: 55 push ebp 80483dc: 89 e5 mov ebp,esp 80483de: 83 ec 10 sub esp,0x10 80483e1: 8b 55 08 mov edx,DWORD PTR [ebp+0x8] 80483e4: 8b 45 0c mov eax,DWORD PTR [ebp+0xc] 80483e7: 01 d0 add eax,edx 80483e9: 89 45 fc mov DWORD PTR [ebp-0x4],eax 80483ec: 8b 45 fc mov eax,DWORD PTR [ebp-0x4] 80483ef: c9 leave 80483f0: c3 ret 其中 80483f1: 55 push ebp;保存ebp现场 80483f2: 89 e5 mov ebp,esp;将esp的值赋给ebp如今是参数、参数、返回地址、ebp现场。 ebp是用来取调用者传过来的参数的也能够用来引用局部变量(在栈上分配)。为啥呢?由于esp老是指向栈顶,刚进入被调用函数的时候将esp赋值给ebp这个时候好找前面压过栈的参数。编译器好实现。 EBP:高级语言经过 EBP 来引用堆栈中的函数参数和局部变量。除了高级编程,它不用于通常算术运算和数据传输。 取参数 80483e1: 8b 55 08 mov edx,DWORD PTR [ebp+0x8] 80483e4: 8b 45 0c mov eax,DWORD PTR [ebp+0xc] 全部函数都是经过eax来返回值的 80483ec: 8b 45 fc mov eax,DWORD PTR [ebp-0x4] leave是下面函数开始的时候的逆操做从新将ebp赋值给esp,而后pop ebp;ebp又恢复到维护foo函数的了,前面的数据还在栈上可是我已经不维护了,没有意义了。 80483db: 55 push ebp 80483dc: 89 e5 mov ebp,esp ret指令是call指令的逆操做,pop如今的esp的保存的数据给EIP(也就是foo函数调用bar函数前call保存的地址),而后esp值-4 其中EBP老是指向当前栈帧的栈底、返回值经过EAX传递。
在逆向中将重点放在函数的识别和参数的传递上是节省体力的方法,函数是一个程序模块,程序就是由这样一个模块一个模块组成的。code