PWN学习之栈溢出
阅读原文时间:2023年07月09日阅读:1

目录

PWN学习之栈溢出

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

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

先让我们来写个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函数的返回值是当s1s2时,返回正数。所以我们想办法让authenticated变量等于0,这样条件才能为假,才能进入恭喜!密码输入正确!的分支。

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

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

源码还是采用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中进行实验,看看真实效果。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章