1.了解几个概念
栈 : 地址下高上低
上esp下ebp
经常使用add进行舍弃参数与平栈操作
od栈窗口的栈中可以存放数据也可以存放地址,还可以进行跟随
函数局部变量依次压栈,局部函数也是一样,一般会在主函数的上方,也就是低地址的位置
字符串与数组的存储形式
存储的形式就是正常按类型的大小的ascii存储
传参的话传递的是字符串与数组的首地址
函数调用与传参栈的变化
首先明确函数调用需要的两个指令 、
call与ret
执行call时:两步操作,第一步push eip 也就是把返回值推入栈中
第二步执行jmp 跳转的call之后的地址
执行ret:pop eip 跳转到call压入的eip之处
--- g:\vc6.0\vcproject\函数栈分析练习.cpp ----------------------------------------------------------------------------------------------------------------------------------
1: #include
2:
3: int Add(int x, int y) {
00401020 55 push ebp
00401021 8B EC mov ebp,esp
00401023 83 EC 44 sub esp,44h
00401026 53 push ebx
00401027 56 push esi
00401028 57 push edi
00401029 8D 7D BC lea edi,[ebp-44h]
0040102C B9 11 00 00 00 mov ecx,11h
00401031 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401036 F3 AB rep stos dword ptr [edi]
4: int z = 0;
00401038 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0
5: z = x + y;//从主函数的实参栈中取值(用ebp调用),推入到寄存器进行计算,计算结果给寄存器,寄存器再给主函数定义的变量中
0040103F 8B 45 08 mov eax,dword ptr [ebp+8]
00401042 03 45 0C add eax,dword ptr [ebp+0Ch]
00401045 89 45 FC mov dword ptr [ebp-4],eax
6:
7: return z;
00401048 8B 45 FC mov eax,dword ptr [ebp-4]
8: }
0040104B 5F pop edi
0040104C 5E pop esi
0040104D 5B pop ebx
0040104E 8B E5 mov esp,ebp
00401050 5D pop ebp
00401051 C3 ret
--- No source file ---------------------------------------------------------------------------------------------------------------------------------------------------------
00401052 CC int 3
00401053 CC int 3
00401054 CC int 3
00401055 CC int 3
00401056 CC int 3
00401057 CC int 3
00401058 CC int 3
00401059 CC int 3
0040105A CC int 3
0040105B CC int 3
0040105C CC int 3
0040105D CC int 3
0040105E CC int 3
0040105F CC int 3
--- g:\vc6.0\vcproject\函数栈分析练习.cpp ----------------------------------------------------------------------------------------------------------------------------------
9:
10: int main(void) {
00401060 55 push ebp
00401061 8B EC mov ebp,esp
00401063 83 EC 4C sub esp,4Ch
00401066 53 push ebx
00401067 56 push esi
00401068 57 push edi
00401069 8D 7D B4 lea edi,[ebp-4Ch]
0040106C B9 13 00 00 00 mov ecx,13h
00401071 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401076 F3 AB rep stos dword ptr [edi]
11: int a = 3;
00401078 C7 45 FC 03 00 00 00 mov dword ptr [ebp-4],3
12: int b = 5;
0040107F C7 45 F8 05 00 00 00 mov dword ptr [ebp-8],5
13: int ret = 0;
00401086 C7 45 F4 00 00 00 00 mov dword ptr [ebp-0Ch],0
14:
15: ret = Add(a, b);//函数调用需要把每一个传入的实际参数(压数进栈用寄存器)从栈中已经定义的位置取出放入寄存器中,然后把寄存器推入栈中保存,释放函数时释放寄存器,与主函数分开释放
0040108D 8B 45 F8 mov eax,dword ptr [ebp-8]
00401090 50 push eax
00401091 8B 4D FC mov ecx,dword ptr [ebp-4]
00401094 51 push ecx
00401095 E8 6B FF FF FF call @ILT+0(Add) (00401005)
0040109A 83 C4 08 add esp,8//平衡调用的函数add(a,b)栈,调用两个参数,平两个参数
0040109D 89 45 F4 mov dword ptr [ebp-0Ch],eax //返回值进行保存,从eax给变量
16:
17: return 0;
004010A0 33 C0 xor eax,eax
18: }
004010A2 5F pop edi
004010A3 5E pop esi
004010A4 5B pop ebx
004010A5 83 C4 4C add esp,4Ch//平衡主函数栈,定义了三个变量平三个参数
004010A8 3B EC cmp ebp,esp
004010AA E8 21 00 00 00 call __chkesp (004010d0)
004010AF 8B E5 mov esp,ebp
004010B1 5D pop ebp
004010B2 C3 ret
--- No source file -----------------------------------------------------
传参栈分析
ELF文件格式学习
简单在linux系统下敲脚本
touch wang.c //新建wang.c
vi wang.c //填充脚本
gcc wang.c //编译wang.c
gcc wang.c -o xinyu.out //编译脚本修改名字输出xinyu.out
./xinyu.out //执行
此时为root用户,不需要赋予权限,如果为普通用户,需要赋予执行编译权限
比如
此时需要chmod权限
chmod u+x *.sh
chmod是权限管理命令change the permissions mode of a file的缩写。
u代表所有者。x代表执行权限。’+’ 表示增加权限。
chmod u+x file.sh 就表示对当前目录下的file.sh文件的所有者增加可执行权限。
bingo
ELF文件格式
GCC编译过程
gcc hello.c -o hello -save-temps --verbose //将.c文件编译成elf文件
在gcc编译的过程主要包括4个阶段
包括: 1.预处理 2.编译 3.汇编 4.链接
预处理和编译
预处理阶段 在这一阶段处理以#开头的预处理指令及删除注释等工作
gcc -E hello.c -o hello.i //单独进行.c文件到预处理
编译阶段 在这一阶段将已经被预处理的文件进行一系列分析及优化并最终形成汇编代码
gcc -S hello.c -o hello.s
gcc -S hello.i -o hello.s //单独进行.c文件到编译阶段
汇编
gcc -c hello.c -o hello.o
gcc -c hello.s -o hello.o //单独进行.c文件到编译阶段或编译到汇编
as是汇编过程中的汇编器,将文件汇编成一个汇编文件 这时的.o文件已经成为了一个汇编文件,可以objdump查看汇编代码 objdump -sd hello.o -M intel
链接
链接阶段可以划分为静态链接和动态链接,在这一阶段将目标文件与其依赖库进行绑定,包括地址与空间的分配,符号绑定和重定位等。
在gcc中默认使用动态链接,静态链接的使用可以通过添加static关键字来开启
gcc hello.o -o hello -static //开启静态链接
链接阶段通过由链接器(ld.so,collect2是对ld.so命令的封装)完成,并得到可执行的elf文件,这时的可执行文件已经包括了大量的库文件并完成了对地址与空间的分配。
ELF文件结构
通常目标文件都会包含代码(.text)、数据(.data)和BSS(.bss)三个节。其中代码节用于保存可执行的机器指令,数据节用于保存已初始化的全局变量和局部静态变量,BSS节则用于保存未初始化的全局变量和局部静态变量。 而除了这三个节之外,简化的目标文件还应包含一个文件头(ELF header)
ELF文件头(ELF header)位于目标文件最开始的位置,包含描述整个文件的一些基本信息,例如ELF文件类型、版本/ABI版本、目标机器、程序入口、段表和节表的位置和长度等
readelf -h hello //我们可以通过readelf来查看elf头文件的文件信息
节头表
一个目标文件中包含许多节,这些节的信息保存在节头表(Section header table)中,表的每一项都是一个Elf64_Shdr结构体(也称为节描述符),记录了节的名字、长度、偏移、读写权限等信息。节头表的位置记录在文件头的e_shoff域中。节头表对于程序运行并不是必须的,因为它与程序内存布局无关,是程序头表的任务,所以常有程序去除节头表,以增加反编译器的分析难度 readelf -S hello //同样,我们也可以通过readelf读出elf文件的节头表
text节 该节区存储了源文件编译生成的机器指令。对开发者来说,这可能是最重要的一个节区,所有的程序逻辑都放在这里。但开发者对该节区能做的控制却很少,能影响它的因素只有开发者写的程序逻辑,以及编译时使用的选项。
.data和.rodata节
.data节中存放所有的已经初始化的全局变量和局部静态变量,这个节区是可读可写的。 .rodata中存在常量数据,从字面上就能看出,ro表示read only,即不可写,因此该节区存储了程序中的常量数据,这个节区是只可读的。
.bss节
该节区存储了所有未初始化或初始化为 0 的全局和静态变量,该节区的设计初衷就是为了节省目标文件的存储空间,该节没有CONTENTS属性,这表示该节在文件中实际上并不存在,只是为变量预留了位置而已,因此该节的sh_offset域也就没有意义了。
内存结构
在内存中,我们的程序通常通过线程来运行,而线程又可以被称为微量级进程,其本质是和进程没有太大区别。 在elf结构中介绍的elf的各个节则存在于各进程空间的各个段中