栈溢出与基本ROP
栈溢出与基本ROP
栈溢出原理
由于C语言对数组引用不做任何边界检查从而导致缓冲区溢出(buffer overflow)成为一种很常见的漏洞。根据溢出发生的内存位置通常可以分为栈溢出和堆溢出°其中’由于栈上保存着局部变量和一些状态信息(寄存器值、返回地址等),一旦发生严重的溢出,攻击者就可以通过覆写返回地址来执行任意代码利用方法包括shellcode注入、ret2libc、ROP等。同时防守方也发展出多种利用缓解机制。
函数调用栈
下面直接举例说明(分为x86和x86-64)
x86
首先被调用函数
func()
的8个参数从后问前依次入栈,当执行call
指令时,下一条指令的地址0x08048415
作为返回地址入栈。然后程序跳转到func()
,在函数开头,将调用函数的ebp
压栈保存并更新为当前的栈顶地址esp
,作为新的栈基址,而esp
则下移为局部变量开辟空间。函数返回时则相反,通过leave
指令将esp
恢复为当前的ebp
,并从栈中将调用者的ebp
弹出,最后ret
指令弹出返回地址作为eip
,程序回到main()
函数中,最后抬高esp
清理被调用者的参数,一次函数调用的过程就结束了。
x86-64
对于x86-64的程序,前6个参数分别通过
rdi
、rsi
、rdx
、rcx
、r8
和r9
进行传递,剩余参数才像x86一样从后向前依次压栈。除此之外,我们还发现func()
没有下移rsp
开辟栈空间的操作,导致rbp
和rsp
的值是相同的,其实这是一项编译优化:根据AMD64 ABI文档的描述rsp
以下128字节的区域被称为red zone,这是一块被保留的内存,不会被信号或者中断所修改。于是func()
作为叶子函数就可以在不调整栈指针的情况下,使用这块内存保存临时数据。
危险函数
第一类危险函数——scanf
、gets
等
1 | char buf[10]; |
第二类危险函数——strcpy
、strcat
、sprintf
等
1 | int len; |
可以用来代替上面的的安全函数:strncpy
、strcat
、snprintf
,因为这些函数都有一个size
参数用来限制长度
ret2text
形成条件:危险函数 + 合适的.text段代码(一般为后门函数)
ret2shellcode
ret2shellcode,即控制程序执行 shellcode 代码。shellcode 指的是用于完成某个功能的汇编代码,常见的功能主要是获取目标系统的 shell。一般来说,shellcode 需要我们自己填充。这其实是另外一种典型的利用方法,即此时我们需要自己去填充一些可执行的代码。
在栈溢出的基础上,要想执行 shellcode,需要对应的 binary 在运行时,shellcode 所在的区域具有可执行权限。
举例:https://lht.wiki/20211026_ctf_ret2shellcode/
ret2libc
- 简单来说就是利用已知的libc函数,来匹配正确的libc版本,从而计算出system函数的地址
- 举例:https://lht.wiki/20211112-ctf-ciscn_2019_c_1/
参考
- 《CTF竞赛权威指南(pwn篇)》
- https://ctf-wiki.org/
写在最后
一些东西写得很简略,比如ret2xxx部分,虽然是学习笔记而已,大致理解就行,但终究还是因为水平不够才写不出来,希望以后能补充更新