漏洞公告:https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-26882[出自:jiwo.org]
漏洞Poc : https://github.com/songjianyang/CVE-2021-26882

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

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

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  #include  #include "ras.h" #include "rasdlg.h" #include "raserror.h" #include   #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\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