我这篇博客是对https://www.cnblogs.com/fanzhidongyzby/p/3250405.html的解释。
ebp/esp
根据计算机的位数不同,有时候叫ebp/esp,有时候叫rbp/rsp。
但都是指栈底/栈顶。
在计算机里,栈和堆分别在空间地址的两头儿,中间放程序代码。
- 栈在地址大的一头儿,堆在地址小的一头儿
- 栈的空间分配是从大地址到小地址,堆的空间分配是从小地址到大地址
- 栈底是指大地址这一头儿,因为是从这一头儿开始分配的,根据先进后出,这一头儿就是底了,地址小的一头儿是栈顶
- ebp 的变化一般都是通过直接赋值给 ebp 寄存器
- 对栈做 push/pop 时,esp 就会 减去/加上 对象的长度
- 也可以直接修改 esp 的值
举例说明:
void test() {
int i = 0;
printf("%ld", i);
}
int main(int argc, char * argv[]) {
@autoreleasepool {
test();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
这段代码在模拟器上的汇编代码如下:
0x104b4eab0 <+0>: pushq %rbp
0x104b4eab1 <+1>: movq %rsp, %rbp
0x104b4eab4 <+4>: subq $0x10, %rsp
0x104b4eab8 <+8>: movl $0x0, -0x4(%rbp)
0x104b4eabf <+15>: movl -0x4(%rbp), %esi
0x104b4eac2 <+18>: leaq 0xd098(%rip), %rdi ; "%ld"
0x104b4eac9 <+25>: movb $0x0, %al
0x104b4eacb <+27>: callq 0x104b59d4a ; symbol stub for: printf
0x104b4ead0 <+32>: addq $0x10, %rsp
0x104b4ead4 <+36>: popq %rbp
0x104b4ead5 <+37>: retq
函数调用过程
假设:函数 A 在调用函数 B
函数调用使用的指令是 call + 函数地址
函数调用过程,主要涉及到 rbp / rsp / rl / pc 四个寄存器
call指令执行时:
- 把 pc 设置为 B 的地址
- 把 rl 设置成函数返回地址,也就是 A 中当前指令的下一条指令地址
- 暂时 rbp/rsp 不变
然后,进入 B 中后:
如果 B 中,一会儿还要调用其他函数(比如叫 C ),那一会儿就得给 rl 寄存器重新赋值了,所以只能先把原来的 rl 保存一下,等 B 准备返回了,再把 原来的 rl 取出来,这样肯定不会有问题了。
这样的 B 叫非叶子函数。将会把 rl 保存在栈中。
如果 B 中不调用其他函数,那就是叶子函数,就不需要把返回地址保存在栈中了
栈溢出
栈溢出只能发生在非叶子函数中
漏洞代码
这样的代码是由安全漏洞的,因为 strcpy 会不检查 data 长度地就把 data 全部复制到 buffer 中去:
void fun(unsigned char *data)
{
unsigned char buffer[BUF_LEN];
strcpy((char*)buffer,(char*)data);//溢出点
}
因为栈增长方向也就是地址减小方向,所以如果 data 的长度超过 BUF_LEN 的话,data 会覆盖掉 ebp / 返回地址
返回地址,但是此时的这块空间已经放了错误的地址了,但 CPU 不知道呀,它就会跳转到那里执行,栈溢出攻击就完成了
通过中间跳板指定 shellcode 地址
上面的操作,达到了下图的结果:
问题这个新返回地址得是一个绝对地址,就像 0x12345678 这样的,不能是 rbp + 0x1111 这样的,因为原来的返回地址就是绝对地址,CPU 拿到新的返回地址,也会照着绝对地址的方式用的
而且由于每次执行的时候,函数的栈地址都不固定,所以没办法正确找到 shellcode 地址。
解决办法是:找到一个系统代码中的指令地址,这个指令是 jmp rsp,并且把 shellcode 放在新返回地址的上面,就可以了。