当前位置: 首页 > 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.dtcms.com/a/192475.html

相关文章:

  • 一文读懂--程序的编译汇编和链接
  • Datawhale 5月llm-universe 第2次笔记
  • Vue 3中ref
  • css画图形
  • BUUCTF——web刷题第一页题解
  • 漂亮的收款打赏要饭网HTML页面源码
  • leetcode-hot-100 (子串)
  • Apple Vision Pro空间视频创作革命:从180度叙事到沉浸式语法的重构——《Adventure》系列幕后技术深度解析
  • [c++项目]云备份项目测试
  • 抢购Python代码示例与技术解析
  • Java中的设计模式
  • C++:字符数组与字符串指针变量的大小
  • 35页AI应用PPT《DeepSeek如何赋能职场应用》DeepSeek本地化部署与应用案例合集
  • 【论文阅读】BEVFormer
  • P8803 [蓝桥杯 2022 国 B] 费用报销
  • TypeScript中文文档
  • 【Java项目脚手架系列】第七篇:Spring Boot + Redis项目脚手架
  • 配置别名路径 @
  • ArcGIS切片方案记录bundle文件
  • 机器学习笔记3
  • 【iOS】alloc的实际流程
  • 106. 从中序与后序遍历序列构造二叉树
  • 本地化部署HomeAssistant语音助手并接入DeepSeek
  • 波导模型(表面等离激元、石墨烯等)本征模式分析、各种类型波导传输效率求解
  • JAVA数组题(7)
  • STL - stack 和 queue 及容器适配器模式的介绍
  • C++11(2)
  • 大语言模型三大演进方向:记忆增强、工具集成与多模态突破
  • 插件双更新:LeetCode 刷题支持正式上线,JetBrains IDE 插件持续升级!
  • 《从零开始入门递归算法:搜索与回溯的核心思想 + 剑指Offer+leetcode高频面试题实战(含可视化图解)》​