写点什么

基于华为开发者空间云主机的软件安全栈溢出攻击实践

  • 2025-10-27
    中国香港
  • 本文字数:6452 字

    阅读完需:约 21 分钟

基于华为开发者空间云主机的软件安全栈溢出攻击实践

本案例由开发者:华为 2024 年第三批次协同育人项目-南航金城学院-孙福清教师提供

最新案例动态,请查阅 《【案例共创】基于华为开发者空间云主机的软件安全栈溢出攻击实践》。小伙伴快来领取华为开发者空间进行实操吧!

一、概述

1. 案例介绍

本案例是在华为云主机(ARM+Linux(ubuntu 24.04 server 定制版))下,进行软件安全的简单的经典栈溢出攻击实践。使用 c 语言编写简单具备栈溢出漏洞的代码,通过 gcc 携带特殊的编译原型,将源码编译链接成可执行文件,利用 gdb 调试工具,查找漏洞点,从而成功执行栈溢出,将函数内部变量溢出为自己的学号。


通过本实践,使得学生建立软件安全意识,在以后的软件编程学习和工作中,重视安全编程实践,编写出更加安全的代码,减少潜在的软件安全漏洞。

2. 适用对象

  • 企业

  • 个人开发者

  • 高校学生

3. 案例时间

本案例总时长预计 90 分钟。

4. 案例流程


说明:


  1. 登录华为开发者空间工作台,领取云主机。

  2. 安装验证代码编辑器 gedit。

  3. 安装验证十六进制文件编辑器 ghex。

  4. 安装验证 c 代码编译 gcc 和调试 gdb 工具。

  5. 编译简单栈溢出漏洞代码。

  6. 调试简单栈溢出漏洞程序。

  7. 栈溢出漏洞攻击实践。

5. 资源总览

本案例预计花费 0 元。


二、资源准备与环境配置

1. 开发者空间配置

面向广大开发者群体,华为开发者空间提供一个随时访问的“开发桌面云主机”、丰富的“预配置工具集合”和灵活使用的“场景化资源池”,开发者开箱即用,快速体验华为根技术和资源。


如果还没有领取云主机进入华为开发者空间工作台界面后点击配置云主机,选择 Ubuntu 操作系统。



在华为开发者空间工作台界面,点击打开云主机 > 进入桌面连接云主机。


2. 安装验证代码编辑器 gedit

本案例需要使用文本编辑软件来进行:c 语言代码的查看、编辑、修改。


Linux 操作系统自带的文本编辑工具 vim,对于初步接触 linux 操作系统的新手来说,难于适应这种命令行的使用方式。本案例使用新手友好熟练的鼠标使用方式的文本编辑器,推荐使用 gedit:


gedit 是一个 GNOME 桌面环境下兼容 UTF-8 的文本编辑器‌。它简单易用,支持良好的语法高亮,对中文支持很好,包括支持 gb2312、gbk 在内的多种字符编码。它还是一个自由软件,每天被数百万使用 GNOME 的 Ubuntu、Fedora 和其他 Linux 发行版的用户所使用。gedit 因其强大的功能和灵活性,在 Linux 社区中广受欢迎,是许多开发者和用户首选的文本编辑器之一。

2.1 安装 gedit

华为开发者空间 - 云主机已经预装 gedit,我们可以在 Terminal 终端窗口中通过如下命令打开代码编辑器 gedit:


gedit
复制代码



若环境中未安装,可以在 Terminal 终端窗口中执行命令,进行安装:


sudo apt install gedit
复制代码


2.2 使用 gedit

使用 gedit 新建代码文件,编辑和保存。



操作步骤


  • 通过mkdir命令创建 stackoverflow 目录,通过cd命令进入该目录,通过ls命令查看当前文件目录为空文件;

  • 通过gedit命令创建 hello.c 文件,并编辑一段hello world代码,点击 gedit 编辑器右上角的保存按钮;

  • 打开一个新的 Terminal 终端窗口,通过ls命令查看当前目录中已经新增了 hello.c 文件;

  • 完成上述操作后,使用gedit + 文件名命令可以再次查看已有的编辑保存的代码文件。


mkdir stackoverflowcd stackoverflowlsgedit hello.c
复制代码


代码:


#include <stdio.h>int main(){    printf("hello world\n");    return 0;}
复制代码


3. 安装验证十六进制编辑器 ghex

GHex 是一款为 GNOME 桌面环境设计的图形化十六进制编辑器,允许用户直接查看和编辑二进制文件的原始字节数据。它支持多种文件格式,能够以十六进制和 ASCII 格式显示文件内容,并提供多级撤销/重做功能。


GHex 是一个简单易用且功能强大、图形化的工具,对于学习和实践软件安全技术的新手非常友好。

3.1 安装 GHex

华为开发者空间 - 云主机中未预装 GHex,需要单独安装。安装时需要 root 权限安装,具体命令如下:


ghexsudo apt install ghex
复制代码


安装过程中根据提示输入y完成安装。


3.2 使用 GHex

使用 gedit 新建十六进制文件,编辑和保存。



操作步骤


  • 通过ghex命令打开编辑器 ghex。

  • 在 ghex 编辑器右上角点击菜单选择 new,创建一个新的十六进制文件。

  • 编写输入一段代码,测试转化成十六进制内容。

  • 在 ghex 编辑器右上角点击菜单选择 Save As,保存文件。

  • 完成上述操作后,使用ghex + 文件名命令可以再次查看已有的编辑保存的代码文件。


ghexghex + 文件名
复制代码



4. Linux c 代码编译 gcc

GCC(GNU Compiler Collection)是一个开源免费的编译器工具集合,主要用于编译多种编程语言的代码,最初是 C 语言编译器,后来支持 C++等其他编程语言。GCC 提供了丰富的编译选项,适应不同的需求。


GCC 是 Linux 开发中不可或缺的工具,掌握它的使用方法能够帮助你更高效地进行程序开发和调试。

4.1 安装 gcc

华为开发者空间 - 云主机中已经预装了gcc 13.3.0版本;如果没有,可以通过如下命令安装:


gcc --versionsudo apt install gcc
复制代码


4.2 使用 gcc

操作步骤


  • 通过gcc命令执行编译步骤“2.2 使用 gedit”中创建的 hello.c 文件,将该文件编译成可执行文件 hello.elf。

  • 通过./hello.elf命令运行编译好的 hello.elf 文件,从返回的日志中我们可以看出程序运行成功。



gcc hello.c -o hello.elf./hello.elf
复制代码

5. Linux C 代码程序调试 gdb

GDB(GNU Debugger)是 Linux 系统中一个功能强大、开源的调试工具,主要用于调试 C、C++等语言编写的程序,可以查看程序运行时的变量值、执行单步调试、设置断点、检查堆栈信息,掌握它的基本用法可以帮助开发者快速定位和修复程序中的问题。通过设置断点、单步执行、查看变量和堆栈信息等功能,可以深入了解程序的运行状态。

5.1 安装 gdb

华为开发者空间 - 云主机中未预装 gdb,需要单独安装。安装时需要 root 权限安装,具体命令如下:


gdbsudo apt install gdb
复制代码



安装过程中根据提示输入y完成安装。

5.2 使用 gdb

操作步骤


  • 通过gcc + 调试程序命令进入调试。在调试程序时,要求 gcc 执行编译时在后面添加-g 调试选项,被编译后的*.elf 文件才能被读取其中的调试符号。如下图日志所示“No debugging symbols found in hello.elf”。

  • 通过gcc -g重新执行 hello.c 文件编译命令。生成新的编译后的文件 hello.elf。

  • 再次执行gcc + 调试程序命令,日志输出“Reading symbols from hello.elf...”,进入程序调试模式。

  • 通过 quit 或 exit 命令退出程序调试模式。



gcc -g hello.c -o hello.elfgdb hello.elf exit / quit
复制代码

三、项目实战:软件安全栈溢出攻击实践

1. 编译栈溢出漏洞代码

1.1 C 栈溢出漏洞代码

栈溢出漏洞通常发生在程序使用不安全的函数(如 strcpy、strcat 等)处理用户输入时,未对输入长度进行严格限制,导致超出缓冲区的数据覆盖了栈中的返回地址。攻击者可以通过精心构造输入数据,将返回地址修改为指向恶意代码(如 Shellcode)的地址,从而实现任意代码执行。


栈溢出漏洞示例代码:


#include <stdio.h>int main() {    int nModified;    char buffer[200];     //学生学号后2位尾号 xx * 4,例如0x24030850,50 * 4 = 200    nModified = 0;    memset(buffer,0,sizeof(buffer));    printf("please input ...\n");    gets(buffer);        printf("nModified=0x%08x\n",nModified);    if(0x24030850 == nModified)  //学生学号,栈溢出要将栈变量溢出复位为学号    {        printf("congratulations,success to stack-overflow variable-nModified\n");    }    else     {        printf("you fail to stack-overflow variable-nModified\n");    }    return 0;}
复制代码


示例代码中的代码语句 gets(buffer) 为不安全的函数,没有来判断输入内容长度超出 buffer 长度的溢出问题,会引入栈溢出漏洞。

1.2 特殊编译选项编译代码

对示例代码需要使用特殊的编译选项进行编译:


gcc [源代码文件名].c -g -w -fno-stack-protector -o [可执行文件名].elf
复制代码


gcc -g:gcc -g 是 GCC 编译器的一个选项,用于在编译时生成调试信息。这些调试信息会被嵌入到生成的可执行文件中,使得调试器(如 GDB)能够提供更详细的调试支持,包括变量名、源代码行号、函数名等信息。用法示例:


gcc 1.c -g -o 1.elf
复制代码


gcc -w:gcc -g 编译器的一个选项,用于关闭所有警告信息。当你在编译代码时使用 -w 选项,GCC 会忽略所有警告,只输出错误信息。这意味着即使代码中存在潜在问题(如未初始化的变量、类型不匹配等),编译器也不会提醒。


本案例中使用是去除掉编译时候输出的很多信息,避免给大家带来困惑。实际工作中不建议使用。用法示例:


gcc 1.c -w -o 1.elf
复制代码


gcc -fno-stack-protector: 是 GCC 编译器的一个选项,用于禁用堆栈保护功能(Stack Smashing Protector,SSP)。


堆栈保护是一种安全机制,用于检测和防止堆栈缓冲区溢出攻击,是一种编译器级别的安全特性,用于检测堆栈缓冲区溢出。它通过在堆栈帧中插入一个“保护值”(canary value),并在函数返回时检查该值是否被篡改。如果保护值被篡改,程序会检测到堆栈溢出并终止运行,从而防止潜在的安全漏洞。


-fno-stack-protector 选项用于禁用堆栈保护功能。这意味着编译器不会在生成的代码中插入堆栈保护机制。禁用堆栈保护功能,这可能会提高程序的性能,但会降低安全性。在大多数情况下,建议保持堆栈保护启用,以防止潜在的安全漏洞。用法示例:


gcc 1.c -fno-stack-protector -o 1.elf
复制代码


gcc -o: 是 GCC 编译器的一个常用选项,用于指定输出文件的名称。默认情况下,GCC 会将编译生成的可执行文件命名为 a.out(在 Linux 和 Unix 系统中),但通过 -o 选项,你可以自定义输出文件的名称,此处将可执行文件名的后缀名命名成 elf,标识这个文件为 linux 的可执行程序文件。用法示例:


gcc 1.c -fno-stack-protector -o 1.elf
复制代码


执行编译



操作步骤


  • 通过gedit overflow.c命令创建 overflow.c 文件;

  • 进入 gedit 代码编辑器,将章节“1.1 C 栈溢出漏洞代码”中的代码复制到 overflow.c 文件;

  • 点击 gedit 代码编辑器右上角的保存按钮,将代码保存到 overflow.c 代码文件;

  • 打开一个新的 Terminal 终端窗口,通过cd stackoverflow/进入到 stackoverflow 目录,通过ls命令查看当前目录中已新增 overflow.c 代码文件;

  • 通过gcc overflow.c -g -w -fno-stack-protector -o overflow.elf命令使用特殊编译选项,编译栈溢出漏洞代码,禁用栈溢出保护,将 overflow.c 代码文件,编译成 overflow.elf 可执行文件。

  • 再次使用 ls 命令查看,stackoverflow 目录中已新增 overflow.elf 可执行文件,程序编译成功。


命令:


gedit overflow.ccd stackoverflow/lsgcc overflow.c -g -w -fno-stack-protector -o overflow.elf
复制代码


注:在执行编译步骤时,日志输出“/home/developer/stackoverflow/overflow.c:9:(.text+0x2c): 警告: the `gets' function is dangerous and should not be used.”。此处已关闭其他编译输出信息,只输出最危险的告警信息。

1.3 执行代码程序

在 Linux 系统中,通过命令行执行程序有多种方式,具体取决于程序的类型、位置以及是否需要指定路径,最常见的集中方法:


  1. 直接执行可执行文件


如果程序是一个可执行文件(如 ELF 格式的二进制文件),并且它位于环境变量 $PATH 中的某个目录下,可以直接输入程序名来执行.


例如:ls 是一个常见的 Linux 命令,它位于 /bin/ls 或 /usr/bin/ls 等 $PATH 中的目录下。直接输入 ls,系统会自动找到并执行。


ls -l
复制代码


  1. 使用相对路径或绝对路径执行可执行文件


如果程序不在 $PATH 中,或者你想明确指定路径,可以使用相对路径或绝对路径来执行程序,例如:


./hello.elf
复制代码



./ 是一个常见的路径表示方式,用于指代当前目录。当 ./ 出现在命令行中时,它通常用于执行当前目录下的某个文件,告诉系统:在当前目录下找到名为 program 的文件并执行它。


如果直接输入文件名(不带路径),例如 ls 命令,系统会按照环境变量 $PATH 中定义的路径顺序查找可执行文件。使用 ./ 可以明确指定文件位于当前目录,避免与 $PATH 中的其他同名文件混淆。


找到文件后,还需要判断文件是否为可执行文件 elf,当前用户是否具备执行权限等等。


  1. 直接执行脚本文件


如果程序是一个脚本文件(如 Bash 脚本、Python 脚本等),满足:确保脚本文件具有执行权限(chmod +x script.py),脚本文件第一行指定了正确的解释器,那么就可以直接类似./script.sh 或者./hello.py 方式执行脚本。



  1. 通过解释器执行脚本文件


如果脚本文件没有执行权限,或者不想修改脚本的权限,可以直接通过解释器执行脚本,例如:bash script.sh 或者 python3 hello.py



执行编译出来的栈溢出漏洞的代码程序



输入内容不够,程序提示栈溢出失败,未能正确将栈变量溢出为指定的学号。

2. 调试栈溢出漏洞代码程序

2.1 GDB 基础调试命令介绍

  1. 启动调试

  2. gdb 调试命令:查看源码

  3. gdb 调试命令:设置断点


设置断点 break 有多种方式:


  • 在函数入口处设置断点:break 函数名,如:break main

  • 在某个文件的特定行设置断点:break 文件名:行号,如 break 1.c:20


本案例就需要简单的设置 main 函数入口即可。



  1. gdb 调试命令:运行

  2. 设置断点后,调试命令 run 运行调试的程序停留在设置断点处。

  3. gdb 调试命令:执行到下一行代码

  4. 一直执行next命令,运行到下一行代码语句,执行到gets(buffer);时,根据 gets 要求键盘输入,输入后继续执行到下一行代码语句。



  1. gdb 调试命令:查看变量、内存等

  2. 在 GDB 中,print 命令用于查看变量、表达式或内存地址的值。它是调试过程中最常用的命令之一,可以帮助你检查程序的状态、变量的值以及内存内容。

  3. gdb 调试命令:设置变量值


在 GDB 中,你可以使用 set 命令来修改变量的值。这在调试过程中非常有用,例如当你需要测试不同的输入条件、修复变量的错误值或验证程序的行为时。


用法:


set <variable> = <value>set nModified = 0x24030850
复制代码


<variable>:要修改的变量名。


<value>:新的值,可以是常量、表达式或其他变量的值



  1. gdb 调试命令:继续执行

  2. 在 GDB 中,continue 命令用于从当前暂停点继续程序的执行,直到遇到下一个断点或程序结束。



  1. gbd 命令:退出调试

2.2 栈溢出调试获取攻击信息

栈溢出原理



栈攻击信息


获取函数栈内变量 buffer 以及 nModified 的内存地址,就能精心设计输入内容。


在使用 GDB(GNU Debugger)调试程序时,可以通过以下命令查看变量的地址:


  • 查看全局变量或静态变量的地址 ,info address <变量名> 这将显示变量的地址。

  • 查看局部变量的地址 对于局部变量,GDB 提供了 print 命令的扩展功能,可以使用 & 操作符来获取变量的地址,print &<变量名> 这将输出变量的地址。


gdb 调试获取栈内变量地址:无源码只有可执行程序时,只能通过 gdb 调试获取



print 获取函数栈内变量的内存地址(示例):


print /x &buffer$1 = 0xffffffffec40print /x &nModified$2 = 0xffffffffed0c
复制代码

2.3 设计输入信息

根据调试的攻击信息,计算溢出条件:



可以看出,当 gets 输入的内容大小为 0xcc+4,输入内容的最后 4 个字节将正好覆盖掉栈内变量 nModified,只要精心设计 输入内容最后四个字节,就能将栈内变量溢出覆盖为任意值。

2.4 安全栈溢出攻击

经过前面的计算,栈溢出攻击时键盘输入的内容字节数量很多,并且最后 4 个字节还是特殊的非 ASCII 码内容(个人学号- 0x24030850),键盘将无法输入。


在 Linux 命令行中,< 是一个输入重定向符号(Input Redirection),将文件的内容作为命令的输入,替代从标准输入(通常是键盘)的输入。因此,需要编辑十六进制文件并重定向到程序替代键盘输入,例如:


./overflow.elf < 1.hex
复制代码


根据前面的设计,输入文件的大小为 0xcc + 4 = 204 + 4 字节,最后四个字节指定内容 0x24030850。要注意字节序,华为云主机(ARM+Ubuntu 24.04 Server 定制版)为小字节,因此编辑十六进制时顺序要按字节颠倒: 50 08 03 24


启动 GHex,新建一个十六进制文件 input.hex



操作步骤


  • 在 Terminal 终端窗口通过ghex命令打开十六进制编辑器 ghex,打开右上角菜单选择 New

  • 在 ghex 编辑器中任意输入 204 个字节,然后输入50 08 03 24四个字节,点击右上角菜单选择 Save As,编辑十六进制文件名称为input.hex

  • 新打开一个 Terminal 终端窗口,通过ls命令确认 input.hex 文件在当前目录中,执行栈溢出漏洞代码程序,将输入文件重定向给程序。输出日志“congratulations,success to stack-overflow variable-nModified”。


./overflow.elf < input.hex
复制代码


至此,成功完成安全栈溢出攻击。


用户头像

提供全面深入的云计算技术干货 2020-07-14 加入

生于云,长于云,让开发者成为决定性力量

评论

发布
暂无评论
基于华为开发者空间云主机的软件安全栈溢出攻击实践_云主机_华为云开发者联盟_InfoQ写作社区