程序的存储结构
分布
在磁盘和内存中的分布如下:
节视图
.data:已经初始化的全局变量/局部静态变量
.bss:未初始化的全局变量/局部静态变量
.got.plt:全局偏移量表,保存全局变量引用的地址
.rodata:只读数据
text:代码节,保存了程序执行的代码
.init:程序初始化和终止的代码
段视图
stack:栈,向低地址生长
heap:堆,向高地址生长
data:读写权限的数据段
code:具有执行权限的数据段
以hello world为例
// gcc -m32 -no-pie -g -o main main.c
#include <stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
objdump -h main #可查看各个节的位置
alientek@ubuntu16:~/Desktop/exc$ objdump -h main
main: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 00000013 08048154 08048154 00000154 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.ABI-tag 00000020 08048168 08048168 00000168 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .note.gnu.build-id 00000024 08048188 08048188 00000188 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .gnu.hash 00000020 080481ac 080481ac 000001ac 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynsym 00000050 080481cc 080481cc 000001cc 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .dynstr 0000004a 0804821c 0804821c 0000021c 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .gnu.version 0000000a 08048266 08048266 00000266 2**1
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .gnu.version_r 00000020 08048270 08048270 00000270 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .rel.dyn 00000008 08048290 08048290 00000290 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .rel.plt 00000010 08048298 08048298 00000298 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
10 .init 00000023 080482a8 080482a8 000002a8 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
11 .plt 00000030 080482d0 080482d0 000002d0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .plt.got 00000008 08048300 08048300 00000300 2**3
CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .text 00000192 08048310 08048310 00000310 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
14 .fini 00000014 080484a4 080484a4 000004a4 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
15 .rodata 00000014 080484b8 080484b8 000004b8 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
16 .eh_frame_hdr 0000002c 080484cc 080484cc 000004cc 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
17 .eh_frame 000000cc 080484f8 080484f8 000004f8 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
18 .init_array 00000004 08049f08 08049f08 00000f08 2**2
CONTENTS, ALLOC, LOAD, DATA
19 .fini_array 00000004 08049f0c 08049f0c 00000f0c 2**2
CONTENTS, ALLOC, LOAD, DATA
20 .jcr 00000004 08049f10 08049f10 00000f10 2**2
CONTENTS, ALLOC, LOAD, DATA
21 .dynamic 000000e8 08049f14 08049f14 00000f14 2**2
CONTENTS, ALLOC, LOAD, DATA
22 .got 00000004 08049ffc 08049ffc 00000ffc 2**2
CONTENTS, ALLOC, LOAD, DATA
23 .got.plt 00000014 0804a000 0804a000 00001000 2**2
CONTENTS, ALLOC, LOAD, DATA
24 .data 00000008 0804a014 0804a014 00001014 2**2
CONTENTS, ALLOC, LOAD, DATA
25 .bss 00000004 0804a01c 0804a01c 0000101c 2**0
ALLOC
26 .comment 00000035 00000000 00000000 0000101c 2**0
CONTENTS, READONLY
27 .debug_aranges 00000020 00000000 00000000 00001051 2**0
CONTENTS, READONLY, DEBUGGING
28 .debug_info 0000008f 00000000 00000000 00001071 2**0
CONTENTS, READONLY, DEBUGGING
29 .debug_abbrev 00000042 00000000 00000000 00001100 2**0
CONTENTS, READONLY, DEBUGGING
30 .debug_line 00000038 00000000 00000000 00001142 2**0
CONTENTS, READONLY, DEBUGGING
31 .debug_str 000000d3 00000000 00000000 0000117a 2**0
CONTENTS, READONLY, DEBUGGING
ldd 命令用于打印程序或者共享库文件所依赖的共享库列表。注意,ldd 本身不是一个二进制程序,而是一个 Shell 脚本,使用文本编辑器 vim 可以查看其内容.
alientek@ubuntu16:~/Desktop/exc$ ldd main
linux-gate.so.1 => (0xf7f4d000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d75000)
/lib/ld-linux.so.2 (0xf7f4f000)
gdb-peda 插件
跑起来以后
gdb-peda$ vmmap
Start End Perm Name
0x08048000 0x08049000 r-xp /home/alientek/Desktop/exc/main
0x08049000 0x0804a000 r--p /home/alientek/Desktop/exc/main
0x0804a000 0x0804b000 rw-p /home/alientek/Desktop/exc/main
0xf7dfe000 0xf7dff000 rw-p mapped
0xf7dff000 0xf7faf000 r-xp /lib/i386-linux-gnu/libc-2.23.so
0xf7faf000 0xf7fb0000 ---p /lib/i386-linux-gnu/libc-2.23.so
0xf7fb0000 0xf7fb2000 r--p /lib/i386-linux-gnu/libc-2.23.so
0xf7fb2000 0xf7fb3000 rw-p /lib/i386-linux-gnu/libc-2.23.so
0xf7fb3000 0xf7fb6000 rw-p mapped
0xf7fd3000 0xf7fd4000 rw-p mapped
0xf7fd4000 0xf7fd7000 r--p [vvar]
0xf7fd7000 0xf7fd9000 r-xp [vdso]
0xf7fd9000 0xf7ffc000 r-xp /lib/i386-linux-gnu/ld-2.23.so
0xf7ffc000 0xf7ffd000 r--p /lib/i386-linux-gnu/ld-2.23.so
0xf7ffd000 0xf7ffe000 rw-p /lib/i386-linux-gnu/ld-2.23.so
0xfffdd000 0xffffe000 rw-p [stack]
elf文件
C语言函数栈帧与传参
函数执行过程
栈:函数在调用过程中,会在内存中开辟一块名为栈帧的空间,用于存放局部变量等数据,由编译器管理。
堆:由程序员管理。
ESP(Extended stack pointer)存放的都是栈顶地址。
EBP(Extended base pointer)该指针总是指向当前栈帧的底部(高地址)。
push和pop命令,会自动来更改 esp寄存器。
每个栈帧对应着一个未运行完的函数,包括主函数。栈帧中保存了该函数的返回地址、和局部变量。
别人的: https://blog.csdn.net/iluo12/article/details/122557685
https://blog.csdn.net/chihiro1122/article/details/127671496
以add程序为例
参数较少,编译成32位可执行程序,才会采用栈传参。
//main2.c
int add(int a,int b)
{
int c = 0;
c = a + b;
return c;
}
int main()
{
int a=0;
int b=1;
int sum=0;
sum=add(a,b);
return 0;
}
gdb-peda 看汇编代码
0x80483f8 <main>: push ebp
0x80483f9 <main+1>: mov ebp,esp #从栈顶开始跑了
0x80483fb <main+3>: sub esp,0x10#可能是流出
0x80483fe <main+6>: mov DWORD PTR [ebp-0xc],0x0 #a
0x8048405 <main+13>: mov DWORD PTR [ebp-0x8],0x1 #b
0x804840c <main+20>: mov DWORD PTR [ebp-0x4],0x0 #sum
0x8048413 <main+27>: push DWORD PTR [ebp-0x8] #b入栈
0x8048416 <main+30>: push DWORD PTR [ebp-0xc] #a入栈
0x8048419 <main+33>: call 0x80483db <add> #调用子函数
0x804841e <main+38>: add esp,0x8
0x8048421 <main+41>: mov DWORD PTR [ebp-0x4],eax
0x8048424 <main+44>: mov eax,0x0
0x8048429 <main+49>: leave
0x804842a <main+50>: ret
在执行main时候,EBP=0xffffd128,栈从ESP=0xffffd118开始生长。
0x80483dc <add+1>: mov ebp,esp
0x80483de <add+3>: sub esp,0x10
0x80483e1 <add+6>: mov DWORD PTR [ebp-0x4],0x0 #c
0x80483e8 <add+13>: mov edx,DWORD PTR [ebp+0x8] #取函数参数
0x80483eb <add+16>: mov eax,DWORD PTR [ebp+0xc] #取函数参数
0x80483ee <add+19>: add eax,edx
0x80483f0 <add+21>: mov DWORD PTR [ebp-0x4],eax #结果存在c
0x80483f3 <add+24>: mov eax,DWORD PTR [ebp-0x4] #返回值放在ax里面
0x80483f6 <add+27>: leave
0x80483f7 <add+28>: ret
进入 add子函数后,EBP=0xffffd108,ESP从0xffffd0f8开始生长。
栈的分布
0000| 0xffffd0f8 --> 0xf7e2da60 (add ebx,0x1845a0)#add的栈帧
0004| 0xffffd0fc --> 0x804847b (<__libc_csu_init+75>: add edi,0x1)
0008| 0xffffd100 --> 0x1
0012| 0xffffd104 --> 0xffffd1c4 --> 0xffffd342 ("/home/alientek/Desktop/exc/main2")
0016| 0xffffd108 --> 0xffffd128 --> 0x0 #add的栈帧
0020| 0xffffd10c --> 0x804841e (<main+38>: add esp,0x8)
0024| 0xffffd110 --> 0x0
0028| 0xffffd114 --> 0x1
0000| 0xffffd118 --> 0x8048439 (<__libc_csu_init+9>: add ebx,0x1bc7)#main的栈帧
0004| 0xffffd11c --> 0x0
0008| 0xffffd120 --> 0x1
0012| 0xffffd124 --> 0x0
0016| 0xffffd128 --> 0x0 #main的栈帧
0020| 0xffffd12c --> 0xf7e17647 (<__libc_start_main+247>: add esp,0x10)
0024| 0xffffd130 --> 0x1
0028| 0xffffd134 --> 0xffffd1c4 --> 0xffffd342 ("/home/alientek/Desktop/exc/main2")
栈踩踏
写一个子函数里面数组越界的例子
//main3.c
void test(void)
{
char tmp[4];
tmp[20]=1;
return;
}
int main()
{
test();
return 0;
}
test函数的汇编指令如下,给tmp[20]赋值实际上写到了0xffffd10c这个地址上了,这里记录了test子函数返回时,应该执行的main函数的地址,把地址破坏了,该程序无法继续运行了。
0x8048447 <test+12>: mov DWORD PTR [ebp-0xc],eax
0x804844a <test+15>: xor eax,eax
0x804844c <test+17>: mov BYTE PTR [ebp+0x4],0x1
这段代码的栈如下
0000| 0xffffd0f0 --> 0x1
0004| 0xffffd0f4 --> 0x400000 ('')
0008| 0xffffd0f8 --> 0xf7e2da60 (add ebx,0x1845a0)
0012| 0xffffd0fc --> 0x80484db (<__libc_csu_init+75>: add edi,0x1)
0016| 0xffffd100 --> 0x1
0020| 0xffffd104 --> 0xffffd1c4 --> 0xffffd342 ("/home/alientek/Desktop/exc/main3")
0024| 0xffffd108 --> 0xffffd118 --> 0x0
0028| 0xffffd10c --> 0x804847a (<main+22>: mov eax,0x0)
0000| 0xffffd110 --> 0xf7fb23dc --> 0xf7fb31e0 --> 0x0 #main的栈帧
0004| 0xffffd114 --> 0xffffd130 --> 0x1
0008| 0xffffd118 --> 0x0 #main的BSP
0012| 0xffffd11c --> 0xf7e17647 (<__libc_start_main+247>: add esp,0x10)
0016| 0xffffd120 --> 0xf7fb2000 --> 0x1b2db0
0020| 0xffffd124 --> 0xf7fb2000 --> 0x1b2db0
0024| 0xffffd128 --> 0x0
0028| 0xffffd12c --> 0xf7e17647 (<__libc_start_main+247>: add esp,0x10)
运行结果
alientek@ubuntu16:~/Desktop/exc$ ./main3
Illegal instruction