首页
论坛
课程
招聘
[缓冲区溢出] [Windows] [Linux] [原创]栈溢出基础及利用
2021-3-7 21:37 4491

[缓冲区溢出] [Windows] [Linux] [原创]栈溢出基础及利用

2021-3-7 21:37
4491

学习,得向基础看齐。之前面试,别人问了我栈溢出,又问了堆溢出,索性今天就栈溢出基础弄个总结,并结合IDA。希望自己可以有新收获。

1. 栈溢出理论

1)我们先回顾一下call和ret指令,可以认为是函数调用栈。
call的操作是,调用一个过程,指挥处理器从新的内存地址开始执行。过程使用ret来将处理器转回到该过程被调用的程序点上。
从物理的角度来解释,call指令将其返回地址压入堆栈,再把被调用过程的地址复制到指令指针寄存器。当过程准备返回时,它的ret指令从堆栈把返回地址弹回到指令指针寄存器。
函数调用栈在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大。

 

图片描述

2)函数调用背景。
函数状态主要涉及三个寄存器--esp,ebp,eip。esp 用来存储函数调用栈的栈顶地址,在压栈和退栈时发生变化。ebp 用来存储当前函数状态的基地址,在函数运行时不变,可以用来索引确定函数参数或局部变量的位置。eip 用来存储即将执行的程序指令的地址。

 

a.将被调用函数的参数压入栈内
图片描述
b.将被调用函数的返回地址压入栈内
图片描述
c.将调用函数的基地址(ebp)压入栈内,并将当前栈顶地址传到 ebp 寄存器内
图片描述
d.将被调用函数的局部变量压入栈内
图片描述
e.将被调用函数的局部变量弹出栈外
图片描述
f.将调用函数(caller)的基地址(ebp)弹出栈外,并存到 ebp 寄存器内
图片描述
g.将被调用函数的返回地址弹出栈外,并存到 eip 寄存器内
图片描述

3)当函数正在执行内部指令的过程中我们无法拿到程序的控制权,只有在发生函数调用或者结束函数调用时,程序的控制权会在函数状态之间发生跳转,这时才可以通过修改函数状态来实现攻击。
我们只需要让溢出数据用攻击指令的地址来覆盖返回地址就可以了。我们可以在溢出数据内包含一段攻击指令,也可以在内存其他位置寻找可用的攻击指令。

 

图片描述
自此,栈溢出的基础理论讲完。

与此相关的技术,有下面这些。
修改返回地址,让其指向溢出数据中的一段指令(shellcode)
修改返回地址,让其指向内存中已有的某个函数(return2libc)
修改返回地址,让其指向内存中已有的一段指令(ROP)
修改某个被调用函数的地址,让其指向另一个函数(hijack GOT)

2. 栈溢出利用

1)实验目的:证明存在栈溢出漏洞
2)实验环境:IDA,OD,小程序。
3)要求(个人要求):个人突破,熟练使用IDA和OD。
准备代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<iostream>
#include<stdio.h>
using namespace std;
 
#define PASSWORD "1234567"         //The macro defines the value of the password
int verify_password(char* password)
{
    int authenticated;
    char buffer[8];               // add local buffer to be overflowed
    authenticated = strcmp(password, PASSWORD);     //compare the two values
    strcpy(buffer, password);    //over flowed here!
    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("incorrect password!\n\n");
        }
        else
        {
            printf("Congratulation! You have passed the verification!\n");
            break;
        }
    }
    return 0;
}
 
 }
 }
}

看到代码后,怎样来利用了?步骤是怎样的?
1)运行程序,观察情况,特别需要注意字符串和一些特殊的函数。
2)在上一步的基础上,我们在关键点上设下断点。
3)控制台交互,观察栈的运行情况,从而发现破绽。

 

1),导入OD,或者IDA,都可以。当然导入OD,较为简单。这里我选择用IDA,执行本地调试。先执行win32_remote.exe,然后在IDA界面,点击debugger,具体的环境设置,网上很全,当然如果有问题,也可以和我交流。按F9开始执行,看到“please input password:”。这个字符串,很重要,将是我们的突破口。
图片描述
(其实用IDA来调试,查看栈的情况,也可以,但是没有OD方便。)
2)通过上一步的字符串,来定位到该位置。alt+T,可以全局搜索该字符串。在00C717D6这里。
图片描述
搜索到后,选中这一代码段的标记,sub_C717B0,快捷键N,实现改名。
图片描述
为了进一步确定,我们可以通过查看伪代码,具体的情况,如图。
图片描述
这里注意到一个关键的函数,sub_C712AD,我们双击进入,看到返回的是另一个函数sub_C71640,我们继续点击,看到了这个代码段。
图片描述
这里可以确认,密码是“1234567”。有些时候,某些程序的密码就在程序的内部,也有的是一个密码生成器,自动生成,需要逆向算法。j_strcmp是一个比较的接口,j_strcpy是将输入的密码,复制到&Dest上。可以确定这个&Dest就是栈的开头。下图是该栈的情况。
图片描述
图片描述
这里有个8很明显。记住。
我们进入OD,Ctrl+G,搜索这个,00C717D6,并F2,下断点。
图片描述
F9运行程序,可以看到正好停在这里。
这时,我输入“12345678”,这个比正确的密码多了一个8,结果竟然成功了。
图片描述
这不是重点,重点是观察寄存器的变化。
3)上一步,我们已经设置了断点,输入了8个数,就成功的通过了。我们来单步执行,重点观察一下寄存器。

  • 这是输入前,栈的情况
    图片描述
  • 输入错误的数据后,栈的情况
    图片描述
    图片描述
    图片描述
  • 输入正确的数据后,栈的情况
    图片描述
    这里是不是说明,当我们填充的数据把008FF934填的只剩下两个,就可以了呢。
  • 我们再次填入12345678,因为我们已经知道这个密码是可以通过验证的。
    图片描述
  • 为了更确信,我们填入123456789,来试试。最后发现错误。
    图片描述
    综上分析,当我们把00BEF64C这一行填满的时候,可以实现栈溢出,从而通过验证。为了验证这种猜想,我们再设计一个输入密码,1234567q,试试看,为什么成不成功。
    图片描述
    图片描述
    最后的结果都成功了。
    后来经过大量的验证,发现如下的密码都可以通过,比如1234567q,1234577q,
    1237890q,1234980q,还有很多。但是也有一些是通不过的,比如:1231237q,1233217q等。
    根据逻辑,现在就有两个问题需要解决,1.有些密码可以,有些不可以,那么它是通过什么来判断的呢? 2.为什么当输入的密码溢出栈后,说白了,就是太长,就会报错,而刚刚好我们把栈填满,就不报错呢?

3. 解答上面的问题

第一个问题

通过查阅资料,得到了下面的一些启发。
程序的关键地方是strcpy(buffer,password);
栈内的依次存放的是authenticated、局部变量char buffer[8]。
图片描述
当我们输入的密码,超过buffer的长度,正好覆盖掉authenticated。而在源代码中authenticated是否等于0,用来说明当前密码正确与否的状态,。转换到汇编代码中,[ebp-4]是否等于0,用来判断密码是否正确。
图片描述
所以,只需要输入任意八位数字,填满buffer,利用字符截断符NULL来填充[ebp-0x4]的值,就可以绕过密码验证。
图片描述

第二个问题

当我们输入更长的数字后,会发生什么呢?
首先authenticated会修改,同时后面的EBP和返回地址也会修改,查阅资料上解释的是,程序崩溃,出现异常。但是我觉得不是的。因为当我故意夸大输入的长度时,才会出现奔溃。当我仅仅输入10个数字的时候,是没有出现崩溃的。 图片描述
至于它的崩溃点是那里,这里可以通过pattern字符来验证,我不做深究。
从逻辑上,可以认为是,当我们输入的长度过长,覆盖了ret,会导致栈无法正常返回,从而堆栈不平衡,导致崩溃。
如果大家对此,有什么好的看法,请私信告诉我。下一篇,堆溢出。


[看雪官方培训] Unicorn Trace还原Ollvm算法!《安卓高级研修班》2021年6月班火热招生!!

最后于 2021-3-8 19:52 被奋进的小杨编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (4)
雪    币: 10849
活跃值: 活跃值 (7453)
能力值: (RANK:600 )
在线值:
发帖
回帖
粉丝
有毒 活跃值 9 2021-3-8 09:02
2
0
等待师傅完善,支持
雪    币: 1724
活跃值: 活跃值 (1351)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
奋进的小杨 活跃值 2021-3-9 09:01
3
0
有毒 等待师傅完善,支持
版主,帮帮看看,哪些需要修改的
雪    币: 6685
活跃值: 活跃值 (6316)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
SSH山水画 活跃值 2 2021-3-10 15:41
4
0
坐等堆溢出
雪    币: 1187
活跃值: 活跃值 (232)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Fruei 活跃值 2021-3-19 08:09
5
0
学习了
游客
登录 | 注册 方可回帖
返回