Through this analysis, we discovered an RPC function named AavmRpcRunSystemComponent that uses the CreateProcess API without RPC impersonation:
.rdata:0000000165FCA550 dq offset sub_1655C5580
.rdata:0000000165FCA558 dq offset sub_1655C55D0
.rdata:0000000165FCA560 dq offset AavmRpcRunSystemComponent
.rdata:0000000165FCA568 dq offset DecryptData
.rdata:0000000165FCA570 dq offset AddNetAlert
When the RPC client is not impersonated, any new process spawned through this function will run with SYSTEM privileges, creating a critical opportunity for privilege escalation. However, before this process is initiated, a DSA_FileVerify check takes place:
__int64 __fastcall AavmRpcRunSystemComponent(__int64 a1, unsigned int whitelist_id, __int64 arguments, DWORD *out_pid)
{
// [...]
char out_string[32];
// [...]
v8 = GetFileById(out_string, whitelist_id); // [1]
// [...]
FileW = CreateFileW((LPCWSTR)out_string, 0x80000000, 1u, 0i64, 3u, 0x8000000u, 0i64);
v12 = FileW;
v21 = (__int64)FileW;
if ( FileW == (HANDLE)-1i64 )
{
// file not found
}
if ( !GetFinalPathNameByHandleW(FileW, szFilePath, 0x104u, 0) )
{
// File path could not be resolved
}
if ( whitelist_id != 2 && !(unsigned __int8)DSA_FileVerify(szFilePath, 0i64, 18i64) ) // [2]
{
LastError = 87; // ERROR_INVALID_PARAMETER
CloseHandle(v12);
return LastError;
}
// [...]
snprintf(combined_arguments, v15, L"%s %s", szFilePath, arguments); // [3]
// [...]
if ( CreateProcessW(szFilePath, combined_arguments, 0i64, 0i64, 0, 0, 0i64, 0i64, &StartupInfo, &ProcessInformation) ) // [4]
{
// Win ?
The DSA_FileVerify function performs several validations:
- Based on the integer argument in [1], it returns a filename. Most of the executable files in this list are repair or setup tools, such as aswOfferTool.exe, SupportTool.exe and AvEmUpdate.exe, which limits the options to those predefined binaries.
- A file signature verification is performed in [2] to ensure only trusted binaries can be executed. This check prevents an attacker from inserting their own malicious binary into the process.
- Finally, the program arguments are constructed in [3], and the process is created with SYSTEM privileges in [4].
Although this function appears to be a promising privilege escalation vector, the constraints of the allow-listed binaries and file signature verification present significant roadblocks. Without the ability to exploit any of the allow-listed programs, this avenue may seem like a dead end.
To overcome this limitation, we decided to experiment with the RPC client bindings found in the aavmrpch.dll library. Using this approach, we began testing the functionality of various RPC interfaces, with particular emphasis on the AavmRpcRunSystemComponent function, to explore potential exploitation paths.
Abusing the update mechanism
The most promising target for exploitation was the AvEmUpdate.exe executable, which accepts a range of command-line arguments. This executable is responsible for installing updates provided as cab or DLL files. Since we could control the arguments passed to it, this presented a compelling opportunity for further exploration.
One particularly interesting argument was /applydll, which allows the process to load a specified DLL. Crucially, because the process runs with SYSTEM privileges, this argument could potentially be abused to escalate privileges. However, the update mechanism includes an additional safeguard: it verifies that the provided DLL is signed by the manufacturer. This signature check prevented us from directly supplying a custom DLL to gain SYSTEM privileges.
TOCTOU race
Despite this limitation, we were confident that we could bypass the integrity check by carefully analyzing and exploiting the logic of the process. We finally found a time of use vs time of check (TOCTOU) issue in the logic, which made the integrity checks bypassable. To exploit this reliably, we employed a combination of OpLocks (opportunistic locks) and junctions.
To control the timing of the file accesses during exploitation and exploit our race reliably we needed a way to put the update process in a waiting state. Here we used OpLock to block access to the DLL file and force the update process to wait for us releasing the OpLock. This works even on processes running as SYSTEM, while operating as an unprivileged user. This gives us time to prepare for the next step.
We also want to be able to switch out the DLL file while holding our OpLock. This is where junctions come in. Junctions are symbolic links that can redirect file system access to a different location. Since an unprivileged user can create junctions, we used this capability to redirect file accesses during the exploitation process while holding our OpLock. We can point the junction to an other location for the next file access and there precisely control which file is accessed for each single file access. For more information on OpLocks and junctions, refer to the code provided by James Forshaw and this article from ZDI.
Here’s how the exploit worked:
- The AvEmUpdate.exe process made multiple file accesses before loading the DLL, likely to verify its legitimacy.
- Using a junction, we redirected the process to a valid, signed DLL for the first three file access attempts.
- On the fourth file access, when the process attempted to load the DLL, we redirected the junction to our malicious DLL containing the privilege escalation payload.
Because we were holding an OpLock on the initial three file accesses, we could dynamically change the target of the junction while the SYSTEM process was waiting for access to the previous file. After updating the junction’s target, we released the OpLock, allowing the process to move on to the next file. We repeated this until the fourth access successfully loaded our malicious DLL.