目录
我记得我在最开始学编程的时候,经常会听到老师说输入
的时候要注意大小,不要超过数组大小否则会造成缓冲区溢出
导致程序崩溃
的。
当时就觉得溢出就溢出咯,崩溃就崩溃咯,难不成还能导致电脑被攻击吗?就偏偏不控制输入长度。
先让我们来写个bug体验一下,下面这段程序要求用户输入字符串并且把数据给buffer数组,如果超过12长度的字符串就会造成缓冲区溢出!
#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;
}
目标环境:
用cl来编译源码并且生成带.asm
的汇编源码文件,编译命令是cl.exe -FAS ./bug.cpp
进入到lab01
目录编译源码后,生成了.exe 和 .asm文件。
然后我们运行bug.exe测试下输入超过12长度的字符串看看程序反应会怎么样,程序不出意外的崩溃掉了。
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函数
处执行代码。
好了接下来我们继续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
函数的返回值是当s1authenticated
变量等于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中进行实验,看看真实效果。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章