当前位置: 首页 > news >正文

【C++】汇编角度分析栈攻击

栈攻击

  • 介绍原理
  • 示例代码
  • 汇编分析

介绍原理

核心原理是通过 缓冲区溢出(Buffer Overflow) 等漏洞,覆盖栈上的关键数据(如返回地址、函数指针),从而改变程序执行流程;

在 C++ 中,每个函数调用都会在栈上创建一个栈帧(Stack Frame),包含:

  • 局部变量:函数内定义的变量。
  • 函数参数:调用函数时传递的参数。
  • 返回地址:函数执行完后返回的地址(保存在 EIP 寄存器)。
  • 帧指针(EBP):指向当前栈帧的基址。
    栈内存是向下增长的(从高地址向低地址)

缓冲区溢出攻击
当程序向缓冲区写入数据时,若未检查输入长度,可能导致数据超出缓冲区边界,覆盖相邻的栈内存区域。攻击者可利用这一点:

  1. 覆盖返回地址为恶意代码的地址。放置攻击者指定的地址(如 shellcode 的起始地址
  2. 在栈上注入恶意代码(如 shellcode)。
  3. 触发溢出:当函数返回时,程序ret到攻击者指定的地址执行;

示例代码

下面示例代码就是通过缓冲区溢出覆盖掉栈的ret返回的地址从而改变函数返回后程序执行的地址(篡改为攻击函数地址);
大致流程:

  1. 先得到攻击函数Hack地址
  2. 调用count函数时候通过数组溢出方式,通过分析汇编代码,将汇编ret的地址修改为我们的Hack函数的地址
  3. 如此,count函数返回后程序就会沿着我们修改的Hack方向运行;
#include <iostream>
#include <iomanip>void Hack()
{unsigned long long x = 0;for (int i = 0; true; i++){if (i % 100000000 == 0){system("cls");std::cout << "\n■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n";std::cout << "\n 你的系统已经被我们拿下! hacked by 黑兔档案局:[ID:000001 ]\n";std::cout << "\n\\>正在传输硬盘数据....已经传输" << x++ << "个文件......\n\n";std::cout << std::setfill('>')<< std::setw(x % 60) << "\n";std::cout << "\n\\>摄像头已启动!<==============\n\n";std::cout << std::setfill('#') << std::setw(x % 60) << "\n";std::cout << "\n\\>数据传输完成后将启动自毁程序!CPU将会温度提升到200摄氏度\n";std::cout << "\n■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n";}}
}int GetAge()
{int rt;std::cout << "请输入学员的年龄:";std::cin >> rt;return rt;
}int count()
{int i{};int total{};int age[10]{};do{age[i] = GetAge();total += age[i];//将AGE[I]保存到数据库中} while (age[i++]);return total;
}int main()
{std::cout << "======= 驴百万学院 学员总年龄统计计算系统 =====\n";std::cout << "\n                API:"<<Hack<<std::endl;std::cout << "\n[说明:最多输入10个学员的信息,当输入0时代表输入结束]\n\n";std::cout << "\n驴百万学院的学员总年龄为:" << count();
}

汇编分析

直接从count函数汇编入手:

int count()42: {
00061200  push        ebp  //当前函数调用前的基址指针(Base Pointer,通常用来指向当前函数的栈帧)压入栈中,以便后续在函数结束时能够恢复到调用前的状态。
00061201  mov         ebp,esp  //将当前栈指针(Stack Pointer,指向当前栈顶)的值赋给基址指针 ebp,这样 ebp 现在指向当前函数count的栈帧
00061203  sub         esp,34h  //esp=esp-52,这条指令将栈指针 esp 减去 0x34h(52 的十进制值),这样就为当前函数的栈帧分配了 52 字节的空间。在函数执行过程中,局部变量和其他数据将会存储在这段空间中。43: 	int i{};
00061206  mov         dword ptr [i],0  // 0移动到变量 i 所在的内存位置。dword ptr 来指示操作数(内存里的数据)的大小为双字(32 位,4字节),这是因为 i 是一个整数类型变量。44: 	int total{};
0006120D  mov         dword ptr [total],0  45: 	int age[10]{};
00061214  xor         eax,eax  //将寄存器 eax 与自身进行异或操作,结果存储回 eax。这个操作的目的是将 eax 清零,因为在这段代码中,eax 被用来存储数组 age 的起始地址。
00061216  mov         dword ptr [age],eax  
00061219  mov         dword ptr [ebp-30h],eax  //48字节偏移量
0006121C  mov         dword ptr [ebp-2Ch],eax //44 
0006121F  mov         dword ptr [ebp-28h],eax  
00061222  mov         dword ptr [ebp-24h],eax  
00061225  mov         dword ptr [ebp-20h],eax  
00061228  mov         dword ptr [ebp-1Ch],eax  
0006122B  mov         dword ptr [ebp-18h],eax  
0006122E  mov         dword ptr [ebp-14h],eax  
00061231  mov         dword ptr [ebp-10h],eax  //16

在这里插入图片描述
栈底ebp存的就是count函数的下一条地址,我们目的是修改这一个地址;

这段代码对应函数栈如下:

在这里插入图片描述

46: 	do47: 	{48: 		age[i] = GetAge();
00061234  call        GetAge (0611D0h)  //调用了一个函数 GetAge,并将返回值存储在寄存器 eax 中。
00061239  mov         ecx,dword ptr [i]  //将变量 i 的值加载到寄存器 ecx 中  ecx=i =0  ecx=i=1
0006123C  mov         dword ptr age[ecx*4],eax  //此时将寄存器 eax 中的年龄age存储到 age[ecx*4] 中,age[ecx*4]将age数组的内存地址偏移ecx*4个字节。此时ecx*4 是因为 age 是一个数组,每个元素占据 4 个字节。 age[0] = eax =age0 age[1] = eax =age149: 		total += age[i];
00061240  mov         edx,dword ptr [i]  //将变量 i 的值加载到寄存器 edx 中  edx =i= 0   edx =i= 1
00061243  mov         eax,dword ptr [total] // 将变量 total 的值加载到寄存器 eax 中  eax = total
00061246  add         eax,dword ptr age[edx*4]  //[]里的理解为字节的位置,而不是元素,将 age[i] 的值加到 total 中     eax = eax+age[0]+age[1]
0006124A  mov         dword ptr [total],eax //将寄存器 eax 中的值存储回变量 total 中。    total = eax50: 		//将AGE[I]保存到数据库中51: 	} while (age[i++]);  //就是让i++并且判断是否这次输入的age[i]==0
0006124D  mov         ecx,dword ptr [i]  //将变量 i 的值加载到寄存器 ecx 中    ecx=i=0
00061250  mov         edx,dword ptr age[ecx*4]  //V将 age[i] 的值加载到寄存器 edx 中  edx = age[0]//edx值放在[ebp-0Ch]这篇内存应该是专门为了与0比较开辟的内存
00061254  mov         dword ptr [ebp-0Ch],edx  //将寄存器 edx 中的值存储到内存中的位置 [ebp-0Ch]。ebp-12 
00061257  mov         eax,dword ptr [i]  //将变量 i 的值加载到寄存器 eax 中   eax = i =0
0006125A  add         eax,1  //将寄存器 eax 中的值加 1		eax = eax+1
0006125D  mov         dword ptr [i],eax  //将寄存器 eax 中的值存储回变量 i 中   i = eax
00061260  cmp         dword ptr [ebp-0Ch],0 //将内存中的位置 [ebp-0Ch]ebp-12 的值与 0 比较    age[0]与0比较
00061264  jne         count+34h (061234h)  //如果不相等,则跳转到 count+34h(52) 处执行  //查询 count  0x00061200h  +34h后是0x00061234。跳到了call GetAge处52: 	return total;
00061266  mov         eax,dword ptr [total]  // total 变量的值加载到寄存器 eax 中53: }
//函数清尾
00061269  mov         esp,ebp  
0006126B  pop         ebp  //在pop ebp指令中,ebp是一个操作数,指示将栈顶元素弹出并将其存储到ebp寄存器中
0006126C  ret  //此时已经返回了[total]算出了正确total,运行结束!

在这里插入图片描述

注意:下面这张图的代码age数组是改为5的,对应下面图片,最后输入的就是Hack的API地址,此时替换为了原来ret指向的地址;
在这里插入图片描述

http://www.xdnf.cn/news/461215.html

相关文章:

  • ArcGIS切片方案记录bundle文件
  • [Java实战]Spring Boot + Netty 实现 TCP 长连接客户端及 RESTful 请求转发(二十六)
  • 【Linux】动静态库的使用
  • 人工智能100问☞第23问:卷积神经网络(CNN)为何擅长图像处理?
  • 双系统重装ubuntu
  • Newton 迭代
  • 【ORB-SLAM3】CreateNewKeyFrame()函数阅读
  • OpenCV CUDA模块中矩阵操作------矩阵元素求和
  • vue3.0的name属性插件——vite-plugin-vue-setup-extend
  • Spring框架的事务管理
  • 2025全网首发:ComfyUI整合GPT-Image-1完全指南 - 8步实现AI图像创作革命
  • 各类开发教程资料推荐,Java / python /golang /js等
  • ARP Detection MAC-Address Static
  • Uniapp开发鸿蒙购物项目教程之样式选择器
  • Gitee DevSecOps:军工软件研发的智能化变革引擎
  • 使用itextsharp5.0版本来合并多个pdf文件并保留书签目录结构
  • 人体肢体工作识别-一步几个脚印从头设计数字生命——仙盟创梦IDE
  • 产品创新怎么算
  • MySQL主从复制与读写分离
  • 模糊综合评价模型建立
  • Leetcode刷题 | Day63_图论08_拓扑排序
  • Ubuntu 20.04 LTS 中部署 网页 + Node.js 应用 + Nginx 跨域配置 的详细步骤
  • x-file-storage
  • AI数字人融合VR全景:开启未来营销与交互新篇章
  • 每日算法 - 【Swift 算法】Two Sum 问题:从暴力解法到最优解法的演进
  • C#数据类型
  • 新能源汽车制动系统建模全解析——从理论到工程应用
  • 【系统架构师】2025论文《WEB系统性能优化技术》
  • Added non-passive event listener to a scroll-blocking
  • 大语言模型 07 - 从0开始训练GPT 0.25B参数量 - MiniMind 实机训练 预训练 监督微调