简单缓冲区溢出原理
本篇原创作者:Rj45
背景
什么是缓冲区溢出?这里我借某台栈溢出靶机里面的第一道题目来解释缓冲区溢出的原理。
可以看到靶机里面有两份权限不同的文件,而我目前拿到的shell是 level0
审计levelOne.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv) {
uid_t uid = geteuid();
setresuid(uid, uid, uid);
long key = 0x12345678;
char buf[32];
strcpy(buf, argv[1]);
printf("Buf is: %sn", buf);
printf("Key is: 0x%08xn", key);
if(key == 0x42424242) {
execve("/bin/sh", 0, 0);
}
else {
printf("%sn", "Sorry try again...");
}
return 0;
}
1、审计:可以看到,程序一开始声明并初始化了key=0x12345678, 并声明了32字节的buf, 然后通过strcpy函数将第一个参数拷贝到buf中, 接着判断key是否为0x42424242,如是则获得shell。(levelOne的权限为level1) key已经初始化为0x12345678,如何才能将其修改为0x42424242?
2、溢出:如何将key修改为0x42424242?--通过缓冲区溢出覆盖key值为0x42424242。什么是缓冲区溢出?在样例程序中声明了一段32字节的buf,此为缓冲区, 当通过strcpy函数将输入的函数第一个参数拷贝到缓冲区的时候,由于strcpy函数为危险函数,其不会对操作对象进行任何检查,故在输入数据不超过32个字节时不会发生任何情况, 但当输入的数据超过32个字节的时候,就会发生溢出,也即所谓的缓冲区溢出。
3、覆盖:根据前面我们学习到的内容,一个程序在载入内存后,其栈区会存在函数的各种变量、参数、栈针、返回地址等,并且各种数据是相邻分布的。这就意味着,一个存在缓冲区溢出的程序,在精准控制溢出范围的情况下,可以精准覆盖内存栈区中某些特殊位置的数据。这就为利用构造了条件,也即在本样例程序中的覆盖key值为0x42424242。
4、危险函数:显而易见,在缓冲区溢出的过程中,最关键的就是strcpy函数。那么还有哪些类似strcpy的危险函数呢?
可以看到这些危险函数集中为IO函数。
反汇编levelOne
1、命令
objdump -d levelOne -M intel
2、反汇编 下面为样例程序的反汇编情况:
000011e9 <main>:
11e9: 8d 4c 24 04 lea ecx,[esp+0x4]
11ed: 83 e4 f0 and esp,0xfffffff0
11f0: ff 71 fc push DWORD PTR [ecx-0x4]
11f3: 55 push ebp
11f4: 89 e5 mov ebp,esp
11f6: 56 push esi
11f7: 53 push ebx
11f8: 51 push ecx
11f9: 83 ec 3c sub esp,0x3c
11fc: e8 ef fe ff ff call 10f0 <__x86.get_pc_thunk.bx>
1201: 81 c3 ff 2d 00 00 add ebx,0x2dff
1207: 89 ce mov esi,ecx
1209: e8 42 fe ff ff call 1050 <geteuid@plt>
120e: 89 45 e4 mov DWORD PTR [ebp-0x1c],eax
1211: 83 ec 04 sub esp,0x4
1214: ff 75 e4 push DWORD PTR [ebp-0x1c]
1217: ff 75 e4 push DWORD PTR [ebp-0x1c]
121a: ff 75 e4 push DWORD PTR [ebp-0x1c]
121d: e8 0e fe ff ff call 1030 <setresuid@plt>
1222: 83 c4 10 add esp,0x10 //调整栈帧
1225: c7 45 e0 78 56 34 12 mov DWORD PTR [ebp-0x20],0x12345678 //声明并初始化了一个变量,地址为ebp-0x20,数据为0x12345678
122c: 8b 46 04 mov eax,DWORD PTR [esi+0x4]
122f: 83 c0 04 add eax,0x4
1232: 8b 00 mov eax,DWORD PTR [eax]
1234: 83 ec 08 sub esp,0x8
1237: 50 push eax
1238: 8d 45 c0 lea eax,[ebp-0x40]
123b: 50 push eax
123c: e8 1f fe ff ff call 1060 <strcpy@plt> //将输入点数据拷贝到ebp-0x40
1241: 83 c4 10 add esp,0x10 //调整栈帧
1244: 83 ec 08 sub esp,0x8
1247: 8d 45 c0 lea eax,[ebp-0x40]
124a: 50 push eax
124b: 8d 83 08 e0 ff ff lea eax,[ebx-0x1ff8]
1251: 50 push eax
1252: e8 e9 fd ff ff call 1040 <printf@plt>
1257: 83 c4 10 add esp,0x10
125a: 83 ec 08 sub esp,0x8
125d: ff 75 e0 push DWORD PTR [ebp-0x20]
1260: 8d 83 14 e0 ff ff lea eax,[ebx-0x1fec]
1266: 50 push eax
1267: e8 d4 fd ff ff call 1040 <printf@plt>
126c: 83 c4 10 add esp,0x10
126f: 81 7d e0 42 42 42 42 cmp DWORD PTR [ebp-0x20],0x42424242 //判断ebp-0x20处的变量是否为0x42424242
1276: 75 18 jne 1290 <main+0xa7> //如果相同则跳转到后门函数
1278: 83 ec 04 sub esp,0x4
127b: 6a 00 push 0x0
127d: 6a 00 push 0x0
127f: 8d 83 24 e0 ff ff lea eax,[ebx-0x1fdc] //将’/bin/sh’压入栈中,作为函数参数
1285: 50 push eax //将0压入栈中作为函数参数
1286: e8 05 fe ff ff call 1090 <execve@plt> //后门
128b: 83 c4 10 add esp,0x10
128e: eb 12 jmp 12a2 <main+0xb9>
1290: 83 ec 0c sub esp,0xc
1293: 8d 83 2c e0 ff ff lea eax,[ebx-0x1fd4]
1299: 50 push eax
129a: e8 d1 fd ff ff call 1070 <puts@plt>
129f: 83 c4 10 add esp,0x10
12a2: b8 00 00 00 00 mov eax,0x0
12a7: 8d 65 f4 lea esp,[ebp-0xc]
12aa: 59 pop ecx
12ab: 5b pop ebx
12ac: 5e pop esi
12ad: 5d pop ebp
12ae: 8d 61 fc lea esp,[ecx-0x4]
12b1: c3 ret
12b2: 66 90 xchg ax,ax
12b4: 66 90 xchg ax,ax
12b6: 66 90 xchg ax,ax
12b8: 66 90 xchg ax,ax
12ba: 66 90 xchg ax,ax
12bc: 66 90 xchg ax,ax
12be: 66 90 xchg ax,ax
栈中的情况如下图:
pwn
如此当填充满32个字节的数据后,在填充4个B,即达成利用条件,进入后门函数。
- 让“链式调用(方法链)”更加自然一点
- ASP.NET应用下基于SessionState的“状态编程框架”解决方案
- 为自定义配置的编辑提供”智能感知”的支持
- 在ASP.NET Core应用中如何设置和获取与执行环境相关的信息?
- 在ASP.NET MVC中如何应用多个相同类型的ValidationAttribute?
- [ASP.NET MVC]如何定制Numeric属性/字段验证消息
- 为.NET Core项目定义Item Template
- 晚绑定场景下对象属性赋值和取值可以不需要PropertyInfo
- 一个关于反序列化的小问题
- 两个简单的扩展方法:TrimPrefix和TrimSuffix
- 谈谈Nullable<T>的类型转换问题
- ASP.NET MVC是如何运行的(3): Controller的激活
- ASP.NET MVC是如何运行的[2]: URL路由
- 一个简单的小程序演示Unity的三种依赖注入方式
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法