漏洞公告:https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-26882
漏洞Poc : https://github.com/songjianyang/CVE-2021-26882

在去年11月份时我向 MSRC 提交了一个 Remote Access API 的堆溢出漏洞,该漏洞在2021年3月被修复,分配编号 CVE-2021-26882 ,并被归类为 Elevation of Privilege,获得 $2000 的奖金。

下面我会讲述漏洞的发现过程和分析。

1、发现过程

起初注意到 Remote Access API 是因为我看了国外研究员 symeon 的一篇文章 Discovery and analysis of a Windows PhoneBook Use-After-Free vulnerability (CVE-2020-1530),他在文章中分析了 phonebook 漏洞的发现过程,简单说就是寻找用来处理文件的函数,然后将函数组合并写成 harness,最后交给 WinAFL 进行 fuzz。

仔细学习之后我就开始动手尝试,打开docs,分析所有 rasapi 相关的函数,然后刻意的选择可以在参数中传入pszPhonebook的函数,将它们组合在一起写成 WinAFL 的 harness。

-w1181

为了保证 fuzz 的效率,我并没有在 harness 中放太多函数,而是把每一个函数都单独写一个 harness,在 fuzz 了很多函数后,RasSetCustomAuthData 给我带来了漏洞。

这是当时用来 fuzz 的 harness:

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

#include <windows.h>
#include <stdio.h>
#include "ras.h"
#include "rasdlg.h"
#include "raserror.h"
#include <tchar.h>

#pragma comment(lib, "rasapi32.lib")
#pragma comment(lib, "Rasdlg.lib")

extern "C" __declspec(dllexport) int main(int argc, char** argv);


int main(int argc, char** argv)
{
DWORD dwCb = 0;
DWORD dwRet = ERROR_SUCCESS;
DWORD dwErr = ERROR_SUCCESS;
DWORD dwErr1 = ERROR_SUCCESS;
DWORD dwEntries = 0;
LPRASENTRYNAME lpRasEntryName = NULL;
DWORD rc;
DWORD dwSize = 0;
LPCSTR lpszPhonebook = argv[1];
DWORD dwRasEntryInfoSize = 0;
LPRASSUBENTRY g_lpRasEntry = 0;
RASDIALPARAMS params = { 0 };
HRASCONN hConn = NULL;
DWORD error;
BOOL bPassword;
RASCREDENTIALS RasCredentials;
RASEAPUSERIDENTITY* pRasEapUserIdentity = new RASEAPUSERIDENTITY();

LPCTSTR pszNewName = _T("RAS Connection 2\0");

if (argc < 2) {
printf("Usage: %s <bpk file>\n", argv[0]);
return 0;
}


dwRet = RasEnumEntriesA(NULL, lpszPhonebook, lpRasEntryName, &dwCb, &dwEntries);

if (dwRet == ERROR_BUFFER_TOO_SMALL)
{
lpRasEntryName = (LPRASENTRYNAME)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwCb);
if (lpRasEntryName == NULL) {
return 0;
}
lpRasEntryName[0].dwSize = sizeof(RASENTRYNAME);

dwRet = RasEnumEntries(NULL, lpszPhonebook, lpRasEntryName, &dwCb, &dwEntries);

if (ERROR_SUCCESS == dwRet) {


for (DWORD i = 0; i < dwEntries; i++)
{
printf("%s\n", lpRasEntryName[i].szEntryName);

rc = RasValidateEntryName(NULL, lpRasEntryName[i].szEntryName);

if (rc == ERROR_INVALID_NAME)
{
printf("--- RasValidateEntryName returned invalid name in CreateTestEntry: %d\n", rc);
}
else if (rc == ERROR_SUCCESS)
{

ZeroMemory(&RasCredentials, sizeof(RasCredentials));
RasCredentials.dwSize = sizeof(RasCredentials);
RasCredentials.dwMask = RASCM_Password;

BYTE AuthData[] = "admin";
dwErr = RasSetCustomAuthData(lpszPhonebook, lpRasEntryName[i].szEntryName, AuthData, sizeof(AuthData));
if (ERROR_SUCCESS != dwErr)
{
printf("RasSetCustomAuthData failed: Error = %d\n", dwErr);
goto down;
}
else
{
printf("Successfully RasSetCustomAuthData\n");
}



}
}
}
down:
//Deallocate memory for the connection buffer
HeapFree(GetProcessHeap(), 0, lpRasEntryName);
RasFreeEapUserIdentity(pRasEapUserIdentity);
lpRasEntryName = NULL;
return 0;
}
return 0;
}

编译好以后,WinAFL 启动即可:

1
2
3
4
5
6
7
8
9
10
11
afl-fuzz.exe 
-i H:\fuzz\pbkinput
-o H:\fuzz\pbkoutput
-D H:\fuzz\DynamoRIO\bin64
-t 20000 --
-target_module ras_fuzz.exe
-coverage_module RASAPI32.dll
-target_method main
-fuzz_iterations 5000
-nargs 2 --
C:\Users\test\ras_fuzz.exe @@

最后得到非常多的崩溃样本:
-w450

经过筛选,我发现这些样本其实属于同一个漏洞,最后提交给了微软。

漏洞分析

漏洞崩溃的位置是 memcpy 函数,入口函数是 RASAPI32.dll 中的 RasSetCustomAuthDataA。

1
2
3
4
5
6
7
8
9
 # Child-SP          RetAddr               Call Site
00 00000000`0014f6b8 00007ff9`b5f0d769 msvcrt!memcpy+0x92
01 00000000`0014f6c0 00007ff9`b5e868a4 RASAPI32!DwSetCustomAuthData+0x145
02 00000000`0014f710 00007ff9`b5e86676 RASAPI32!RasSetCustomAuthDataW+0x194
03 00000000`0014f7b0 00000001`4000125f RASAPI32!RasSetCustomAuthDataA+0x166
04 00000000`0014fc60 00000001`40001580 RAS_fuzz!main+0x1ef
05 00000000`0014fef0 00007ff9`c15e7034 RAS_fuzz!main+0x510
06 00000000`0014ff30 00007ff9`c309cec1 KERNEL32!BaseThreadInitThunk+0x14
07 00000000`0014ff60 00000000`00000000 ntdll!RtlUserThreadStart+0x21

-w1408

经过反编译分析,其中 v15 变量是用户可控的,可以通过修改 Phonebook 中的特定字段,来改变其值。

DwSetCustomAuthData:
-w507

文件中,可控的位置就是 CustomAuthData 字段的数据,箭头指向的 6 就是一个可控的数据,如果我们将 6 改成一个很大的数,就会造成整数溢出,memmove 的 len 如果被整数溢出,就会变成很大的数值,比如 0xfffffff。

phonebook:
-w536

修改数值后,memmove 的 len 确实被整数溢出,最后造成崩溃。
-w198