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. 关于config配置问题

    RabbitMq程序需要配置 <runtime>    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1&q ...

  2. P7597 「EZEC-8」猜树 加强版

    #include<bits/stdc++.h>using namespace std;#define rg register#define inf 0x3f3f3f3f#define ll ...

  3. jmeter 录制排除模式

    jmeter录制时,静态的资源不需要,可以在录制的时候直接排除. .*\.(bmp|css|js|gif|icov|jpeg|png|swf|woff|woff2|htm|html).* .*\.(j ...

  4. 常用的jquery 中一些js

    目录: 1.验证用户登录信息 2. 获取下拉框所选中的元素 3.  动态获取 id 和对应文本框的值  4. table 中 tr  的隐藏 5 . 更换图片  6. ajax  进行提交 7. 判断 ...

  5. jmeter加密解密(解密篇)

    上一篇已经讲解了公钥加密,这篇讲解公钥解密.解密比较简单,直接操作吧. 需求是:接口中的请求体的部分参数需要先加密再请求,返回的结果中部分字段需解密. 1.在请求下新建beanshell后置处理程序, ...

  6. Kettle启动时报错Cannot create java virtual machine & A java exception has occurred

    开源免费--最喜欢的四个字没有之一 1.官网下载 https://sourceforge.net/projects/pentaho/files/Data%20Integration/ 下载完后,解压即 ...

  7. Winform 控件命名规范

    前言 最近 Winform 项目做得比较多,控件命名规范上常用的能记住,但是有些总要查,写个记录吧.方便以后自己用,大家也可以参考. 标准控件 序号 控件类型简写 控件类型 1 btn Button ...

  8. Excel备忘录

    1. 导入文本文件(.txt) 2. 排序 3. 批量填充空白 选定区域,Ctrl+G,定位,空值. 输入内容,Ctrl+Enter. 4. 清除无法修改的背景色. 5. 身份证号 数字精度为15位, ...

  9. HCNP Routing&Switching之BGP基础

    前文我们了解了路由注入带来的问题以及解决方案相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15362604.html:今天我们来学习下新的路由协议BG ...

  10. 查看显卡报错:NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver. Make sure that the latest NVIDIA driver is installed and running.

    当输入nvidia-smi时出现 NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver. Make ...