漏洞公告: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。
为了保证 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: 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 @@
最后得到非常多的崩溃样本:
经过筛选,我发现这些样本其实属于同一个漏洞,最后提交给了微软。
漏洞分析 漏洞崩溃的位置是 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
经过反编译分析,其中 v15 变量是用户可控的,可以通过修改 Phonebook 中的特定字段,来改变其值。
DwSetCustomAuthData:
文件中,可控的位置就是 CustomAuthData 字段的数据,箭头指向的 6 就是一个可控的数据,如果我们将 6 改成一个很大的数,就会造成整数溢出,memmove 的 len 如果被整数溢出,就会变成很大的数值,比如 0xfffffff。
phonebook:
修改数值后,memmove 的 len 确实被整数溢出,最后造成崩溃。