版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/swjtu100/article/details/49963353
0x00 全局变量
- 整数(00x12345678)在内存中的表示方式:
小端机:逻辑上低字节放在内存低地址(高高低低原则),78 56 34 12.Intel x86
大端机:逻辑上高字节放在内存低地址,12 34 56 78.PowerPC - 全局变量的线性地址在编译时已经写定了,故每个进程中同一个全局变量的线性地址是相同的。
- 在CPU的保护模式下,对于同一个程序的每个进程都有自己独立的线性地址空间(0—4GB),这种机制叫虚拟系统。
注:寄存处—>进程 仓库—>物理存储系统
寄存处编号—>线性地址 仓库编号—>物理内存地址 - 程序访问内存用的是线性地址(mov指令中的地址就是线性地址,用户态程序无法直接访问物理内存), 虚拟管理系统将该地址转换成对应物理地址进行访问。
- 同一程序两个进程的线性地址相同,但对应物理内存是不同的。
0x01 指针
- 作用:地址、类型
指针类型信息(读写字节大小)不放在指针变量中,而是放在了与该地址相关的赋值语句中,即mov指令中dword指明指针类型。
int *pi; int gi; pi = &gi;
00C6139E mov dword ptr ds:[00C67138h],0C6713Ch ;pi存着gi地址
*pi = 12;
00C613A8 mov eax,dword ptr ds:[00C67138h];将pi中存的gi地址给eax
00C613AD mov dword ptr [eax],0Ch ;将0Ch赋给gi
注:&pi = 00C67138h 、 &gi = 0C6713Ch
指针强制转换
强制转换并没有实际的指令,转换的影响不是发生在转换的时候,而是用转换后的身份去访问内存时体现在指令中的。
eg:int i; int *pi; short *ps; char *pc;
运行17行后:
运行18行后:
运行19行后:
0x02 函数调用和局部变量
1) 函数调用
int Add(int x,int y)
{
int sum;
sum = x+y;
return sum;
}
void main()
{
int z;
z = Add(1,2);
printf("%d\n",z);
}
反汇编代码
call指令由相对定位进行跳转:跳转到子函数的 = call指令下一条指令起始地址 + 偏移量
call指令地址 + call指令长度(5) + 偏移量- 偏移量:fffff99b为负数补码,原整数 = -665h。数值位取反加1
故00d511c2h = 00d51822h +5h -665h - 每次压栈,32位机用4字节存储栈值。X86中,push指令使ESP的值减4
- call指令功能:a) push 返回地址 b) jump 函数入口地址
2) 参数传递
- 参数地址 = EBP + 偏移量
- 局部变量地址 = EBP – 偏移量
Visual C++6.0中第一个局部变量地址为EBP-4,VS2005版本开始则为EBP – 8,这是因为吸取了Stack Guard的溢出攻击防护机制
3) 函数调用返回
- ret指令功能:相当于pop eip,将栈顶保存的返回地址弹入EIP
4) 函数返回值
- 一般将返回值存在EAX中,函数在执行ret指令前mov eax , 返回值
- 子函数返回值较小时,将其先保存到母函数栈帧局部变量区临时内存中,然后再赋给母函数局部变量
- 子函数返回值较大时,先分配一段内存用于保存返回值,并将该内存地址传给子函数,将返回值存入该内存,母函数通过该地址获取返回值
5) 平衡栈
- 调用方清栈,call返回后执行add esp,x
- 被调方清栈,执行ret x
前者用空间代价换取了变参功能
0x03 函数指针
- 函数指针成功赋值的前提:参数表、调用惯例、返回类型一致
- 函数指针的作用:让函数指针指向不同函数,实现同样的调用代码调用不同的函数。相当于面向对象中虚函数的作用
int(*pfunc)(int,int);
int _cdecl add(int x,int y)
{
int sum;
sum = x + Y;
return sum;
}
void main(0
{
pfunc = add;
pfunc = &add;
pfunc(1,2);
add(1,2);
}
- 反汇编代码
- 函数指针不能强制转换
0x04 数组
- 用首部地址+偏移量的形式访问
void main()
{
int array[5];
int i = 0;
for(i;i<5;i++)
{
array[i] = i;
}
for(i = 0;i <7;i++)
{
printf("%d ",array[i]);
}
}
- 反汇编代码
- 变量在栈中的分布
- 无法从反编译结果看出某块内存是结构体还是相邻的局部变量
0x05 对齐
- 对齐:基本数据(如单字节、双字节、四字节整数)存放处的地址必须能被自己数据类型的大小整除。若不满足要求,不同体系结构的CPU反应不同。有的直接结束进程的执行,有的访问速度变慢(x86)。
- 结构体的对齐
首先选定一个盒子,依次将字段往盒子中放,盒子放不下后,用下一个盒子存放
限制条件:
a)盒子长度 = min{max{sizeof(成员变量)},对齐长度}
b)字段放入位置:离盒子头部偏移 = n *sizeof(成员变量) n = 0,1,2,….
c)适用VS编译器 - eg:
当对齐长度为4时,sizeof(Person) = 12
struct Person
{
char c1;
short s;
char c2;
int i;
};
- 网络编程中,协议头部一般用结构体表示,在自己手动构造数据包强制转换并赋给头部结构体时应注意对齐问题
struct ip_hdr
{
char version;
char ihl;
unsigned char tos;
unsigned short tot_len;
unsigned short id;
unsigned short frag_off;
unsigned char ttl:
unsigned char protocol;
unsigned short check;
unsigned int saddr;
unsigned int daddr;
}
发送方:
char buf[128];
int * pdataLen;
buf[0] = 1; //设定标志类型
pdataLen = (int *)&buf[1];
*pdataLen = htonl(12); //设定数据长度
//填充数据部分12字节
buf[5] = 1;
buf[6] = 2;
...
buf[16] = 12;
...
//将buf开始的17个字节发送出去,包括头部5字节,数据12字节
send(sockethnd,buf,5 + 12,0);
接收方:
struct hdr* phdr;
char buf[128];
int dataLen;
recv(sockethnd,buf,5,0);
phdr = (struct hdr*)buf;
printf("hdr flag is %\n",dataLen);
第五行指针赋值过程会因为结构体的对齐出现问题。
解决办法:
a) 调整结构体成员变量的顺序达到对齐
b) 填充reserved字段以达到对齐