windows kernel exploitation基础教程

author:P3nro5e

来源: http://poppopret.blogspot.com/2011/06/windows-kernel-exploitation-part-1.html

0x00 概述

1.WTFBBQ?

由于现代Windows操作系统的多种缓解技术(ASLR,DEP,SafeSEH,SEHOP,/GS...),用户态的软件变得越来越难以利用.在安全社区,驱动安全问题变得越来越受关注。

这个系列的文章中,我试图共享在windows系统上关于内核利用的探索结果.因为在网上关于这个话题的文档不是很多-不是很容易理解-我已经发现发表这些文章对于像我这样的新手来说是有帮助的.当然,这些文章中的错误欢迎在评论中指出.

实际上,在阅读完 "A guide to Kernel Exploitation" (1]这本书中关于windows的章节后我已决定研究这个驱动(作者用于阐明在驱动中最为经典的弱点和利用这些弱点的方法).这个驱动称为DVWDDriver- Damn Vulnerable Windows Driver的缩写-且可以在该地址上获取该章节.说实话,仅通过阅读这本书而没有查看源代码中的细节和一些附加paper,并不能得到书中所有的”干货”.这就是我写这些文章的原因.

在这篇文章中,我将描述驱动及其弱点,在下一篇文章中我将试图共享我们利用那些弱点的相关理解.当然,我将不会虚构任何东西,并且所有描述的东西都是基于可获取的文档的.这一系列的文章的目的是给出不同利用方法的全局概要并提供带有注释的源代码的技术细节

2.Damn Vulnerable Windows Driver –陈述 DVWDDriver可以控制三种不同的IOCTL(I/O Control Code):

  • DEVICEIO_DVWD_STORE: 允许将用户态的buffer复制到存储KMD(内核态驱动)的一个全局结构体的buffer

  • DEVICEIO_DVWD_OVERWRITE:允许检索位于内核态的buffer.这将通过将内核态中的buffer复制到给出地址的buffer中.是的,没有做关于地址的检查且将看到这里存在一个弱点

  • DEVICEIO_DVWD_STACKOVERFLOW:允许将被传递进参数的buffer复制到本地buffer

用DvwdHandleIoctlStore() (将调用TriggerStore()函数)处理第一种IOCTL.基本上, ProbeForRead() 函数会检查结构体(包含buffer及其大小)是否指向用户态的内存地址({buffer, size}).然后它调用SetSavedData()函数(目的是将buffer的内容复制进全局结构体的buffer中(当然这位于内核态).在进行复制操作之前,函数再次使用ProbeForRead()例程,但这次是在缓冲区指针上使用.使用它是为检查buffer是否也位于用户态中.

DvwdHandleIoctlOverwrite()函数处理第二个IOCTL,这将调用TriggerOverwrite()函数.TriggerStore()用同样的方式,这个函数检查传递到参数中({buffer, size })的结构体指针是否指向一个用户态的地址(address<=0x7FFFFFFF).然后,它调用GetSavedData()函数(为了将含有全局结构体的buffer复制到被传递到参数中的结构体的buffer中.然而,这里没有做附加检查(以确认目标buffer是否位于用户态).

前两个IOCTL允许利用Arbitrary Memory Overwrite弱点,且将在下一幅图中明白其中的原因:

DvwdHandleIoctlStackOverflow()处理第三个IOCTL,这将调用弱点函数TriggerOverflow(),我们将看到这个函数具有基于栈的缓冲区溢出弱点

3.第一种弱点:覆盖任意内存

我们已经看到GetSavedData()函数不会检查目标buffer(以参数的形式接收)的指针是否在用户态.函数将这个存储在全局结构体中的数据复制进该buffer中.问题是用户不检查用户控制的指针.所以,如果用户态的进程指定一个任意值-如一个位于内核态中的地址-函数最终覆盖任意内核内存范围内的值.且因为它可能写入到KMD的全局结构体的buffer中(有了 DEVICEIO_DVWD_STORE IOCTL ),我们可以在我们想要的地址上写入任意数量的数据.这被称为Arbitrary Memory Overwrite 弱点或 write-what-where 弱点.

这是一个注释过的弱点函数的源代码:

在下篇文章中我们将看到利用这种弱点的方法.

4. 第二种弱点:基于栈的缓冲区溢出

TriggerOverflow()函数仅检查buffer是否接收用户态的参数,是否将用户态的参数复制进本地buffer.本地buffer仅64字节长.显然这是种典型的缓冲区溢出弱点,因为这里没检查源buffer的大小。好吧,它发生在内核态,所以不算太经典.

5. 测试平台

下篇文章将在Windows Server 2003 SP2(32-bit)上展示利用技术

为了快速加载驱动这里使用“OSR DriverLoader”工具(下载地址: http://bbs.pediy.com/showthread.php?t=100473&highlight=osr+loader)

引用 0x00

(1] A Guide to Kernel Exploitation (Attacking the Core), by Enrico Perla & Massimiliano Oldani http://www.attackingthecore.com

(2] ProbeForRead() 例程 http://msdn.microsoft.com/en-us/library/ff559876(VS.85).aspx

(3] OSR Driver Loader 下载 http://bbs.pediy.com/showthread.php?t=100473&highlight=osr+loader

0x01 使用HalDispatchTable利用任意内存覆盖弱点

在这篇文章中我们将看到在DVWDDriver中利用write-what-where弱点的方法的相关陈述.该方法是覆盖某个内核调度表中的一个指针.内核使用这种表存储多种指针.某种表的例子:

  • SSDT(系统服务描述符表) nt!KeServiceDescriptorTable存储系统调用的地址.内核使用它以调度系统调用(更多信息在(1]中).

  • HAL Dispatch Table nt!HalDispatchTable.HAL(硬件抽象层)软件层的一种,使用它的目的是使系统从硬件中孤立.基本上,它允许在机器上(带有不同硬件)运行相同系统.这种表存储HAL使用的例程指针.

这里我们将改写HalDispatchTable()中一个特定的指针.让我们看看这样做的原因及其方法吧

1. NtQueryIntervalProfile()和HalDispatchTable

NtQueryIntervalProfile()和HalDispatchTable根据(3],NtQueryIntervalProfile()是ntdll.dll中导出的未公开的系统调用.它调用内核可执行程序ntosknl.exe导出的KeQueryIntervalProfile()函数.如果我们反汇编那个函数,我们可看到如下:

因此位于nt!HalDispatchTable+0x4地址上的例程调用完成(看红色方框).所以如果我们覆盖那个地址上的指针-也就是说HalDispatchTable中的第二个指针-带有我们shellcode地址;然后我们调用函数NtQueryIntervalProfile(),将执行我们的 shellcode.

2.利用方法论

笔记: 驱动使用的GlobalOverwriteStruct是全局结构体,它用于存储buffer及它的大小.

为利用Arbitrary Memory Overwrite弱点,基本的想法是:

1.使用DVWDDriver的IOCTL DEVICOIO_DVWD_STORE:为把我们的shellcode地址存储进GlobalOverwriteStruct结构体(内核态)的buffer.记住我们传递到参数的地址必须位于用户内存地址空间(address<=0x7FFFFFFF),因为这是在IOCTL句柄中使用函数ProbeForRead()完成检查的.好吧,没问题,我们仅是把一个指针传递到我们的shellcode(当然,它指向用户态)!因此,传递到驱动的结构体含有这个指针且buffer的大小为4字节.

2.然后,使用DVWDDriver的IOCTL DEVICOIO_DVWD_OVERWRITE是为了将内容写入buffer(地址位于被存储进GlobalOverwriteStruct的buffer)-也就是说之前添加的shellcode地址-被传递进参数的地址.记住这时,在IOCTL句柄中没有进行检查,所以这个地址可以是任意位置,不管是在用户态还是内核态.因此,我们将传递在HalDispatchTable中第二个入口地址,当然,这在内核态.

3.所以综上所述,我们滥用IOCTL DEVICOIO_DVWD_OVERWRITE是为了写我们想要的what及我们想要的where:

  • what = 我们shellcode的地址

  • where = nt!HalDispatchTable+0x4的地址

要利用这些类型和的弱点,重点是需要理解控制那两个构成成分 NB:这里我们可覆盖完整的地址(4字节 )但是案例中只能覆盖1字节.在这样的情节中,需要用一个在用户态的地址覆盖Most Significant Byte 的第二个入口的HalpatchTable:例如,我们可占用0x01.然后,需要把NOP sled放在0x01000000-0x02000000 (标记为 RWX的内存区域)范围内(在末尾带有跳转到我们shellcode的指令).

hey..等等!我必须讨论下我们使用的shellcode

3. shellcoding…patch我们的访问令牌并回到ring 3层

在windows中,访问令牌(或仅被称为Token)被使用于描述一个进程或线程的上下文安全.特别地,它存储User SID,Groups SIDs和一个特权列表.基于这些信息,内核可决定被要求的行为是否被授权(访问控制).在用户空间中,在一个令牌上可能得到一个句柄.更多关于句柄的信息会在(4]中给出.这是用于描述一个访问令牌的structure _TOKEN细节:

SIDs的指针列表被存储进UserAndGroups(显示_SID_AND_ATTRIBUTES).我们可检索Token中包含的信息,如下所示:

4. 总结…

在利用阶段中这是我们需要做到的:

  1. 为得到HalDispatchTable的偏移,可在用户态中加载内核可执行程序ntokrnl.exe.然后推算出它在内核态中的地址.
  2. 检索我们shellcode的地址.这通常是被用于patch 访问令牌的函数地址.但值得注意的是HalDispatchTable中被覆盖的指针(通常指向一个函数)将会用到4个参数(在4个值在被压入栈前: call dword ptr [nt!HalDispatchTable+0x4]).所以,我们使用一段带有4个参数的函数的shellcode,仅是因为兼容性。
  3. 在ntdll.dll中检索NtQueryIntervalProfile()系统调用的地址
  4. 用我们的shellcode函数地址覆盖 nt!HalDispatchTable+0x4的指针.是的一个带有4个参数的指针(patch 进程的令牌).这将通过连续两次调用callingDeviceIoControl() 发送2次IOCTL:DEVICOIO_DVWD_STORE 和 thenDEVICOIO_DVWD_OVERWRITE来完成,这在图2中解释了.
  5. 为触发shellcode调用函数NtQueryIntervalProfile().
  6. 当然..这时进程正在System用户下运行,因此我们可弹出shell或做一些其它我们想做的!

下图是由(2]中给出的全局概要:

5.利用代码

这是DVWDDriver作者开发的利用代码.当我读完那些代码时,我已经添加了许多注释以确保完全理解它们.随着上一次利用的进行,这应该变得更容易理解,这里没有什么需要注意的了=)

6. w00t ?

接着试图利用

引用 0x01

(1] SSDT Uninformed article http://uninformed.org/index.cgi?v=8&a=2&p=10

(2] Exploiting Common Flaws in Drivers, by Ruben Santamarta http://reversemode.com/index.php?option=com_content&task=view&id=38&Itemid=1

(3] NtQueryIntervalProfile(), http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Profile/NtQueryIntervalProfile.html

(4] Windows Internals, book by Mark Russinovich & David Salomon

0x02 通过LDT利用Arbitrary Memory Overwrite弱点

在上篇文章中我们明白了在DVWDDriver(基于覆盖位于内核调度表HalDispatchTable的一个指针)中write-what-where 弱点的一种利用方法.这种技术依赖于未公开的系统调用,也因此出现了一个技术上的问题(在下次系统更新后是否还存在这个系统调用),此外,新的技术细节将在这篇文章中(基于硬件特殊结构体GDT和LDT,它们在不同的windows版本中也仍然保持相同的特性)讲到.

首先,需要GDT和LDT的背景知识,因此我们将用到Intel 手册=)

1. Windows GDT and LDT

根据Intel 手册(2],分段是用段选择子(16-位值)实现的,通常,一个逻辑地址组成如下:

  • 一个偏移地址,32-位值,
  • 一个段选择子,16位值.

这是段和页机制的全局概要(逻辑地址—>线性地址->物理地址):

上图展示了逻辑地址被转换成线性地址的过程(因为有分段).然后我们可看到页机制.基本上,它由线性地址转换成物理地址组成.这通常是一种没用的Intel特性.线性地址==物理地址.windows使用页机制,所以线性地址仅是另一种分割成三个成分的结构.为得到物理地址那些成分的值被当做数组中的偏移来使用

无论如何,我们可看到段选择子引用一个表中的入口且在线性地址空间中这个入口通常描述一个段(段描述符):这个表是GDT.好的,但是它是如何工作的呢,LDT呢?让我们回头看看Intel手册..

我们学习的GDT(全局描述符表)和(局部描述符表)是两种段描述符表.我们也可看看这个直观图:

在每个系统启动时,必须创建一个GDT.整个系统的每个处理器有一个单一的GDT(是称为”“全局”表的原因)并在系统上可与所有任务共享.虽然LDT可被一个单一任务或一组相互之间有关系的任务使用.但它可有可无;一个LDT被定义为一个单一的GDT入口(特别是对一个进程而言),意味着在进程上下文切换期间,入口被替代成GDT.

为了给出更多细节,GDT一般含有:

  • 一对内核态代码和数据段描述符, DPL=0(DPL定义被引用段的特权级,等等)
  • 一对用户态代码和数据段描述符, DPL=3
  • 一个TSS(任务状态段),DPL=0.阅(3]
  • 3个附加的数据段入口
  • 一个任意的LDT入口

默认,一个新进程没有任何被定义的LDT,然而如果进程发送一个创建它的命令,它将可被分配.如果一个进程有一个相应的LDT,那么如下所示在LdtDescriptorfield(内核结构体_KPROCESS对应进程的)中将会找到一个指针.

2. Call-Gate

Call-Gate允许访问带有不同特权级的代码段,” Call-Gate”促进控制程序控制在不同特权级之间的传输.它们通常仅被使用于操作系统或可执行程序(使用特权机制).

Call-Gate是一种GDT或LDT的入口.它是特殊描述符的一种(被称为Call-Gate描述符).它的大小和段描述符相同(8字节),但是一些成分没有以相同的方式划分.下图出自(1]且清晰地展示了不同点:

事实上,Call-Gate对跳转到不同段或不同特权级的运行(ring)来说是有用的.当调用Call-Gate时,将会做如下工作:

  1. 处理器访问Call-Gate描述符,
  2. 通过使用包含Call-Gate的段选择子来定位我们最终想要访问的代码段描述符,
  3. 它检索代码段描述符的基地址并用其加上Call-Gate描述符的偏移值
  4. 得到想要获取的代码的线性地址(代码段描述符的线性地址 = 基地址 +偏移值)

文章(4]解释了我们添加Call-Gate(允许我们在ring0到ring3中运行代码)的方法. 所以,我将不会重复一些在好文章中陈述过的东西,但这只陈述对我们来说即将有用的东西:

  • 在我们将要执行的payload下,“段选择子”域必须引用段描述符.因为如果在Ring0中要有执行它的完全特权,那么必须引用内核代码段(CS)描述符.正确值是0x0008.
  • 如果我们需访问来自用户态的Call-Gate,“DPL”必须等于3.
  • “Offset”必须是我们想要执行的代码地址.
  • 因为Call-Gate,“Type”必须等于12.

After that, we need to know how to call our Call-Gate...

之后,我们需要知道调用我们Call-Gate的方法..

首先我们将使用x86指令FAR CALL(0x9A).这与一般的CALL不同,因为我们必须指定一个偏移(32-位)AND一个段选择子(16-位),在我们的案例中,我们仅需为段选择子放置正确的值,且我们必须离开在0x00000000上的索引.当然,这里我们使用了两次调用;第一次调用是为了到Call-Gate描述符然后Call-Gate描述符指向我们想要执行的代码.让我们看看创建段选择子的方法:

所以: * 位0,1:我们从用户态中调用Call-Gate,因此我们将把值11(对于ring3来说,十进制为值3)放置于此. * 位2:将值设为1因为我们将把Call-Gate描述符置于LDT中; * 位3..15:这是GDT/LDT中的索引.我们将Call-Gate放在LDT中的首位,因此我们在这将值设为0.

3.利用方法论

现在我们已介绍完关于GDT和LDT的相关知识.我们可开始介绍利用方面的知识.

基本上,利用是由一个创建的新LDT构成.然后,我们把新入口添加到LDT中-仅是一个入口- 一个Call-Gate描述符(在解释它之前这已放置了正确的值).

接着为了用伪造的LDT描述符覆盖LDT描述符,我们需要用到write-what-where弱点

  • what = 伪造LDT的LDT描述符,
  • where =GDT中LDT的位置.通过一个称为LDTDescriptor的KGDTENTRY结构体来描绘LDT, 正如我们之前看到过的,它是一个_KPROCESS结

构体的入口(内核用于存储关于特定进程信息的结构体).因此我们可以通过检索_KPROCESS(==_KPROCESS的地址)得到我们想要写入的位置并将其加上恰当的偏移值(windowsServer 2003 SP2中为0x20).

最后,我们可以在当前进程LDT的第一(且仅有)入口上通过FAR CALL调用我们的Call-Gate.这将允许其跳转到我们的shellcode.

4. Shellcoding

好了我们已经明白利用是如何工作的了.我们将重用在上一篇文章中使用过的shellcode(关于利用带有write-what-where弱点的HalDispatchTable).但是这里有一个问题..在我们的payload执行之后我们需要从Call-Gate返回.一个FAR CALL将会跳转到Call-Gate,也就是说EIP指向的段将会改变,因此在执行之后我们需要一个FAR RET(0xCB).通过这样做我们可以跳转到我们利用中的下一条指令. 无论如何,重点是记住在内核态而不是用户态中指向KPCR(内核处理程序控制区域)的FS段描述符(它指向TEB结构体(线程执行块)).事实上:

• 在内核态中,FS=0x30 • 在用户态中,FS=0x3B

因此,在内核态中,在执行我们的shellcode之前,必须把FS设为0x30,然后在返回之后将其置为0x3B 前两个原因DVWDExploit的作者已经用ASM写了一个wrapper(ReturnFromGate)来实现那些操作.这是该wrapper的地址(必须被放置到Call-Gate描述符的偏移范围内).

5.利用细节

好的,我们已经彻底理解这个利用的细节.这是它的工作流程:

  1. 检索在内核态中被执行的payload地址(命名为KernelPayload),那表明是patch 当前进程的Access Token的代码
  2. 检索_KPROCESS结构体的地址
  3. 检索GDT中LDT描述符的地址(定位于_KPROCESS+偏移(0x20))
  4. 在ntdll.dll内使用ZwSetInformationProcess()系统调用创建一个新的LDT.该工作由称为SetLDTEnv()的函数完成.
  5. 将KernelPayload的地址放到wrapper,ReturnFromGate将可以从它那里调用shellcode,然后将这个wrapper放入可执行内存.
  6. 用称为PrepareCallGate32()的函数创建Call-Gate描述符.当然,为了在ring0到ring3之间执行代码,我们已经明白恰当填充Call-Gate区域的方法.
  7. 可以用PrepareLDTDescriptor32()函数创建LDT描述符(对应上一个被创建的LDT)
  8. 通过使用弱点,将之前创建的一个对应的伪造LDT覆盖GDT中的LDT描述符: • 利用DVWDDriver的IOCTL DEVICEIO_DVWD_STORE把新的LDT描述符存储进GlobalOverwriteStruct • 编写新的LDT描述符- 在GlobalOverwriteStruct中-位于GDT中的现存LDT描述符,谢谢 DVWDDriver的 IOCTL DEVICEIO_DVWD_OVERWRITE
  9. 然后我们需要强制进行一个进程的上下文切换.事实上,GDT中的LDT段描述符仅在上下文切换后更新.为了达到目的我们仅需要休息一段时间.
  10. 最后使我们的FAR CALL通向Call-Gate.那将触发wrapper的执行且在内核态中执行我们的shellcode
  11. 从我们的shellcode返回时,正在运行的进程SID = NT AUTHORITY\SYSTEM,这时我们可以做任何我们想做的! 一幅图或许可以帮助理解... =)

6.利用代码

7. w00t ?

利用运行结果如下:

再次w00t !!

引用 0x02

(1] GDT and LDT in Windows kernel vulnerability exploitation, by Matthew "j00ru" Jurczyk & Gynvael Coldwind, Hispasec (16 January 2010)

(2] Intel Manual Vol. 3A & 3B http://www.intel.com/products/processor/manuals/

(3] Task State Segment (TSS) http://en.wikipedia.org/wiki/Task_State_Segment

(4] Call-Gate, by Ivanlef0u http://www.ivanlef0u.tuxfamily.org/?p=86

0x04 利用基于栈的缓冲区溢出弱点-(绕过 cookie)

在这篇文章中,当我们把很大的buffer传递到驱动(有DEVICEIO_DVWD_STACKOVERFLOW IOCTL)中时,我们将利用驱动中基于栈的缓冲区溢出弱点.主要是我们已得到位于内核态中的buffer且我们可以像在用户态中那样溢出它(内核态中的缓冲区溢出概念和用户态中的溢出概念相同),正如我们在这个系列中的第一篇文章中看到的,使用RtlCopyMemory()函数是一件很糟糕的事.

首先我们将明白在驱动中检测弱点的方法接着我们将成功利用进程

1. 触发弱点

为了触发弱点,我已写了一小段代码:

代码浅显易懂,它仅发送512-字节的垃圾数据(事实上是511个’A’+’\0’).这应该足以溢出驱动使用的buffer了,它才64-字节长) 好的,让我们编译并运行上面的代码吧,这是我们得到的结果:

BOUM!一个很棒的BSOD发生了!

现在我们将用于测试的windows VM附加到远程内核调试器中,那事实上正在另一台windows VM中运行.所有关于使用VMware搭建的远程调试环境的细节已在这篇文章中(1]给出.

我们再次运行代码,将buffer发送到驱动后,windows VM冻结了.

…同时,远程内核调试器检测到了”fatal system error”:

为了得到更多信息,我们输入!analyze –v,接着我们得到结果如下:

所以这是内核栈已被溢出的证明.我们可以看到在崩掉栈时,我们所有的’A’(0x41)在栈转储中.但意识到重要的错误信息是: DRIVER_OVERRAN_STACK_BUFFER (f7)那意味着通过内核可以直接检测到栈溢出.这个错误可以确认为使用了一种机制Stack-Cookie-也被称为Stack-Canary-用于避开栈溢出…. 原理和用户态中的一样(在MS Visual Studio的链接器中使用有用的/GS标志).通常,一个安全cookie(伪随机4-字节值)被放在栈上(位于ebp的值和局部变量之间),因此我们想要达到目的且为了溢出存储在EIP中的值,我们不得不溢出该值.当然,在这个函数的末尾,检查安全cookie值而不顾原来的值(预期值).如果它们不匹配,那么我们在被触发之前将会出现fatal error.

2. Stack-Canary ?

如果我们反汇编弱点函数,我们将看到如下:

在函数的末尾 有个__SEH_prolog4_GS调用:这是一个函数,它被用于:

• 创建对应于写入了__try{}__except{}函数的异常句柄块(EXCEPTION_REGISTRATION_RECORD) • 创建Stack-Canary

无论如何,在函数的末尾中,我们可以看到一个__SEH_epilog4_GS的调用;这是一个函数(检索当前Stack-Canary的值)并调用__security_check_cookie()函数.这末尾函数目的是用Stack-Canary的预期值与当前值进行比较.这个预期值(symbol: __security_cookie)将被存储进.data段中.如果值不匹配,会像上次测试那样崩掉系统(BSOD).

3.内核态中绕过Stack-Canary的方法

要绕过Stack-Canary,目标是在检查cookie之前(在调用 __security_check_cookie() 函数之前)触发异常.所以,想法是生成内存故障异常(由于访问了在用户态中的一块未被映射的区域,而不是在内核态中).为了实现该想法,我们将使用CreateFileMapping()和MapViewOfFileEx()API调用构造一块被映射的内存区域(匿名映射)(阅读(1])然后用shellcode的地址(稍后将会编写)填充该区域.

在发送一个DEVICEIO_DVWD_STACKOVERFLOW IOCTL时,重点理解我们如何将用户态的buffer指针,及它的大小传递到驱动.技巧是用此方式(buffer的末端放置在接下来未被映射的页中)调整buffer指针,这足以将buffer仅有的最后四字节放置在匿名映射范围外.DVWDDriver的作者的书中用这幅图相当好地阐明这一点:

通过这样做,当驱动要读取buffer(用于复制)时,它将终止试图读取在用户态未被映射的内存区域.所以将会触发异常,在内核态将可能使用SEH利用绕过Stack-Canary.

4. Shellcoding

  1. _KPRCB中找到对应当前线程的 _KTHREAD结构体.
  2. _KTHREAD中找到对应当前进程的_EPROCESS结构体,
  3. 在_EPROCESS查找带有PID=4的进程(uniqueProcessId=4);该"System"进程SID== NT AUTHORITY\SYSTEM SID
  4. 检索那进程的令牌地址
  5. 对应我们想要提权的进程中找到_EPROCESS.
  6. 用”System”进程的令牌替换进程的Token.
  7. 使用SYSEXIT指令返回到用户态中.在调用SYSEXIT之前,正如在(2]中解释的那样调整寄存器.为了直接跳转到用户态中的payload那将用完全特权运行.

首先找到在Windows Server 2003 SP2中内核结构体的偏移.为了达到目的,我们将使用kd进行深入了解那些结构体

从这:我们可以推算出偏移(帮助你在Windows Server 2003 SP2 上编写shellcode)

• _KTHREAD:定位于fs:[0x124](在 FS段描述符指向 _KPCR的位置) • _EPROCESS: 从_KTHREAD开始到0x38 • 一个双链表,它链接所有_EPROCESS结构(所有进程中).被定位于从_EPROCESS开始到0x98偏移范围.在该双链表内,它也对应下一元素(Flink)的指针. • _EPROCESS.UniqueProcessId: 它是相应进程的PID.从_EPROCESS开始定位于0x94偏移上 • _EPROCESS.Token: 该结构体含有访问令牌.在_EPROCESS中的偏移是0xD8.(必须用8调整)

当然,在使用这段shellcode前,需要替换进程的PID来提升特权,在SYSEXIT后分别使用EIP和ESP值,在发送buffer前我们将用代码实现它.

5. 利用方法论

利用的过程如下:

  1. 创建一块可执行的内存区域并将上一段shellcode(用于交换令牌)放入区域中。
  2. 类似地,创建一块可执行的内存区域并将shellcode(在提权之后执行)放入其中。
  3. 更新第一段shellcode:提升进程的PID,在SYSEXIT后使用EIP,在SYSEXIT后使用ESP.(4]中采用了此方法.
  4. 为我们的buffer构造一块匿名映射区域
  5. 用第一段shellcode的地址填充这块映射区域
  6. 用此方式(最后4字节位于一块未被映射的内存区域)调节buffer指针
  7. 将buffer发送到驱动(用the DEVICEIO_DVWD_STACKOVERFLOW IOCTL).

6.利用代码

这是利用程序的主函数.鉴于上一个利用程序,它应该相当易懂:

7.你机器上的一切都属于我们

为了测试,我已放置了从Metasploit中获取(带有计算器calc.exe shellcode)的payload.然而我们可以做其他任何事。。。。

引用

(1] CreateFileMapping() function http://msdn.microsoft.com/en-us/library/aa366537(v=vs.85).aspx

(2] MapViewOfFileEx() function http://msdn.microsoft.com/en-us/library/aa366763(v=VS.85).aspx

(3] Remote Debugging using VMWare http://www.catch22.net/tuts/vmware

(4] Local Stack Overflow in Windows Kernel, by Heurs http://www.ghostsinthestack.org/article-29-local-stack-overflow-in-windows-kernel.html

(5] Exploiting Windows Device Drivers, by Piotr Bania http://pb.specialised.info/all/articles/ewdd.pdf

评论

todaro2015-06-03 19:08:32

感谢翻译!!!!!!!!