吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2252|回复: 20
收起左侧

[分享] 栈溢出---《0day2安全》

  [复制链接]
rgzz 发表于 2023-2-15 23:08

基础知识

二进制文件概述

PE文件格式

PE(Portable Executable)是 Win32 平台下的可执行文件(如:"*.exe","*.dll"),PE文件规定了所有信息(二进制机器代码、字符串、菜单、图标、位图、字体等)在可执行文件中如何组织。

PE 文件格式把可执行文件分成若干个数据节(section):

  • .text    二进制的机器代码
  • .data    初始化的数据块
  • .idata    动态链接库
  • .rsrc    程序的资源

系统栈的工作原理

内存的不同用途

缓冲区溢出:大缓冲区向小缓冲区复制,撑爆了小缓冲区,从而冲掉了和小缓冲区相邻内存区域的其它数据而引起的内存问题。

进程使用的内存划分:

  1. 代码区
  2. 数据区
  3. 堆区
  4. 栈区
    image-20220519164948399.png

函数调用过程

同一文件不同函数的代码在内存代码区中是散乱无关的,但都在同一个 PE 文件的代码所映射的一个 “节” 里。

intfunc_B(int arg_B1, int arg_B2)
{
    int var_B1, var_B2;
    var_B1=arg_B1+arg_B2;
    var_B2=arg_B1-arg_B2;
    return var_B1*var_B2;
}
intfunc_A(int arg_A1, int arg_A2)
{
    int var_A;
    var_A = func_B(arg_A1,arg_A2) + arg_A1;
    return var_A;
}
int main(int argc, char **argv, char **envp)
{
    int var_main;
    var_main=func_A(4,3);
    return var_main;
}

当函数被调用时,系统栈会为这个函数开辟一个新的栈帧,并把它压入栈中。这个栈帧的内存空间被它所属的函数独占。当函数返回时,系统栈会弹出该函数所对应的栈帧。
image-20220221193711651.png

函数调用时,栈中的变化:
image-20220519170737556.png

函数调用相关约定

image-20220519171050801.png

如果要明确使用某一种调用约定,在函数前加上调用约定的声名即可。默认调用是__stdcall 调用方式,从右向左将参数入栈。

特例:C++类成员中的 this 指针,一般用 ECX 寄存器传递。用GCC编译器编译,他会作为最后一个参数压栈。

函数调用步骤:

  1. 参数入栈
  2. 返回地址入栈
  3. 代码区跳转
  4. 栈帧调整:
    保存当前栈帧状态值,已备后面恢复本栈帧时使用( EBP 入栈);
    将当前栈帧切换到新栈帧(将 ESP 值装入 EBP,更新栈帧底部);
    给新栈帧分配空间(把 ESP 减去所需空间的大小,抬高栈顶);  

__stdcall 调用约定,函数调用指令:

                ;调用前
push 参数 3 ;假设该函数有 3 个参数,将从右向左依次入栈
push 参数 2
push 参数 1
call 函数地址;call 指令将同时完成两项工作: 
;a)向栈中压入当前指令在内存中的位置,即保存返回地址。 
;b)跳转到所调用函数的入口地址函数入口处
push ebp ;保存旧栈帧的底部
mov ebp, esp ;设置新栈帧的底部(栈帧切换)
sub esp, xxx ;设置新栈帧的顶部(抬高栈顶,为新栈帧开辟空间)

函数返回的步骤:

  1. 保存返回值:通常保存在 EAX 中。

  2. 弹出当前栈帧,恢复上一个栈帧。
    具体操作:

    1. 在堆栈平衡的基础上,给 ESP 加上栈帧的大小,降低栈顶,回收当前栈帧的空间
    2. 将当前栈帧底部保存的前栈帧 EBP 值弹入 EBP 寄存器,恢复出上一个栈帧。
    3. 将函数返回地址弹给 EIP 寄存器。
      image-20220519172202601.png
  3. 跳转

函数返回时,相关指令:

add esp, xxx ;降低栈顶,回收当前的栈帧
pop ebp;将上一个栈帧底部位置恢复到 ebp,
retn;这条指令有两个功能: 
;a)弹出当前栈顶元素,即弹出栈帧中的返回地址。
;至此,栈帧恢复工作完成。 
;b)让处理器跳转到弹出的返回地址,恢复调用前的代码区

image-20220221170612199.png

修改邻接变量

修改邻接变量原理

函数的局部变量在栈中相邻排列。如果局部变量有数组之类的缓冲区,并且程序中存在数组越界缺陷,那么越界的数组就能破坏相邻变量,甚至能破坏 EBP 、返回地址。

#include <stdio.h>
#define PASSWORD "tel:1234567"
int verify_password (char *password)
{
    int authenticated;
    char buffer[8];// add local buffto be overflowed
    authenticated=strcmp(password,PASSWORD);
    strcpy(buffer,password);//over flowed here!
    return authenticated;
}
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;
        }
    }
}

当程序执行到 int verify_password(char *password)时,栈帧状态如下图:
image-20220221170630782.png

改变程序流程思路:

可以发现,authenticated 变量来源于 strcmp 函数的返回值,它被返回给main函数作为验证标志。当 authenticated 为 0 时,标识验证成功;反之,验证不成功。

当我们输入超过 7 个字符的密码(注意:字符截断符 NULL 将占用一个字节),就有机会把 authenticated 覆盖为 0,从而绕过密码验证。

突破密码验证程序

推荐使用的环境 备 注
操作系统 Windows XP SP3 其他 Win32 操作系统也可进行本实验
编译器 Visual C++ 6.0 如使用其他编译器,需重新调试
编译选项 默认编译选项 VS2003 和 VS2005 中的 GS 编译选项会使栈溢出实验失败
build 版本 debug 版本 如使用 release 版本,则需要重新调试

说明: 如果完全采用实验指导所推荐的实验环境,将精确地重现指导中所有的细节;否则需要根据具体情况重新调试。

(1)先验证一下正确密码,输入“1234567”,通过验证,结果如下图所示:
image-20220519210442088.png

(2)再来分析一下具体覆盖时,栈中的情况,输入“qqqqqqq”,因为“qqqqqqq”>“1234567”,所以 strcmp 应该返回 1,即 authenticated 为 1。
image-20220519213526513.png

局部变量名 内存地址 偏移 3 处的值 偏移 2 处的值 偏移 1 处的值 偏移 0 处的值
buffer[0~3] 0x0012FB18 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’)
buffer[4~7] 0x0012FB1C NULL 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’)
authenticated 0x0012FB20 0x00 0x00 0x00 0x01

观察内存时,注意 “内存数据” 与 “数值数据” 的区别。Win32 系统在内存中由低位向高位存储一个 4 字节的双字(DWORD),但在作为 ”数值“ 应用的时候,却是按照由高位字节向低位字节进行解释。“内存数据” 中的 DWORD 和我们逻辑上使用的 “数值数据” 是按字节序逆序过的。

(3)输入超过 7 个字符,“qqqqqqqqrst”,结果如下图:
image-20220519214229348.png

局部变量名 内存地址 偏移 3 处的值 偏移 2 处的值 偏移 1 处的值 偏移 0 处的值
buffer 0x0012FB18 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’)
0x0012FB1C 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’)
authenticated 被覆盖前 0x0012FB20 0x00 0x00 0x00 0x01
authenticated 被覆盖后 0x0012FB20 NULL 0x74 (‘t’) 0x73 (‘s’) 0x72(‘r’)

我们已经知道,通过溢出 buffer 我们能修改 authenticated 的值,若要改变程序流程,就需要把 authenticated 覆盖为 0,而我们的字符截断符 NULL,就刚好能实现,当我们输入 8 个 ‘q' 时,buffer所拥有的 8 个字节将全部被 ’q‘ 填充,而 NULL 则刚好写入内存 0x0012FB20 出,即下一个双字的低位字节,恰好能把 authenticated 从 0x tel:00 00 00 01 改成 0x 00 00 00 00,如下图所示:
image-20220519215420951.png

局部变量名 内存地址 偏移 3 处的值 偏移 2 处的值 偏移 1 处的值 偏移 0 处的值
buffer 0x0012FB18 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’)
0x0012FB1C 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’)
authenticated 被覆盖前 0x0012FB20 0x01
authenticated 被覆盖后 0x0012FB20 0x00 (NULL)

经上述分析,我们只要输入 8 个(大于 ”1234567“) 字符的字符串,那么最后的 NULL 就能将 authenticated 低字节中的 1 覆盖为 0,从而绕过验证程序。

authenticated = strcmp( password, PASSWORD ),
当输入的字符串大于 ”1234567“时,返回1(0x tel:00 00 00 01),这时可以用NULL 淹没 authenticated 的低位字节从而突破验证;
当输入的字符串小于 ”1234567“时,返回 -1(0x FF FF FF FF),这时如果任然用上述方法淹没,其值变为 0xFF FF FF 00,所以这时是不能冲破验证程序的。

修改函数返回地址

返回地址与程序流程

更改邻接变量对环境要求很苛刻。而更改 EBP 和函数返回地址,往往更通用,更强大。

上节实验输入 7 个 “q“ ,程序栈状态:

局部变量名 内存地址 偏移 3 处的值 偏移 2 处的值 偏移 1 处的值 偏移 0 处的值
buffer 0x0012FB18 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’)
0x0012FB1C NULL 0x71 (‘q’) 0x71 (‘q’) 0x71 (‘q’)
authenticated 0x0012FB20 0x00 0x00 0x00 0x01
前栈帧 EBP 0x0012FB24 0x00 0x12 0xFF 0x80
返回地址 0x0012FB28 0x00 0x40 0x10 0xEB

如果继续增加输入的字符,我们就能让字符串中相应位置字符的 ASCII 码覆盖掉这些栈帧状态值。

这里用 19 个字符作为输入,看看淹没返回地址会对程序产生什么影响。出于双字对齐的目的,我们输入的字符串按照 “ 4321 ” 为一个单元进行组织,最后输入的字符串为“ 4321432143214321432”。
image-20220519232154440.png

局部变量名 内存地址 偏移 3 处的值 偏移 2 字节 偏移 1 字节 偏移 0 字节
buffer[0~3] 0x0012FB18 0x31 (‘1’) 0x32 (‘2’) 0x33 (‘3’) 0x34 (‘4’)
buffer[4~7] 0x0012FBIC 0x31 (‘1’) 0x32 (‘2’) 0x33 (‘3’) 0x34 (‘4’)
authenticated(被覆盖前) 0x0012FB20 0x00 0x00 0x00 0x01
authenticated(被覆盖后) 0x0012FB20 0x31 (‘1’) 0x32 (‘2’) 0x33 (‘3’) 0x34 (‘4’)
前栈帧 EBP(被覆盖前) 0x0012FB24 0x00 0x12 0xFF 0x80
前栈帧 EBP(被覆盖后) 0x0012FB24 0x31 (‘1’) 0x32 (‘2’) 0x33 (‘3’) 0x34 (‘4’)
返回地址(被覆盖前) 0x0012FB28 0x00 0x40 0x10 0xEB
返回地址(被覆盖后) 0x0012FB28 0x00(NULL) 0x32 (‘2’) 0x33 (‘3’) 0x34 (‘4’)

image-20220519233124853.png

返回地址用于在当前函数返回时重定向程序的代码。在函数返回的“ retn” 指令执行时,栈顶元素恰好是这个返回地址。“retn”指令会把这个返回地址弹入 EIP 寄存器,之后跳转到这个地址去执行。

返回地址本来是 0x004010EB,对应的是 main 函数代码区的指令,现在我们通过溢出 buff 覆盖返回地址为 0x00323334,函数返回时,将 0x00323334 装入 EIP 寄存器,从内存 0x00323334 处取址,由于此处没有合法指令,处理器不知如何处理,报错。

但如果这里是一个有效的指令地址,就能让处理器跳转到任意指令区去执行,我们可以通过淹没返回地址而控制程序的执行流程。

控制程序的执行流程

用键盘输入字符的 ASCII 表示范围有限,很多值(如 0x11、 0x12 等符号)无法直接用键盘输入,所以我们将程序的输入由键盘改为从文件中读取字符串

#include <stdio.h>
#define PASSWORD "tel:1234567"
int verify_password (char *password)
{
    int authenticated;
    char buffer[8];
    authenticated=strcmp(password,PASSWORD);
    strcpy(buffer,password);//over flowed here!
    return authenticated;
}
main()
{
    int valid_flag=0;
    char password[1024];
    FILE * fp;
    if(!(fp=fopen("password.txt","rw+")))
    {
            exit(0);
    }
    fscanf(fp,"%s",password);
    valid_flag = verify_password(password);
    if(valid_flag)
    {
            printf("incorrect password!\n");
    }
    else
    {
            printf("Congratulation! You have passed the verification!\n");
    }
    fclose(fp);
}

程序的基本逻辑和上一节中的代码大体相同,只是现在将从同目录下的 password.txt 文件中读取字符串。

推荐使用的环境 备 注
操作系统 Windows XP SP3 其他 Win32 操作系统也可进行本实验
编译器 Visual C++ 6.0 如使用其他编译器,需重新调试
编译选项 默认编译选项 VS2003 和 VS2005 中的 GS 编译选项会使栈溢出实验失败
build 版本 debug 版本 如使用 release 版本,则需要重新调试

用 VC6.0 将上述代码编译链接(使用默认编译选项, Build 成 debug 版本),在与 PE 文件同目录下建立 password.txt 并写入测试用的密码之后,就可以用 OllyDbg 加载调试了。  

动态调试时,需要我们做的工作:

(1)摸清楚栈中的状况,如函数地址距离缓冲区的偏移量等。
(2)得到程序中密码验证通过的指令地址,以便程序直接跳去这个分支执行。
(3)在 password.txt 文件的相应偏移处填上这个地址。

这样 verify_password 函数返回后就能直接跳转到验证通过的分支执行了。

用OllyDbg 加载 可执行文件,【找到验证的程序分支的指令地址为】按G调出程序执行的流程图,分析一下程序执行流程。
image-20220605154207339.png
从上面的流程图中,可以发现,在401111处的指令进行了程序验证。

0x00401102 调用了 verify_password 函数,之后在 0x0040110A 处将EAX中的返回值取出,在 0x0040110D处与0比较,然后决定跳转到提示验证通过的分支或是提示验证失败的分支。

提示验证通过的分支从 0x00401122处的参数压栈开始。如果我们把返回地址覆盖成这个地址,那么在 0x00401102处的函数调用返回后,程序将跳转到验证通过的分支,而不是进入分支判断代码。

通过动态调试,发现栈帧中的变量分布情况基本没变。这样我们按如下方法构造 password.txt 中的数据。

image-20220605162225968.png
构造思路:用2个 “4321”来填充 buffer[8],第3个“4321”来覆盖 authenticated,第4个“4321”覆盖前栈帧 EBP,第5个“4321” 的 ASCII码值 0x34333231 修改成验证通过分支的指令地址 0x00401122。

在构造 password.txt 时,我们需要用到一个软件 Ultraedit,通过它来编辑十六进制。

构造步骤:

  1. 创建一个 password.txt文件,写入5个“4321”,放在实验程序的目录中。
    image-20220605163446828.png
  2. 用 Ultraedit32 打开 password.txt
    image-20220605163838554.png
  3. 切换至十六进制编辑模式。
    image-20220605163926732.png
  4. 将最后4个字节修改为新的返回地址 0x00401122,注意:由于“大顶端”,我们需要逆序输入这4个字节
    image-20220605164113427.png

将 password.txt 保存后,用 OllyDbg 加载程序并调试,可以看到最终的栈状态如表所示。

局部变量名 内存地址 偏移 3 处的值 偏移 2 处的值 偏移 1 处的值 偏移 0 处的值
buffer[0~3] 0x0012FB14 0x31 (‘1’) 0x32 (‘2’) 0x33 (‘3’) 0x34 (‘4’)
buffer[4~7] 0x0012FB18 0x31 (‘1’) 0x32 (‘2’) 0x33 (‘3’) 0x34 (‘4’)
authenticated(被覆盖前) 0x0012FB1C 0x00 0x00 0x00 0x01
authenticated(被覆盖后) 0x0012FB1C 0x31 (‘1’) 0x32 (‘2’) 0x33 (‘3’) 0x34 (‘4’)
前栈帧 EBP(被覆盖前) 0x0012FB20 0x00 0x12 0xFF 0x80
前栈帧 EBP(被覆盖后) 0x0012FB20 0x31 (‘1’) 0x32 (‘2’) 0x33 (‘3’) 0x34 (‘4’)
返回地址(被覆盖前) 0x0012FB24 0x00 0x40 0x11 0x07
返回地址(被覆盖后) 0x0012FB24 0x00 0x40 0x11 0x22

程序执行状态如下图所示。
image-20220605164434401.png
由于站内EBP被覆盖为无效值,使得程序在退出时堆栈无法平衡,导致崩溃。



免费评分

参与人数 17吾爱币 +18 热心值 +16 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
魔道书生 + 2 + 1 我很赞同!
GONIMA + 1 + 1 用心讨论,共获提升!
bbsv + 1 谢谢@Thanks!
yp17792351859 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
a1239761234 + 1 + 1 谢谢@Thanks!
sam喵喵 + 1 谢谢@Thanks!
bailemenmlbj + 1 + 1 谢谢@Thanks!雖然看不懂!
weihaozang + 1 + 1 谢谢@Thanks!
麻辣书生 + 1 + 1 热心回复!
ll090822 + 1 + 1 热心回复!
wcch123 + 1 + 1 我很赞同!
allspark + 1 + 1 用心讨论,共获提升!
mossfly + 1 + 1 谢谢@Thanks!
w92vv + 1 + 1 谢谢@Thanks!
为之奈何? + 1 + 1 我很赞同!
theStyx + 2 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

yonghu99999 发表于 2023-2-16 07:20
跟着大佬一起学习
zhangjj001 发表于 2023-2-16 08:09
okman110 发表于 2023-2-16 08:17
tompz2002 发表于 2023-2-16 08:25
虽然目前看不懂,但是还是要谢谢大佬的付出
EXiaoLu 发表于 2023-2-16 08:32
学习了,感谢大佬
鹤舞九月天 发表于 2023-2-16 08:50
写的好,有时间我也试下
aodamiao45 发表于 2023-2-16 11:16
大佬,学习了
aa2923821a 发表于 2023-2-16 11:31
谢谢 正好学习一下
旧尘 发表于 2023-2-16 12:19
感谢楼主分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-27 21:21

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表