PWN配置

一,栈帧是什么


栈帧,也就是stack frame,其本质就是一种栈,只是这种栈专门用于保存函数调用过程中的各种信息(参数,返回地址,本地变量等)。栈帧有栈顶和栈底之分,其中栈顶的地址最低,栈底的地址最高,SP(栈指针)就是一直指向栈顶的。在x86-32bit中,我们用 %ebp 指向栈底,也就是基址指针;用 %esp 指向栈顶,也就是栈指针。下面是一个栈帧的示意图:

一般来说,我们将 %ebp 到 %esp 之间区域当做栈帧(也有人认为该从函数参数开始,不过这不影响分析)。并不是整个栈空间只有一个栈帧,每调用一个函数,就会生成一个新的栈帧。在函数调用过程中,我们将调用函数的函数称为“调用者(caller)”,将被调用的函数称为“被调用者(callee)”。在这个过程中,1)“调用者”需要知道在哪里获取“被调用者”返回的值;2)“被调用者”需要知道传入的参数在哪里,3)返回的地址在哪里。同时,我们需要保证在“被调用者”返回后,%ebp, %esp 等寄存器的值应该和调用前一致。因此,我们需要使用栈来保存这些数据。

  二,函数的调用


我们直接通过实例来看函数是如何调用的。这是一个有参数但没有调用任何函数的简单函数,我们假设它被其他函数调用。

int MyFunction(int x, int y, int z) { int a, b, c; a = 10; b = 5; c = 2; ... } int TestFunction() { int x = 1, y = 2, z = 3; MyFunction1(1, 2, 3); ... }
对于这个函数,当调用时,MyFunction()的汇编代码大致如下:

_MyFunction: push %ebp ; //保存%ebp的值 movl %esp, $ebp ; //将%esp的值赋给%ebp,使新的%ebp指向栈顶 movl -12(%esp), %esp ; //分配额外空间给本地变量 movl $10, -4(%ebp) ; movl $5, -8(%ebp) ; movl $2, -12(%ebp) ;
此时调用者做了两件事情:第一,将被调用函数的参数按照从右到左的顺序压入栈中。第二,将返回地址压入栈中。这两件事都是调用者负责的,因此压入的栈应该属于调用者的栈帧。我们再来看看被调用者,它也做了两件事情:第一,将老的(调用者的) %ebp 压入栈,此时 %esp 指向它。第二,将 %esp 的值赋给 %ebp, %ebp 就有了新的值,它也指向存放老 %ebp 的栈空间。这时,它成了是函数 MyFunction() 栈帧的栈底。这样,我们就保存了“调用者”函数的 %ebp,并且建立了一个新的栈帧
 只要这步弄明白了,下面的操作就好理解了。在 %ebp 更新后,我们先分配一块0x12字节的空间用于存放本地变量,这步一般都是用 sub 或者 mov 指令实现。在这里使用的是 movl。通过使用 mov 配合 -4(%ebp), -8(%ebp) 和 -12(%ebp) 我们便可以给 a, b 和 c 赋值了。

三,堆栈溢出原理

在计算机里,堆栈是内存里的一段区域。堆一般由程序员分配释放,如果程序员不释放,程序结束时可能由操作系统回收,分配方式类似于数据结构中的链表;栈由操作系统自动分配释放,存放函数的参数值、局部变量、返回地址等,分配方式类似于数据结构中的栈。以堆栈溢出为代表的缓冲区溢出已经成为最普遍的安全漏洞,由此引发的安全问题比比皆是。堆栈溢出的原因一般有以下几种:
1,函数调用层次太深。函数递归调用时,系统要在栈中不断保存函数调用时的现场和产生的变量,,如果递归调用太深,就会造成栈溢出,这是递归无法返回。再者,当有函数调用层次过深时也可能导致栈无法容纳这些调用的返回地址而造成栈溢出。

2,动态申请空间(可参考下文了解更多)C/C++动态内存开辟详解(含常见错误以及经典面试题)_利刃Cc的博客-CSDN博客_c++动态开辟空间使用之后没有释放。由于c语言中没有垃圾资源自动回收机制,因此,需要程序主动释放已经不再使用的动态地址空间。申请的动态空间使用的是堆空间,动态空间使用不会造成堆溢出。
3,数组访问越界。C语言没有提供数组下标越界检查,如果在程序中出现数组下标访问超出数组范围,在运行过程中可能会内存访问错误。

4.指针非法访问。指针保存了一个非法的地址,通过这样的指针访问所指向的地址时会产生内存访问错误。

堆溢出:不断的new一个对象,一直创建新的对象

栈溢出:死循环或者是递归太深的原因,可能太大或者没有终止。

通常堆栈溢出是指调用堆栈溢出,溢出是指这个数据结构的满溢,不能存放更多数据,其他的数据结构也会遇到这种情况。即使数据结构并非固定容量,而是可扩展的,在有限的内存空间下仍是有满溢的机会。

在一些高级语言中,类似python, java, go等,有一些机制用于防止栈溢出,比如,python默认的递归深度是1000,当递归调用超过这个深度后就会引发异常。此外,编译器层面上也有对堆栈进行保护,其中最著名的是Stack Guard和Stack-smashing Protectection。在操作系统的层面上,为了减少堆栈溢出带来的危害,还有类似于地址空间随机化的机制。

四,操作系统内置的安全机制

Windows:

1,DEP(data execution prevention):使得系统能够标记一个或多个内存页的属性为不可执行(意味着代码不能在该内存页中执行),有助于缓解缓冲区溢出攻击的危害。

2,ASLR(address space layout randomization):加载地址随机化机制

开启方式:vs开发环境中的链接器下的高级选项中可以设置映像随机化,堆栈随机化,PEB和TEB随机化

3,GS机制:缓冲区安全检查,编译器会为每个函数调用的入口增加额外的随机数(被称为canary),它位于EBP之前,同时在data内存区存放该随机数的副本,当函数发生溢出时,它会被覆盖,函数在返回之前会比较data区中的副本的值和栈帧的真实值,如果两者不同,则触发异常处理流程。

开启方式:vs开发环境中的c/c++下的代码生成中的开启缓冲区安全检查

Linux:

1,NX机制(no-execute):同Windows上的DEP功能相似

关闭NX机制(于是JMP与ESP无法使用);编译时增加 -z execstack; 开启NX机制:编译时增加 -z noexecstack(默认开启)

2,PIE机制(position-independent-executables):位置独立的可执行区域,同Windows系统的ASLR功能相似,一般同NX机制配合使用,能够有效组织在堆栈上运行恶意代码,PIE有三种工作模式:1,工作模式0:关闭进程地址空间随机化

          2,工作模式1:开启mmap机制,stack和vdso的地址随机化

           3,工作模式2:在1的基础上,增加堆地址随机化

3,Cannary栈保护:同Windows上的GS机制,一种预防缓冲区溢出的手段,

开启cannary栈保护,在编译时增加 -fstack-protector(只为含char数组的函数加入保护代码)和-fstack-protector-all(为所有函数加入保护代码)

关闭cannary保护,在编译时增加 -fno-stack-protector(默认不开启)

4,RelRo机制:设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。

全部开启RelRo机制:在编译时增加 -z now

部分开启RelRo机制:在编译时增加 -z lazy(默认配置)

关闭RelRo机制: 在编译时增加 -z norelro

五,配置pwn环境的虚拟机

创建ubuntu64位系统的虚拟机,输入相应指令,配置pwntools,pwndbg,libcsearcher动态库。

六,pwndbg基础动态调试的使用

使用
pwndbg的使用方式与GDB类似,我们可以用它来进行动态调试

步骤如下:

1.编译

首先需要编译目标二进制文件,确保在编译时开启调试信息。例如,使用gcc编译:

gcc -g -o target target.c
2.启动pwndbg

在终端中输入以下命令启动pwndbg

gdb -q target
 这将启动GDB并自动加载pwndbg插件。接下来,附加到目标二进制文件:

attach $(pgrep target)
3.开始调试

现在可以开始使用pwndbg进行动态调试。以下是一些常用的命令:

break:
设置断点。
run:
启动程序。
continue:
继续执行程序。
next:
单步执行程序,跳过函数调用。
step:
单步执行程序,进入函数调用。
backtrace:
显示函数调用栈。
info:
显示程序信息,例如变量值、寄存器值、内存地址等。
x:
显示内存中的内容。
set:
设置变量的值。

context:
显示当前上下文信息,包括寄存器值、堆栈信息和内存映射等。
heap:
显示堆信息,包括堆块的大小、地址和状态等。
telescope:
显示一段内存区域的内容。
ropgadget
 显示二进制文件中可用的ROP gadget。
search:
在内存中搜索指定的字符串或模式。
asm:
将汇编指令转换成机器码。
breakpoints:
显示断点信息。
vmmap:
显示进程的虚拟内存映射。
pattern:
生成用于缓冲区溢出的模式字符串。

七,ret2text

最简单的栈溢出利用:ret2text
通过栈溢出修改call指令保存在栈上的返回地址(eip的值),这样cpu执行ret指令的时候,就会将被修改的值从栈上取出放入eip寄存器中,紧接着执行eip所指向的位置的指令,这样就相当于控制了程序的执行流。

  • 26
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值