PWN学习之栈溢出

前言

我记得我在最开始学编程的时候,经常会听到老师说输入的时候要注意大小,不要超过数组大小否则会造成缓冲区溢出导致程序崩溃的。

当时就觉得溢出就溢出咯,崩溃就崩溃咯,难不成还能导致电脑被攻击吗?就偏偏不控制输入长度。

写bug

先让我们来写个bug体验一下,下面这段程序要求用户输入字符串并且把数据给buffer数组,如果超过12长度的字符串就会造成缓冲区溢出!

bug.cpp源码

#include <stdio.h>
void bug()
{
char buffer[12]={"1"};
scanf("%s",buffer);
}
int main()
{
__asm //加nop是为了方便在OD里面定位到
{
nop;
nop;
nop;
}
bug();
__asm //可以忽略不用理会
{
nop;
nop;
nop;
}
return 0;
}

目标环境:

  • Windows 7旗舰版(64位)
  • VC++ 6.0
  • Notepad++

用cl来编译源码并且生成带.asm的汇编源码文件,编译命令是cl.exe -FAS ./bug.cpp

进入到lab01目录编译源码后,生成了.exe 和 .asm文件。

然后我们运行bug.exe测试下输入超过12长度的字符串看看程序反应会怎么样,程序不出意外的崩溃掉了。

OD动态调试bug.exe

OK我们用OD进行调试来看看堆栈中实际的情况。

我这里用的是x32dbg,载入后找到3条nop指令就可以定位到调用bug函数附近的汇编代码了。

0x0040103A位置下断点。

接着F9让程序运行到这里,此时我们注意观察堆栈,接着马上要按F7了。

按F7让程序进入bug函数的内部,并且这时候仔细看堆栈。

我们会发现进入函数内部后首先是栈顶发生了变化,之前的栈顶为0x0018FF3C,现在的栈顶为0x0018FF38,栈顶减少了4字节,说明发生了push操作,push xx等于esp-4也就是0x0018FF38的地址,并且此时把内容送入esp(栈顶)地址。

可以发现送入esp地址的内容是调用bug函数的下一句代码的地址,也就是当F7 进入call bug函数的时候,其实是先执行了push eip的操作,因为此时eip=下一句代码地址,然后再jmp bug函数处执行代码。

OD调试观察溢出

好了接下来我们继续F8单步执行,当F8后此时注意ebp被压入了堆栈,估计这个问题会有很多新手做ctf pwn的时候被坑,他们计算出溢出大小后直接+4进行了此地址的覆盖,以为覆盖到了ret地址,其实ret地址在下面,导致拿不到flag。

当F8执行完sub esp,0xC汇编指令后,栈顶位置减了12字节,用来存放buffer数组的数据。

之后我们一路执行到call函数这里的时候,发现又有两个数据入栈了,这是调用scanf函数术后的参数,我们可以不用管它,然后我们在F8步过call后,程序要求我们输入字符串,我们输入AAAAAAAAAAAABBBBCCCC 12个A代表填满buffer数组,BBBB代表溢出覆盖ebp寄存器,CCCC代表溢出覆盖ret地址。

-----------------------------------溢出前--------------------------------
0018FF20 00407034 bug.00407034 |参数1:"%s"
0018FF24 0018FF28 L"1" |参数2:buffer数组地址
0018FF28 00000031 ;buffer数组起始位置
0018FF2C 00000000 ;buffer
0018FF30 00000000 ;buffer
0018FF34 0018FF48 ;ebp寄存器
0018FF38 0040103F ;call bug函数的下一句汇编指令地址 |返回到 bug.00401046 自 bug.00401000 -----------------------------------溢出后----------------------------------
0018FF20 00407034 bug.00407034 |参数1:"%s"
0018FF24 0018FF28 "AAAAAAAAAAAABBBBCCCC" |参数2:buffer数组地址
0018FF28 41414141 ;buffer数组起始位置
0018FF2C 41414141 ;buffer
0018FF30 41414141 ;buffer
0018FF34 42424242 ;ebp寄存器
0018FF38 43434343 ;call bug函数的下一句汇编指令地址 |返回到 bug.00401046 自 bug.00401000

栈溢出攻击之突破密码验证

这里其实主要就是利用栈溢出覆盖掉局部变量的值,让其改变流程。

#include <stdio.h>
#define PASSWORD "1234567"
int verify_password(char *password)
{
int authenticated;
char buffer[8];
authenticated = strcmp(password,PASSWORD);
_asm nop;
strcpy(buffer,password);//缓冲区溢出!!!!
return authenticated;
} int main()
{
int valid_flag = 0;
char password[1024];
while(1)
{
printf("Please input password:");
scanf("%s",password);
valid_flag = verify_password(password);
if(valid_flag)
{
printf("密码错误\n\n");
}else
{
printf("恭喜!密码输入正确!\n");
break;
}
}
}

只有当密码是1234567的时候才会停止循环,并且输出密码正确。

用溢出方式来破解密码程序,首先strcmp函数的返回值是当s1<s2时,返回为负数;当s1=s2时,返回值= 0;当s1>s2时,返回正数。所以我们想办法让authenticated变量等于0,这样条件才能为假,才能进入恭喜!密码输入正确!的分支。

所以我们只要输入8位数字,这时候\0 结尾符刚好能覆盖到 authenticated变量使其等于0,从而改变程序判断。

可以看到je会走到密码正确的分支。

x64位栈溢出

源码还是采用bug.cpp的然后用VisualStudio编译成x64的。

载入IDA来看一下他的汇编代码,可以发现32位寄存器都变成了64位寄存器。

还有x64和x86的还有个区别就是函数调用栈的区别,在x64中只有fastcall函数调用约定,定义如下。

参数1、参数2、参数3、参数4分别保存在 RCX、RDX、R8D、R9D ,剩下的参数从右往左依次入栈,被调用者实现栈平衡,返回值存放在 RAX 中。
#C语言代码
int fastcall_sum = fastcall_add(1, 2, 3, 4, 5, 6, 7);
#----------------------汇编代码--------------------------------
00007FF6577A366E mov dword ptr [rsp+30h],7 ;超过参数4保存在栈中
00007FF6577A3676 mov dword ptr [rsp+28h],6 ;超过参数4保存在栈中
00007FF6577A367E mov dword ptr [rsp+20h],5 ;超过参数4保存在栈中
00007FF6577A3686 mov r9d,4 ;参数4
00007FF6577A368C mov r8d,3 ;参数3
00007FF6577A3692 mov edx,2 ;参数2
00007FF6577A3697 mov ecx,1 ;参数1
#调用fastcall_add函数
00007FF6577A369C call fastcall_add (07FF6577A11C2h)
00007FF6577A36A1 mov dword ptr [fastcall_sum],eax # 返回值
#-------------------------------------------------------------- #---------------------fastcall_add函数--------------------------
int __fastcall fastcall_add(int a, int b, int c, int d, int e, int f, int g)
{
00007FF6D22D1790 mov dword ptr [rsp+20h],r9d ;参数4给临时变量
00007FF6D22D1795 mov dword ptr [rsp+18h],r8d ;参数3给临时变量
00007FF6D22D179A mov dword ptr [rsp+10h],edx ;参数2给临时变量
00007FF6D22D179E mov dword ptr [rsp+8],ecx ;参数1给临时变量
00007FF6D22D17A2 push rbp ;rbp入栈 ,和x86一样待会会用到
00007FF6D22D17A3 push rdi ;rdi入栈 , 不清楚
00007FF6D22D17A4 sub rsp,0E8h ;将栈顶向下拉232个字节
00007FF6D22D17AB mov rbp,rsp ;把rsp栈顶给了rbp 方便寻址定位
00007FF6D22D17AE mov rdi,rsp ;把rsp栈顶也给了rdi
00007FF6D22D17B1 mov ecx,3Ah ;循环变量58
00007FF6D22D17B6 mov eax,0CCCCCCCCh ;烫烫烫烫
00007FF6D22D17BB rep stos dword ptr [rdi] ;循环58此
00007FF6D22D17BD mov ecx,dword ptr [rsp+108h] int sum = a+b+c+d+e+f+g;
00007FF6D22D17C4 mov eax,dword ptr [b]
00007FF6D22D17CA mov ecx,dword ptr [a]
00007FF6D22D17D0 add ecx,eax ;a+b
00007FF6D22D17D2 mov eax,ecx
00007FF6D22D17D4 add eax,dword ptr [c];+c
00007FF6D22D17DA add eax,dword ptr [d];+d
00007FF6D22D17E0 add eax,dword ptr [e];+e
00007FF6D22D17E6 add eax,dword ptr [f];+f
00007FF6D22D17EC add eax,dword ptr [g];+g
return sun;
00007FF6D22D17F2 mov dword ptr [sum],eax;存放总和 }
00007FF6D22D17F8 lea rsp,[rbp+0E8h]
00007FF6D22D17FF pop rdi
00007FF6D22D1800 pop rbp
00007FF6D22D1801 ret # 没做栈平衡

我们在汇编代码中找到调用bug函数的地方,因为我们bug.cpp中bug函数并没有参数,所以不需要关心这些参数调用方式。

然后注意看bug函数的这个地方,我们发现栈顶被拉低了96个字节,也就是12*8其中在这里一个char占用了8位,然后12个元素x8就是96个字节了。然后我们需要输入超过8x4=32个字符才能溢出。

在x64dbg中进行实验,看看真实效果。

PWN学习之栈溢出的更多相关文章

  1. PWN学习之整数溢出

    目录 PWN学习之整数溢出 整数溢出 溢出和回绕 漏洞多发函数 整数溢出例子 PWN学习之整数溢出 整数溢出 如果一个整数用来计算一些敏感数值,如缓冲区大小或数值索引,就会产生潜在的危险.通常情况下, ...

  2. PWN学习之格式化字符串漏洞

    目录 PWN学习之格式化字符串漏洞 格式化输出函数 格式化字符串漏洞 漏洞利用 使程序崩溃 栈数据泄露 任意地址内存泄漏 栈数据覆盖 任意地址内存覆盖 PWN学习之格式化字符串漏洞 格式化输出函数 可 ...

  3. [二进制漏洞]PWN学习之格式化字符串漏洞 Linux篇

    目录 [二进制漏洞]PWN学习之格式化字符串漏洞 Linux篇 格式化输出函数 printf函数族功能介绍 printf参数 type(类型) flags(标志) number(宽度) precisi ...

  4. pwn学习(1)

    0x00 简介 入职之后,公司发布任务主搞pwn和re方向,re之前还有一定的了解,pwn我可真是个弟弟,百度了一番找到了蒸米大佬的帖子,现在开始学习. 0x01 保护方式 NX (DEP):堆栈不可 ...

  5. pwn学习之一

    刚刚开始学习pwn,记录一下自己学习的过程. 今天完成了第一道pwn题目的解答,做的题目是2017年TSCTF的bad egg,通过这道题学习到了一种getshell的方法:通过在大小不够存储shel ...

  6. pwn入门之栈溢出练习

    本文原创作者:W1ngs,本文属i春秋原创奖励计划,未经许可禁止转载!前言:最近在入门pwn的栈溢出,做了一下jarvisoj里的一些ctf pwn题,感觉质量都很不错,难度循序渐进,把自己做题的思路 ...

  7. CTF必备技能丨Linux Pwn入门教程——栈溢出基础

    这是一套Linux Pwn入门教程系列,作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的一些题目和文章整理出一份相对完整的Linux Pwn教程. 课程回顾>>Linux ...

  8. pwn学习日记Day5 基础知识积累

    知识杂项 int mprotect(const void *start, size_t len, int prot); mprotect()函数把自start开始的.长度为len的内存区的保护属性修改 ...

  9. Arm pwn学习

    本文首发于“合天智汇”公众号 作者:s0xzOrln 声明:笔者初衷用于分享与普及网络知识,若读者因此作出任何危害网络安全行为后果自负,与合天智汇及原作者无关! 刚刚开始学习ARM pwn,下面如有错 ...

随机推荐

  1. jdbc 数据库连接 长时间空闲 断开连接 ApplicationContext.xml

    数据库连接 长时间空闲 断开连接solution: <property name="validationQuery" value="select 1"/& ...

  2. 深入剖析 Laravel 服务容器

    https://cloud.tencent.com/developer/article/1340400

  3. 03 依赖注入--01控制反转、IoC模式

    控制反转Inversion of Control DI和IoC几乎都是成对出现的,我们在理解依赖注入之前首先要弄明白什么是IoC,也就是控制反转,体现的就是控制权的转移,即控制权原来在A中,现在需要B ...

  4. 【Golang】Go 通过结构(struct) 实现接口(interface)

    一.通过结构(struct) 实现 接口(interface) 1.在了解iris框架的时候,经常看到有这样去写的使用一个空结构体作为接收器,来调用方法,有点好奇这样做有什么意义. 解释:在 Go 语 ...

  5. mysql int(3)与int(10)的数值范围相同吗?

    提问: mysql的字段,unsigned int(3), 和unsinged int(6), 能存储的数值范围是否相同.如果不同,分别是多大? 回答: 不同,int(3)最多显示3位无符号整体,in ...

  6. 在Windows Server 2012R2离线安装.net framework3.5

    最近新装了一台Windows Server 2012 R2的服务器,安装数据库时,出现了提示安装不上 .net framework3.5的情况,经过网络上多次的资料查找及反复试验终于找到了一个可以解决 ...

  7. 怒肝 Linux 学习路线,这回不难

    Linux 学习路线 by 鱼皮. 原创不易,请勿抄袭,违者必究! 大家好,我是鱼皮,又花 1 周肝出了 Linux 学习资料全家桶,包括学习路线.命令手册.视频.书籍.文档.实战教程.社区.工具.大 ...

  8. 从commons-beanutils反序列化到shiro无依赖的漏洞利用

    目录 0 前言 1 环境 2 commons-beanutils反序列化链 2.1 TemplatesImple调用链 2.2 PriorityQueue调用链 2.3 BeanComparator ...

  9. Rigidbody钢体移动时抖动问题

    Rigidbody移动时抖动问题 撞墙抖动 Unity中物体移动有非常多的方式: 比如: transform.position += dir*speed*Time.deltaTime; transfo ...

  10. 用最简单的方式理解 IoC 控制反转

    思想引入 假设一个系统原先只设定有一个默认的方法去完成业务,这里举例这个原先设定开发的是 UserDaoImpl(可能有些牵强,但是不影响我们对逻辑的理解)这样一个业务. 后来有一天,需求变了,业务流 ...