漏洞公告: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。
为了保证 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 |
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 @@ |
最后得到非常多的崩溃样本:
经过筛选,我发现这些样本其实属于同一个漏洞,最后提交给了微软。
漏洞分析
漏洞崩溃的位置是 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 确实被整数溢出,最后造成崩溃。