Talos Vulnerability Report

TALOS-2025-2150

Asus Armoury Crate AsIO3.sys authorization bypass vulnerability

June 16, 2025
CVE Number

CVE-2025-3464

SUMMARY

An authorization bypass vulnerability exists in the AsIO3.sys functionality of Asus Armoury Crate 5.9.13.0. A specially crafted hard link can lead to an authorization bypass. An attacker can create a hard link to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Asus Armoury Crate 5.9.13.0

PRODUCT URLS

Armoury Crate - https://rog.asus.com/us/content/armoury-crate/

CVSSv3 SCORE

8.8 - CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

CWE

CWE-285 - Improper Authorization

DETAILS

Armoury Crate is a centralized software application developed by ASUS, designed to manage and customize the settings of ASUS hardware components and peripherals. It provides users with a unified interface to control various features, such as RGB lighting, system performance, fan speeds, and device configurations.

AsIO3.sys driver creates a device called Asusgio3. When we use the accesschk tool to check which users have the authority to interact with this driver, even as an Administrator (integrity level High) we get the following response:

accesschk.exe -o \Device\Asusgio3

\Device\Asusgio3
  Error getting security:
  Access is denied.
No matching objects found.

By reversing the IRP_MJ_CREATE handler of AsIO3.sys we can learn that Asus developers have decided to implement their own mechanism to allow or restrict communication with this driver.

Please keep in mind that further code address are related to the following image base address:
Python>idaapi.get_imagebase()
0x140000000

When we try to obtain a handle to \Device\Asusgio3 there is a check to determine whether the application attempting this action has the proper SHA-256 image hash or if its process ID is on the allowed list.
Function responsible for checking whether an app obtaining a handle has the proper SHA256 hash is one visible below:

Line 1  bool sub_140002EF4() // Is AsusCertService.exe trying to obtain a handle ?
Line 2  {
Line 3    bool result; // bl
Line 4    _UNICODE_STRING *PoolWithTag; // rax
Line 5    _UNICODE_STRING *win32ImagePath; // r15
Line 6    void *fileContent; // rax
Line 7    void *v4; // r12
Line 8    __int64 blockSize; // rdi
Line 9    __int64 _fileSize; // rsi
Line 10   char *ptrFileContent; // r14
Line 11   char NumberOfBytes[20]; // [rsp+34h] [rbp-F4h] BYREF
Line 12   UNICODE_STRING ntImagePath; // [rsp+38h] [rbp-F0h] SPLIT BYREF
Line 13   __int64 fileSize; // [rsp+48h] [rbp-E0h] BYREF
Line 14   __int64 v12; // [rsp+50h] [rbp-D8h]
Line 15   char *v13; // [rsp+58h] [rbp-D0h]
Line 16   __int64 v14; // [rsp+60h] [rbp-C8h]
Line 17   _UNICODE_STRING *v15; // [rsp+68h] [rbp-C0h]
Line 18   int sha_ctx[28]; // [rsp+70h] [rbp-B8h] BYREF
Line 19   char calculatedSHA256[32]; // [rsp+E0h] [rbp-48h] BYREF
Line 20
Line 21
Line 22   if ( pZwQueryInformationProcess(
Line 23          (HANDLE)0xFFFFFFFFFFFFFFFFLL,
Line 24          ProcessImageFileNameWin32,
Line 25          0LL,
Line 26          0,
Line 27          (PULONG)NumberOfBytes) == 0xC0000004 )
Line 28   {
Line 29     win32ImagePath = (_UNICODE_STRING *)ExAllocatePoolWithTag(PagedPool, *(unsigned int *)NumberOfBytes, 'pPR');
Line 30     if ( win32ImagePath )
Line 31     {
Line 32       memset(PoolWithTag, 0, *(unsigned int *)NumberOfBytes);
Line 33       pZwQueryInformationProcess(
Line 34         (HANDLE)-1LL,
Line 35         ProcessImageFileNameWin32,
Line 36         win32ImagePath,
Line 37         *(unsigned int *)NumberOfBytes,
Line 38         0LL);
Line 39
Line 40       if ( (int)Win32PathToNtPath(win32ImagePath, &ntImagePath) >= 0 )
Line 41       {
Line 42         fileContent = GetFileContent(&ntImagePath, &fileSize);
Line 44         if ( fileContent )
Line 45         {
Line 46           blockSize = 64LL;
Line 47           v12 = 64LL;
Line 48           _fileSize = fileSize;
Line 49           v14 = fileSize;
Line 50           ptrFileContent = (char *)fileContent;
Line 51           v13 = (char *)fileContent;
Line 52           Z_SHA256_Init((SHA256_CTX *)sha_ctx);
Line 53           if ( _fileSize < 64 )
Line 54             blockSize = _fileSize;
Line 55           v12 = blockSize;
Line 56           while ( _fileSize > 0 )
Line 57           {
Line 58             if ( _fileSize - blockSize < 0 )
Line 59               blockSize = _fileSize;
Line 60             v12 = blockSize;
Line 61             Util::AES_Block_encrypt(sha_ctx, ptrFileContent, blockSize);
Line 62             ptrFileContent += blockSize;
Line 63             v13 = ptrFileContent;
Line 64             _fileSize -= blockSize;
Line 65             v14 = _fileSize;
Line 66           }
Line 67           SHA_Final(sha_ctx, calculatedSHA256);
Line 68           result = memcmp(calculatedSHA256, &g_sha256Hash, 0x20uLL) == 0;
Line 69           ExFreePoolWithTag(fileContent, 0x705052u);
Line 70         }
Line 71       }
Line 72       RtlFreeUnicodeString(&ntImagePath);
Line 73       ExFreePoolWithTag(win32ImagePath, 0);
Line 74     }
Line 75   }
Line 76   return result;
Line 77 }

When we dump hash from global variable g_sha256Hash visible at line 68 it looks in the following way:

Python>binascii.b2a_hex(idc.get_bytes(0x0000000140009150,32))
b'c5c176fc0cbf4cc4e37c84b6237392b8bea58dbccf5fbbc902819dfc72ca9efa'

Calculating SHA256 hash for AsusCertService.exe we see that it is the same hash:

PS C:\Users\icewall> Get-FileHash -Path "C:\Program Files (x86)\ASUS\AsusCertService\AsusCertService.exe" -Algorithm SHA256

Algorithm       Hash                                                                   Path
---------       ----                                                                   ----
SHA256          C5C176FC0CBF4CC4E37C84B6237392B8BEA58DBCCF5FBBC902819DFC72CA9EFA       C:\Program Files (x86)\ASUS\AsusCertService\AsusCertService.exe

So, only AsusCertService.exe service and also processes whose PIDs are added by it on allowed list are able to obtain a handle to Asusgio3 device. In in other case we get the status Access is denied.
We can bypass this authorization mechanism using a hard link.
Let us assume the following directory structure:

C:\tmp>dir

 Directory of C:\tmp

02/10/2025  02:25 PM    <DIR>          .
09/25/2024  12:11 PM           503,144 AsusCertService.exe
02/06/2025  02:17 PM            14,848 TestCon2.exe
               2 File(s)        517,992 bytes
               1 Dir(s)  1,352,796,958,720 bytes free

Where:

TestCon2.exe - is our custom application shared with vendor being a proof-of-concept. 
AsusCertService.exe - is original Asus service exectuable file copied from "C:\Program Files (x86)\ASUS\AsusCertService\AsusCertService.exe"

First we make a hard link pointing to our test app:

C:\tmp>mklink /h core.exe TestCon2.exe
Hardlink created for core.exe <<===>> TestCon2.exe

Then we run the symlink (core.exe), and we should see:

Time to swap the files...
Press any key to continue . . .

Our PoC is just waiting for user input.
Before we run our test app further and try to open a handle to \Device\Asusgio3 device we change the hard link destination:

C:\tmp>del core.exe

C:\tmp>mklink /h core.exe AsusCertService.exe
Hardlink created for core.exe <<===>> AsusCertService.exe

We are ready to press any key for TestCon2.exe and run it further. Now when it tries to open a handle to Asusgio3 , ZwQueryInformationProcess at line 33 will return path to hardlink pointing to asus service.
As a consequence, the GetFileContent function at line 42 will read the file content of AsusCertService.exe. In that way we bypass the authorization mechanism and obtain a handle to Asusgio3 device.

Files swaped...result?
Successfully opened handle to device!
last error : 0x0
Returned bytes : 40
Mapped mem : 0x2234c230000
00000000  00 00 00 00 00 00 00 00 c8 27 00 80 ff ff ff ff  | .........'...... |
00000010  00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | . .............. |
00000020  00 00 23 4c 23 02 00 00                          | ..#L#...         |
--------------------------------------
00000000  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................ |
00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................ |
(...)

Keeping in mind that, due to the authorization bypass, any user can obtain a handle to the device, which exposes numerous functionalities critical from a security perspective. Among the others:

  • mapping any physical memory address in virtual address space of calling process
  • giving an access to in/out (I/O Port Communication) instructions
  • giving a possibility to read/write value from/to MSR register at certain index
  • and more…

We believe that this vulnerability is critical and provides a potential attacker with numerous easy ways to escalate privileges and take control of the entire system.

TIMELINE

2025-02-18 - Vendor Disclosure
2025-06-16 - Vendor Patch Release
2025-06-16 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.