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

【C语言】第七课 字符串与危险函数​​

C语言中的字符串处理既是基础,也是安全漏洞的重灾区。理解C风格字符串的底层原理及其危险函数的运作方式,对于编写安全代码和进行逆向工程分析至关重要。

🧩 C风格字符串的本质

C风格字符串本质上是以空字符'\0'(ASCII值为0)结尾的字符数组。这个终止符是字符串的“生命线”,它告诉字符串处理函数字符串在哪里结束。

  • 内存中的表示:字符串 "Hello" 在内存中实际存储为 {'H', 'e', 'l', 'l', 'o', '\0'}
  • 长度与容量:字符串的长度strlen() 返回的值(不包含'\0'),而容量是字符数组实际占用的总字节数。长度不能超过容量减一(必须为'\0'留出空间)。
  • 声明方式
    char str1[] = "Hello"; // 编译器自动计算大小,包含'\0'
    char str2[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 手动指定大小并初始化
    char str3[10]; // 未初始化,后续需要手动添加'\0'
    

⚠️ 危险的字符串操作函数

C标准库提供了一系列字符串操作函数,但它们大多不检查目标缓冲区的边界,这是导致缓冲区溢出的根源。

以下是几个常见的高危函数及其安全注意事项:

函数用途危险原因安全替代建议
strcpy(dest, src)将源字符串复制到目标缓冲区src长度 > dest容量,导致溢出strncpy(dest, src, dest_size-1) 并手动添加 dest[dest_size-1] = '\0'
strcat(dest, src)将源字符串追加到目标字符串末尾若合并后总长度 > dest容量,导致溢出strncat(dest, src, dest_size - strlen(dest) - 1)
sprintf(dest, format, ...)格式化输出到字符串若生成的字符串长度 > dest容量,导致溢出snprintf(dest, dest_size, format, ...)
gets(dest)从标准输入读取一行到dest极度危险! 无法限制读取长度,必然溢出绝对不要使用!fgets(dest, size, stdin) 代替
scanf("%s", dest)读取字符串若输入过长,导致溢出始终指定宽度:scanf("%19s", dest) // 假设dest大小为20

安全函数的注意事项

  • strncpy不会自动添加终止符:如果源字符串长度超过或等于指定的最大复制长度,strncpy 不会 在目标末尾添加 '\0'你必须手动添加以确保字符串正确终止。
    char dest[10];
    strncpy(dest, "ThisIsAVeryLongString", sizeof(dest) - 1); // 只复制前9个字符
    dest[sizeof(dest) - 1] = '\0'; // 手动添加终止符,这是关键!
    
  • strncat 相对安全:它会自动在追加的字符串末尾添加 '\0',但你必须确保目标缓冲区有足够的剩余空间(包括终止符)。

💥 缓冲区溢出漏洞详解(以栈溢出为例)

缓冲区溢出是当数据写入缓冲区时,超出了缓冲区的边界,覆盖了相邻内存区域的行为。栈溢出是其中最常见且最危险的一种。

漏洞代码示例
#include <stdio.h>
#include <string.h>void vulnerable_function(const char* input) {char buffer[16]; // 在栈上分配一个16字节的缓冲区strcpy(buffer, input); // 🚨 危险!无边界检查的复制printf("Buffer: %s\n", buffer);
}int main() {char large_input[256] = "This string is definitely longer than sixteen bytes...";vulnerable_function(large_input);return 0;
}
溢出过程与逆向分析

在逆向工程中,理解函数调用时的栈帧布局至关重要。当调用 vulnerable_function 时,栈帧通常如下布局(简化示意,具体取决于编译器和架构):

内存地址(高)栈帧内容说明
ebp + 8参数 input传递给函数的参数
ebp + 4返回地址 (Return Address)这是攻击者的主要目标!
ebp保存的上一帧ebp (Saved EBP)
ebp - 4局部变量 buffer[12-15]
ebp - 8局部变量 buffer[8-11]
ebp - 12局部变量 buffer[4-7]
ebp - 16局部变量 buffer[0-3]
  1. 正常操作:如果输入的字符串长度小于16字节(包括结尾的'\0'),strcpy 会正常复制,不会破坏栈上的其他数据。
  2. 发生溢出:当输入远长于16字节时,strcpy 会持续复制,超出 buffer 的边界。
  3. 覆盖关键数据
    • 首先会覆盖保存的EBPebp指向的位置)。
    • 继续覆盖返回地址ebp + 4指向的位置)。攻击者可以精心构造输入数据,使这个返回地址指向他们注入的恶意代码(通常也在栈上)或现有的特殊函数
  4. 劫持程序流程:当 vulnerable_function 执行完毕,准备返回时,CPU会从栈上取出那个已被覆盖的返回地址,并跳转到该地址执行。程序的控制流就此被劫持
在调试器(GDB)中观察溢出
  1. 编译代码:使用调试信息编译(gcc -g -o program program.c)。
  2. 启动GDBgdb ./program
  3. 设置断点:在 vulnerable_functionstrcpy 之后设置断点。
    (gdb) break vulnerable_function
    (gdb) break *(vulnerable_function+某偏移量) # 在strcpy之后设置断点
    
  4. 运行并传递超长参数
    (gdb) run $(python -c "print 'A'*256)") # 使用一串'A'作为输入
    
  5. 观察栈内存
    • strcpy之前,使用 x/20xw $esp 查看栈内存(正常)。
    • strcpy之后,再次使用 x/20xw $esp,你会看到返回地址和被保存的EBP已被字符’A’(ASCII码0x41)覆盖
    (gdb) x/8xw $ebp # 查看ebp附近的内存
    0xffffd00c: 0x41414141 0x41414141 0x41414141 0x41414141 # 覆盖的EBP和返回地址
    0xffffd01c: 0x41414141 0x41414141 0x41414141 0x41414141
    
  6. 继续执行:当函数返回时(stepicontinue),程序会尝试跳转到地址 0x41414141(即"AAAA")去执行,这显然是一个非法地址,会导致段错误(Segmentation fault)。在真实的攻击中,这个地址会被替换为精心计算的、指向恶意代码的有效地址。

🛡️ 如何防范缓冲区溢出

  1. 使用安全函数:优先使用带 n 版本的函数(如 strncpy, strncat, snprintf)并正确使用它们(特别是为strncpy手动添加终止符)。
  2. 动态内存管理:如果可能,使用malloc根据字符串实际长度动态分配足够的内存,但记得最后要free
  3. 现代编译器和操作系统保护机制
    • 栈保护器(Stack Canaries):编译器(如GCC的-fstack-protector)会在栈上的返回地址前插入一个随机值(canary)。函数返回前检查该值是否被修改,若被修改则终止程序。
    • 数据执行保护(DEP/NX):将数据所在的内存页(如栈)标记为不可执行,即使攻击者注入了代码,也无法运行。
    • 地址空间布局随机化(ASLR):随机化进程内存布局(栈、堆、库的地址),使得攻击者难以预测恶意代码的准确地址。
  4. 静态代码分析工具:使用工具扫描代码,自动识别潜在的缓冲区溢出风险。
  5. 代码审计:养成良好的编程习惯,始终对外部输入保持怀疑,并手动检查所有缓冲区操作的边界。

💎 总结

理解C风格字符串和危险函数是C编程和逆向分析的基石。'\0'终止符是生命线,缓冲区边界是高压线。通过调试器亲眼目睹栈溢出如何覆盖返回地址,是理解整个漏洞机理最直观的方式。在开发中,务必摒弃危险的函数,采用安全替代方案,并利用现代系统的保护机制,从根本上减少漏洞的产生。


文章转载自:

http://oiCih9rQ.qptbn.cn
http://kAzN1iz1.qptbn.cn
http://YgkxKw9B.qptbn.cn
http://BfzALcpF.qptbn.cn
http://Qq7kVEDx.qptbn.cn
http://80aDoDz5.qptbn.cn
http://NLpZPJV6.qptbn.cn
http://ZjzmzmMN.qptbn.cn
http://P2uTOWmy.qptbn.cn
http://4QjuW6Nk.qptbn.cn
http://iKIp7PjD.qptbn.cn
http://ol6sfscS.qptbn.cn
http://gOE2UrKp.qptbn.cn
http://RKiyiO1z.qptbn.cn
http://lHywfSD6.qptbn.cn
http://k5fm0lcS.qptbn.cn
http://PQvqDx49.qptbn.cn
http://UNNASnrA.qptbn.cn
http://hIuoVxtw.qptbn.cn
http://UiKdJtlm.qptbn.cn
http://WUtCGQQh.qptbn.cn
http://Y45kYw4x.qptbn.cn
http://o623bEYE.qptbn.cn
http://8gJctRKj.qptbn.cn
http://BmHKk3DU.qptbn.cn
http://m8mOB6nX.qptbn.cn
http://0oqHsUgF.qptbn.cn
http://2xWMDgSF.qptbn.cn
http://7p5Jf5AR.qptbn.cn
http://hQmvCqjU.qptbn.cn
http://www.dtcms.com/a/384788.html

相关文章:

  • Java 网络编程全解析
  • GD32VW553-IOT V2开发版【三分钟快速环境搭建教程 VSCode】
  • Docker 与 VSCode 远程容器连接问题深度排查与解决指南
  • 流程图用什么工具做?免费/付费工具对比,附在线制作与下载教程
  • IT运维管理与服务优化
  • javaweb XML DOM4J
  • 用C#生成带特定字节的数据序列(地址从0x0001A000到0x0001C000,步长0x20)
  • 解析预训练:BERT到Qwen的技术演进与应用实践
  • PCB 温度可靠性验证:从行业标准到实测数据
  • 机器人要增加力矩要有那些条件和增加什么
  • MongoDB 在物联网(IoT)中的应用:海量时序数据处理方案
  • 6U VPX 板卡设计原理图:616-基于6U VPX XCVU9P+XCZU7EV的双FMC信号处理板卡
  • 【芯片设计-信号完整性 SI 学习 1.2.2 -- 时序裕量(Margin)】
  • Elasticsearch核心概念与Java实战:从入门到精通
  • Flink 内部状态管理:PriorityQueueSet解析
  • ChatBot、Copilot、Agent啥区别
  • LeetCode 热题560.和为k的子数组 (前缀和)
  • 掌握多边形细分建模核心技术:从基础操作到实战技巧详解
  • [特殊字符] Python在CentOS系统执行深度指南
  • 机器人控制器开发(定位——cartographer ros2 使用1)
  • 7 制作自己的遥感机器学习数据集
  • FPGA 40 DAC线缆和光模块带光纤实现40G UDP差异
  • 强化学习【value iterration】【python]
  • 代码随想录算法训练营第四十天|01背包 二维 01背包 一维 416.分割等和子集
  • 力扣:1547. 切棍子的最小成本
  • LeetCode 2962.统计最大元素出现至少K次的子数组
  • ESP8266无法连接Jio路由器分析
  • 傅里叶变换与现代深度学习
  • 【LeetCode】2785. 将字符串中的元音字母排序
  • APIPark:重新定义AI时代的API网关 —— 从100+模型统一接入到企业级应用