(转)深入理解section之ZwCreateSection
http://hi.baidu.com/charme000/item/23945ecdb49926d2964452a1
原型:
NTSTATUS ZwCreateSection(
OUT PHANDLE SectionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize OPTIONAL,
IN ULONG SectionPageProtection,
IN ULONG AllocationAttributes,
IN HANDLE FileHandle OPTIONAL
);
参数理解:
-out:
SectionHandle:这个接受返回的section的句柄。这和创建文件对象是一个套路
-in:
DesiredAccess:访问的权限,有以下几个,跟文件对象真的很相似,呼呼
SECTION_EXTEND_SIZE 可扩展大小
SECTION_MAP_EXECUTE
SECTION_MAP_READ
SECTION_MAP_WRITE
SECTION_QUERY 一般的驱动的话 ,就设置这个
SECTION_ALL_ACCESS
ObjectAttributes:这个跟其他地方的一样。用InitializeObjectAttributes初始化就好了,注意一点就可以了,如果不是运行在系统线程上下文中的话,那必须指定OBJ_KERNEL_HANDLE...而且千万不能用OBJ_OPENLINK这个属性值。
MaximumSize:指定section的大小,字节大小,这个大小是对齐到内存页的大小的。所以我们一般把他初始化成一个内存页的大小,也就是4KB。
LARGE_INTEGRE sectionSize;
and sectionSize.HighPart,0
mov sectionSize.LowPart,SECTION_SIZE
最后一个参数是一个FileHandle,如果这个参数是NULL 的话,那我们这个参数是必须指定的。这是个什么逻辑呢?
我们很自然的这样想:如果没有指定文件,那么按MaximumSize造一个section,那么如果指定了filehandle的话,表示我们要按照文件的大小来确定section的大小。就是这么一个逻辑。
SectionPageProtection:
PAGE_READONLY
PAGE_READWRITE
PAGE_WRITECOPY
PAGE_EXECUTE
PAGE_EXECUTE_READ
PAGE_EXECUTE_READWRITE
PAGE_EXECUTE_WRITECOPY
我们可以这样想,实际上内核对象没有什么神秘的,只是在内存中申请了一个内存块,这个内存块有一些自己的属性,要访问的话,我们需要用微软提供的一些函数安全高效的使用他们。
那么既然是内存块的话,必然要涉及到内存的属性,上面的这个参数就是指定我们要把section映射到什么样的内存页上的。
再说的明确点就是:如果filehandle是null,那么就按内存页文件大小来建section,如果是指定的一个文件句柄,那么久按这个文件的大小来建section。
AllocationAttributes:这个指定了这个section本身的属性
SEC_BASED 0x00200000 // Map section at same address in each process
SEC_NO_CHANGE 0x00400000 // Disable changes to protection of pages
SEC_IMAGE 0x01000000 // Map section as an image
SEC_VLM 0x02000000 // Map section in VLM region
SEC_RESERVE 0x04000000 // Reserve without allocating pagefile storage
SEC_COMMIT 0x08000000 // Commit pages; the default behavior 默认使用这个
SEC_NOCACHE 0x10000000 // Mark pages as non-cacheable
FileHandle貌似一般都是null的,呼呼。
返回值理解:
成功-STATUS_SUCCESS
失败-
STATUS_FILE_LOCK_CONFLICT
STATUS_INVALID_FILE_FOR_SECTION
STATUS_INVALID_PAGE_PROTECTION
STATUS_MAPPED_FILE_SIZE_ZERO
STATUS_SECTION_TOO_BIG
字面意思理解就可以了。
运行级别:
PASSIVE_LEVEL
相关的API:
CreateFileMapping
说明:
函数返回的SectionHandle,如果不用了,要用ZwClose来关闭。
扩展:
(1)看到他联系的这个api,实际上file mapping,我们会以为ZwCreateSection就是跟文件相关的东西。实际上这样理解不合理,因为系统上面好多的东西都是被看做文件的,最常见的比如硬盘设备。
szFileName1 db '\\.\PHYSICALDRIVE0',0 ; 打开第一个物理硬盘(PhysicalDrive0)
可以当做文件打开,但是不是所有的都能看做文件!!
那么看看相关的一系列函数ZwOpenSection,我们用ZwOpenSection可以打开\Device\PhysicalMemory。这就不是文件,所以我觉得吧section的相关的东西理解为操作内存块更为妥当。
这个地方谢谢师傅的提醒,,我差点就给理解偏了!!呼呼!
(2)这个函数返回的是一个句柄,既然是句柄,我们要考虑是私有的句柄还是共享的句柄 。这个共享和私有是在哪里区分的?是靠什么区分的?
我找不到相关的解答。我来猜测下吧:
对于user mode的话,就无所谓私有和共享了,因为加载到一个进程地址空间的那些个exe啊或者是dll啊,标示他们的实例句柄都是整个低2GB空间都共享的。只有涉及到了driver的时候才有了私有个共享句柄的这样的概念。
而且我觉得如果只能在kernel mode使用的句柄叫做私有句柄。能够在kernel mode和user mode下传递使用的是共享句柄。
那么我觉得这样分是为安全考虑的。
1>要指定为私有句柄的话,在初始化对象属性的时候指定OBJ_KERNEL_HANDLE。这样这个句柄就不能被用户态程序使用了。
2>如果非要共享的话,一般考虑三种策略:
----在kernel mode创建句柄传递给user mode。而不是user mode创建句柄传递给kernel mode。因为不安全。其实这里我不是很清楚为什么是不安全的。
----初始化对象属性的时候指定OBJ_FORCE_ACCESS_CHECK,也就是说user mode要使用这个句柄的话,必须要通过一个权限验证。
----通过调用ObReferenceObjectByPointer 来对这个要共享的句柄做一个引用。每个内核对象内部都维护一个引用计数,所以这样调用之后实际上就是维持了一个kernel mode对句柄的计数,这样就不会因为user mode随意递减计数而在不适当的时候关闭内核对象而使得handle无效了。
当然最后的话,还要自己递减这个计数,调用ObDeferenceObject就可以了。
实际上hook的切入点很多时候都在这里。
比如我们这样写一个:
NTSTATUS Fake_ZwCreateSection( OUT PHANDLE SectionHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN PLARGE_INTEGER MaximumSize OPTIONAL, IN ULONG SectionPageProtection, IN ULONG AllocationAttributes, IN HANDLE FileHandle OPTIONAL) { PFILE_OBJECT FileObject; POBJECT_NAME_INFORMATION wcFilePath; HANDLE ProcessID; ProcessID=PsGetCurrentProcessId(); if (SectionPageProtection & (PAGE_EXECUTE|PAGE_EXECUTE_READ |PAGE_EXECUTE_READWRITE |PAGE_EXECUTE_WRITECOPY) ) { if (NT_SUCCESS(ObReferenceObjectByHandle( FileHandle, 0,NULL,KernelMode, &FileObject,NULL))) { if (IoQueryFileDosDeviceName(FileObject,&wcFilePath)==STATUS_SUCCESS) { DbgPrint("ProcessID:0x%08X %ws\r\n", ProcessID,wcFilePath->Name.Buffer); } else { DbgPrint("ProcessID:0x%08X %ws\r\n",ProcessID,FileObject->FileName.Buffer); }
ObDereferenceObject(FileObject); ExFreePool(wcFilePath); } else { KdPrint(("ProcessID:0x%08X--FileHandle:0x%08X\r\n", ProcessID,FileHandle)); } } return ZwCreateSection(SectionHandle, DesiredAccess,ObjectAttributes, MaximumSize,SectionPageProtection, AllocationAttributes,FileHandle); }
实际上上面这一小段就是围绕一个filehandle展开的一些查询,我们当然可以对SectionHandle做一些手脚,但是前提是要考虑上面提的那个问题!!
(3)api CreateFileMapping和native api ZwCreateSection除了上面我说的针对范围的区别外,还有好多的区别:
Protect里面,api不能指定PAGE_EXECUTE_XXXX相关的保护标志。
Attributes里面,api不能指定SEC_NO_CHANGE SEC_BASED
而且SEC_VLM这个标志只有在win2000下面才能使用,而且对intel平台没有实现。
(4)在vista以后的系统上,ZwCreateSection里面的参数部分发生了一些变化。SEC_IMAGE变成了0100000h后面变成了5个0.这是MJ发现的。具体的阐述可以看
http://www.debugman.com/read.php?tid=3217
(5)看到MJ介绍这个(4)里面提到的问题的时候,我看到一个函数MmLoadSystemImage,很显然是内存相关的,而且貌似在系统启动后,加载驱动的时候就开始使用上ZwCreateSection了。所以我觉得有必要再这里扩展下。
首先,驱动加载的话,我能想到的就是调用ZwLoadDriver。但是对于win2000(不知道xp怎么样)上w2k.sys这个驱动是在系统启动的时候就加载的,而且加载的方式不是ZwLoadDriver,那他是怎么加载的。查了下资料:原来是ZwSetSystemInformation这个函数。
他内部调用了MmLoadSystemImage。
我看了下wrk对这个函数的实现
NTSTATUS
NTAPI
MmLoadSystemImage(IN PUNICODE_STRING FileName,
IN PUNICODE_STRING NamePrefix OPTIONAL,
IN PUNICODE_STRING LoadedName OPTIONAL,
IN ULONG Flags,
OUT PVOID *ModuleObject,
OUT PVOID *ImageBaseAddress)
实际上这个ModuleObject就是指向section的。那么也就是说在这个函数的内部必然的调用了ZwCreateSection,事实也正是这样的。由此也就出现了Mj说的那个问题!
由此看来,这个ZwCreateSection貌似还很重要嘛,这么早据被派上用场了。那么我们也看到了,实际上w2k.sys的加载就是这样完成的。那么引申出一个加载去东的方法:
_loadDriver:
mov dword ptr [stack],esp
push ImageBaseAddress
push ModuleObject
push 0
push 0
push 0
push driverName
mov edi,805af342h;;MmLoadSystemImage在xp sp3上面的地址
call edi
or eax,eax
jne _fail
mov edi,dword ptr [ImageBaseAddress]
mov ebx,[edi+3ch]
mov ebx,[edi+18h+10h];;;熟悉pe结构的都知道,现在指向了EntryPoint,
add edi,ebx
push 0
push 0
call edi ;;也就是call entry
_fail:
mov esp,[stack]
ret
driverName:
db 'charme.sys',0
ImageBaseAddress dd 0
ModuleObject dd 0
end _loadDriver
非常完美,呼呼!!
(6)我现在特别想知道下ZwCreateSection这个内部是怎么实现的,哪怕是浅尝呢!那我们看看吧!
我用windbg看了半天貌似就进不去么,直接看wrk怎么实现的吧!
Status = MmCreateSection(&SectionObject,
DesiredAccess,
ObjectAttributes,
MaximumSize,
SectionPageProtection,
AllocationAttributes,
FileHandle,
NULL);
if (NT_SUCCESS(Status))
{
Status = ObInsertObject ((PVOID)SectionObject,
NULL,
DesiredAccess,
0,
NULL,
SectionHandle);
}
return Status;
}
哦,原来是MmCreateSection啊!呼呼,很奇妙啊!看到这个实现,然后看看MmCreateSection的参数,我发现一个很奇怪的地方,就是MmCreateSection的第一个参数不是句柄了。但是filehandle还在,而且回忆MmLoadSystemImage的内部实现,他总是先ZwCreateFile,然后ZwCreateSection,而且后者的FileHandle参数传递的就是前者的返回者。看来一般情况下,我们总是走这样的一个流程,虽然前面说了FileHandle参数可以为NULL,NULL的时候表示是根据内存页来创建一个Section。
那么我们可以这样想,比如有一个文件,我们不能读他,也不能写他,反正就是百毒不侵吧!那么我们要怎么才能读取它,改写他?这是一个问题!看看360的比赛题目我发现,貌似就有这样的挑战。
那么我想,我们可以这样写一段代码!我们要读取这个百毒不侵的文件,可以考虑把他映射以下,但是我说了,即使是直接使用MmCreateSection,它的参数里面也要设计FileHandle这个参数,问题是这个百毒不侵的文件根本就不允许我们获得有效的文件句柄,那么下面的工作就全白费了。那么我们就想,不适用FileHandle参数,而实现完整的映射。这样来看看:
///////////////////////////////////////////////////////////////
NTSTATUS NTAPI
MmCreateSection (OUT PVOID * Section,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize,
IN ULONG SectionPageProtection,
IN ULONG AllocationAttributes,
IN HANDLE FileHandle OPTIONAL,
IN PFILE_OBJECT File OPTIONAL)
///////////////////////以上是注释/////////////////////////////
NTSTATUS status;
LARGE_INTEGER li;
LARGE_INTEGER off;
SIZE_T st;
PFILE_OBJECT fo;
PVOID pSection;
li.QuadPart=1024*1024*2;
PVOID pCharme=0;
HANDLE hSection;
status=MmCreateSection(&pSection,SECTION_ALL_ACCESS,NULL,&li,PAGE_READWRITE,SEC_RESERVE,NULL,fo);
if (NT_SUCCESS(status))
{
status=ObInsertObject(pSection,NULL,SECTION_ALL_ACCESS,0,NULL,&hSection);
if(NT_SUCCESS(status))
{
status=MmMapViewOfSection(pSection,IoGetCurrentProcess(),&pCharme,0,100,&off,&st,1,MEM_TOP_DOWN,PAGE_READWRITE);
b=*((PBYTE)pCharme);
ZwClose(hSection);
}
else
{
DbgPrint(...)
}
}
else
{
DbgPrint(...)
}
pCharme就是最后映射好的可以读取的内存块的首地址。这个时候我们就可以操作刚才的文件了。
(7)翻到一个师傅帮别人解决了的问题。我也顺便看了下,当时我还回答了下呢,其实没有涉及要害!http://www.debugman.com/read.php?tid=3545
现在我来回答下。
首先ZwCreateFile和ZwCreateSection调用之前都得用 InitializeObjectAttributes宏来初始化一个Object_Attributes结构。按常规的逻辑想:
create file 的时候初始化属性结构肯定要填充fielname为一个有效值的。
而调用ZwCreateSection的时候我们需要的只是filehandle,所以filename就为NULL就可以了。
那个兄弟只初始化了一次对象属性。虽然把filename改成NULL之后可以了,但是实际上只是没有编译错误了,我敢保证他绝对不会实现他的那个映射的目的。因为他ZwCreateFile的时候filename是NULL,那么建什么文件呢?
总结:
这个函数貌似我扩展的最多了,累死了!就到这里吧,貌似这个函数只有结合相关的比如ZwOpenSection等函数才能写个完整的程序出来。
我就不写了。其实写的话也很简单了,弄透彻了原理和内部的一些机制。写就很简单了!greate!