(转)深入理解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!

posted @ 2013-01-06 11:32  himessage  阅读(9406)  评论(0编辑  收藏  举报