漏洞分析技术实验2:ROP技术基础
实验2:ROP技术基础
1.实验环境
Linux虚拟机(Ubuntu)
源文件(rop1.c)
1 |
|
2.实验原理
ROP的全称为Return-oriented Programming(返回导向编程)
即计算机安全漏洞利用技术:
绕过可执行空间保护、代码签名等安全保护机制执行恶意代码
通过控制被调用的堆栈对程序的控制流进行劫持,完成某些特定功能
多与栈溢出漏洞结合利用:
覆盖栈帧的返回地址和其他变量,将控制流转移到期望的地址中
3.实验要求
针对实验一,通过gdb调试rop1,确定shellcode的地址;此外,通过rop1.py的调试脚本确定shellcode地址;最终拿到shell权限。相关详细分析过程写入报告,并比较两种方法的特点。
针对实验二的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 | gdb rop1 |
运行 gdb,用其 pwndbg 插件对可执行文件 rop1 进行分析。disass vuln 为查看 vuln 函数的汇编指令。当然也可以拖入 ida 对其进行静态分析。
payload中除了 shellcode 外,要填充足够长度以覆盖返回地址。Buf数组的地址为 ebp-0x88 ,即buf 距离 ebp 有0x88字节,ebp 距离返回地址又 0x4 字节(32位时),覆盖返回地址前先填充 0x8c 个字节。

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

初步脚本
1 | # coding=utf-8 |
确定覆写返回地址的值
Shellcode中要填充的长度解决了,要确定覆盖返回地址的内容,即将要覆盖为的buf数组的地址,在终端进行gdb调试程序,查看内存得到的地址和真正执行程序的地址是不一样的,gdb的调试环境会影响buf变量在内存中的地址。
方法1:开启core dump(用命令可开启)
脚本中我们将shellcode的地址暂时写入一个无意义的值,由于不存在返回地址为0xdeadbeef,当程序执行至此时会崩溃,当程序执行至此时会崩溃,在当前目录下会生成一个core文件。

通过gdb查看core中包含的崩溃信息
1 | gdb ./rop1 core |

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

此时esp指针位于返回地址的下一个位置,因此buf的地址是esp-0x4-0x8c,即地址esp-0x90
buf的地址是0xffffd010,用这个地址替换脚本中的0xdeadbeef,执行脚本即获取shell。

方法2:在脚本中进行gdb调试获取buf数组真实地址
1 | from pwn import * |
利用 gdb.attach(p,’b vuln’) ,直接在脚本中进行gdb调试,弹出新的窗口后执行 c,让程序运行直到崩溃,在通过 x/s $esp-090 查看buf的地址,此时查看到的地址就是真实的地址(同core dump调试一样)。
最终脚本
1 | from pwn import * |
4.2 实验二:ROP绕过NX保护
准备工作(32位)
只开启Canary栈保护:
1 | gcc rop1.c -o rop2 -m32 -fno-stack-protector |
查看rop2进程栈的权限为rw,不可执行。

checksec确认:

漏洞利用(32位)
不能在栈上执行shellcode,但程序中用到了libc库中的read和printf函数。libc.so中保存了大量的可用函数,考虑调用system(‘/bin/sh’)来获取shell。
首先,通过print system 获得system地址(由于关闭了ASLR,system函数在内存中地址不会发生变化)。
1 | gdb rop2 |

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

ROP构造(32位)
最终的payload就是
1 | ’a’*0x8c+p32(sys_addr)+p32(0xdeadbeef)+p32(binsh_addr) |
其中0xdeadbeef是system函数的返回地址,因为获取shell后没有别的操作了,就写一个0xdeadbeef作为返回地址。返回地址后面是system函数的参数,‘/bin/sh’的地址。
攻击脚本(32位)
1 | from pwn import * |

准备工作(64位)
64 位和32 位不同,64位中参数存放在寄存器中,多于6个参数才会放在栈上。
只开启Canary栈保护:
1 | gcc rop1.c -o rop3 -m64 -fno-stack-protector |

漏洞利用(64位)
由于参数不会直接放在栈上,需要寻找类似于 pop rdi;ret的gadget,将参数从栈中弹出到rdi寄存器后,返回到返回地址处继续执行。本例子在栈中事先压入参数’/bin/sh’地址和system地址。
该实验将在栈中事先压入参数’/bin/sh’地址和system地址。首先同32位实验一样,找到系统中system函数地址以及’/bin/sh’存储地址,分别为0x7ffff7a52390和0x7ffff7b99d57,可以看出与32位系统中的地址值不同。


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

ROPgadgets查找结果如下,其中0x21102是相对于libc的偏移。
1 | ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --only "pop|ret"|grep rdi |

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

sys_addr和binsh_addr地址的获取和32位系统下方法一样,但是值发生了变化。
1 | p64(gadget_addr)+ p64(binsh_addr)+p64(sys_addr)+p64(0xdeadbeef) |
攻击脚本(64位)
1 | from pwn import * |

5.实验总结
通过实验,对ROP技术有了直观的认知,与此同时学习了利用python写pwn漏洞的利用脚本。
参考链接
- 标题: 漏洞分析技术实验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 进行许可。